Skip to content

feat(subagent): sub-agent system v2 — config, discovery, skills, secret protocol (#974)#977

Merged
bug-ops merged 3 commits intomainfrom
feat/subagent-v2
Feb 26, 2026
Merged

feat(subagent): sub-agent system v2 — config, discovery, skills, secret protocol (#974)#977
bug-ops merged 3 commits intomainfrom
feat/subagent-v2

Conversation

@bug-ops
Copy link
Owner

@bug-ops bug-ops commented Feb 26, 2026

Summary

Completes epic #974 by implementing the remaining operational features on top of the existing SubAgentDef / SubAgentManager / FilteredToolExecutor / PermissionGrants foundation.

Issues 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_message status fields, or tracing logs. The approval flow stores the value exclusively in PermissionGrants with 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

…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).
@github-actions github-actions bot added enhancement New feature or request size/XL labels Feb 26, 2026
@bug-ops bug-ops enabled auto-merge (squash) February 26, 2026 16:01
…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.
@bug-ops bug-ops merged commit 480a96a into main Feb 26, 2026
28 checks passed
@bug-ops bug-ops deleted the feat/subagent-v2 branch February 26, 2026 16:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment