feat(subagents): programmatic dispatch API via a unified resolution pipeline - #832
Open
northwang-lucky wants to merge 7 commits into
Open
northwang-lucky wants to merge 7 commits into
northwang-lucky wants to merge 7 commits into
Conversation
…ture (G1) Introduce the G1 programmatic subagent dispatch API on the upstream base, using the pipeline architecture pattern from the old feat/subagent-pipeline branch. This is the first slice of the replat-pipeline branch — G1 only, with G2/G3/C deferred to later slices. New files: - lib/subagent-dispatch.ts: createDispatchRuntime() factory, resolveSubagentResources() unified resolution pipeline (G1 shell), globalThis.__piSubagentDispatch registry, registerDispatchRuntime(). - lib/subagent-state.ts: centralized globalThis typed registry for active-dispatch tracking (getActiveDispatches()). - lib/subagent-dispatch.test.mjs: S1 + S4 contract suite (15 tests). Modified files: - lib/subagents.ts: extend SubagentRunInfo with G4 fields (model, thinking, activeTools, tools) so the dispatch module can read authoritative effective values from the run. - lib/rpc-manager.ts: import registerDispatchRuntime and wire the production binding after SUBAGENT_CONTROLLER creation. Architecture decisions: - Pipeline pattern: resolveSubagentResources() produces a typed ResolvedSubagentResources plan; dispatch reads effective values from SubagentRunInfo (run-anchored, not param-echoed). - G5 concurrency gating deferred (upstream SubagentQueue handles it). - AbortSignal listener detached on all terminal paths (no leaks). - onUpdate callback exceptions never break dispatch (try/catch). - No side-channel writes to production request objects; test fakes correlate via _runIds tracking and completion event childSessionId. - _getParentState on the request is a frozen-test DI seam (P5 ledger: adjudication-p5-audit-findings §2); only as unknown as in the module. Three gates: - tsc --noEmit: clean (0 errors) - npm run lint: clean (0 errors) - npm test: 1035 pass / 0 fail (upstream baseline 1020 + 15 new)
… + G4 model/thinking (R2b) - Add extensionFilterKey + filterExtensionsBySource to subagent-runtime.ts (C fix: stable key derivation from npm/file-path extension sources, real-shape guardrail for sourceInfo.path) - Wire G3 extension filtering (extensions/denyExtensions) into start() using filterExtensionsBySource with allow/deny sets - Wire G2 tool resolution (tools/disallowedTools) into start(): request.tools ?? profile.tools as base, disallowedTools subtract after merge - Replace ephemeral with persistSession (three-level fallback: request → profile → true) and add auditSessionManager for in-memory sessions - Add model/thinking/activeTools to resourceSnapshot and initialRun (G4: authoritative effective values from three-level fallback) - Update SubagentResourceSnapshot interface with model/thinking fields - Update StartSubagentRequest with G2/G3/G6 fields (tools, disallowedTools, extensions, denyExtensions, excludeTools, persistSession) - Update dispatch: persistSession forwarding + sessionId on request - All shared test suites byte-identical to replat-api branch - 1064 tests pass, tsc clean, lint clean
…dd behavioral test - Add profilePersistSession param to resolveSubagentResources so the three-level fallback (dispatch → profile → true) is preserved in the unified pipeline, not lost during the R2b-to-R2c refactoring. - Restore G6 source assertion to read from dispatch.ts with the original three-level regex (strength recovered). - Add behavioral test proving profile.persistSession=false propagates through the resolver when the request omits persistSession.
The ported feature branches had dropped the AGENTS.md coverage their predecessor carried: add the file-map rows for lib/subagent-dispatch.ts and lib/subagent-state.ts plus a short section describing the programmatic dispatch entry point, the per-dispatch parameter overrides, and the centralized resolveSubagentResources() pipeline.
- The dispatch runtime never reads settings; concurrency policy belongs
to the upstream SubagentQueue. Remove the interface member, the
production binding, and the fake key (kept byte-identical with the
incremental branch's contract suite).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of the discussion in #829 — this is the unified resolution pipeline of the two reference implementations (the incremental variant is #831). Same behavior contract (the dispatch contract suite is byte-identical between the two branches), different internal structure: one
resolveSubagentResources()pipeline produces a typed resolution plan consumed once by the runtime, instead of per-control inline logic. Either one is ready to walk forward; whichever the maintainer prefers, we'll rework against your review (or close both without hard feelings).What it adds (5 commits, branched from
main@8366762):lib/subagent-dispatch.ts— programmatic dispatch API (registerDispatchRuntime()->globalThis.__piSubagentDispatch:startSubagentDispatch(parentSessionId, params), per-dispatchtools/disallowedTools/extensions/denyExtensions/excludeTools,signal/onUpdate, lifecycle events carrying runtime-resolved effective model/thinking/activeTools — not request echoes) plus the unifiedresolveSubagentResources()planlib/subagent-state.ts— one typedglobalThisregistry replacing scattered lazy-global writes, hot-reload safepersistSession: false— in-memory child session (nothing hits disk) with audit metadata redirected to the parent; profile-level fallback additionally pinned by a driven behavioral testSubagentQueue(b77a25f)Tests: 1065 passing (upstream 1020 + 45),
tsc/eslintclean; same live validation as #831 (dispatch round-trip, non-echoed effective values, in-memory run leaves no.jsonlwith audit on the parent).