Skip to content

v0.2.0 orchestration + memory subsystem#214

Merged
unifiedh merged 25 commits intomainfrom
unifiedh_fixes_v0.2.0
Feb 24, 2026
Merged

v0.2.0 orchestration + memory subsystem#214
unifiedh merged 25 commits intomainfrom
unifiedh_fixes_v0.2.0

Conversation

@unifiedh
Copy link
Collaborator

@unifiedh unifiedh commented Feb 24, 2026

  • 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

    • Applies 5 architect-noted fixes from post-implementation review: dedup normalizeTaskResult, rename FundingProtocol params, add role column to children table, wire plan review to reviewPlan(), and adds 10 new test files (186 tests)
    • Includes auto-topup credits from USDC mid-loop to prevent runtime death

    Changes

    Orchestration Engine (new)

    • Task Graph (src/orchestration/task-graph.ts): DAG-based task decomposition with dependency resolution, cycle detection, retry logic, and goal progress tracking
    • Orchestrator (src/orchestration/orchestrator.ts): State-machine tick loop (idle → classifying → planning → plan_review → executing → complete/failed) with agent matching, funding, and replanning
    • Planner (src/orchestration/planner.ts): LLM-powered goal decomposition with custom role definitions, cost estimation, and dependency validation
    • Plan Mode (src/orchestration/plan-mode.ts): Execution state controller with phase transitions, plan persistence/review, and replan triggers
    • Health Monitor (src/orchestration/health-monitor.ts): Agent health checks (heartbeat staleness, stuck tasks, error loops, credit depletion) with auto-heal actions
    • Messaging (src/orchestration/messaging.ts): Typed inter-agent messaging with priority routing, retry backoff, and pluggable transport
    • Simple Tracker (src/orchestration/simple-tracker.ts): Agent tracker and funding protocol implementations
    • Types (src/orchestration/types.ts): Shared interfaces for AgentTracker, FundingProtocol, OrchestratorTickResult

    Memory Subsystem (new)

    • Context Manager (src/memory/context-manager.ts): Model-aware context assembly with token-budget enforcement
    • Compression Engine (src/memory/compression-engine.ts): 5-stage progressive compression cascade (compact → compress → summarize → checkpoint → emergency truncate)
    • Event Stream (src/memory/event-stream.ts): Append-only event log with compaction and pruning
    • Knowledge Store (src/memory/knowledge-store.ts): Cross-agent knowledge base with category-based retrieval
    • Enhanced Retriever (src/memory/enhanced-retriever.ts): Metadata-based relevance scoring with dynamic budget, feedback tracking, and query enhancement
    • Agent Context Aggregator (src/memory/agent-context-aggregator.ts): Triage-based child update aggregation to prevent parent context explosion

    Inference (new)

    • Unified Inference Client (src/inference/inference-client.ts): Multi-provider client with tier routing, retry/failover, circuit breaker, and survival mode
    • Provider Registry (src/inference/provider-registry.ts): Model configuration registry with tier resolution and provider management

    Architect Fixes (5)

    1. Fix 1: Extracted duplicate normalizeTaskResult — now single export from task-graph.ts, imported in orchestrator.ts
    2. Fix 2: Renamed FundingProtocol params addrchildAddress in types.ts and simple-tracker.ts
    3. Fix 3: Added role column to children table via MIGRATION_V9_ALTER_CHILDREN_ROLE in schema.ts; SimpleAgentTracker.getIdle() now reads role from DB
    4. Fix 4: Wired handlePlanReviewPhase to call reviewPlan() — loads plan from KV, handles auto/supervised/consensus modes
    5. Fix 5: 10 new test files (186 tests) — see below

    Modified Files

    • src/state/schema.ts: Added MIGRATION_V9_ALTER_CHILDREN_ROLE
    • src/state/database.ts: Wired V9 role migration, added orchestration/memory DB helpers
    • src/agent/loop.ts: Integrated orchestrator tick and auto-topup
    • src/agent/context.ts: Added memory budget exports
    • src/agent/system-prompt.ts: Added orchestration context injection
    • src/heartbeat/tasks.ts: Added orchestration heartbeat tasks
    • src/memory/ingestion.ts: Enhanced memory ingestion pipeline

    Test Coverage (186 new tests)

    Unit Tests (5 files, 114 tests)

    File Tests Coverage
    orchestrator.test.ts 26 tick phases, matchTaskToAgent, funding, collectResults, handleFailure, planReview
    planner.test.ts 26 validatePlannerOutput, buildPlannerPrompt, planGoal, replanAfterFailure, custom roles
    simple-tracker.test.ts 18 getIdle, getBestForTask, updateStatus, register, SimpleFundingProtocol
    health-monitor.test.ts 22 checkAll (healthy/unhealthy/dead/stuck/error loop), autoHeal (fund/restart/reassign/stop)
    agent-context-aggregator.test.ts 22 triageUpdate (error/financial/blocked/completed/heartbeat), aggregateChildUpdates

    Integration Tests (5 files, 72 tests)

    File Tests Coverage
    plan-execute-flow.test.ts 14 Full lifecycle, replan on failure, plan rejection, edge cases
    multi-agent-coordination.test.ts 12 Parallel assignment, result collection, dependency chains, failure propagation
    compression-cascade.test.ts 12 Threshold triggers, stage execution, stage 3→4 fallthrough, metrics
    inference-failover.test.ts 17 Provider resolution, 429/503 failover, circuit breaker, survival mode
    memory-retrieval.test.ts 17 Scored retrieval, dynamic budget, feedback tracking, query enhancement

    Test plan

    • pnpm exec tsc --noEmit — zero type errors
    • pnpm vitest run — all tests pass (existing + 186 new)
    • Verify normalizeTaskResult exported once from task-graph.ts
    • Verify addr: removed from types.ts (now childAddress)
    • Verify role column migration in schema.ts V9
    • Verify handlePlanReviewPhase calls reviewPlan()

Open with Devin

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>
devin-ai-integration[bot]

This comment was marked as resolved.

unifiedh and others added 4 commits February 24, 2026 23:55
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>
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View 12 additional findings in Devin Review.

Open in Devin Review

Comment on lines +819 to +823
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));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 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:

  1. handleReplanningPhase calls this.persistPlannerOutput(goal.id, output, "replan") → stores under orchestrator.replan.<goalId>
  2. State transitions to plan_review
  3. handlePlanReviewPhase looks up orchestrator.plan.<goalId>not found
  4. Because planRow is 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.

Suggested change
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));
}
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

unifiedh and others added 2 commits February 25, 2026 00:25
…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>
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View 14 additional findings in Devin Review.

Open in Devin Review

Comment on lines +611 to +624
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));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 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:

  1. Reset old tasks (line 611-620):
UPDATE task_graph SET status = 'pending', ... WHERE goal_id = ? AND status IN ('failed', 'blocked')
  1. Set goal active (line 622):
updateGoalStatus(this.params.db, goal.id, "active");
  1. 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.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

unifiedh and others added 18 commits February 25, 2026 00:33
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>
@unifiedh unifiedh merged commit a6b2b11 into main Feb 24, 2026
0 of 6 checks passed
@unifiedh unifiedh deleted the unifiedh_fixes_v0.2.0 branch February 24, 2026 17:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants