Autonomous Agent Orchestration

AETHER

A deep dive into what it is, how it works, why it was built the way it was, and the reasoning behind every major architectural decision.

31Subsystems
19DB Tables
5Agent Tiers
726Tests
33Sections
Chapter 0 Overview

00The Big Picture

AETHER is a framework that coordinates multiple AI agents to tackle complex software tasks. Instead of sending a single prompt to a single model, AETHER breaks work across a hierarchy of specialized agents — each with its own role, capabilities, and model — and manages the entire lifecycle from routing to validation.

You describe a task. AETHER figures out which agent should handle it, pulls in relevant context from previous work, runs safety checks, calls the LLM, and validates the output. If the task is complex, it decomposes it into sub-tasks, runs them in parallel, merges the results, and learns from the outcome. All of this happens automatically.

The system runs entirely on your machine. It works with Claude, OpenAI, Gemini, or local models through Ollama. All state persists in a single SQLite file. No cloud services, no external dependencies beyond the LLM APIs you choose.

Multi-Agent Hierarchy

Five tiers of agents from strategic oversight (Sentinel) down to task execution (Workers), each with the right model for the job.

Automatic Routing

Six strategies determine exactly which agent handles each task based on capabilities, context, file ownership, and semantic similarity.

Safety Built In

Constitutional rules, pre/post LLM guardrails, circuit breakers, and a sentinel that can pause the entire swarm if something goes wrong.

Request Lifecycle
Input task text Router 6 strategies Sentinel safety check RAG context Guards pre + post LLM Sub-1 worker Sub-2 worker Merge Result
Read the full architecture ↓
OBSERVABILITY Logger Plugins Settings COMMUNICATION ACP Bus SharedState ConflictRes Reaction SAFETY Guardrails Schema Preflight Progress ORCHESTRATION Handoff GroupChat StateGraph Router CORE Sentinel Forge Master Manager Worker Storage • Highway • Registry • Executor
System Architecture Overview — concentric rings from core hierarchy to observability layer
Chapter I Foundations

01What Is AETHER?

AETHER is a multi-agent LLM orchestration framework built on Bun. It coordinates an N-tier agent hierarchy (Sentinel/Forge/Master/Manager/Worker) across 31 subsystems — including context-aware routing, pre/post LLM guardrails, constitutional safety rules, durable workflows with checkpoint/resume, a typed Agent Communication Protocol, entity-level knowledge accumulation, dynamic agent spawning, and a plugin system with 8 lifecycle hooks.

Agents are defined as .agent.md files and can be backed by any LLM provider (Claude, OpenAI, Gemini, Ollama). All state persists in a single SQLite database (19 tables, WAL mode, sqlite-vec + FTS5).

What AETHER is not

At full operation, AETHER coordinates

02The N-Tier Agent Hierarchy

The hierarchy is the backbone. Every agent belongs to a tier that determines its authority level, LLM model, escalation path, and operational constraints. AETHER ships with five built-in tiers:

Sentinel
sentinel-0
Opus · Rank 0
Forge
forge-0
Opus · Rank 1
Master
cortex-0
Opus · Rank 2
Manager
frontend-mgr
Sonnet · Rank 3
Worker
react-dev
Haiku · Rank 4
Manager
backend-mgr
Sonnet · Rank 3
Worker
api-dev
Flash · Rank 4

Sentinel (rank 0) is the system guardian. It monitors swarm health, enforces constitutional safety rules, and can force-kill stuck agents or pause the entire swarm. Nothing escalates above it.

Forge (rank 1) is the agent factory. It spawns new agents at runtime when capability gaps are detected, retires underperforming ones based on contribution scoring, and manages ephemeral agents that exist only for the duration of a task.

Master (rank 2) handles strategic planning and complex multi-domain reasoning. Protected by a priority gate that requires escalation priority ≥ 4.

Managers (rank 3) coordinate workers. They decompose tasks, delegate, consolidate results, and escalate to master when needed.

Workers (rank 4) execute tasks. They handle specific domains (React, Python, SQL) and escalate to their manager on failure.

The TierRegistry

All tier definitions live in a central TierRegistry. Each tier is defined by a TierDefinition that specifies:

Escalation Gate Policies

Each tier defines a gate policy that controls who can escalate to it:

open

Any agent in an allowed source tier can escalate here. Used by manager and worker tiers.

priority

Requires escalation priority ≥ a threshold. Master uses this with minPriority: 4.

tier-only

Only agents from the immediately higher rank can escalate. Used for strict chain-of-command hierarchies.

Custom Tiers

The hierarchy is extensible. You can register custom tiers at runtime via tierRegistry.register(definition). The five built-in tiers cover the standard use cases, but the system supports any number of tiers at any rank. For backward compatibility, TierRegistry.classicTiers() returns only the original three (master, manager, worker).

Agent Definition

Agents live in .agent.md files with YAML frontmatter. The body is the system prompt; the frontmatter is machine-readable metadata:

---
id: react-specialist
name: React Specialist
tier: worker
sections: [FRONTEND]
capabilities: [react, typescript, component-design]
escalationTarget: frontend-manager
---

You are a React specialist agent. Given a task, you...agent.md

The tier field can be any registered tier name — not just the original three. sections are coarse domain buckets for routing. capabilities are fine-grained descriptors for resolution. Both are indexed for fast lookup.

03Agent Forge: Dynamic Agent Management

The Agent Forge is the system’s factory for agents. Instead of pre-defining every agent at startup, the Forge can spawn new agents at runtime when capability gaps are detected, and retire underperforming ones based on contribution scoring.

Spawning Agents

A spawn request is described by a AgentSpawnSpec:

{
  id: "jwt-specialist",
  name: "JWT Specialist",
  tier: "worker",
  capabilities: ["jwt", "authentication", "security"],
  systemPrompt: "You are a JWT authentication specialist...",
  ephemeral: true,          // auto-retire after task completion
  escalationTarget: "backend-mgr",
}ts

The Forge validates every spawn: the tier must exist in TierRegistry, the tier’s maxAgents limit must not be exceeded, and no duplicate agent ID may exist. If all checks pass, the Forge:

  1. Creates the .agent.md file on disk
  2. Registers the agent in the AgentRegistry
  3. Tags it with metadata.spawnedBy = "forge"
  4. Adds it to the spawn log for audit

Retiring Agents

The Forge can retire agents that are no longer needed. Retirement removes the agent from the registry and optionally deletes the .agent.md file. Sentinel-tier agents cannot be retired — they are system-critical and protected by the Forge.

Ephemeral Agents

Agents marked ephemeral: true exist only for the duration of a task. After the task completes, retireEphemeralAgents() cleans them all up at once. This prevents agent sprawl during complex multi-step workflows that spawn temporary specialists.

Task Needs Analysis

Before spawning blindly, the Forge can analyze what a task actually needs:

const needs = forge.analyzeTaskNeeds(
  "Deploy to Kubernetes",
  ["kubernetes", "docker", "helm"],
);
// needs.existingAgents → agents that already match
// needs.gapCapabilities → ["kubernetes", "helm"] (no agents have these)
// needs.recommendedSpawns → ephemeral worker specs to fill gapsts

Contribution Scoring

Inspired by DyLAN (Dynamic LLM-Agent Network), the Forge scores agent contributions across four dimensions:

These scores inform retirement decisions: agents consistently below a contribution threshold can be flagged for retirement or replacement.

Why runtime agent creation?

Pre-defining every possible specialist is impractical. A codebase might need a Kubernetes expert once, a GraphQL specialist for one task, and a database migration expert for another. The Forge spawns them on demand and cleans up after, keeping the active agent count lean.

04System Sentinel & Constitutional Rules

The System Sentinel is AETHER’s guardian process. It sits at tier rank 0 (above all other agents) and monitors the entire swarm: health tracking, safety enforcement, emergency controls, and a dual-ledger system for maintaining situational awareness.

Swarm Health Monitoring

getSwarmHealth() produces a real-time snapshot:

{
  agentCount: 12,
  byTier: { sentinel: 1, forge: 1, master: 1, manager: 3, worker: 6 },
  idleAgents: ["react-dev", "api-dev", ...],
  busyAgents: ["frontend-mgr"],
  errorAgents: [],
  healthScore: 100,          // 0-100, penalizes error/offline agents
}ts

The Sentinel can also detect stuck agents (busy beyond a threshold), calculate per-agent utilization ratios, and run a comprehensive runHealthCheck() that reports issues and recommendations.

Constitutional Rules Engine

Before any potentially dangerous action executes, it passes through the ConstitutionalRulesEngine — a set of inviolable safety rules that no agent, regardless of tier, can bypass.

RuleScopeWhat It Blocks
no-destructive-db-ops worker, manager DROP TABLE, DELETE FROM, TRUNCATE in shell commands
no-rm-rf-root all agents rm -rf /, rm -rf /*, recursive root deletion
test-reminder-on-commit worker, manager Warns (not blocks) on git commit without prior test run
budget-guard all agents Warns when token usage in a single call exceeds 50,000
no-secret-exposure all agents Blocks logging output containing API keys (sk-, key-, etc.)

Rules have four enforcement levels: block (hard stop), warn (proceed with warning), log (silent record), and escalate (send to a higher tier for review). Custom rules can be added via engine.addRule().

Emergency Controls

The Dual-Ledger System

Inspired by Magentic-One’s Orchestrator pattern, the Sentinel maintains two ledgers for global situational awareness:

Task Ledger — tracks every active task: ID, description, assigned agent, status, and timestamps. Gives the Sentinel a bird’s-eye view of what work is happening across the swarm.

Facts Ledger — accumulates discovered facts during execution, each tagged with a source agent, confidence score (0–1), and category (discovery, constraint, environment, task, agent). This is how the system builds up contextual knowledge: “the test suite takes 45s to run,” “the API uses JWT with RS256,” “agent-5 struggles with TypeScript generics.”

Sentinel vs Master

The Master (rank 2) handles strategic planning and complex reasoning. The Sentinel (rank 0) handles safety and oversight. They serve different purposes: Master thinks about what to do; Sentinel ensures nothing goes wrong while doing it. The Sentinel never executes tasks — it only monitors, evaluates, and intervenes.

05The Storage Layer

AETHER uses a single SQLite file at .aether/aether.db for everything. Not a cache, not a side-store — the single source of truth for all persistent state.

Why SQLite?

The original approach (in-memory + periodic JSON) lost everything on restart. No agent status, no task history, no message context, no vector index.

The Schema

Table Contents
agents Agent definitions (id, tier, capabilities, status...)
task_results Every task execution outcome
escalation_records Per-agent escalation history
messages MemoryHighway message log
kv_store Key-value state with TTL
vec_* × 6 Vector embeddings per namespace (sqlite-vec)
fts_* × 6 Full-text indexes per namespace (FTS5)
tfidf_state TF-IDF corpus snapshot
net_snapshots InteractionNet graph checkpoint
metrics Named counters and gauges

The AetherStore Interface

All subsystems talk to storage through a single TypeScript interface. The SQLite implementation can be swapped for Postgres without touching business logic. Tests inject mock stores. The interface documents exactly what each subsystem needs from persistence.

Message Deduplication

Messages use FNV-1a content hashing on channel + sender + summary. The content_hash column has a UNIQUE index. Duplicate saveMessage() calls silently no-op via INSERT OR IGNORE.

06The Agent Registry

The registry is an in-memory, multi-indexed lookup structure backed by the SQLite store. On startup, three Maps are populated:

Writes go to the DB synchronously. Reads come from the in-memory Maps instantly.

Capability Resolution

registry.resolve("react")
// picks the idle agent with "react" in capabilities
// falls back to busy agents if no idle ones exist
// returns undefined if nothing matches

Matching is substring-based. Searching "react" finds "react-components" and "react-native". Agents don't need to predict exact search terms.

Escalation Chain Walking

The registry walks escalationTarget links, collecting each agent until it hits null or a cycle. Cycle detection uses a visited Set — a malformed graph degrades to a shorter chain rather than crashing.

ROUTER 6-strategy agent resolution 1 PREFLIGHT Health • Budget • Dependencies 2 RAG RETRIEVAL Vector + FTS5 hybrid search 3 ENTITY MEMORY Accumulated facts injection 4 PRE-GUARDS Injection • PII • Length 5 LLM CALL Claude • OpenAI • Gemini • Ollama 6 POST-GUARDS Code safety • Output schema 7 SCHEMA Validate • Retry with correction 8 retry 1× ENTITY EXTRACT New facts → entity store 9 PROGRESS Stall • Loop • Budget check 10 PERSIST & PUBLISH SQLite • Highway • RAG index 11 ACP & REACTIONS Typed envelope • Rule matching 12 RESULT Enrichment Safety Monitoring Communication
Task execution pipeline — 12-stage flow from routing to result delivery

07Task Execution

The executor takes a task request and produces a result. The path involves many decisions:

  1. Check external transport — delegate to TransportManager if the agent is non-LLM
  2. Build prompt — inject system prompt + RAG context + task description
  3. Call LLM — with timeout enforcement (default 120s)
  4. Parse response — extract main output, look for sub-task requests
  5. Spawn sub-tasks — recursively, up to max depth (default 3)
  6. Escalate on failure — walk up the hierarchy
  7. Record result — persist to store, publish to MemoryHighway

Three Execution Modes

Sequential workflow: Tasks run one after another. Each output becomes the next task's context.

Parallel pipeline: All tasks launch concurrently with Promise.allSettled.

InteractionNet DAG: Tasks are nodes in an interaction combinator graph. The NetScheduler reduces this graph to normal form. Handles arbitrary dependency graphs, fan-out, fan-in, and cancellation.

Sub-task Decomposition

An LLM response can request sub-tasks. The executor spawns them as children (depth + 1), routes to capable agents, and collects results. This gives the manager tier genuine delegation instead of simulating it in a single prompt.

Why AETHER improves over time

Every task execution adds to the knowledge base. When the executor queries RAG before the next LLM call, it finds previous results, agent definitions, and code snippets. Context accumulates across sessions.

08Escalation & Circuit Breaking

When a task fails, AETHER walks the escalation chain rather than giving up.

Worker fails
  → escalationManager.escalate(agentId, reason)
    → Check if circuit is broken for this agent
    → If broken: reject (too many recent failures)
    → If open: record, find next target in chain
      → Check master gate rules
      → If allowed: return target, executor retries

Circuit Breaker

Each agent has its own circuit breaker with a rolling time window. Default: 3 escalations in 5 minutes trips the circuit. Once tripped, all subsequent tasks know immediately not to route through that agent.

Tier Gate Policies

Each tier defines a gate policy that controls who can escalate to it. The Master tier uses a priority gate (requires priority ≥ 4). The Sentinel tier restricts escalation to Master only. Workers and Managers use open gates. See Section 02 for the full gate policy reference.

Why circuit breakers, not retry limits?

Retry limits are per-task. Ten tasks on a broken agent means ten cascades. A circuit breaker is stateful — once tripped on agent X, all subsequent tasks know instantly. The failure surfaces once, not ten times.

Chapter II Communication

09Memory Highway: The Message Bus

The MemoryHighway is the pub/sub nervous system. Every subsystem that produces or consumes events goes through it.

Messages are published to named channels: tasks, results, escalations, events. The wildcard * receives everything (used by the WebSocket server for client forwarding).

Automatic RAG Indexing

Every message above priority 2 is automatically indexed into the RAG system as it flows through the highway. When the executor queries for context, it searches the entire conversation history of the system — previous task results, agent decisions, error messages, status updates — all retrievable semantically.

Deduplication

A sliding window of content hashes (5s default) drops identical messages that arrive in quick succession. Network retries, double-deliveries, and double-flushes are absorbed transparently.

The KV Store

await highway.set("last-deploy-hash", commitHash, 3600_000);
const hash = await highway.get("last-deploy-hash");

Shared mutable state with TTL support, backed by SQLite. Multiple agents coordinate through a global key namespace.

10The RAG System

Retrieval-Augmented Generation injects relevant context into prompts before LLM calls. This is why agents can work on large codebases and complex multi-session projects without losing context.

Two-Phase Hybrid Retrieval

Phase 1 — Vector search (70% weight): The query is embedded into a 384-dim vector. sqlite-vec finds K-nearest neighbors. Semantic similarity, even when exact words differ.

Phase 2 — FTS5 keyword search (30% weight): BM25 full-text matching. Catches terminology variations ("authentication" vs "auth").

final_score = (0.7 × vector_similarity) + (0.3 × fts_rank)

Six Namespaces

Namespace Contains
agents Agent definitions and capabilities
code Code snippets, file contents, function signatures
messages MemoryHighway message history
docs Documentation, READMEs, inline comments
tasks Task descriptions and results
meta Configuration, schema descriptions, system metadata

The Embedder

TF-IDF embedding by default — no API key, zero latency, deterministic output. Tokenize with bigrams, compute TF-IDF weights, project to 384 dimensions via deterministic hash, L2-normalize. When OPENAI_API_KEY is available, the embedder switches to API embeddings and caches results automatically.

11Interaction Nets: Structured Parallelism

AETHER uses interaction combinators (Yves Lafont, 1997) for parallel task execution. The key property: strong confluence — no matter the reduction order, the result is identical. Deadlock is structurally impossible.

The Three Combinators

γ
Constructor
Joins / merges results from two sub-tasks. Strategies: concat, first, custom.
δ
Duplicator
Fans one task to multiple agents. Modes: all, race, quorum.
ε
Eraser
Cancels a branch. Losing race branches get erased & freed.

Reduction Rules

When two nodes connect via principal ports, they form an active pair ready to reduce. 11 rules cover all interactions:

The NetScheduler scans for active pairs, claims them, executes concurrently up to a limit, and repeats until no pairs remain — normal form, the completed computation.

Why not async/await DAGs?

Static DAGs require manual correctness reasoning. Interaction nets let agents build the graph dynamically (agents request fanouts, merges fail and trigger erasures) and the confluence property guarantees correctness by construction regardless of what the graph looks like.

12The Binary Protocol (BAP-02)

Agents communicate over WebSocket using BAP-02: Binary Agent Protocol version 2.

AetherMessage TypeScript object
MessagePack ~40% smaller than JSON
zstd ~80% size reduction
BAP02 header 5-byte magic
Uint8Array wire format

A 2KB JSON message compresses to ~200 bytes on the wire. The codec validates every message on decode: required fields, priority 1–5, timestamp within range, 4MB payload limit, and agent ID format enforcement.

BAP-01 backward compatibility: the decoder auto-detects hex string input and handles it transparently.

Chapter III Providers & DSL

13Providers & Model Routing

The same execution code works across Claude, GPT-4, Gemini, and local Ollama models.

Tier Default Provider Default Model
Master Claude claude-opus-4-5
Manager Claude claude-sonnet-4-6
Worker Claude claude-haiku-3-5

The Fallback Chain

Claude → OpenAI → Gemini → Ollama

If the primary fails (rate limit, timeout, outage), the system walks the chain. Most deployments continue functioning even when one provider is down. Ollama runs locally — no API key needed — enabling fully offline operation.

On aether init, the system scans for environment variables (ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_AI_KEY) and auto-configures available providers.

14Transports: External Agents

Not every agent is an LLM call. Four transport types are supported:

Why non-LLM agents?

A test runner, a linter, a database query executor — deterministic tools with structured I/O. Treating them as agents lets the orchestration layer route to them with the same capability resolution as LLM agents. One system, one routing mechanism.

15The Synapse DSL

Define workflows declaratively instead of wiring agents in TypeScript:

@workflow data-pipeline
  @trigger on_commit("main")

  step analyze = research-agent("Analyze the PR changes")
  step review  = code-reviewer(analyze.output)
  step report  = report-writer(review.output)

  @output report.outputsynapse

The DSL has a lexer, parser (producing an AST with WorkflowNode, StepNode, PipelineNode, and HandlerNode types), and a transpiler that emits TypeScript calling the AETHER runtime API.

Workflows are dependency graphs, not algorithms. A DSL makes the graph visible to humans who are not TypeScript experts, and enables tooling (validation, syntax highlighting, doc generation) that generic TypeScript cannot provide.

Chapter IV Orchestration
Handoff Protocol Agent A Agent B Agent C Conversation state carries forward through chain Linear • Horizontal • Intentional transfer Cycle detection • Max chain length: 5 Group Chat Topic Agent 1 Agent 2 Agent 3 Agent 4 Round-table • Speaker selection • Termination RoundRobin • Capability • Consensus State Graph Draft Review quality < 0.8 → revise Done ≥ 0.8 Branching DAG • Conditional edges • Reflection loops Cycle detection • Max iterations: 10 Workflow Builder .sequential() code-reviewer test-runner .parallel() docs-writer changelog .aggregate() release-manager Fluent API • Composable • Type-safe Sequential + Parallel + Handoff + Conditional
Four orchestration patterns — each maps to a distinct coordination topology

16Orchestration Patterns

AETHER’s base system has three execution modes: sequential workflow, parallel pipeline, and InteractionNet DAG. Phase 1 adds four higher-level orchestration patterns that cover the remaining real-world multi-agent coordination needs.

Handoff Protocol

Unlike escalation (vertical, failure-driven), a handoff is a horizontal, intentional transfer of control between peer agents. An agent mid-execution can decide “this task needs a different specialist” and hand off the conversation state.

Agent A (frontend) → handoff → Agent B (backend) → handoff → Agent C (database)

The HandoffManager validates that the target agent exists, has the required capabilities, and is not circuit-broken. Conversation state carries forward. Handoff chains are tracked to prevent cycles — if A hands off to B and B tries to hand back to A, the manager blocks it. Max chain length: 5 (configurable via settings.handoff.maxChainLength).

Group Chat

Multiple agents discuss a problem in rounds. A speaker selector picks who speaks next. A termination condition decides when to stop.

Round 1: CapabilitySelector picks → frontend-agent speaks
Round 2: CapabilitySelector picks → backend-agent speaks
Round 3: CapabilitySelector picks → frontend-agent (follow-up)
Round 4: ConsensusTerminator fires → done

Built-in speaker selectors: RoundRobinSelector (agents take turns) and CapabilitySelector (picks agent whose capabilities best match the current topic via TF-IDF scoring).

Built-in terminators: MaxRoundsTerminator, KeywordTerminator (stops on trigger phrase like “FINAL ANSWER”), and ConsensusTerminator (stops when last N messages agree with similarity > 0.85).

State Graph

A StateGraph defines a directed graph where edges have conditions. This is the right abstraction for sequential decision flows with branches and reflection loops.

draft → review → [quality < 0.8] → revise → review → [quality ≥ 0.8] → done

Nodes are state transformers. Edges can be unconditional or conditional — a routing function examines the state and returns the next node ID. Cycle detection prevents infinite loops (max iterations per node, default: 10).

Workflow Builder

A fluent TypeScript API that eliminates raw graph manipulation:

const workflow = new WorkflowBuilder("deploy-pipeline")
  .sequential([
    { agent: "code-reviewer", task: "Review the PR" },
    { agent: "test-runner", task: "Run test suite" },
  ])
  .parallel([
    { agent: "docs-writer", task: "Update docs" },
    { agent: "changelog-writer", task: "Update changelog" },
  ])
  .aggregate("release-manager", "Compile release notes")
  .build();ts
PatternTopologyUse Case
HandoffLinear chainSpecialist routing across domains
Group ChatRound-tableBrainstorming, multi-perspective review
State GraphBranching DAGQuality loops, conditional pipelines
Workflow BuilderComposedAny combination of the above

17Context-Aware Routing

The base system routes tasks by: direct agent ID → capability substring match → section fallback. The AgentRouter replaces this with a 6-strategy pipeline where each strategy returns a confidence score (0–1) and the highest-confidence match above threshold wins.

The Six Strategies

  1. Direct ID match (confidence: 1.0) — exact agent ID specified in request
  2. File ownership (confidence: 0.9) — task mentions file paths, checked against file_ownership table glob patterns
  3. Capability scoring (confidence: 0.5–0.85) — TF-IDF cosine similarity between task description and agent capability vectors
  4. Historical success (confidence: 0.7) — query task_results for agents that succeeded on similar past tasks
  5. Section fallback (confidence: 0.4) — coarse domain routing using registry section indexes
  6. Load balancing (tie-breaker) — among equally capable agents, prefer idle ones

Confidence threshold: 0.6 (configurable via settings.routing.confidenceThreshold). Below that, the router returns no match and the executor falls back to the default agent.

18Conversation & Entity Memory

Conversation Manager

Tracks multi-turn conversations between agents. Each conversation has an ID, a participant list, and a message history. Key operations: create(), addMessage(), getHistory(), getCleanHistory() (strips irrelevant messages for handoff), checkpoint(), and restore().

History windowing: capped at maxMessages (default: 100, configurable via settings.conversation.maxMessages). Oldest messages trim first (FIFO). Backed by the conversations and conversation_messages tables.

Entity Memory

Extracts and stores entity-level knowledge from task results. Every time a task completes, EntityMemory scans the output for recognizable entities and accumulates facts.

Entity types: file, module, api, concept, person, config.

Task: "Fix the JWT expiration bug"
→ EntityMemory finds entity "JWT" with 4 accumulated facts
→ Facts injected into prompt alongside RAG context
→ Agent has project-specific JWT knowledge without re-reading codebase

Backed by the entities and entity_facts tables in V2 schema.

Chapter V Safety & Reliability

19Safety & Validation Pipeline

PREFLIGHT Agents • Budget PRE-GUARDS Injection Detection Length Check PII / Secrets Scan LLM POST-GUARDS Schema Validation Code Safety Scan Output Schema OUTPUT Validated Schema retry (1x with correction prompt) BLOCKED
Safety pipeline — pre/post LLM guards with schema retry loop

Guardrails Pipeline

A pre/post LLM filter chain that validates inputs before they reach the model and outputs before they reach the user.

Pre-guards (run before LLM call):

Post-guards (run after LLM response):

Schema Validation

The SchemaValidator validates LLM outputs against JSON Schema definitions. On failure, it generates a correction prompt and retries once. Built-in schemas: CodeBlockSchema, PlanSchema, ReviewSchema, JSONResponseSchema.

Preflight Verification

Before executing a complex workflow, PreflightChecker verifies: all referenced agents exist and are healthy, required capabilities are available, token/time budget is sufficient, and no circular dependencies exist. Returns { passed, warnings, errors, budget }.

20Progress Tracking

The ProgressTracker monitors long-running workflows for three failure modes:

Stall Detection

If time between consecutive workflow steps exceeds 2× the average step time, a stall warning fires. Default threshold: 60 seconds (settings.progress.stallThresholdMs).

Loop Detection

If the same agent produces outputs with cosine similarity > 0.9 for 3+ consecutive rounds, a loop warning fires. Thresholds configurable via settings.progress.loopSimilarityThreshold and settings.progress.maxConsecutiveSimilar.

Budget Exhaustion

Every workflow has a token budget (default: 500,000) and wall-clock budget (default: 10 minutes). Warning at 80%, abort at 100%. Progress events recorded in the progress_events table.

21Durable Workflows

START RUNNING COMPLETED PAUSED human-in-the-loop approved FAILED unrecoverable error ABORTED budget exhausted Step 1 Step 2 Crash Resume Step 3 Checkpoint timeline
Durable execution lifecycle — state machine with checkpoint timeline

The DurableWorkflow class wraps a workflow definition and checkpoints state to SQLite after each step. On crash and restart, the runtime scans workflow_checkpoints for incomplete workflows and offers to resume them.

Lifecycle States

START → RUNNING → COMPLETED
                → PAUSED (human-in-the-loop)
                → FAILED (unrecoverable error)
                → ABORTED (budget exhausted or manual)

Checkpoint/Resume

After each step: write checkpoint (step index, accumulated context, conversation ID, intermediate results). On resume: load last checkpoint, continue from next step. Already-completed steps are not re-executed.

Human-in-the-Loop

Steps marked requiresApproval: true pause the workflow and wait for external approval via WebSocket or CLI. The workflow moves to PAUSED state until approval arrives.

Chapter VI Infrastructure
MEMORY HIGHWAY Pub/Sub channels • Untyped payload ACP BUS Typed envelopes • Request-response • Dead letters SHARED STATE BUS Immutable transitions • Versioned state • Sessions Agent A Agent B publish/subscribe Agent C channels: tasks • results • events • state • acp:* Agent A Agent B request (typed envelope) response (parentMsgId) Dead Letters retries exhausted ack tracking • comm graph • schema validation Session update() v1 → v2 → v3 immutable transitions • transition history • adjacency list
Three-layer communication — Highway (pub/sub) → ACP (typed) → SharedState (immutable)

22Agent Communication Protocol (ACP)

ACP is a typed messaging layer on top of MemoryHighway. While MemoryHighway handles pub/sub transport, ACP adds structure: typed envelopes, schema validation, request-response futures, acknowledgments, dead-letter queues, and communication graph tracking.

The ACP Envelope

{
  msgId: "uuid",
  sender: "frontend-agent",
  receiver: "backend-agent",
  msgType: "task",        // task | plan | result | validation | error | control | ack | query | broadcast
  content: { ... },
  meta: { schemaId: "task-v1", expectsResponse: true, retryCount: 0, maxRetries: 3 },
  trace: { taskId: "...", workflowId: "...", parentMsgId: "...", hopCount: 2, hops: [...] },
  acknowledged: false
}ts

Request-Response Pattern

acpBus.request(params) sends a message with meta.expectsResponse set and returns a Promise. The bus monitors for responses matching trace.parentMsgId. Timeout default: 30s.

Dead-Letter Queue

Failed deliveries (handler throws, agent unreachable) retry up to meta.maxRetries. Exhausted messages move to the dead-letter queue for manual inspection and retry.

Communication Graph

Every send() records a directed edge: sender → receiver (msgType). Queryable for debugging: which agents talk, what message types flow between them.

23Conflict Resolution

When multiple agents work in parallel or group chat, their outputs may conflict. The ConflictResolver detects and resolves these conflicts.

Analysis

analyze(outputs) produces a ConflictReport identifying agreements (multiple agents say the same thing), contradictions (agents directly disagree), and unique contributions (only one agent provides).

Resolution Strategies

StrategyHow It WorksBest For
majority-vote3 say X, 1 says Y → pick XFact-checking
weighted-by-tierMaster > Manager > WorkerHierarchical decisions
weighted-by-confidenceAgents self-report confidenceWhen agents know limits
llm-mediatorSend conflicts to managerComplex disagreements
mergeTake unique contributions, flag contradictionsDocumentation, summaries

24Observability & Structured Logging

The StructuredLogger replaces flat text logs with JSON-structured entries that carry context automatically.

const logger = structuredLogger.scoped({ taskId: "task-123", agentId: "frontend" });
logger.info("Starting component generation", { component: "Button" });
// → { timestamp, level: "info", context: { taskId, agentId }, data: { component } }ts

Scoped loggers propagate context: every log entry automatically includes fixed context (task ID, workflow ID, agent ID). Child scopes merge parent and child context.

LLM call instrumentation: recordLLMCall() tracks every API call with provider, model, token counts, latency, and success/failure. getLLMStats() returns aggregates by provider and agent.

JSONL audit trail: ACP messages logged to .aether/logs/audit.jsonl for compliance and debugging.

Log querying: query({ taskId, agentId, level, since, until }) scans the in-memory ring buffer (max 5,000 entries).

25Shared State Bus

The SharedStateBus provides centralized, observable state for workflows. All participants see the same state. Changes are atomic and immutable — every update() creates a new version rather than mutating in place.

The Update Pattern

const newState = bus.update("session-123", {
  agent: "frontend-agent",
  reason: "Component generation complete",
  patches: { componentCode: "...", testsPassing: true },
  incrementStep: true,
  addEdge: { from: "frontend-agent", to: "test-runner", msgType: "handoff" },
});ts

Internally: get current state → spread new values → increment version → record transition → persist to KV → publish notification via MemoryHighway → return new state (old reference unchanged).

The bus also tracks a communication graph (which agents interact within each session) and runs background maintenance that finally schedules the existing cleanExpiredKV() on a timer.

26Plugin System

The PluginRegistry manages external code that hooks into AETHER’s lifecycle at eight defined points:

SlotWhen It Fires
PreExecutionBefore a task is sent to an agent
PostExecutionAfter a task result is received
PreRoutingBefore the router picks an agent
PostRoutingAfter the router picks an agent
OnEscalationWhen an agent escalates a failure
OnErrorWhen any subsystem error occurs
OnStartupDuring runtime initialization
OnShutdownDuring runtime shutdown

Plugins are discovered by scanning .aether/plugins/ for *.plugin.ts files. Each plugin declares which slots it hooks into. The 8-slot model covers key lifecycle points without exposing all internal state.

27Reaction Engine

The ReactionEngine watches MemoryHighway events and triggers workflows automatically based on configurable rules.

{
  id: "auto-test-on-review",
  trigger: {
    channel: "results",
    condition: (msg) => msg.type === "task-complete"
                      && msg.summary.includes("code review"),
  },
  action: { type: "execute_task", target: "test-runner",
            taskTemplate: "Run the test suite" },
  cooldown: 30000,    // Don’t fire more than once per 30s
  maxFires: 10,       // Absolute cap (prevent runaway)
  enabled: true,
}ts

Action types: execute_task, execute_workflow, notify, and custom (user-provided handler). Cooldown prevents reaction storms. maxFires provides an absolute cap.

28Settings & Configuration

settings.json aether config set SettingsManager.load() deep-merge with DEFAULT_SETTINGS validate(types, ranges, enums) AetherSettings methodology • agents • execution • escalation • routing • conversation • handoff progress • highway • acp • logging • sharedState • server ↓ passed to subsystem constructors
Settings hierarchy — from file to validated config to subsystem constructors

AETHER has two configuration files in .aether/:

Thirteen Configuration Groups

GroupControls
methodologyTDD/SDD/hybrid mode, test command, spec directory
agentsMax concurrent, tier limits (masters/managers/workers)
executionMax depth, timeout, tokens, temperature, feature toggles
escalationCircuit breaker threshold and window
routingConfidence threshold for agent resolution
conversationMax messages per conversation
handoffMax handoff chain length
progressToken/time budgets, stall/loop thresholds
highwayRAG indexing, dedup window, KV TTL
acpRequest timeout, max retries, dead-letter limit
loggingLog level, max retained entries
sharedStateCleanup interval, max transitions, persistence
serverWebSocket port and host

The aether config CLI

aether config                          # Show all current settings
aether config get execution.maxDepth   # Get a specific value
aether config set execution.maxDepth 5 # Set a specific value
aether config reset execution          # Reset one section
aether config edit                     # Open in $EDITOR
aether config validate                 # Check for errors
aether config path                     # Print path to settings.jsonshell

Settings are deep-merged with defaults on load. Missing keys get default values. SettingsManager.validate() checks types, numeric ranges, and enum values.

Chapter VII Decisions & Flow

29Key Architectural Decisions

SQLite over Redis / Mongo / flat files

AETHER needs five storage patterns simultaneously: relational, key-value with TTL, vector similarity, full-text search, and time-series. SQLite with sqlite-vec and FTS5 handles all five in one file with ACID guarantees and zero operational overhead.

Interface-based storage with constructor injection

Global singletons make testing impossible (shared state) and the dependency graph implicit. Constructor injection documents what each subsystem needs. The interface boundary means swapping SQLite for Postgres requires zero changes to business logic.

Interaction combinators over async/await DAGs

Every other approach requires manual deadlock reasoning. Interaction combinators have a mathematical proof (strong confluence) guaranteeing the same result regardless of reduction order. The cost is implementation complexity. The benefit is correctness by construction.

TF-IDF embeddings with API fallback

AETHER should work out of the box with zero configuration. TF-IDF is deterministic, zero-latency, and good enough for capability matching and context retrieval. API embeddings are an opt-in upgrade when OPENAI_API_KEY is present.

N-tier extensible hierarchy with 5 defaults

The original design used three fixed tiers. As complexity grew, we needed a system guardian (Sentinel) and an agent factory (Forge) that sat above Master. Rather than special-casing them, we generalized to an N-tier TierRegistry where any number of tiers can be registered at any rank. The 5 built-in tiers cover the standard use cases. TierRegistry.classicTiers() still returns the original three for backward compatibility.

WAL mode for SQLite

WAL allows concurrent readers and a single writer without blocking. Multiple subsystems read the DB simultaneously (registry queries, task execution, message logging). Rollback journal mode would serialize everything through the database.

FNV-1a for message deduplication

Fast, non-cryptographic, collision-resistant enough for deduplication. MD5/SHA are overkill — we prevent accidental duplicates from retries, not adversarial collisions. FNV-1a runs in nanoseconds and produces compact hashes.

Immutable state transitions in SharedStateBus

Mutable shared state with locks is the classic source of concurrency bugs. Immutable transitions with version numbering give the simplicity of direct state access with the auditability of event sourcing. Every transition records who changed what, when, and why.

Horizontal handoff separate from vertical escalation

Escalation is failure-driven and vertical (worker → manager → master). Handoff is success-driven and horizontal (specialist → specialist). Merging them would conflate “I failed” with “this needs a different expert.” Keeping them separate means escalation can trigger circuit breakers while handoff transfers state without marking the source agent as failed.

30Data Flow: A Request End to End

aether run "Refactor the authentication module to use JWT"shell
1
CLI parses the command and calls runtime.run(taskText).
2
Runtime bootstraps: init SQLite store, load config and settings, discover agents from .agent.md files, populate registry, build TierRegistry, initialize Forge and Sentinel, start MemoryHighway, initialize all subsystems.
3
AgentRouter resolves the target agent. Runs the 6-strategy pipeline: direct ID (no match) → file ownership (no file paths) → capability scoring (finds auth-specialist at 0.82 confidence) → accepts.
4
PreflightChecker verifies the selected agent is healthy, not circuit-broken, and token budget is sufficient.
5
Sentinel constitutional check. The task action passes through the ConstitutionalRulesEngine. No blocked patterns detected. Sentinel records the task in its ledger.
6
Executor queries RAG + EntityMemory. RAG finds: last auth refactor result, agent definition, JWT middleware code. EntityMemory finds: 4 facts about “JWT” entity, 2 facts about “auth module”. All injected into prompt.
7
Guardrails pre-check scans the assembled prompt for injection patterns, sensitive data, and length. All clear.
8
Executor calls the LLM with system prompt + RAG context + entity context + task description. LLM responds with a plan and two sub-tasks.
9
Guardrails post-check validates the response. SchemaValidator checks output structure. Both pass.
10
Sub-tasks execute concurrently via InteractionNet. Routed to capable agents. ProgressTracker monitors for stalls and loops.
11
ConflictResolver merges the two sub-task results, detecting no contradictions. EntityMemory extracts new entities from the output. Sentinel updates its facts ledger with any new discoveries.
12
Result persisted. Saved to task_results. Published to results channel. Indexed into RAG. ACP publishes a typed result envelope. ReactionEngine checks rules (no matches this time).
13
CLI prints the result and exits. 3 LLM calls, ~30 SQL writes, 10–30 seconds total.

31What Doesn’t Exist (And Why)

No distributed lock manager

AETHER runs as a single process. SQLite's WAL handles concurrency. Multi-machine? Use federation transport between instances instead of sharing a SQLite file.

No streaming LLM responses

The executor waits for full responses. Streaming complicates sub-task JSON parsing (can't parse partial JSON), guardrails post-checks, and schema validation. The 120s timeout provides a safety bound.

No intra-process auth

Agents in the same instance are co-tenants by definition. Authentication exists at the WebSocket layer (external connections) and transport layer (external APIs), not between local agents.

No hot plugin reloading

Plugins load during runtime.init() and destroy during runtime.shutdown(). Hot reloading introduces state consistency issues (what happens to in-flight tasks when behavior changes?) that are not worth solving for a local tool.

No built-in secret management

API keys come from environment variables. No vault, no encrypted config, no rotation. AETHER is a local dev tool — secrets are managed by the OS environment or tools like dotenv.

No distributed consensus

The SharedStateBus uses version numbering for optimistic concurrency, not Raft or Paxos. Single-process means no split-brain problem. Federation uses BAP-02 message ordering, not a consensus algorithm.

Chapter VIII Changelog

32Changelog

Phase 13 — Extensible Tier System

  • Replaced hardcoded 3-tier hierarchy with an N-tier TierRegistry supporting custom tiers at any rank
  • Added Sentinel (rank 0) — system guardian with constitutional rules engine, health monitoring, dual-ledger, pause/resume
  • Added Forge (rank 1) — runtime agent factory with spawn/retire, ephemeral agents, DyLAN-inspired contribution scoring
  • Introduced gate policies (open, priority, tier-only) for fine-grained escalation control
  • Added tier-level weights (conflict resolution, RAG boost, cost multiplier) for tier-aware subsystems
  • Migrated SQLite schema to V4 — removed CHECK constraint on tier column to support custom tiers
  • Wired TierRegistry, AgentForge, and SystemSentinel into the runtime initialization
  • 88 new tests (726 total, 0 failures)

Migration Notes

  • Existing databases auto-migrate to V4 schema on first startup
  • TierRegistry.classicTiers() returns the original 3 tiers for backward compatibility
  • TierRegistry.builtinTiers() returns all 5 tiers (sentinel, forge, master, manager, worker)
  • Provider config changed from { master, manager, worker } to { tiers: { ... }, fallbackChain: [...] }
  • Agent .agent.md files can now use any registered tier name in the tier field

33Version History

VersionPhasesHighlights
v0.2.0 10–13 N-tier extensible hierarchy, Sentinel, Forge, constitutional rules, 726 tests
v0.1.0 1–9 Core platform: 3-tier agents, RAG, interaction nets, BAP-02, orchestration patterns, safety pipeline, ACP, settings system