Skip to content

Latest commit

 

History

History
544 lines (407 loc) · 29.4 KB

File metadata and controls

544 lines (407 loc) · 29.4 KB

ATN Architecture Audit: Fractal Agent Unification

Date: 2026-03-25 Scope: Full codebase audit of atn/ runtime, focusing on orchestrator vs. child agent divergences Goal: Produce a refactoring plan to make every agent, from root orchestrator to deepest sub-agent, behave identically


Phase 1: Current Architecture Map

1. Agent Definition & Registration

(a) Orchestrator

  • Defined in orchestrator/__init__.py:360 via create_orchestrator_agent().
  • Returns a pipeline-mode AgentDefinition with a single cognitive step configured with tool_executors: "orchestrator".
  • Hardcoded ID: ORCHESTRATOR_ID = "orchestrator" (orchestrator/__init__.py:14).
  • Registered via runtime.setup_orchestrator() (runtime.py:2712), which also injects a status briefing and activates it.
  • Model aliases resolved inline (orchestrator/__init__.py:382-388).
  • Provider is a fallback chain: [primary, "claude_max", "anthropic", "gemini"] (orchestrator/__init__.py:400-404).
  • System prompt is the 318-line _DEFAULT_SYSTEM_PROMPT with {user_md_path} substitution.

(b) Regular Cognitive Agents

  • Created via _create_agent() in orchestrator/tools.py:894 when mode == "cognitive".
  • Returns a cognitive-mode AgentDefinition (no steps list, uses AgentMode.COGNITIVE).
  • ID is auto-generated hierarchically via runtime.generate_child_id(parent_id) → e.g., "orchestrator.1".
  • parent_id derived from caller_id (injected via _caller_id in execute_tool).
  • System prompt generated by build_delegate_prompt() in delegate_prompts.py.
  • Also registered in DelegateRegistry for UI observability.
  • Provider stored in defn.provider (string or list), resolved at execution time.

Divergence: Mode mismatch

The orchestrator is AgentMode.PIPELINE with a cognitive step. Regular cognitive agents are AgentMode.COGNITIVE. This is the root structural divergence: it causes two completely different execution paths (_execute_pipeline vs _execute_cognitive_agent).


2. Execution Lifecycle

(a) Orchestrator

  1. trigger_run()defn.mode == PIPELINE_execute_pipeline() (runtime.py:601).
  2. Pipeline drains inbox, iterates through steps (just one cognitive step).
  3. The cognitive step goes to CognitiveStepExecutor.execute() (steps/cognitive.py:108).
  4. Because tool_executors == "orchestrator" and provider.supports_orchestrate, routes to _orchestrate() (steps/cognitive.py:268).
  5. _orchestrate() calls provider.send_orchestrate() with a _tool_executor closure.
  6. The tool executor closure calls execute_tool(name, input, runtime, caller_id=context.agent_id) (steps/cognitive.py:601).
  7. Result recorded via StepResult in the pipeline's step_results.
  8. Conversation history managed via runtime.conversation (the global orchestrator ConversationStore).
  9. Session continuity: BridgeProvider _session_id enables SDK resume across turns.

(b) Regular Cognitive Agents

  1. trigger_run()defn.mode == COGNITIVE_execute_cognitive_agent() (runtime.py:838).
  2. This is a completely separate 320-line method that duplicates much of the pipeline logic.
  3. Resolves provider via _resolve_provider_for_model() (runtime.py:1487).
  4. Creates a new BridgeProvider instance per execution (not shared).
  5. Builds system prompt, drains inbox, builds user message, all inline.
  6. Creates its own _tool_executor closure (runtime.py:979) that routes through execute_tool.
  7. Creates its own _on_chunk callback for output streaming (runtime.py:994).
  8. Calls sub_provider.send_orchestrate() directly (bypassing CognitiveStepExecutor entirely).
  9. Handles result, token usage, delegate registry sync, parent notification, all inline.
  10. Conversation history managed via per-agent ConversationStore (runtime.get_agent_conversation_store()).
  11. History injected into system prompt as text (no SDK session resume for children).

Divergences:

Concern Orchestrator Cognitive Agent
Entry point _execute_pipelineCognitiveStepExecutor_orchestrate _execute_cognitive_agent (monolithic)
Provider lifecycle Shared singleton in CognitiveStepExecutor._providers Fresh instance per execution in _active_providers
Tool executor _route_tool_call in cognitive.py Inline closure in runtime.py
Streaming Via _make_event_emitters in cognitive.py Inline _on_chunk in runtime.py
Conversation store Global runtime.conversation Per-agent _agent_conversations[id]
Session resume Via BridgeProvider _session_id History text appended to system prompt
Completion notification Pipeline finally block emits events _on_agent_completed() posts inbox messages
Failure propagation _notify_parent_of_failure() (only in pipeline finally) _notify_parent_of_failure() (in cognitive finally)
Delegate registry sync Not registered in DelegateRegistry Syncs status to DelegateRegistry
Token tracking Via _accumulate_usage from step output Inline in _execute_cognitive_agent

3. Tool Routing

(a) Orchestrator

  1. LLM emits tool call → bridge relays to _tool_executor in _orchestrate() (cognitive.py:302).
  2. _route_tool_call() checks connector prefix first, then calls execute_tool() (cognitive.py:582-603).
  3. caller_id=context.agent_id is passed; this was recently fixed at line 601.
  4. execute_tool() injects _caller_id into tool input dict (tools.py:2176).
  5. Full orchestrator tool surface: 40+ tools (_TOOLS in tools.py:52-697).

(b) Regular Cognitive Agents

  1. LLM emits tool call → bridge relays to _tool_executor closure in _execute_cognitive_agent() (runtime.py:979).
  2. Shell tool check first (_SHELL_TOOL_EXECUTORS), then connector prefix check, then execute_tool().
  3. caller_id=defn.id is passed correctly.
  4. Scoped tool surface: only _DELEGATE_TOOL_NAMES (9 tools) via _get_delegate_tools() (tools.py:1853).

Divergence: Tool routing path

  • Orchestrator: _route_tool_call()execute_tool() (clean abstraction in cognitive.py)
  • Child agents: inline closure in runtime.py with shell tool handling mixed in
  • Non-bridge providers get _SHELL_TOOLS appended for children but NOT for orchestrator (bridge providers have built-in file/bash tools)

4. Provider/Model Selection

(a) Orchestrator

  • Provider chain defined at agent creation (orchestrator/__init__.py:400): [primary, "claude_max", "anthropic", "gemini"].
  • CognitiveStepExecutor._resolve_provider_chain() picks the first available from the chain.
  • Fallback is automatic with retry logic in _orchestrate() and execute().
  • Provider is a shared singleton registered in cognitive._providers["claude_max"].
  • Model can be changed via set_orchestrator_model() which re-registers the agent.

(b) Regular Cognitive Agents

  • Model stored in defn.provider / defn.cognitive_model.
  • _resolve_provider_for_model() (runtime.py:1487) creates a new provider instance per execution:
    • gemini-* → new OpenAICompatibleProvider
    • gpt-* / o1-* / o3-* → new OpenAICompatibleProvider
    • Default → new BridgeProvider(model=model_name)
  • No fallback chain: if the selected provider fails, the agent fails.
  • Provider stored in _active_providers[agent_id] during execution, cleaned up after.

Divergence: Fallback and lifecycle

  • Orchestrator has automatic provider fallback; children don't.
  • Orchestrator shares a long-lived provider; children create disposable instances.

5. Inbox & Messaging

(a) Orchestrator

  • Inbox drain happens in _execute_pipeline()self.inbox.drain(defn.id) (runtime.py:625).
  • Work messages fed to cognitive step as {inbox} template substitution (cognitive.py:494-519).
  • Conversation history prepended to inbox text (for non-bridge providers) via context.runtime.conversation.get_history_for_prompt().
  • HIGH/URGENT messages wake orchestrator via _inbox_watcher_loop().

(b) Regular Cognitive Agents

  • Inbox drain in _execute_cognitive_agent() (runtime.py:910).
  • Messages assembled into user_message inline.
  • Conversation history appended to system prompt (not user message) via sliding window.
  • Same watcher loop triggers wake on HIGH/URGENT.

Divergence: History injection point

  • Orchestrator: history in user message (via {inbox} substitution) or bridge SDK resume.
  • Children: history in system prompt (bloats system prompt over time).

6. Parent-Child Notifications

(a) Orchestrator (as parent)

  • Receives child_completed WORK messages in inbox from _on_agent_completed() (runtime.py:1290).
  • If actively running, receives direct injection via send_user_message() on its BridgeProvider.
  • Receives child_error ALERT messages from _notify_parent_of_failure() (runtime.py:1227).

(b) Regular Cognitive Agents (as parent)

  • Same _on_agent_completed() mechanism works identically.
  • Same _notify_parent_of_failure() mechanism works identically.
  • Provider lookup checks both resolved_parent and raw parent_id (handles "orch" alias).

Divergence: Minimal. Notification flow is one of the best-unified areas. The "orch" vs "orchestrator" alias handling (_resolve_parent_agent_id) is a wart but functional.


7. Heartbeat & Scheduling

(a) Orchestrator

  • Can have heartbeat set via update_agent tool.
  • Scheduled via _heartbeat_table / _schedule_table just like any agent.
  • Planning review timer is orchestrator-specific (_post_planning_review(), runtime.py:1672).

(b) Regular Cognitive Agents

  • Heartbeat configured at creation via create_agent (not yet exposed in schema, but HeartbeatConfig exists in models).
  • _scheduler_loop() handles heartbeat identically for all agents.
  • Heartbeat posts WORK message to agent's own inbox, triggering a new execution.

Divergence: Minimal. Scheduling is well-unified. Planning review is orchestrator-specific by design.


8. Conversation Persistence

(a) Orchestrator

  • Uses runtime.conversation, a global ConversationStore at data_dir/conversations/.
  • History recorded by _orchestrate(): runtime.conversation.add_assistant_turn() (cognitive.py:367).
  • User messages recorded externally (by CLI/WebSocket handler posting to inbox).
  • Supports session archival and reset via new_conversation().

(b) Regular Cognitive Agents

  • Each agent gets its own ConversationStore at data_dir/agents/<id>/conversations/.
  • Created lazily via get_agent_conversation_store() (runtime.py:2831).
  • User turn recorded in _execute_cognitive_agent() before execution.
  • Assistant turn recorded in _execute_cognitive_agent() after execution.
  • No session archival/reset mechanism.

Divergence: Separate persistence paths. Same ConversationStore class, but different locations, different recording points, and different lifecycle management.


9. Bridge Process Lifecycle

(a) Orchestrator

  • Uses the shared BridgeProvider instance from cognitive._providers["claude_max"].
  • Bridge subprocess is long-lived: spawned lazily on first request, kept alive.
  • Session ID persists across orchestration turns (SDK resume).
  • _clear_bridge_session() resets session stats on conversation reset.
  • Interrupt via interrupt_orchestrator()cognitive._providers["claude_max"].interrupt().

(b) Regular Cognitive Agents

  • Each execution creates a new BridgeProvider instance (runtime.py:872).
  • Bridge subprocess spawned per execution, closed in finally block (runtime.py:1141-1144).
  • No session resume: each execution is a fresh SDK session.
  • Interrupt via interrupt_delegate()_active_providers[agent_id].interrupt().

Divergence: Lifecycle model

  • Orchestrator: persistent singleton process, session resume across turns.
  • Children: ephemeral process per execution, no session resume.
  • This means children pay cold-start cost on every execution (bun startup, SDK init).

10. Pipeline vs. Cognitive Mode

The "cognitive step inside a pipeline" pattern

The orchestrator is defined as mode=PIPELINE with one cognitive step. This was likely an early design choice when the framework only had pipeline mode. The cognitive step executor (CognitiveStepExecutor) handles the LLM call, but the surrounding pipeline machinery provides:

  • Step iteration (only 1 step, so trivially wraps)
  • Step-level event emission (STEP_STARTED, STEP_COMPLETED)
  • Step result recording
  • Token usage extraction from step output
  • StepContext construction with all services

When mode=COGNITIVE was added for child agents, _execute_cognitive_agent() was written as a parallel implementation that bypasses the pipeline/step machinery entirely. It duplicates:

  • Inbox draining
  • Connector startup
  • Provider setup
  • Tool executor construction
  • Streaming callback setup
  • Result processing
  • Token tracking
  • Status bookkeeping
  • Event emission
  • Cleanup

This duplication is the core problem. The orchestrator's execution path goes through 3 clean layers (pipeline → step executor → _orchestrate()), while children use 1 monolithic method that copies logic from all 3.


Phase 2: Divergence Classification

Accidental Divergences (should be unified)

# Divergence Location Impact
A1 Two execution paths: pipeline+cognitive-step vs monolithic _execute_cognitive_agent runtime.py:598-601 (dispatch), runtime.py:838-1177 (cognitive path) Root cause of most other divergences. ~320 lines of duplicated logic.
A2 Provider lifecycle: shared singleton vs ephemeral per-execution runtime.py:872 (creates new), runtime.py:1141 (closes after) Children pay cold-start cost every execution. No session resume.
A3 Tool routing: _route_tool_call() vs inline closure with shell-tool mixin cognitive.py:582 vs runtime.py:979 Two code paths doing the same thing. Shell tools only available in one.
A4 Conversation history injection: user message vs system prompt cognitive.py:513-518 vs runtime.py:937-974 Different approaches to the same problem. System prompt approach bloats over time.
A5 Streaming callback: _make_event_emitters() vs inline _on_chunk cognitive.py:394 vs runtime.py:994 Children's output goes to delegate log; orchestrator's goes to step events.
A6 Token tracking: _accumulate_usage() from step output vs inline runtime.py:3078 vs runtime.py:1044-1051 Different accumulation paths for the same data.
A7 DelegateRegistry sync: orchestrator not registered, children are tools.py:948-956 UI sees children in delegate tree but not orchestrator.
A8 Provider fallback: orchestrator has chain, children don't orchestrator/__init__.py:400 vs runtime.py:872 Children are fragile: single provider failure = agent failure.
A9 Mode flag mismatch: orchestrator is PIPELINE, functionally acts as COGNITIVE orchestrator/__init__.py:419 Confusing: snapshot shows mode: pipeline for the orchestrator.
A10 Completion events: pipeline emits after finally, cognitive emits in finally runtime.py:810-828 vs runtime.py:1159-1177 Different ordering of cleanup and event emission.

Necessary Divergences (orchestrator is genuinely special)

# Divergence Justification
N1 Orchestrator cannot be unregistered (runtime.py:503) Root agent must always exist.
N2 Full tool surface: orchestrator gets 40+ tools, children get 9 Children shouldn't be able to manage connectors, providers, planning, etc.
N3 Global conversation store: orchestrator conversation is the "chat" UI User interacts with orchestrator via the main chat; agent conversations are separate windows.
N4 Status briefing injection (runtime.py:2930) Only orchestrator needs initial fleet status.
N5 Planning review messages (runtime.py:1672) Only orchestrator handles planning.
N6 Model selector in UI: set_orchestrator_model() Orchestrator model is a global setting. Children have their own model config.

Missing Capabilities (children should have but don't)

# Capability Current State
M1 Provider fallback chain Children get a single provider; should support fallback like orchestrator.
M2 Session resume Children's BridgeProvider is ephemeral; long-running agents should persist sessions.
M3 Heartbeat in create_agent schema HeartbeatConfig exists but create_agent tool schema doesn't expose it clearly.
M4 Shell tools for bridge providers Bridge children get SDK-native tools but can't use the simpler shell tool abstraction for non-bridge fallback.
M5 Conversation reset No way to reset a child agent's conversation without removing and recreating it.

Phase 3: Requirements Specification

Core Requirement: Fractal Agent Identity

An agent at any level of the hierarchy MUST behave identically in terms of execution lifecycle, tool routing, inbox handling, and notification flow. The only differences are in configuration (tool surface, system prompt, provider) not in mechanism.

Testable Assertions

Execution Lifecycle

  1. R-EXEC-1: All cognitive agents (including orchestrator) MUST be dispatched through the same execution method.
  2. R-EXEC-2: The execution method MUST support both fresh and resumed sessions.
  3. R-EXEC-3: Completion and failure MUST trigger the same notification pathway for all agents.
  4. R-EXEC-4: Cleanup (provider close, status update, event emission) MUST follow the same order for all agents.

Tool Routing

  1. R-TOOL-1: All cognitive agents MUST route tool calls through the same function.
  2. R-TOOL-2: caller_id MUST be passed for every tool call from every agent.
  3. R-TOOL-3: Tool surface MUST be configurable per agent (not per execution path).
  4. R-TOOL-4: Shell tools for non-bridge providers MUST be handled uniformly.

Provider Management

  1. R-PROV-1: Provider selection MUST support fallback chains for all agents.
  2. R-PROV-2: Provider lifecycle (shared vs ephemeral) MUST be a configuration choice, not a code path difference.
  3. R-PROV-3: Session resume MUST be available to any agent using BridgeProvider.

Inbox & Messaging

  1. R-INBOX-1: Inbox drain and message assembly MUST use the same code for all agents.
  2. R-INBOX-2: Conversation history injection MUST use the same strategy for all agents.
  3. R-INBOX-3: Parent notification on completion MUST work identically regardless of agent level.

Conversation Persistence

  1. R-CONV-1: Every cognitive agent MUST have a ConversationStore.
  2. R-CONV-2: The orchestrator's ConversationStore MUST be a standard agent ConversationStore (possibly with additional UI integration).
  3. R-CONV-3: Session archival/reset MUST be available for any agent.

Registration

  1. R-REG-1: All cognitive agents MUST be registered with mode=COGNITIVE in AgentDefinition.
  2. R-REG-2: Tool surface MUST be specified in AgentDefinition configuration (not derived from execution path).

Phase 4: Refactoring Plan

Overview

The refactoring has one central move: make the orchestrator a cognitive-mode agent and route all cognitive agents through a single unified execution path. The _execute_cognitive_agent() method becomes the ONE way any cognitive agent runs, with configuration (tool surface, system prompt, provider chain) driving the differences.

Step 1: Unify the Orchestrator's Mode (Risk: LOW)

What: Change create_orchestrator_agent() to return mode=COGNITIVE instead of mode=PIPELINE with a cognitive step.

Files:

  • orchestrator/__init__.py: Change AgentDefinition to use AgentMode.COGNITIVE, populate provider, cognitive_model, system_prompt, max_turns, etc.
  • runtime.py:598-601: The dispatch in trigger_run() will now route orchestrator to _execute_cognitive_agent().

Details:

  • Move step config fields (provider, model, system, max_turns, tool_executors) to cognitive-mode fields.
  • Provider fallback chain → defn.provider (already supports str | list[str]).
  • tool_executors: "orchestrator"defn.tools = ["atn_full"] (or similar flag).

Risk: Low. The change is in definition construction. The execution path change is the critical part (Step 2).

Dependencies: None (can be done first).


Step 2: Unify _execute_cognitive_agent() to Handle All Cognitive Agents (Risk: MEDIUM)

What: Enhance _execute_cognitive_agent() to handle orchestrator-specific needs, then remove the orchestrator's pipeline execution path.

Files:

  • runtime.py:838-1177: Enhance to support:
    • Provider fallback chain (from defn.provider when it's a list)
    • Full vs scoped tool surface (from defn.tools config)
    • Shared vs ephemeral provider lifecycle (from new config flag)
    • Orchestrator-specific conversation recording (to runtime.conversation)

Key changes:

  1. Provider selection: Replace _resolve_provider_for_model() with logic that also supports fallback chains. When defn.provider is a list, try each in order.
  2. Tool surface: Replace hardcoded _get_delegate_tools() with a configurable tool list:
    • "atn_full" → all orchestrator tools
    • "atn_core" → the current 9 delegate tools
    • Custom lists via defn.tools
  3. Provider lifecycle: If defn.id == ORCHESTRATOR_ID (or a new persistent_session: bool flag), reuse provider from _active_providers instead of creating a new one.
  4. Conversation: Record to both per-agent store AND runtime.conversation when defn.id == ORCHESTRATOR_ID.

Risk: Medium. This is the core change. Must preserve orchestrator's session resume, conversation continuity, and interrupt behavior. Integration test coverage needed.

Dependencies: Step 1 (orchestrator must be cognitive-mode).


Step 3: Remove _execute_pipeline Special-Casing for Cognitive Steps (Risk: LOW)

What: Simplify _execute_pipeline() now that cognitive-mode agents never go through it.

Files:

  • runtime.py:613-833: Remove the if defn.mode == AgentMode.COGNITIVE branch from trigger_run() (it's now the only path for cognitive agents). Pipeline execution remains for actual pipeline agents.
  • steps/cognitive.py: _orchestrate() is still used by pipeline agents with cognitive steps, but no longer by the orchestrator.

Risk: Low. Pipeline agents with cognitive steps still work via CognitiveStepExecutor. The orchestrator just doesn't go through this path anymore.

Dependencies: Steps 1 and 2 (orchestrator must be fully migrated).


Step 4: Unify Tool Routing (Risk: LOW)

What: Consolidate the two tool executor closures into one shared function.

Files:

  • runtime.py:979-991: Extract _tool_executor closure into a proper method.
  • steps/cognitive.py:302-306: _route_tool_call already does this; unify with the runtime closure.

Proposed function:

async def route_tool_call(
    self, name: str, tool_input: dict, agent_id: str
) -> dict:
    """Universal tool router for all cognitive agents."""
    # Shell tools (non-bridge providers)
    if name in _SHELL_TOOL_EXECUTORS:
        return await _SHELL_TOOL_EXECUTORS[name](tool_input)
    # Connector tools (mcp_ prefix)
    if self.connectors:
        parsed = self.connectors.parse_tool_name(name)
        if parsed:
            cid, tool_name = parsed
            return await self.connectors.call_tool(cid, tool_name, tool_input)
    # Framework tools
    return await execute_tool(name, tool_input, self, caller_id=agent_id)

Move to runtime.py as a method on Runtime. Both execution paths call it.

Risk: Low. Pure refactor, same logic.

Dependencies: Step 2 (but can be done in parallel).


Step 5: Provider Fallback for Children (Risk: LOW)

What: Support defn.provider as a fallback chain (list of strings) for cognitive agents.

Files:

  • runtime.py:866-872: Enhance _resolve_provider_for_model() to accept a list and try each.
  • orchestrator/tools.py:924: When creating cognitive agents, optionally build a fallback chain.

Risk: Low. Additive feature, no breaking changes.

Dependencies: Step 2.


Step 6: Clean Up Debug Prints (Risk: NONE)

What: Remove all print(f"[DEBUG]...") statements.

Files and lines:

File Line Statement
runtime.py 990 print(f"[DEBUG] _tool_executor: ...")
runtime.py 1318 print(f"[DEBUG] _on_agent_completed: ...")
runtime.py 1320 print(f"[DEBUG] _on_agent_completed: NO parent_id...")
orchestrator/tools.py 939 print(f"[DEBUG] create_agent: ...")
orchestrator/tools.py 2177 print(f"[DEBUG] execute_tool: ...")

Risk: None. These are development artifacts.

Dependencies: None (do anytime).


Step 7: Eliminate Dead Code (Risk: LOW)

What: Remove or clean up code that becomes unnecessary after unification.

Candidates:

  1. _execute_cognitive_agent() duplication: after Step 2, the old pipeline-as-orchestrator path is dead.
  2. interrupt_orchestrator() (runtime.py:1466-1481): special method to find orchestrator's provider. After unification, interrupt_delegate() works for all agents including orchestrator.
  3. _get_bridge_provider() (runtime.py:1528-1545): special method with orchestrator vs delegate branching. After unification, just check _active_providers.
  4. _clear_bridge_session() (runtime.py:2915-2929): reaches into cognitive._providers["claude_max"]. After unification, operates on _active_providers[ORCHESTRATOR_ID].
  5. _resolve_parent_agent_id() "orch" alias (runtime.py:1183-1194): the "orch" shorthand predates the unified agent registry. After cleanup, all agents use full IDs.
  6. DelegateRegistry.generate_child_id() duplication: both DelegateRegistry and Runtime have generate_child_id() with identical logic (agent_registry.py:98 and runtime.py:1385). Consolidate to one.

Risk: Low. Each removal is small and testable.

Dependencies: Steps 1-4 (most dead code is created by the unification).


Suggested Execution Order

Phase A (Preparation, can be done immediately):
  Step 6: Remove debug prints
  Step 7 (partial): Remove generate_child_id duplication

Phase B (Core unification):
  Step 1: Change orchestrator to cognitive mode
  Step 2: Enhance _execute_cognitive_agent for all agents
  Step 4: Unify tool routing

Phase C (Cleanup):
  Step 3: Simplify pipeline execution
  Step 5: Provider fallback for children
  Step 7 (remainder): Dead code removal

Phase D (Enhancement):
  M2: Session resume for persistent children
  M3: Heartbeat in create_agent schema
  M5: Conversation reset for any agent

Risk Summary

Step Risk Impact if wrong Mitigation
1 LOW Orchestrator won't start Simple revert, just agent definition change
2 MEDIUM Orchestrator or all cognitive agents break Feature-flag: keep both paths, switch via config
3 LOW Pipeline agents with cognitive steps break Only touch dispatch, not pipeline logic
4 LOW Tool calls fail Keep old closures as fallback during transition
5 LOW No downside; additive n/a
6 NONE n/a n/a
7 LOW Reference errors Run full test suite after each removal

Appendix A: File Map

File Lines Role
runtime.py 3125 Central runtime: execution, scheduling, providers, lifecycle
orchestrator/tools.py 2183 Tool definitions + executors for LLM tool calls
orchestrator/__init__.py 433 Orchestrator agent definition + system prompt
steps/cognitive.py 649 CognitiveStepExecutor: LLM call abstraction
providers/bridge.py 842 Claude Max bridge subprocess provider
providers/base.py 292 Abstract provider interface
models.py 331 Core data models (AgentDefinition, ExecutionRecord, etc.)
agent_registry.py 278 DelegateRegistry: hierarchy tracking
inbox.py 87 InboxManager: message queues
conversation.py 287 ConversationStore: persistent conversation history
delegate_prompts.py 262 System prompt builder for delegate sub-agents
tool_registry.py 389 Unified tool registry (connectors + pipeline tools)
steps/base.py 50 StepContext and StepExecutor base class

Appendix B: Debug Print Inventory

See Step 6 above for the complete list (5 statements across 2 files).

Appendix C: "Orch" Alias Problem

The codebase uses both "orch" and "orchestrator" as IDs for the root agent:

  • ORCHESTRATOR_ID = "orchestrator" (the canonical ID in the agent registry)
  • "orch" used as parent_id shorthand in delegate generation (generate_child_id("orch")"orch.1")
  • _resolve_parent_agent_id() maps "orch""orchestrator" at runtime
  • get_children() checks both mappings

This creates a split-brain: children's parent_id says "orch" but the registry key is "orchestrator". The resolution function papers over it, but it's a source of subtle bugs (e.g., provider lookup for parent injection checks both).

Recommendation: After unification, standardize on "orchestrator" everywhere. Change generate_child_id to use the full ORCHESTRATOR_ID for the root. Existing delegate IDs ("orch.1", "orch.1.2") keep working via a one-time migration or backward-compat alias.