# Architecture This package is a **thin extension** of the official DeepSeek Harness Agent Teams bundle. It implements no second task board, journal, or agent lifecycle. Each component hooks a specific official seam, checks that the seam still has the expected shape, and fails closed when it does not. The package contains three independent Cordis rows. All three load the same content-addressed module graph under `runtime//`. ``` agent-teams-model-override route selection at creation + roster model repair agent-teams-manager real nested manager/worker teams + zone authority + transcript read repair agent-teams-teammate-model model switching for a live teammate ``` ## 1. Route selection at creation The official `spawn_teammate` tool accepts only `name`, `description`, `prompt`, and `context`. The model of the child is inherited from the direct parent. The lower subagent seam already supports `request.agentOptions` (`provider`, `model`, `reasoningEffort`), including cold resume. The Team layer simply never passed those fields. ``` spawn_teammate_with_model -> validate provider/model/effort against llm + subagentModelSelection -> official spawn_teammate.execute(...) inside a scoped AsyncLocalStorage store -> official roster.spawn / spawnTeammate -> plugin shadow on startContinuable/startActivation adds agentOptions to request -> native child agent on the selected route -> durable descriptor v3 keeps the route for cold resume ``` Key properties: * The delegating agent's own options are not mutated. * The store is created by one tool call and consumed once, by the first native start whose `request.parent` is that agent. Parallel calls, other parents, and other teams cannot see it. * The official handler is called lazily, with unchanged arguments, so name checks, member limits, journal handling, and provisioning remain official code. * The tool name is different from `spawn_teammate`; the old schema and handler cannot be mixed up. ### Roster model repair `roster.list()` reads `agent.options.model`, which is a creation-time snapshot. The session's actual route is held by the session model selection and used for the request and the persona line. The plugin shadows `agentTeams.listMembers` and replaces only `model` with the effective choice (`pending`, then `lastUsed`, then the request header, then the creation options). `provider` is left untouched because in a teammate row it means the creation backend (`spawn`/`fork`), not the LLM provider. ## 2. Real nested teams The official roster allows only the Lead to create teammates (`TEAM_LEAD_REQUIRED`), and the shared board allows only the Lead to reassign work. The manager component removes that bottleneck without faking a global role: ``` manager calls spawn_team_worker -> runtime.runWithParent(caller, manager, ...) scoped identity channel -> original teams.spawnTeammate(realLead, request) all official checks run as Lead -> official roster.spawn -> shadow on the native start rewrites request.parent to the manager -> native continuation: Session.header.parentSession = manager manager catalog + descriptor v3 + route inheritance shared root journal and task board ``` The child is a normal continuable teammate. It is not a private subagent and it does not create a second team. ### Admission and recovery Nested admission and cold recovery use the official journal/activation machinery: `materialize`, `holdOwnership`, `reconcileProvisioning`, the activation registry (`requireManager().resident` on 0.2.1, `requireContinuations().activations` on 0.2.0), and `Symbol.for('dsh.subagent.deliverPrompt')`. The bridge checks the full lineage against the single root journal; ordinary subagents cannot leak into the team. Delivery is recognized in both protocol shapes: * historical Team mail: `source.kind === 'team-message'` plus mailbox; * 0.2.1 relay: `{ kind: 'agent-message', form: 'relay', senderSessionId }`. For a nested child, the native `send_message` authorizes the hop as the team root, which fails because the durable parent is the manager. The delivery shadow re-hops the message to the actual parent after checking membership in the root journal. Both the Lead and the manager can therefore wake a nested worker. ### Zone authority A manager's zone is computed from **actual** `parentSession` headers, not from a declared `member -> manager` record: * task reassignment/update/complete/reopen/delete requires both the current owner and the target to be inside the caller's actual descendant zone; * `claim` is never widened; * the Lead is outside every manager's zone; * an out-of-zone write fails with `TEAM_TASK_OUT_OF_ZONE` before any board transition. The same rule extends the native `interrupt_agent` operation. A manager may interrupt only its own descendants. The service still calls the native `interrupt` as the real Lead after the zone check, preserving pending inbox/activation and the "next message wakes it" behavior. Self-interrupt and out-of-zone targets have explicit error codes. ### Nested transcript read repair The Team panel opens a member session with `uiWorkspace.openSession({ parentSessionId: Lead, childSessionId, mode })`, including for workers created by a manager. The Host history API validates that the supplied parent is the child's actual durable parent; for a nested worker it is the manager, so the read fails with `subagent does not belong to the supplied parent (subagent/unauthorized)`. The component wraps only the history read surface: ``` sessionController.page / follow -> read the child's actual header (live agent or controller.inspect) -> if the request is kind=subagent, the child's origin is subagent, and the supplied parent is a real ancestor of the actual parent: rewrite address.parentSessionId to the actual parent -> otherwise pass the request through unchanged ``` It does not change membership, the task board, or the session directory, and it does not grant access to unrelated sessions. If the supplied parent is not an ancestor, the Host's original refusal stands. Header reads are cached per child for the lifetime of the component. The installation is fail-safe: if `page`/`follow` cannot be wrapped, the manager component still loads. ## 3. Model switching for a live teammate `switch_teammate_model` resolves the target inside the caller's authority (Lead: whole team; manager: own zone) and commits through the official session controller: ``` selectForNextRequest(agent, selection) -> durable model/selection event -> re-install route listeners used by installModelSelection -> next request and persona line use the new route ``` On DSH 0.2.1 the method lives on `sessionController.agents` (`ApiSessionAgentController`), not on the root service. The component resolves both layouts (`commitTarget()`), reports the surface it used, and falls back to appending the durable event only when the commit method is absent. That fallback is explicit: the response says `via: "session-append"` and includes a warning, because a durable event without listeners does not change the route. Teammates are disposed after each turn, so a target may not have a live Agent when the tool is called. In that case the validated selection is stored in component memory and committed on the next `agent/created`, before the first request is assembled. This is `via: "pending"`; a Host restart in that window loses the pending selection. ## 4. Runtime delivery ``` lib/*.js -> npm run build -> sha256 over sorted names + contents -> runtime/<20-hex>/ -> package.json exports['.'] and cordis.patch.yml point at the same graph ``` The graph is immutable and complete: every relative import in `lib/` must resolve inside the graph, and the build refuses a dependency that leaves it. The Host loads the graph entry, not the editable `lib/` files. This is why a change to `lib/` is not live until a build and a bundle reload. The manager component's initialization is guarded by `runtimeSupported` and a report that covers the roster, the activation registry, the delivery symbol, and the board update seam. Failure means "do not install this capability", not "throw during a turn". The base component and already-created nested participants remain usable while another component is loaded. ## 5. Compatibility model The package is tested against DSH `0.2.1-rc.2` and also supports the `0.2.0-rc.2` layout. The important version-sensitive points are: | Contract | 0.2.0 | 0.2.1 | | --- | --- | --- | | Start seam | `startContinuable` | `startActivation` | | Activation registry | `requireContinuations().activations` | `requireManager().resident` | | Closing flag | `activation.inbox.closing` | `activation.closing` | | Drain | `drainContinuableChildren` | `drainChildren` | | Team mail | mailbox + `team-message` | relay `agent-message` | | Model commit | root controller | `sessionController.agents` | Contract tests against the installed `app.asar` pin these shapes. A future Harness that renames or moves one of them fails the tests and makes the corresponding capability refuse to install, instead of silently degrading. ## 6. Security boundaries * No network calls, telemetry, or credential reads. * No global mutation of the Lead's role or `AgentOptions`. * No cross-zone task or interrupt authority. * No arbitrary session reads: the history repair only follows a genuine ancestor path. * No hidden model fallback: an invalid route is an error. * No privilege escalation from model selection: the same tools, limits, depth, and board rules apply.