Repository navigation
feat: a subagent card can open subagent cards of its own, one level down - #690
Merged
Merged
Conversation
The mod answers a worker's errand from the first main-loop Stop whose background_tasks is empty. An errand the worker sent to another session (a subagent card, or a send that handed back a ticket) is not one of Claude Code's tasks, so the turn that handed it off reported "I handed it off" as the result and the worker was parked under the session it was waiting on. WorkerAnswered now ignores a worker that still awaits an outcome: an errand it sent is open, or a result for it has not been read by a turn of its own. The outcome reaching its prompt starts the turn that answers. A worker in that state is not parked, and one whose own worker is closed hears the stop through its inbox instead of waiting on silence.
A worker used to spawn with LICH_SUBAGENT_CARDS=off, which kept its own subagents native and was also how the plugin's worker-answer mod told a worker. Cards now nest two levels (maxSubagentDepth): - The sessions row's subagent column holds the depth (spawn: caller's depth + 1), carried through resume and fork. reopen read it as a bool, which would fail on a depth of 2. - Every Claude Code session gets LICH_SUBAGENT_DEPTH; LICH_SUBAGENT_CARDS=off now means only "subagents stay native": the setting is off, the card is at the limit, or the installed plugin predates 0.19.0, whose mod tells a worker by the depth. - The briefing has a third route: a card at the limit is told its Agent tool stays inside it and to fan out with that, not with new sessions. Tests changed because the contract changed: the store's subagent mark is now a depth (SessionSubagentDepth), SpawnBriefing and providerArgs take a SubagentRoute instead of a bool, and the terminal's env helper adds the depth. The plugin ceiling is already 0.20.0 (#687): nesting rides the same 0.19 contract as compacting.
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.
A Claude Code worker opened as a subagent card can now turn its own general-purpose subagents into cards, one level down. Two commits: a fix the feature depends on, then the feature.
1. fix(relay): a worker reports only once the work it handed off is back
The worker-answer mod answers a worker's errand from the first main-loop
Stopwhosebackground_tasksis empty. An errand the worker sent to another session (a card, or asend_to_sessionthat handed back a ticket) is not one of Claude Code's tasks, so the turn that handed it off reported "I handed it off" as the result, and the worker was parked under the session it was waiting on. This already happens today when a worker opens a session through MCP orlich open --subagent.relay.WorkerAnsweredignores a worker that still awaits an outcome (awaitsOutcomeLocked): an errand it sent is open, or a result for it has not been read by a turn of its own (inboxEntry.seen, set on the first busy after the nudge). The outcome reaching its prompt starts the turn that answers. Private errands are excluded, since nothing resumes the worker for them.finishedWorkerLocked,finishIfTurnEnded).stoppedthrough its inbox instead of waiting on silence (SessionClosed). Top-level callers keep today's silent close.2. feat: nested subagent cards
subagentcolumn holds the depth (caller's depth + 1,spawn.markSubagent), carried through resume and fork.reopenscanned that column into abool, which would fail on a depth of 2. Rows written before this change hold 1, which is what they were.LICH_SUBAGENT_DEPTH.LICH_SUBAGENT_CARDS=offnow means only "subagents stay native": the setting is off, the card is atmaxSubagentDepth(2), or the installed plugin predates 0.19.0, whose mod tells a worker only byoff.relay.SubagentRoute: a card at the limit is told its Agent tool stays inside it, and to fan out with that rather than with new lich sessions, which would nest by another door.docs/hooks/README.md(sixth variable),mod-answer.md,cli.md,ceilings.md, provider table in both READMEs, CHANGELOG.Tests changed because the contract changed: the store's subagent mark is now a depth (
SessionSubagentDepth), andSpawnBriefing/providerArgstake aSubagentRouteinstead of a bool.Plugin side and release order
The client side is lichdotdev/lich-plugin#58:
worker-answer.jstells a worker byLICH_SUBAGENT_DEPTH > 0and falls back toLICH_SUBAGENT_CARDS=offunder an older lich;agent-cards.jsdrops thesubagent/branch gate when the depth is present and toasts why a worker stays native.NestedSubagentCardsReleaseis0.19.0, the same contract minor #687 opened for compacting (ceiling already0.20.0). That plugin PR has to land before 0.19.0 is cut: a 0.19.0 without it would get nested cards switched on by this lich and never answer for the workers that open them.Gate
gofmt -l .clean;go vet ./...clean (with a placeholderfrontend/dist, which the embed needs).go test ./...green;-raceon relay, terminal, spawn, store and agentplugin.-count=100without a failure.