The doc-typecheck opt-out ratio gate now fails at 74/146 after merging
master. The model and branded-ID blocks are self-contained, so check
them as ordinary ts fences (72/146 remain opted out); re-record the
pairing sidecar.
session.create's reply named only the id, and the session-added frame's
agentPreset — which the server already derives via sessionListFields — was
stripped by the wire schema it never joined. A client therefore could not
label a session it just created (or learned live) until the next full list
refresh: the header's preset label rendered nothing for exactly the sessions
made in this tab. The create reply is the commit point that knows the
RESOLVED composition (a caller that named none gets the default the header
recorded), so both carriers now say it.
interrupt_agent(agent_id) passes the calling agent as the ancestor
authority for ctx.subagents.interrupt(); the service verifies live
registry identity and recorded lineage, so a direct child or deeper
descendant stops with the same generic parameter while send_message keeps
its exact-direct-parent authority.
Discovery: list_agents gains an optional scope. descendants walks the new
SubagentService.listDescendants() — one lineage trace flattened in stable
pre-order across ordinary and one-shot intermediates, each entry carrying
its verified parentId and depth — and every status now comes from the
live Agent registry (running/idle/complete).
Refs #1535
The composer's single primary action now stops a running continuable
child: Session.cancel() routes a continuable address through
subagent.interrupt (ordinary sessions keep session.cancel; one-shot
addresses stay uncancellable), and InputBar switches the same Send button
to Stop while a continuable child runs. A running parent-offline child
keeps the default composer with disabled input so that Stop stays
reachable; the read-only takeover returns once it stops.
Refs #1535
The continuable-subagent core-data page and the continuation lifecycle
Agent Note claimed no public subagent cancellation existed; interrupt()
is now that one public stop. Both pages and their Chinese counterparts
state the shipped contract and link the owning interrupt note.
Refs #1535
A browserless keyless-replay e2e boots the shipped web composition, holds
a continuable child's turn open with a replay hang entry, queues a
follow-up and interrupts over plain HTTP, and proves from the real
session state that the turn aborted, the follow-up parked without a new
turn, and a waking send resumed the preserved FIFO order.
Refs #1535
ctx.subagents.interrupt() stops one live continuable child's current turn
via Agent.cancel(cause, { keepInbox: true }) under either a human durable
parent address or an exact live ancestor Agent. Fire-and-return: admission
is synchronous, quiescence is not awaited. Pending inbox work, the
Activation, and published descendants are preserved; only a later waking
send resumes the parked FIFO queue. Absent, one-shot, and disposing
targets are accepted no-ops.
The new Host RPC subagent.interrupt calls only that primitive with user
authority — no catalog, history, persistence, or parent-registry lookup —
so a live child stays stoppable while its parent Agent is offline.
Refs #1535
Rewires this layer onto the standing-mount model:
- serviceForAgent roots its search at the agent's standing mount (parent
scope key → live mount fiber) — the composition no longer lives under the
agent's own fiber, and two agents on one preset now address ONE instance,
which the sharing test asserts instead of distinctness.
- viewFor/historyPage take a registry view SCOPE. A live agent is that scope;
a cold read uses the recorded preset's standing key via standingKeyFor —
composing plugins but starting no agent, session, or turn. A header without
a preset (a pre-roster log) renders through the DEFAULT preset's standing
layer; an unusable preset degrades the read to generic cards, never fails
it. This turns produced-files and chat-scroll green structurally, with the
token counts untouched (no resume, so the projections fold stays detached).
- The detached projections baseline now includes every standing unit's key at
its empty fold (todos: null): the standing mount registers units
deterministically, which is what makes the client's "omitted key =
capability absence → clear" rule safe again. seeded-history's contract test
asserts the new shape.
- The standard preset's realm preamble no longer claims a shared label pools
instances — provide() throws on the second registration under one realm
symbol; labels join REALMS.
A preset is now ONE composition per process, not one per session. The roster
mounts it once under a synthetic standing scope; each agent joins by having
its scope key parented to the mount's. Two mechanisms in dsh-scope carry the
whole change: registration views walk the parent chain (global → preset →
agent, nearest shadowing farthest — ScopedLayers.chainLayers), and scoped
event dispatch admits a listener tagged with an ancestor of the carrier key,
which is what lets a standing composition's plan/compaction/token listeners
observe each agent composed under it while a sibling preset's stay deaf.
The preset plugins already key their state by Session/Agent — they predate
presets and were written for the shared world — so sharing one instance is a
return to their design, not a rewrite. Preset ymls are unchanged: one mount
per preset means one Entry per preset, whose entry-local realms keep two
presets' services apart exactly as they kept two sessions' apart before.
The standing scope hangs off the service's UNTRACED context (selfCtx): a
method invoked through the traceable proxy sees this.ctx rebound to the
caller and carrying its shadow, and a subtree minted from that resolves every
service through the shadow's fiber instead of each entry's own inject store —
preset rows then fail on the very services they declare.
A standing mount survives its agents deliberately. The composition a running
session joined must outlive the file changing or disappearing underneath it;
reclamation happens at whole-tree teardown, and file edits reach only future
generations (the authoring layer swaps the pointer, never disposes a joined
generation).