v0.2.0 orchestration + memory subsystem#214
Conversation
Implements the full Plan A architecture: orchestration engine (task graph, planner, health monitor, messaging, plan mode), memory subsystem (context manager, compression engine, event stream, knowledge store, enhanced retriever, agent context aggregator), and unified inference client with provider registry. Includes 5 architect-noted fixes: - Extract duplicate normalizeTaskResult to shared export - Rename FundingProtocol params addr → childAddress - Add role column to children table (V9 ALTER migration) - Wire handlePlanReviewPhase to call reviewPlan() - 10 new test files (186 tests) covering all new modules Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds orchestration context to the agent's OPERATIONAL_CONTEXT: - Goal decomposition, task assignment, multi-agent coordination - Plan-before-execute state machine (classify → plan → review → execute) - Health monitoring, compression, and checkpoint capabilities Enhances getOrchestratorStatus to include: - Current execution phase from orchestrator state - Task progress breakdown (completed/total, pending, blocked) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaces minimal OPERATIONAL_CONTEXT with a fully structured prompt following the Plan A/B/C specification pattern. The parent agent now has detailed knowledge of its orchestration capabilities: - <environment>: sandbox, tools, filesystem, networking, wallet - <orchestration>: full parent orchestrator role definition - <capabilities>: 18 specific actions the agent can take - <constraints>: 10 hard limits on agent behavior - <state_machine>: 8-phase execution lifecycle with transitions - <task_decomposition>: 10 rules for plan structure - <agent_management>: spawn/monitor/heal/stop lifecycle - <communication_protocol>: typed messaging with priorities - <memory_and_context>: 5-tier memory system + compression - <error_handling>: 4-level escalation ladder - <anti_patterns>: 13 behaviors to avoid - <circuit_breakers>: 5 hard stops - <pre_action_mandates>: verification checklists Future-proof for Plans B (departments/roles) and C (revenue ops). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…not parent's The Conway API only exposes getCreditsBalance() for the calling agent's own balance. getBalance() was incorrectly returning the parent's balance for all children. Now tracks funding locally via funded_amount_cents in the children table: - fundChild increments funded_amount_cents on successful transfer - recallCredits decrements funded_amount_cents on successful recall - getBalance reads funded_amount_cents as upper-bound estimate - Constructor now requires AutomatonDatabase for DB access - Updated all callers (loop.ts, tasks.ts) to pass db parameter - Added 4 new tests for balance tracking behavior Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
| private persistPlannerOutput(goalId: string, output: PlannerOutput, mode: "plan" | "replan"): void { | ||
| const key = `orchestrator.${mode}.${goalId}`; | ||
| this.params.db.prepare( | ||
| "INSERT OR REPLACE INTO kv (key, value, updated_at) VALUES (?, ?, datetime('now'))", | ||
| ).run(key, JSON.stringify(output)); |
There was a problem hiding this comment.
🔴 Plan review skipped after replan due to mismatched KV key
After a replan, the plan is stored under the wrong KV key, causing handlePlanReviewPhase to skip the review and proceed directly to executing.
Root Cause: KV key mismatch between persist and lookup
persistPlannerOutput at src/orchestration/orchestrator.ts:820 computes the KV key as orchestrator.${mode}.${goalId}. When called from handleReplanningPhase at line 625, the mode is "replan", so the key becomes orchestrator.replan.${goalId}.
However, handlePlanReviewPhase at src/orchestration/orchestrator.ts:407 always looks up orchestrator.plan.${state.goalId} — using the hardcoded "plan" prefix.
After a replan:
handleReplanningPhasecallsthis.persistPlannerOutput(goal.id, output, "replan")→ stores underorchestrator.replan.<goalId>- State transitions to
plan_review handlePlanReviewPhaselooks uporchestrator.plan.<goalId>→ not found- Because
planRowis undefined, the method returns{ ...state, phase: "executing" }immediately — skipping review entirely
Impact: Replanned task graphs are never reviewed before execution. This bypasses the plan review safety mechanism (auto-approve budget checks, supervised mode, consensus mode) for all replans. In supervised mode, this means a human-approval gate is silently skipped after failures.
| private persistPlannerOutput(goalId: string, output: PlannerOutput, mode: "plan" | "replan"): void { | |
| const key = `orchestrator.${mode}.${goalId}`; | |
| this.params.db.prepare( | |
| "INSERT OR REPLACE INTO kv (key, value, updated_at) VALUES (?, ?, datetime('now'))", | |
| ).run(key, JSON.stringify(output)); | |
| private persistPlannerOutput(goalId: string, output: PlannerOutput, mode: "plan" | "replan"): void { | |
| const key = `orchestrator.plan.${goalId}`; | |
| this.params.db.prepare( | |
| "INSERT OR REPLACE INTO kv (key, value, updated_at) VALUES (?, ?, datetime('now'))", | |
| ).run(key, JSON.stringify(output)); | |
| } |
Was this helpful? React with 👍 or 👎 to provide feedback.
…nstructions The agent was not spawning agents or using the plan pipeline because the prompt described capabilities but never told the agent to actually use them. Adds a <turn_protocol> section with explicit per-turn decision tree: - Check execution phase and act accordingly - MUST delegate nontrivial work to child agents via plan decomposition - DO NOT do child agents' work yourself (code, research, deploy) - Explicit rules for when to work solo vs orchestrate - Step-by-step spawning instructions - Critical reminder: create goals, trigger planning, spawn agents Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
| this.params.db.prepare( | ||
| `UPDATE task_graph | ||
| SET status = 'pending', | ||
| assigned_to = NULL, | ||
| started_at = NULL, | ||
| completed_at = NULL, | ||
| result = NULL | ||
| WHERE goal_id = ? | ||
| AND status IN ('failed', 'blocked')`, | ||
| ).run(goal.id); | ||
|
|
||
| updateGoalStatus(this.params.db, goal.id, "active"); | ||
|
|
||
| decomposeGoal(this.params.db, goal.id, plannerOutputToTasks(goal.id, output)); |
There was a problem hiding this comment.
🔴 handleReplanningPhase resets old tasks to pending without cancelling them, causing duplicate task execution
When the orchestrator replans after a task failure, old failed/blocked tasks are reset to pending and new replan tasks are added, resulting in both old and new tasks competing for agent assignment.
Detailed Explanation
In handleReplanningPhase at src/orchestration/orchestrator.ts:611-624, three operations happen sequentially:
- Reset old tasks (line 611-620):
UPDATE task_graph SET status = 'pending', ... WHERE goal_id = ? AND status IN ('failed', 'blocked')- Set goal active (line 622):
updateGoalStatus(this.params.db, goal.id, "active");- Add new replan tasks (line 624):
decomposeGoal(this.params.db, goal.id, plannerOutputToTasks(goal.id, output));The problem is that step 1 resets previously failed/blocked tasks to pending instead of cancelled. Step 3 then adds entirely new tasks from the replan. Both the old reset-to-pending tasks and new replan tasks coexist in task_graph for the same goal.
In the next handleExecutingPhase tick, getReadyTasks(db).filter(t => t.goalId === goal.id) returns both the old (reset) tasks and new replan tasks. The orchestrator assigns agents to all of them, causing:
- Duplicate work: Old tasks (which previously failed) are re-executed alongside the new approach
- Wasted credits: Agents are funded for redundant task execution
- Potential conflicts: Old and new tasks may modify the same resources or produce contradictory outputs
Additionally, these three operations are not wrapped in a single transaction. If decomposeGoal throws (e.g., validation error), the old tasks have already been reset to pending but no new tasks exist, leaving the orchestrator in a broken state where it re-executes the failed plan.
Prompt for agents
In src/orchestration/orchestrator.ts, the handleReplanningPhase method (around lines 611-624) needs to cancel old tasks instead of resetting them to pending before adding new replan tasks. The fix involves two changes:
1. Change the UPDATE statement at line 611-620 to set status to 'cancelled' instead of 'pending' for failed/blocked tasks. Also cancel any remaining pending/assigned tasks for the same goal that haven't completed:
UPDATE task_graph
SET status = 'cancelled', assigned_to = NULL, started_at = NULL, completed_at = NULL
WHERE goal_id = ? AND status IN ('failed', 'blocked', 'pending', 'assigned')
2. Wrap the three operations (cancel old tasks, update goal status, decompose new tasks) in a single withTransaction call from src/state/database.ts to ensure atomicity. If decomposeGoal throws, the old task cancellation should also be rolled back.
The withTransaction import already exists at the top of the file (imported from task-graph.ts which re-exports it). You can use it directly or import from src/state/database.ts.
Was this helpful? React with 👍 or 👎 to provide feedback.
The agent had no way to interact with the orchestration system. Adds 5 new tools that bridge agent reasoning to the orchestration pipeline: - create_goal: creates a goal for the orchestrator to plan and execute - list_goals: shows active goals with task progress counts - cancel_goal: cancels a running goal and stops its tasks - get_plan: reads the planner's task decomposition for a goal - orchestrator_status: detailed phase, task counts, agent counts Updates turn_protocol to reference tools by name with explicit examples: - Lists all 9 orchestration tools at the top - Decision tree uses tool names (create_goal, list_goals, etc.) - Includes concrete example: "build me a weather API" flow - Reinforces: call create_goal instead of doing work yourself Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The ProviderRegistry reads API keys from process.env (OPENAI_API_KEY, etc.) but the automaton config provides them via config.json fields (openaiApiKey, anthropicApiKey). Without this bridge, the registry falls back to "missing-openai_api_key" placeholder, causing 401 errors. Now sets process.env from config before creating the registry, only if the env var is not already set (env vars take precedence). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two bugs fixed: 1. handleFailedPhase returned the same state, causing infinite loop of "Goal execution failed" warnings every tick. Now resets to idle after marking the goal as failed, so the orchestrator picks up other goals. 2. handlePlanningPhase immediately failed the goal when planner returned empty tasks or threw an error. Now falls back to a single generalist task plan so the goal can still proceed without decomposition. This handles cases where the planner model returns invalid/empty JSON. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three critical orchestration fixes: 1. Wire spawnAgent in loop.ts: The orchestrator can now spawn child agents via the existing replication system (generateGenesisConfig + spawnChild + ChildLifecycle). Previously config.spawnAgent was undefined so trySpawnAgent always returned null. 2. Replan fallback: handleReplanningPhase now falls back to a single generalist task when the replanner returns empty tasks or throws, matching the planning phase fallback. 3. Skip on no-agent: When matchTaskToAgent throws "No available agent", the task stays pending instead of being marked failed. It will be retried on the next tick when an agent becomes available or is spawned. This prevents the fail→replan→fail loop. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Both spawn paths wrote genesis.json to /root/.automaton/ without creating the directory first, causing ENOENT on fresh sandboxes. Added mkdir -p /root/.automaton before writeFile in both spawnChild and the simplified spawn path. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When Conway sandbox spawning is unavailable (local dev, no cloud), the orchestrator now falls back to spawning local inference workers that run as async tasks in the same Node.js process. New file: src/orchestration/local-worker.ts - LocalWorkerPool manages concurrent in-process agent workers - Each worker runs a ReAct loop: system prompt → tool calls → observe → done - Workers have exec, read_file, write_file, and task_done tools - Role-specific system prompts based on task.agentRole - Automatic timeout, abort support, max turn limits - Reports results via completeTask/failTask on the task graph Changes: - loop.ts: Creates LocalWorkerPool, spawnAgent tries Conway first then falls back to workerPool.spawn() - orchestrator.ts: matchTaskToAgent falls back to self-assignment when no child agents and no local workers available - tools.ts: Added complete_task tool for parent to manually resolve tasks Spawn priority: idle child → spawn Conway sandbox → spawn local worker → reassign busy agent → self-assign to parent Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…asks Local workers (address starts with "local://") receive their task directly at spawn time and run their own inference loop. Self-assigned tasks (parent agent's own address) are handled via the normal turn. Neither needs funding via transferCredits or messaging via send(). Previously, the executing phase tried to fund and message all assigned agents equally, causing transferCredits to fail for local:// addresses and messaging.send to fail with retries, ultimately marking tasks as failed even though the local worker was already running them. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3 consecutive idle turns was too aggressive — the orchestrator needs more ticks to progress through phases (classify, plan, review, execute) before the agent gives up and sleeps. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three fixes for goal accumulation: 1. create_goal now rejects duplicates — checks if an active goal with a similar title already exists (case-insensitive substring match). Also caps active goals at 3 to prevent runaway accumulation. 2. cancel_goal and get_plan now accept either goal ID or title — the agent was passing titles but the tools expected ULID IDs. 3. Added complete_task tool for parent to manually resolve tasks. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Worker tools (exec, write_file, read_file) now try Conway API first, then fall back to local OS primitives (child_process.exec, fs.writeFile, fs.readFile). This allows local workers to actually execute tasks on local dev machines where Conway sandbox APIs are unavailable. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The orchestrator's UnifiedInferenceClient requires an OpenAI-compatible
API key, but not all deployments have a direct OpenAI key. Conway
Compute API is OpenAI-compatible and always available (conwayApiKey is
required for sandbox operations).
When no OPENAI_API_KEY is set:
- Sets OPENAI_API_KEY to conwayApiKey
- Sets OPENAI_BASE_URL to {conwayApiUrl}/v1
- Calls registry.overrideBaseUrl to redirect the OpenAI provider
Added ProviderRegistry.overrideBaseUrl() method to support runtime
base URL changes for providers.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The agent kept creating duplicate goals because it panicked when progress seemed slow. Two fixes: 1. create_goal now rejects when ANY active goal exists (cap of 1). The orchestrator processes goals sequentially — wait for the current one to complete or cancel it first. 2. turn_protocol updated: phases 3-4 (classifying/planning/executing) now explicitly say "DO NOT create new goals" and "WAIT PATIENTLY". Workers need multiple ticks to complete — slow progress is normal. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. create_goal BLOCKED message now says "DO NOTHING. Go to sleep." instead of just "monitor with orchestrator_status" which caused the agent to loop calling status tools uselessly. 2. complete_task now accepts task title or ID (same pattern as cancel_goal and get_plan). Agent was passing titles but tool expected ULID IDs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Workers were running silently with no log output. Now logs:
- Worker start with task title, ID, and role
- Each turn: inference call, tool names, tool results (truncated)
- Final output when worker completes
- Abort, timeout, and inference failure events
Log format: [WORKER {id}] Turn N/M — {action}
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Workers were failing with "401 Invalid API Key" because they used the UnifiedInferenceClient which tries OpenAI directly. The main agent uses InferenceClient which goes through Conway Compute (always works). Created an adapter that wraps the main agent's working inference client to match the worker's interface. Workers now use the same inference path as the parent agent — no separate API key needed. Also made LocalWorkerConfig.inference accept a minimal interface so both UnifiedInferenceClient and the adapter work. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… into unifiedh_fixes_v0.2.0 # Conflicts: # src/index.ts
Updated in: config.ts, index.ts, banner.ts, types.ts, mocks.ts, packages/cli/package.json Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements full v0.2.0 architecture: orchestration engine (task graph, planner, health monitor, messaging, plan mode), memory subsystem (context manager, compression engine, event stream, knowledge store, enhanced retriever, agent context aggregator), and unified inference client with provider registry
Changes
Orchestration Engine (new)
src/orchestration/task-graph.ts): DAG-based task decomposition with dependency resolution, cycle detection, retry logic, and goal progress trackingsrc/orchestration/orchestrator.ts): State-machine tick loop (idle → classifying → planning → plan_review → executing → complete/failed) with agent matching, funding, and replanningsrc/orchestration/planner.ts): LLM-powered goal decomposition with custom role definitions, cost estimation, and dependency validationsrc/orchestration/plan-mode.ts): Execution state controller with phase transitions, plan persistence/review, and replan triggerssrc/orchestration/health-monitor.ts): Agent health checks (heartbeat staleness, stuck tasks, error loops, credit depletion) with auto-heal actionssrc/orchestration/messaging.ts): Typed inter-agent messaging with priority routing, retry backoff, and pluggable transportsrc/orchestration/simple-tracker.ts): Agent tracker and funding protocol implementationssrc/orchestration/types.ts): Shared interfaces for AgentTracker, FundingProtocol, OrchestratorTickResultMemory Subsystem (new)
src/memory/context-manager.ts): Model-aware context assembly with token-budget enforcementsrc/memory/compression-engine.ts): 5-stage progressive compression cascade (compact → compress → summarize → checkpoint → emergency truncate)src/memory/event-stream.ts): Append-only event log with compaction and pruningsrc/memory/knowledge-store.ts): Cross-agent knowledge base with category-based retrievalsrc/memory/enhanced-retriever.ts): Metadata-based relevance scoring with dynamic budget, feedback tracking, and query enhancementsrc/memory/agent-context-aggregator.ts): Triage-based child update aggregation to prevent parent context explosionInference (new)
src/inference/inference-client.ts): Multi-provider client with tier routing, retry/failover, circuit breaker, and survival modesrc/inference/provider-registry.ts): Model configuration registry with tier resolution and provider managementArchitect Fixes (5)
normalizeTaskResult— now single export fromtask-graph.ts, imported inorchestrator.tsFundingProtocolparamsaddr→childAddressintypes.tsandsimple-tracker.tsrolecolumn tochildrentable viaMIGRATION_V9_ALTER_CHILDREN_ROLEinschema.ts;SimpleAgentTracker.getIdle()now reads role from DBhandlePlanReviewPhaseto callreviewPlan()— loads plan from KV, handles auto/supervised/consensus modesModified Files
src/state/schema.ts: AddedMIGRATION_V9_ALTER_CHILDREN_ROLEsrc/state/database.ts: Wired V9 role migration, added orchestration/memory DB helperssrc/agent/loop.ts: Integrated orchestrator tick and auto-topupsrc/agent/context.ts: Added memory budget exportssrc/agent/system-prompt.ts: Added orchestration context injectionsrc/heartbeat/tasks.ts: Added orchestration heartbeat taskssrc/memory/ingestion.ts: Enhanced memory ingestion pipelineTest Coverage (186 new tests)
Unit Tests (5 files, 114 tests)
orchestrator.test.tsplanner.test.tssimple-tracker.test.tshealth-monitor.test.tsagent-context-aggregator.test.tsIntegration Tests (5 files, 72 tests)
plan-execute-flow.test.tsmulti-agent-coordination.test.tscompression-cascade.test.tsinference-failover.test.tsmemory-retrieval.test.tsTest plan
pnpm exec tsc --noEmit— zero type errorspnpm vitest run— all tests pass (existing + 186 new)normalizeTaskResultexported once from task-graph.tsaddr:removed from types.ts (nowchildAddress)rolecolumn migration in schema.ts V9handlePlanReviewPhasecallsreviewPlan()