ares Architecture Deep Dive (I): The Big Picture — Why Another Agent Framework?

I didn't set out to build a framework. I set out to solve a problem: Agents kept dying, and I couldn't figure out why.

It started with a simple chatbot. One Leader, two Subs, a handful of tools. Worked fine in dev. In production, the Leader would silently stop responding after 20 minutes. No error, no panic, no crash log. Just... silence.

After three days of debugging, I found it: a goroutine leak in the LLM client. One leaked goroutine per request, eventually hitting the OS thread limit. The fix was one line. But finding it took 72 hours because I had zero visibility into what the Agent was doing.

That's when I realized: the problem isn't "how to build an Agent." The problem is "how to keep an Agent alive in production."


The Three Questions

Every Agent framework answers one question: "How do I make an Agent call an LLM?" That's the easy part. The hard questions are:

  1. What happens when the Agent dies? (Resurrection)
  2. How does it remember what it was doing? (State Recovery)
  3. How do I know what went wrong? (Observability)

ares is built around these three questions. Everything else — the DAG engine, the memory system, the evolution engine — exists because answering these questions properly requires a lot of infrastructure.


The Architecture: Five Layers

graph TB
    subgraph API ["Layer 1: API Contract"]
        Bootstrap["Bootstrap Factory"]
        Interfaces["AgentService / Runtime / Evolution / Arena / MemoryService / LLMService"]
    end

    subgraph Runtime ["Layer 2: Runtime"]
        RT["Runtime Manager"]
        Leader["Leader Agent"]
        Sub["Sub Agents"]
        PluginBus["PluginBus"]
        Strategy["Active Strategy<br/>evolved params"]
    end

    subgraph Workflow ["Layer 3: Workflow"]
        DAG["MutableDAG<br/>evolvable topology"]
        Exec["DynamicExecutor"]
        Checkpoint["Checkpoint Resume"]
        GraphPatch["GraphPatchExecutor<br/>insert/remove/replace nodes"]
    end

    subgraph Memory ["Layer 4: Memory"]
        Session["Session Memory"]
        Distilled["Distilled Memory"]
        Retrieval["Vector Retrieval"]
        MemConfig["Memory Config<br/>max_history, session_ttl..."]
        MemPatch["MemoryPatchExecutor<br/>runtime config evolution"]
    end

    subgraph Evolution ["Layer 5: Evolution Engine"]
        GA["GA Population<br/>N individuals, 7 selectors<br/>3 crossover, 6 mutation"]
        Tick["Background Ticker<br/>5min interval"]
        Sched["Scheduler<br/>OnAgentEnd callback"]
        Adapter["GenomePopulationAdapter<br/>Run()"]
        Genomes["6 Genomes<br/>Workflow / Scheduler / Knowledge<br/>Recovery / Planner / Memory"]
        Diff["Diff Engine<br/>4 Differs"]
        Coord["Coordinator<br/>Apply / Reject / Delay"]
        Execs["5 Executors<br/>Graph / Recovery / Knowledge<br/>Memory + StrategyStore"]
    end

    subgraph Infra ["Layer 6: Infrastructure"]
        Events["EventStore"]
        Storage["VectorStore"]
        LLM["LLM Adapters"]
        Tools["Tool Registry"]
        Evidence["Evidence Store"]
    end

    Bootstrap --> RT
    RT --> Leader
    Leader --> Sub
    Leader --> DAG
    DAG --> Exec
    Exec --> PluginBus
    Exec --> GraphPatch
    Session --> Distilled
    Distilled --> Retrieval
    RT --> Events
    DAG --> Events
    Leader --> LLM
    Sub --> Tools

    Tick --> Adapter
    Sched --> Adapter
    Adapter --> GA
    GA --> Genomes
    Genomes --> Diff
    Diff --> Coord
    Coord --> Execs
    Execs --> DAG
    Execs --> MemConfig
    Adapter --> Strategy
    Strategy --> RT
    DAG --> Events
    Exec --> Events

Layer 1: API Contract — What the outside world sees. Interfaces only, no implementations. The Bootstrap factory wires everything together. You call bootstrap.New() and get a fully connected system.

Layer 2: Runtime — Who does the work. The Runtime Manager owns agent lifecycles: birth, death, resurrection. The Leader plans and delegates. Sub Agents execute. The PluginBus connects everything. The Active Strategy carries evolved parameters (tool selection, search depth, scheduler strategy) that the GA engine produces.

Layer 3: Workflow — How work flows. The MutableDAG defines task dependencies. The DynamicExecutor runs them in topological order. The GraphPatchExecutor can insert, remove, or replace nodes at runtime — this is how DAG topology evolution works. Checkpoint Resume lets you pick up where you left off after a crash.

Layer 4: Memory — What agents remember. Session memory is short-term. Distilled memory is long-term compressed knowledge. Retrieval finds relevant memories via vector search. The MemoryPatchExecutor adjusts memory configuration (max_history, session_ttl, etc.) at runtime via coordinator patches.

Layer 5: Evolution Engine — How agents improve themselves. The GA Population holds N individuals with different strategy parameters. A background ticker (5-minute interval) and an agent-completion scheduler both trigger evolution cycles. The adapter runs selection → crossover → mutation → scoring, then submits the best strategies to 6 Genomes (Workflow, Scheduler, Knowledge, Recovery, Planner, Memory). The Diff Engine compares old vs new snapshots, producing patches. The Coordinator decides Apply/Reject/Delay. 5 Executors apply the approved patches to the live system, and the Strategy Store makes the evolved strategy available to running agents.

Layer 6: Infrastructure — What holds it all up. EventStore records everything. VectorStore indexes memories. LLM Adapters talk to providers. Tool Registry manages capabilities. Evidence Store feeds evolution decisions.


The Design Principles

1. Agents are disposable.

This is the most important principle. An Agent is not a precious snowflake — it's a goroutine with a heartbeat. If it dies, the Runtime creates a new one and restores its state from the EventStore. This sounds wasteful until you realize it's the only way to guarantee recovery.

Honest reflection: We considered making Agents long-lived and resilient. Tried circuit breakers, retry loops, graceful degradation. It worked — until it didn't. The problem is that you can't predict every failure mode. A goroutine leak, a deadlock, an OOM kill — no amount of defensive coding covers all of them. Making Agents disposable means any failure is recoverable, because you always have a fresh start point.

2. Record everything, replay anything.

Every action — LLM call, tool invocation, task assignment, memory query — is an event in the EventStore. Want to know what happened? Replay the events. Want to restore state? Replay the events. Want to debug? Replay the events.

3. Plugins, not hardcoding.

The PluginBus lets you extend behavior without modifying core code. Checkpoint snapshots, route decisions, tool invocations — all handled by plugins. The Runtime doesn't know or care which plugins are active.

4. The API layer is a contract, not an implementation.

api/core/ defines interfaces. internal/ implements them. api/bootstrap/ wires them together. You can swap implementations without changing the contract. This matters when you want to test with mocks, or switch from in-memory to PostgreSQL.


What Makes This Different

Most Agent frameworks are "LLM orchestration engines" — they focus on prompt chains and tool calling. ares is an Agent operating system — it focuses on keeping Agents alive in production.

CapabilityTypical Frameworkares
Agent lifecycleStart and hopeBirth → Death → Resurrection
State managementIn-memory structEvent Sourcing + Checkpoint
Failure handlingTry/catchAutomatic resurrection with state recovery
ObservabilityLogsLogs + Events + Metrics + Traces
ExtensibilitySubclassPlugin system with capability discovery
Self-improvementNoneGA engine with 7 selectors, 3 crossover, 6 mutation operators, 6 evolvable genomes (Workflow/Scheduler/Knowledge/Recovery/Planner/Memory), multi-objective scoring, and background ticker-driven evolution cycles

The Honest Truth

This project started as a chatbot and grew into something I didn't plan. The evolution engine wasn't in any roadmap — it emerged from the question "what if Agents could optimize their own prompts?" The chaos engineering arena came from "what if I could kill an Agent and watch it recover?" The plugin system came from "what if I could add checkpoint support without touching the executor?"

Each feature was born from a real problem, not a feature checklist. That's why the architecture looks the way it does — it's not designed top-down, it's evolved bottom-up.

Honest reflection: The codebase is bigger than it needs to be. The quant trading module, the interview demo, the MCP dashboard — these are experiments that should probably live in separate repos. The core (Runtime + Workflow + Memory + Events) is solid. The periphery is still finding its shape.

But that's how real projects work. You don't design the perfect architecture on day one. You solve problems, accumulate code, and occasionally stop to refactor. The refactoring we did in v0.2.4 — unified naming, API layer thinning, module logging — was one of those "stop and clean up" moments.


What's Next

This series walks through each layer in detail:

#TopicWhat You'll Learn
IThis articleThe big picture
IIAgent Harmony ProtocolHow agents communicate
IIIMemory DistillationHow agents remember and forget
IVWorkflow EngineHow tasks flow through a DAG
VTool Invocation LayerHow agents use tools
VISecurity & ObservabilityHow to see what's happening
VIIRuntime & LifecycleHow agents live and die
VIIIEvent SystemHow state is recorded and recovered
IXArena / Fault InjectionHow to break things deliberately
XRetrieval SystemHow to find relevant memories
XIAutonomous EvolutionHow agents improve themselves
XIISecurity HardeningHow to defend against threats
XIIIBootstrap & API LayerHow to wire without pain
XIVPlugin SystemHow to extend without touching
XVMCP IntegrationHow to teach agents to use tools
XVIFlight RecorderHow to record and replay execution
00SDK LayerOne line of code to start an agent
00Knowledge Graph BuildFrom markdown to 27K edges (AKG)
00Storage Layerpostgres/embedding/models/query/repositories/services
00LLM Client LayerFailover, DeepSeek Reasoning, multi-provider abstraction
00Evaluation FrameworkEvaluatorRegistry, LLMJudge, Bench
00Config Systemares.yaml schema, YAML-driven flags
00Quant Trading ModuleThe experiment we keep honest about

Each article follows the same pattern: the problem → the design journey → the trade-offs → the honest reflection.

No marketing. No "10x faster than X." Just engineers talking about engineering.