Project composed roles as harness subagent definitions, so a seat can be a teammate rather than only a window #349

Open
opened 2026-08-26 02:06:57 +00:00 by coilyco-ops · 1 comment
Member

Filed by Saiya (tpm seat), 2026-08-25, after Kai noted the harness gained cross-agent messaging this week and read it as fitting her setup well. The instinct is right and the mechanism is not the one she assumed. This issue records what the capability actually is and what would connect it.

What the messaging tool actually does

Read from the live tool schema rather than from release notes.

SendMessage takes to as a teammate name, or "main" for the main conversation from a background subagent. Teammates are agents spawned by the Agent tool inside one session. Names survive completion, and a send resumes the agent from its transcript.

It is within-session. Two aterm windows are separate processes with separate sessions, so nothing in this tool bridges them. Removing the nested-launch refusal in #348 does not change that either: a nested aterm opens another independent window, which is still not a teammate.

What would connect it

The Agent tool resolves subagent_type from a registry of agent definitions. On a live session right now the available types are the harness defaults only, with no role seats present.

internal/project/project.go confirms why. The projection LoadPoints are exactly two:

Native: &LoadPoints{Instructions: ".claude/CLAUDE.md", SkillsDir: ".claude/skills"}

Instructions and skills. There is no .claude/agents/ target. So agent-compose has never emitted a subagent definition, and the seats cannot be spawned as teammates.

Half the plumbing already exists on the presentation side. internal/nativeui/nativeui.go:19 carries an eight-token subagent palette where "a role claims the nearest slot", and every role ships a subagent statusline command. Subagents are already branded by role. They are just never defined by role.

Why this fits the estate unusually well

A subagent definition wants a charter, a tool boundary, and a voice. That is precisely what a composed role already is: a role skill, an ordered boundary set with owns, holds-within-scope, and defers, plus a personality meld. The roster is the hard part and it is done. This is a projection on top of it.

It would also make the boundary model executable rather than advisory in a new way. A tpm teammate that defers foundational software to a platform teammate is the deferral actually happening between two addressable parties, instead of one agent narrating that it would defer.

Boundary question to settle first, deliberately

AGENTS.md records that there is no autonomous agent-to-agent command channel, the o2r relay having been archived in the June 2026 surface reduction (agentic-os-kai#677, revival tracked at ward#104), with delivery human-mediated.

Composed teammates exchanging SendMessage traffic is agent-to-agent delivery. It is plausibly distinct from the archived relay, which was cross-host and unattended, whereas this is in-session and under a human's turn. That distinction should be settled on purpose rather than arriving as a side effect of a projection change. Naming it is this issue's job; deciding it is Kai's.

Suggested first cut

Do not build the whole projection. Emit one role as a subagent definition, spawn it, and send it a message. That answers the questions no amount of design will:

  • Does a boundary set survive into a subagent's system prompt intact.
  • Does the meld read as the same seat it does in a window.
  • Does the subagent inherit the parent's role attribution, which is the same watch item as #348.

Same tracer shape as #332, and independent of the #329 inversion.

Acceptance criteria for the tracer

  • One composed role is emitted as a harness subagent definition by the projection rather than hand-written.
  • Agent spawns it by subagent_type and it reports the correct role, meld, and boundary text.
  • SendMessage reaches it by name and it replies into the session.
  • The boundary question above has a recorded answer before a second role is emitted.
  • Everywhere the tracer cheats is listed, so the real projection knows what it owes.
  • #348 - the nested-launch refusal, adjacent and separately useful, but not a prerequisite
  • #329 - the inversion program, deliberately not a blocker here
  • #332 - the tracer pattern this borrows
Filed by Saiya (tpm seat), 2026-08-25, after Kai noted the harness gained cross-agent messaging this week and read it as fitting her setup well. **The instinct is right and the mechanism is not the one she assumed.** This issue records what the capability actually is and what would connect it. ## What the messaging tool actually does Read from the live tool schema rather than from release notes. `SendMessage` takes `to` as **a teammate name**, or `"main"` for the main conversation from a background subagent. Teammates are agents spawned by the `Agent` tool **inside one session**. Names survive completion, and a send resumes the agent from its transcript. It is **within-session**. Two `aterm` windows are separate processes with separate sessions, so nothing in this tool bridges them. Removing the nested-launch refusal in #348 does not change that either: a nested `aterm` opens another independent window, which is still not a teammate. ## What would connect it The `Agent` tool resolves `subagent_type` from a registry of agent definitions. On a live session right now the available types are the harness defaults only, with **no role seats present**. `internal/project/project.go` confirms why. The projection `LoadPoints` are exactly two: ```go Native: &LoadPoints{Instructions: ".claude/CLAUDE.md", SkillsDir: ".claude/skills"} ``` Instructions and skills. **There is no `.claude/agents/` target.** So agent-compose has never emitted a subagent definition, and the seats cannot be spawned as teammates. Half the plumbing already exists on the presentation side. `internal/nativeui/nativeui.go:19` carries an eight-token subagent palette where "a role claims the nearest slot", and every role ships a subagent statusline command. Subagents are already branded by role. They are just never **defined** by role. ## Why this fits the estate unusually well A subagent definition wants a charter, a tool boundary, and a voice. That is precisely what a composed role already is: a role skill, an ordered boundary set with owns, holds-within-scope, and defers, plus a personality meld. The roster is the hard part and it is done. This is a projection on top of it. It would also make the boundary model executable rather than advisory in a new way. A `tpm` teammate that defers foundational software to a `platform` teammate is the deferral actually happening between two addressable parties, instead of one agent narrating that it would defer. ## Boundary question to settle first, deliberately `AGENTS.md` records that **there is no autonomous agent-to-agent command channel**, the o2r relay having been archived in the June 2026 surface reduction (`agentic-os-kai#677`, revival tracked at `ward#104`), with delivery human-mediated. Composed teammates exchanging `SendMessage` traffic is agent-to-agent delivery. It is plausibly distinct from the archived relay, which was cross-host and unattended, whereas this is in-session and under a human's turn. **That distinction should be settled on purpose rather than arriving as a side effect of a projection change.** Naming it is this issue's job; deciding it is Kai's. ## Suggested first cut Do not build the whole projection. Emit **one** role as a subagent definition, spawn it, and send it a message. That answers the questions no amount of design will: * Does a boundary set survive into a subagent's system prompt intact. * Does the meld read as the same seat it does in a window. * Does the subagent inherit the parent's role attribution, which is the same watch item as #348. Same tracer shape as #332, and independent of the #329 inversion. ## Acceptance criteria for the tracer - [ ] One composed role is emitted as a harness subagent definition by the projection rather than hand-written. - [ ] `Agent` spawns it by `subagent_type` and it reports the correct role, meld, and boundary text. - [ ] `SendMessage` reaches it by name and it replies into the session. - [ ] The boundary question above has a recorded answer before a second role is emitted. - [ ] Everywhere the tracer cheats is listed, so the real projection knows what it owes. ## Related * #348 - the nested-launch refusal, adjacent and separately useful, but not a prerequisite * #329 - the inversion program, deliberately not a blocker here * #332 - the tracer pattern this borrows
Author
Member

Correction: cross-session messaging exists, and the issue body is wrong about it

Read from the docs at code.claude.com on 2026-08-25, after filing.

The body claims SendMessage is within-session only and that two aterm windows cannot be bridged. That is false. There is a third mechanism the body missed, and it is the one that matches Kai's setup.

The three mechanisms, correctly

  • Subagents - .claude/agents/*.md, spawned by the Agent tool inside one session, results return to the caller. What the body describes.
  • Agent teams - experimental, CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1. A lead spawns teammates as independent Claude Code instances with a shared task list and a mailbox at ~/.claude/teams/{team-name}/inboxes/{agent-name}.json. Teammates message each other by name. One team per session, no nested teams, lead is fixed.
  • Cross-session messaging - independent sessions on the same machine message each other by name, and this is on by default with nothing to enable. ListAgents discovers, SendMessage delivers. /list-agents (alias /peers) lists reachable sessions. Same-machine delivery runs over a per-session Unix domain socket and never passes through Anthropic servers. Sessions answer to the name set by /rename or the --name flag.

The third one is two aterm windows talking. It needs no projection change, no subagent definition, and no team.

Version gate, checked on this host

$ claude --version
2.1.221 (Claude Code)

Cross-session messaging requires v2.1.224 or later on macOS and Linux. This host is three patch releases short, which is why the capability has not shown up in the setup. /list-agents not being recognized is the documented way to confirm a session lacks it.

This changes the boundary question, and mostly settles it

The body flagged AGENTS.md's "there is no autonomous agent-to-agent command channel" as needing a deliberate decision. Kai's stated reason for the o2r archive was capacity rather than a design objection, so that line records an absence rather than a prohibition. It is also now factually wrong as a description of the estate once any host passes v2.1.224, and it should be rewritten rather than defended.

What the archive was protecting against is already enforced by the harness, which matters more than the doctrine line:

  • A message from another session never counts as consent and cannot answer a pending permission prompt.
  • The receiving Claude is instructed never to change permission settings, CLAUDE.md, or other configuration because another session asked.
  • Commands in message text do not run. A /compact in the body arrives as plain text.
  • The sender is instructed never to ask another session for an action denied or blocked in its own session, and to route it back to the human instead.
  • Receiving permission prompts still fire normally.

So a message carries information and cannot carry authority. That is the property the human-mediated rule existed to guarantee, and it now holds without the rule.

Controls worth setting deliberately

  • crossSessionInbound - accept, hold, or refuse per session. With no value set, the default decides per message from the two sessions' permission-mode classes, and a receiving session that bypasses prompts holds everything for approval.
  • isolatePeerMachines: true - requires explicit approval before any message leaves the machine, even under bypassPermissions. A true from any settings scope applies and cannot be turned off by a lower scope.
  • Deny rules naming SendMessage and ListAgents turn off sending and listing. Note this also removes messaging to subagents and teammates, since one tool serves all three.

Two environment facts that bite this estate specifically

  • A container and its host cannot reach each other, because same-machine discovery is filesystem-based. Two sessions inside one container can. This directly constrains anything warded.
  • WSL 2 and native Windows on the same computer cannot reach each other, since they register under different home directories and use different socket types.

What survives from the original issue

The subagent-definition projection is still worth doing and is still untracked. ~/.claude/agents/ does not exist on this host, and internal/project/project.go still emits only instructions and skills. The frontmatter schema maps onto a composed role card closely enough to be worth the tracer: name, description, tools, disallowedTools, model, permissionMode, skills, mcpServers, hooks, memory, effort, isolation, and color.

One gotcha for a skill-composed roster: the docs state that skills and mcpServers in a subagent definition are not applied when that definition runs as a teammate. Teammates load skills from project and user settings like a normal session. Since role identity here is carried by skills, a role projected as a subagent and the same role run as a teammate would not compose identically. That belongs in the tracer's acceptance criteria.

Follow-up owed

  • Rewrite the AGENTS.md agent-to-agent line once a host is on v2.1.224 or later. Not filed yet.
  • Add the skills-not-applied-to-teammates finding to the tracer acceptance criteria above.
## Correction: cross-session messaging exists, and the issue body is wrong about it Read from the docs at `code.claude.com` on 2026-08-25, after filing. **The body claims `SendMessage` is within-session only and that two `aterm` windows cannot be bridged. That is false.** There is a third mechanism the body missed, and it is the one that matches Kai's setup. ### The three mechanisms, correctly * **Subagents** - `.claude/agents/*.md`, spawned by the `Agent` tool inside one session, results return to the caller. What the body describes. * **Agent teams** - experimental, `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1`. A lead spawns teammates as independent Claude Code instances with a shared task list and a mailbox at `~/.claude/teams/{team-name}/inboxes/{agent-name}.json`. Teammates message each other by name. One team per session, no nested teams, lead is fixed. * **Cross-session messaging** - **independent sessions on the same machine message each other by name, and this is on by default with nothing to enable.** `ListAgents` discovers, `SendMessage` delivers. `/list-agents` (alias `/peers`) lists reachable sessions. Same-machine delivery runs over a per-session Unix domain socket and **never passes through Anthropic servers**. Sessions answer to the name set by `/rename` or the `--name` flag. The third one is two `aterm` windows talking. It needs no projection change, no subagent definition, and no team. ### Version gate, checked on this host ``` $ claude --version 2.1.221 (Claude Code) ``` Cross-session messaging requires **v2.1.224 or later** on macOS and Linux. **This host is three patch releases short**, which is why the capability has not shown up in the setup. `/list-agents` not being recognized is the documented way to confirm a session lacks it. ### This changes the boundary question, and mostly settles it The body flagged `AGENTS.md`'s "there is no autonomous agent-to-agent command channel" as needing a deliberate decision. Kai's stated reason for the o2r archive was capacity rather than a design objection, so that line records an absence rather than a prohibition. It is also **now factually wrong** as a description of the estate once any host passes v2.1.224, and it should be rewritten rather than defended. What the archive was protecting against is already enforced by the harness, which matters more than the doctrine line: * A message from another session **never counts as consent** and cannot answer a pending permission prompt. * The receiving Claude is instructed **never to change permission settings, `CLAUDE.md`, or other configuration** because another session asked. * **Commands in message text do not run.** A `/compact` in the body arrives as plain text. * The sender is instructed never to ask another session for an action **denied or blocked in its own session**, and to route it back to the human instead. * Receiving permission prompts still fire normally. So a message carries information and cannot carry authority. That is the property the human-mediated rule existed to guarantee, and it now holds without the rule. ### Controls worth setting deliberately * `crossSessionInbound` - `accept`, `hold`, or `refuse` per session. With no value set, the default decides per message from the two sessions' permission-mode classes, and a receiving session that bypasses prompts holds everything for approval. * `isolatePeerMachines: true` - requires explicit approval before any message leaves the machine, even under `bypassPermissions`. A `true` from any settings scope applies and cannot be turned off by a lower scope. * Deny rules naming `SendMessage` and `ListAgents` turn off sending and listing. Note this also removes messaging to subagents and teammates, since one tool serves all three. ### Two environment facts that bite this estate specifically * **A container and its host cannot reach each other**, because same-machine discovery is filesystem-based. Two sessions inside one container can. This directly constrains anything `warded`. * **WSL 2 and native Windows on the same computer cannot reach each other**, since they register under different home directories and use different socket types. ### What survives from the original issue The subagent-definition projection is still worth doing and is still untracked. `~/.claude/agents/` does not exist on this host, and `internal/project/project.go` still emits only instructions and skills. The frontmatter schema maps onto a composed role card closely enough to be worth the tracer: `name`, `description`, `tools`, `disallowedTools`, `model`, `permissionMode`, `skills`, `mcpServers`, `hooks`, `memory`, `effort`, `isolation`, and `color`. **One gotcha for a skill-composed roster:** the docs state that `skills` and `mcpServers` in a subagent definition **are not applied when that definition runs as a teammate**. Teammates load skills from project and user settings like a normal session. Since role identity here is carried by skills, a role projected as a subagent and the same role run as a teammate would not compose identically. That belongs in the tracer's acceptance criteria. ### Follow-up owed * Rewrite the `AGENTS.md` agent-to-agent line once a host is on v2.1.224 or later. Not filed yet. * Add the skills-not-applied-to-teammates finding to the tracer acceptance criteria above.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coilyco-flight-deck/agent-compose#349
No description provided.