| last_updated | 2025-11-06 |
|---|---|
| status | stable |
| audience | developer |
| layer | App-Layer Implementation |
How the CLI implements agent delegation using amplifier-foundation library and amplifier-core session forking.
Agent concepts & authoring: → Bundle Guide Kernel mechanism: → SESSION_FORK_SPECIFICATION.md
amplifier-app-cli implements agent delegation by:
- Using
AgentResolverandAgentLoaderfrom amplifier-foundation - Resolving agent files from CLI-specific search paths
- Supporting environment variable overrides for testing
- Compiling agent configs via bundle compilation
- Using amplifier-core's
session.fork()for sub-session creation
First-match-wins resolution (highest → lowest priority):
┌─────────────────────────────────────────────────────────────┐
│ 1. Environment Variables │
│ AMPLIFIER_AGENT_ZEN_ARCHITECT=~/test-zen.md │
│ → For testing changes before committing │
├─────────────────────────────────────────────────────────────┤
│ 2. User Directory │
│ ~/.amplifier/agents/zen-architect.md │
│ → Personal overrides and custom agents │
├─────────────────────────────────────────────────────────────┤
│ 3. Project Directory │
│ .amplifier/agents/project-reviewer.md │
│ → Project-specific agents (committed to git) │
├─────────────────────────────────────────────────────────────┤
│ 4. Bundle Agents │
│ bundles/developer-expertise/agents/zen-architect.md │
│ → Agents bundled with bundles │
└─────────────────────────────────────────────────────────────┘
Environment variable format: AMPLIFIER_AGENT_<NAME> (uppercase, dashes → underscores)
# Testing agent changes
export AMPLIFIER_AGENT_ZEN_ARCHITECT=~/test-zen.md
amplifier run "design system" # Uses test versionfrom amplifier_foundation import AgentResolver
from pathlib import Path
import os
# Build search paths (CLI-specific)
# Bundle agents discovered via bundle system
# Then project and user directories
search_paths = [
Path(".amplifier/agents"), # Project
Path.home() / ".amplifier" / "agents", # User
# Bundle agents added via bundle discovery
]
# Create resolver
resolver = AgentResolver(search_paths=search_paths)
# Check environment variable override first (CLI-specific)
agent_name = "zen-architect"
env_var = f"AMPLIFIER_AGENT_{agent_name.upper().replace('-', '_')}"
agent_path = os.environ.get(env_var)
if not agent_path:
# Fall back to resolver
agent_path = resolver.resolve(agent_name)
# Load agent
from amplifier_foundation import AgentLoader
loader = AgentLoader(resolver=resolver)
agent = loader.load_agent(agent_name)Uses amplifier-core's session.fork() with agent config overlay:
# In parent session
parent_session = AmplifierSession(config=parent_mount_plan)
# Load agent config
agent = agent_loader.load_agent("zen-architect")
agent_mount_plan_fragment = agent.to_mount_plan_fragment()
# Fork session with agent overlay
sub_session = await parent_session.fork(
config_overlay=agent_mount_plan_fragment,
task_description="Design authentication system"
)
# Execute in sub-session
result = await sub_session.execute("Design the auth system")Bundles can control which tools spawned agents inherit using the spawn section:
# In bundle.md
spawn:
exclude_tools: [tool-task] # Agents inherit all tools EXCEPT these
# OR
tools: [tool-a, tool-b] # Agents get ONLY these toolsHow it works: Before merging parent and agent configs, apply_spawn_tool_policy() filters the parent's tools based on the spawn policy:
# In agent_config.py
def apply_spawn_tool_policy(parent: dict) -> dict:
"""Filter parent tools before merging with agent overlay."""
spawn_config = parent.get("spawn", {})
# If spawn.tools specified, use explicit list
if "tools" in spawn_config:
filtered_parent["tools"] = spawn_config["tools"]
return filtered_parent
# If spawn.exclude_tools specified, filter those out
exclude_tools = spawn_config.get("exclude_tools", [])
if exclude_tools:
filtered_parent["tools"] = [
t for t in parent["tools"]
if t.get("module") not in exclude_tools
]
return filtered_parentCommon pattern: Prevent delegation recursion by excluding tool-task:
tools:
- module: tool-task # Coordinator can delegate
- module: tool-filesystem
- module: tool-bash
spawn:
exclude_tools: [tool-task] # But agents can't delegate furtherDefault behavior: If no spawn section, agents inherit all parent tools (backward compatible).
Sub-sessions support multi-turn conversations through automatic state persistence. When a sub-session completes, its state (transcript and configuration) is saved to persistent storage, enabling the parent session to resume the conversation across multiple turns.
The system automatically persists sub-session state after each execution:
# After sub-session execution, before cleanup
from amplifier_app_cli.session_store import SessionStore
# Capture current state
context = child_session.coordinator.get("context")
transcript = await context.get_messages() if context else []
metadata = {
"session_id": sub_session_id,
"parent_id": parent_session.session_id,
"agent_name": agent_name,
"created": datetime.now(UTC).isoformat(),
"config": merged_config, # Full merged mount plan
"agent_overlay": agent_config, # Original agent config
}
# Persist to storage
store = SessionStore() # Project-scoped: ~/.amplifier/projects/{project}/sessions/
store.save(sub_session_id, transcript, metadata)Storage Location: ~/.amplifier/projects/{project-slug}/sessions/{session-id}/
transcript.jsonl- Conversation historymetadata.json- Session configuration and metadatabundle.md- Bundle snapshot (if applicable)
Resume a previous sub-session by providing its session_id:
from amplifier_app_cli.session_spawner import resume_sub_session
# Resume by session ID
result = await resume_sub_session(
sub_session_id="parent-123-zen-architect-abc456",
instruction="Now add OAuth 2.0 support"
)
# Returns: {"output": str, "session_id": str}Resume Process:
- Load transcript and metadata from
SessionStore - Recreate
AmplifierSessionwith stored configuration - Restore transcript to context via
add_message() - Execute new instruction with full conversation history
- Save updated state
- Cleanup and return
Key Design Points:
- Stateless: Each resume loads fresh from disk (no in-memory caching)
- Deterministic: Uses stored merged config (independent of parent changes)
- Self-contained: All state needed for reconstruction persists with session
- Resumable: Survives parent session restarts and crashes
The task tool provides a unified interface for both spawning new sub-sessions and resuming existing ones:
Spawn new sub-session (agent parameter required):
# Via task tool
result = tool_execute({
"agent": "zen-architect",
"instruction": "Design authentication system"
})
# Returns: {"response": str, "session_id": "parent-123-zen-architect-abc456"}Resume existing sub-session (session_id parameter triggers resume):
# Via task tool - note session_id instead of agent
result = tool_execute({
"session_id": "parent-123-zen-architect-abc456", # From previous spawn
"instruction": "Add OAuth 2.0 support"
})
# Returns: {"response": str, "session_id": "parent-123-zen-architect-abc456"}Input Schema:
{
"agent": str, # Optional - required for spawn, not needed for resume
"instruction": str, # Required - task for agent to execute
"session_id": str, # Optional - when provided, triggers resume instead of spawn
"model_role": str, # Optional - semantic role override (e.g., "coding", "fast")
"provider_preferences": list, # Optional - ordered fallback chain for provider/model
}Routing Logic: If session_id provided → resume_sub_session(), else → spawn_sub_session()
Control which provider/model a spawned agent uses via provider_preferences:
result = await task_tool.execute({
"agent": "foundation:explorer",
"instruction": "Quick analysis",
"provider_preferences": [
{"provider": "anthropic", "model": "claude-haiku-*"},
{"provider": "openai", "model": "gpt-4o-mini"},
]
})- System tries each preference in order until finding an available provider
- Model names support glob patterns (e.g.,
claude-haiku-*→ latest haiku) - See amplifier-foundation for
ProviderPreferencedetails
The model_role parameter lets the caller override the agent's default model role for a specific delegation. The routing matrix resolves the role to a concrete provider/model based on installed providers.
# Delegate with a model role override
result = await task_tool.execute({
"agent": "foundation:explorer",
"instruction": "Analyze these UI screenshots and suggest improvements",
"model_role": "vision"
})Precedence (highest to lowest):
provider_preferenceson the delegation call — explicit provider/model pinningmodel_roleon the delegation call — semantic role override- Agent frontmatter
model_role— the agent's own declared preference - No preference — session default (resolved from the
generalrole)
If both model_role and provider_preferences are provided in the same call, provider_preferences wins.
Available roles are injected into session context by the routing hook at session start. The standard roles are:
| Role | Use for |
|---|---|
general |
Versatile catch-all, no specialization needed |
fast |
Quick parsing, classification, file ops, bulk work |
coding |
Code generation, implementation, debugging |
ui-coding |
Frontend/UI code — components, layouts, styling, spatial reasoning |
security-audit |
Vulnerability assessment, attack surface analysis, code auditing |
reasoning |
Deep architectural reasoning, system design, complex multi-step analysis |
critique |
Analytical evaluation — finding flaws in existing work |
creative |
Design direction, aesthetic judgment, high-quality creative output |
writing |
Long-form content — documentation, marketing, case studies, storytelling |
research |
Deep investigation, information synthesis across multiple sources |
vision |
Understanding visual input — screenshots, diagrams, UI mockups |
image-gen |
Image generation, visual mockup creation, visual ideation |
critical-ops |
High-reliability operational tasks — infrastructure, orchestration |
# Turn 1: Initial delegation
response1 = await task_tool.execute({
"agent": "zen-architect",
"instruction": "Design a caching system"
})
session_id = response1["session_id"] # Save for later
# Turn 2: Resume with refinement
response2 = await task_tool.execute({
"session_id": session_id,
"instruction": "Add TTL support to the cache"
})
# Turn 3: Continue iteration
response3 = await task_tool.execute({
"session_id": session_id,
"instruction": "Add eviction policies"
})
# Each turn builds on previous contextMissing Session:
# Attempting to resume non-existent session
try:
await resume_sub_session("fake-id", "test")
except FileNotFoundError as e:
print(f"Session not found: {e}")
# Error: "Sub-session 'fake-id' not found. Session may have expired..."Corrupted Metadata:
# If metadata.json is corrupted
try:
await resume_sub_session("corrupted-id", "test")
except RuntimeError as e:
print(f"Session corrupted: {e}")
# Error: "Corrupted session metadata for 'corrupted-id'..."Observability: Resume operations emit session:resume events for monitoring and debugging.
# List all agents (includes bundle agents)
amplifier agent list
# Example output:
# zen-architect developer-expertise
# bug-hunter developer-expertise
# design-intelligence:art-director user-bundle
# project-reviewer project# Show agent configuration
amplifier agent show zen-architect
# Output: Agent config in YAML formatAgents are loaded automatically when bundles specify them:
# In bundle.md (Smart Single Value format)
agents: all # Load all discovered agents
# Or: agents: [zen-architect, bug-hunter] # Load specific agents
# Or: agents: none # Disable agentsThen use via task tool delegation within sessions (see bundle documentation for details).
# Test modified agent without committing
export AMPLIFIER_AGENT_ZEN_ARCHITECT=~/work/test-zen.md
amplifier run "design system" # Uses test version
# Unset to use normal resolution
unset AMPLIFIER_AGENT_ZEN_ARCHITECT# Use one-off agent for specific task
export AMPLIFIER_AGENT_SPECIAL_ANALYZER=/tmp/special.md
amplifier run "analyze codebase"
unset AMPLIFIER_AGENT_SPECIAL_ANALYZERamplifier-app-cli uses amplifier-foundation library for ALL agent functionality:
What CLI provides (policy):
- CLI-specific search paths (user, project, bundle directories)
- Environment variable override mechanism
- CLI commands for listing/showing agents
- Integration with bundle system
What library provides (mechanism):
- Agent file format parsing (markdown + YAML frontmatter)
- AgentResolver (path-based resolution)
- AgentLoader (loading and parsing agent files)
- Agent schemas (Agent, AgentMeta, SystemConfig)
- First-match-wins resolution logic
Boundary: CLI calls library APIs, library doesn't know about CLI.
Agent Concepts:
- → amplifier-foundation - Agent system design and API
- → Bundle Guide - How to create bundles and agents
Kernel Mechanism:
- → SESSION_FORK_SPECIFICATION.md - Session forking contract
Document Version: 1.1 Last Updated: 2025-11-06 (Added multi-turn sub-session resumption)