feat(subagent): sub-agent system v2 — config, discovery, skills, secret protocol (#974)#977
Merged
feat(subagent): sub-agent system v2 — config, discovery, skills, secret protocol (#974)#977
Conversation
…et protocol (#974) Implements remaining epic #974 issues (#963–#973) on top of the existing SubAgentDef/SubAgentManager/FilteredToolExecutor/PermissionGrants foundation. Config and bootstrap (#973, #964): - Add SubAgentConfig to Config with enabled, max_concurrent (default 1), extra_dirs fields; serde default via explicit impl - Wire SubAgentManager in runner.rs: load definitions from .zeph/agents/ and ~/.config/zeph/agents/ plus extra_dirs at startup - Add AgentBuilder::with_subagent_manager() builder method Skill injection (#967): - spawn() accepts Option<Vec<String>> skill bodies; prepends them as a fenced ```skills block in the sub-agent system prompt when present - MockProvider::with_recording() added for call-inspection in tests Foreground/background split (#970): - AgentCommand::Spawn and @mention block the agent loop and poll status until the sub-agent reaches a terminal state, streaming updates to user - AgentCommand::Background retains fire-and-forget behavior Secret request/approval protocol (#969): - Sub-agent emits [REQUEST_SECRET: key] marker to trigger the protocol - Main agent validates key against definition's allowed list, prompts user - On approval: approve_secret() adds TTL-bounded grant to PermissionGrants, deliver_secret() sends key name over mpsc channel; secret value is never serialized into message history or logs - deny_secret() sends None over the channel to immediately unblock the loop - tokio::select! arms on cancel.cancelled() during secret wait; sub-agent exits cleanly on cancellation without deadlock - AgentCommand::Approve/Deny wired to deliver_secret()/deny_secret() Tests: SubAgentConfig deserialization (absent/partial/full), skill injection with and without skills, secret approval and deny flows (2899 tests total).
This was
linked to
issues
Feb 26, 2026
This was
unlinked from
issues
Feb 26, 2026
…andler The fetched secret value was immediately dropped without being used — dead code that triggered GitHub Advanced Security's cleartext-logging detector on the expose() call. Removed the unused fetch entirely.
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.
Summary
Completes epic #974 by implementing the remaining operational features on top of the existing
SubAgentDef/SubAgentManager/FilteredToolExecutor/PermissionGrantsfoundation.SubAgentConfig(enabled,max_concurrent,extra_dirs); bootstrap wiring loads definitions from.zeph/agents/and~/.config/zeph/agents/at startupAgentCommand::Spawn/@mentionblock the loop and stream status; background retains fire-and-forget[REQUEST_SECRET: key], user approves, secret delivered viaPermissionGrantswithout touching message history;tokio::select!ensures cancellability during wait;deny_secret()unblocks immediately viaNoneon the channelIssues already complete before this PR (no changes needed): #963, #965, #966, #968, #971, #972.
Security
Secret values are never serialized into LLM message history,
last_messagestatus fields, or tracing logs. The approval flow stores the value exclusively inPermissionGrantswith TTL;revoke_all()is called on sub-agent termination.Audited by security agent: all three original CRITICAL findings (secret in history, deny deadlock, missing cancellation) resolved.
Test plan
cargo +nightly fmt --check— passescargo clippy --workspace -- -D warnings— 0 warningscargo nextest run --workspace --lib --bins— 2899 passed, 11 skippedSubAgentConfigdeserialization, skill injection with/without skills, secret approve and deny flows