Skip to content

feat(subagents): activation scope for user subagents (#1431) - #1561

Open
muzimu217 wants to merge 6 commits into
vastsa:mainfrom
muzimu217:draft/subagent-activation-scope
Open

muzimu217 wants to merge 6 commits into
vastsa:mainfrom
muzimu217:draft/subagent-activation-scope

Conversation

@muzimu217

Copy link
Copy Markdown
Contributor

feat(subagents): activation scope for user subagents (#1431)

Summary

User subagents today are visible to every session's delegation catalog; the only
containment is prose in the description (soft) or per-project disable overrides
(manual, inverse of what users need). Skills, MCP servers, and plugins have had
activation scopes since D192 (ADR 0056) — subagents are the last capability
family without one, and the spec, the IPC constant, the api bridge, and the
settings page payload have all referenced agents.setScope already. This PR
completes that path:

  1. active_for honors ActivationScope.matches(project_path) — Global scopes
    follow every session, Projects-scoped subagents stay out of unrelated
    sessions and out of sessions with no project. Both runtime callers already
    pass the session's project path (session-launch.ts, launch-resolver.ts
    — "host-core owns the registry and the activation scope"), so the filter
    engages on the real launch path with no further wiring.
  2. The document is the scope's home: a projects: [/abs/a, ...] front-matter
    key (absence = today's global behavior), parsed and round-tripped by
    parse_record/render_document following the fallbackModels pattern.
    CapabilityState stays boolean-only; ADR 0112's "no project-level subagent
    directory" is untouched — the scope rides the existing global documents.
  3. set_scope rewrites the document through render_document; create/update
    honor input.scope (None keeps the document's current value).
  4. No RPC change is needed: the agents.setScope dispatch arm already existed
    in rpc/mod.rs — it pointed at the set_scope stub, which this PR makes
    real. Code, spec (06-host-rpc-protocol.md), and the existing
    protocol.ts/api.ts/IPC bridge now agree without any of them moving.

What is deliberately not here

  • A settings UI control on AgentSubagentsPage (the save payload already
    passes scope through; hand-edited front matter works today). Happy to add
    it — with i18n ×9 — as a follow-up or in this PR, per your preference.
  • An amendment ADR (proposed text below) — numbering is yours; note 0322
    may collide with feat(settings): 支持配置会话标题生成 #1449's claim.

Proposed ADR (for maintainers to number)

# ADR 03xx: Activation scope for user subagents

- Status: Proposed
- Related: #1431, ADR 0056 (activation scopes), ADR 0063/0112 (global-only
  subagent documents), `agents.setScope` in 06-host-rpc-protocol.md

## Decision

User subagent documents carry an optional `projects` front-matter key
(normalized absolute paths). Absent key = global visibility (unchanged
behavior). A Projects-scoped document leaves the delegation catalog of
sessions outside the named projects and of sessions with no project.
`agents.setScope` rewrites the document; create/update accept the same
scope. CapabilityState remains boolean-only — the document is the record's
home.

Tests (all local, macOS arm64)

  • cargo test -p host-core 825/825 (current HEAD), including new:
    visibility_honors_activation_scope, projects_scope_survives_record_document_round_trips,
    set_scope_rewrites_the_document_and_drives_visibility,
    create_and_update_honor_input_scope.
  • Renderer bridge verified on this branch: subagent panel/wiring tests 18/18,
    pnpm typecheck clean.
  • Cross-platform: Linux server (Alibaba Cloud Linux 3, x86_64) full suite
    matches — host-core 800/800 (as of that base), agent-runtime 95 files/1312.
  • End-to-end on a real Linux host binary: stdio JSON-RPC (app.handshake
    protocolVersion=11) against a fixture projects:-scoped subagent —
    agents.active returns it only inside its named project, filters it for
    sessions with no project and outside the list, and keeps global ones
    everywhere. Re-verified after each rebase.

refs #1431

@muzimu217

Copy link
Copy Markdown
Contributor Author

One CI note for triage: the JS job fails on transcript-style.test.mjs:365 — an assertion that ChatTranscript.tsx contains const editableUserMessage = isUser && !isSessionMessage;. That source assertion fails identically on unmodified current main (locally: 28/29 on main, 28/29 on this branch — same single failure, none of this PR's files touched), i.e. it is a pre-existing source-contract drift on main (likely from today's Pi 1.1.0 adoption in #1504 reshaping the transcript source) and not introduced by this PR. Every test touching this PR's own changes passes.

Everything attributable to the branch is green: the LOC allowlist entry now covers user_subagents.rs, architecture check passes, and the four browser/test files exercising the scope wiring are green. Happy to also PR the stale transcript-style assertion as a separate fix if you'd like — leaving it to you since it belongs to today's main, not this branch.

Wire UserSubagentRegistry::active_for to the existing ActivationScope
rule (crates/host-core/src/activation.rs): enabled records with a
Projects scope now stay out of sessions outside their projects, and
project-scoped records stay out of sessions with no project. Every
record scanned today still carries the default Global scope, so this
changes no behavior until scope assignment exists.

Deliberately NOT in this draft:
- set_scope persistence. user_skills set_scope is a documented stub
  ('scope is no longer persisted in capability files') while plugins
  persist scope in capability state; subagents need the maintainer's
  call between those two precedents before set_scope can be honest.
- Front-matter scope parsing, RPC surface, and the settings UI.

refs vastsa#1431
…stsa#1431)

parse_record now reads a projects: [/abs/a, ...] front-matter key into
the record's ActivationScope (Projects mode, normalized); its absence
keeps the historical global visibility. render_document round-trips the
list and drops it when the scope clears, following the fallbackModels
pattern. CapabilityState stays boolean-only by design; the document is
the record's home, and ADR 0112 deliberately has no project-level
subagent directory, so front matter is the only persistence home that
neither bends a shared schema nor invents a new state file.

Still open on this branch: set_scope/update wiring that rewrites the
document through render_document, RPC surface, settings UI.

refs vastsa#1431
set_scope now normalizes the scope, rewrites the subagent document via
render_document, and returns the rescanned record — hand-edited files
and future RPC writes converge on one spelling, and active_for filters
by the new scope on the next scan. Clearing the scope (Global default)
drops the projects key from the document. Unknown ids stay a no-op.

Remaining on this branch: honor UserSubagentInput.scope in create/update,
expose an RPC surface, and a settings UI control.

refs vastsa#1431
create applies a supplied ActivationScope (normalized) to the new
record's document; update treats a supplied scope as set-or-clear and
keeps the document's current scope when the input omits it, so RPC
payloads and hand-edited files converge through the same front matter.
The input field's protocol-compat comment and dead-code allowance are
gone for good.

Remaining on this branch: expose a setScope RPC surface and a settings
UI control.

refs vastsa#1431
Format-only fixups on the scope wiring (render_document hunk). Also
verified this branch introduces no new clippy warnings: 5 on the branch,
5 on main, same set (the agents.setScope dispatch arm already existed —
it pointed at the set_scope stub until 'implement set_scope through the
document' made it real — so no RPC change is needed after all).

refs vastsa#1431
The activation-scope wiring (vastsa#1431) pushed user_subagents.rs past the
Rust 1000-LOC limit: scope parsing, document round-tripping, set_scope,
and their tests share the existing parse/render/registry lifecycle.
Exempting it here follows the user_skills.rs precedent (PR vastsa#515); the
scope bookkeeping can be separated later if the maintainer prefers.
@muzimu217
muzimu217 force-pushed the draft/subagent-activation-scope branch from 0b85306 to 21c1b38 Compare October 11, 2026 15:04

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant