Architecture & Concepts
A deep-dive into Automergent's internals — the phase-driven agent loop, layered prompt composition, context window management, tool system, sub-agent orchestration, session persistence, and sandboxing.
1. System Overview
Design Philosophy
Automergent is a terminal-native AI coding agent written in Go. It runs entirely in your command line — no Electron, no VS Code extension, no browser. This means:
- Zero IDE dependency — works in any terminal emulator or remote SSH session
- Sub-millisecond responsiveness — native Go binary, no JS runtime overhead
- Works everywhere — Linux, macOS, Windows (WSL); headless servers included
- Composable — pipe output, script interactions, integrate into CI/CD
Tech Stack
| Layer | Technology | Purpose |
|---|---|---|
| TUI Framework | charm.land/bubbletea/v2 | Elm-architecture terminal UI |
| Styling | charm.land/lipgloss/v2 | Declarative ANSI styling |
| Markdown Render | charm.land/glamour | Terminal-native markdown |
| AST Parsing | tree-sitter (via CGO) | Language-aware code intelligence |
| AI Providers | Gemini, OpenAI, Anthropic, DeepSeek, Ollama | Multi-provider abstraction |
| Configuration | Viper | YAML + env var config |
| Sandboxing | bubblewrap (Linux), seatbelt (macOS) | OS-level process isolation |
Directory Layout
Automergent/
├── cmd/automergent/ # CLI entrypoint
├── internal/
│ ├── agent/ # Agent loop, orchestration, approval policies
│ │ ├── agent.go # Core Agent struct + Execute()
│ │ ├── builtin/ # Built-in agent definitions (explore, review, etc.)
│ │ ├── custom/ # User-defined agent loader
│ │ ├── init.go # INIT phase decomposer
│ │ ├── subagents.go # Sub-agent spawning & management
│ │ └── autocompact.go # Context window auto-compaction
│ ├── ai/ # Provider abstraction (Gemini, OpenAI, Anthropic)
│ ├── config/ # Viper configuration, validation
│ ├── context/ # Budget management, compaction, dependency tracking
│ │ ├── manager.go # Central context coordinator
│ │ ├── budget.go # Token budget allocation
│ │ ├── compaction.go # 6-tier progressive compaction pipeline
│ │ └── adaptive.go # Anchor+estimate token counting
│ ├── prompt/ # Phased prompt system (13-layer composer)
│ │ ├── composer.go # Layered prompt assembly
│ │ ├── phase_manager.go # Phase state machine
│ │ ├── behavioral.go # Behavioral rule injection
│ │ └── bases/ # Model-specific base prompts
│ ├── sandbox/ # OS-level sandboxing (bubblewrap/seatbelt)
│ ├── session/ # Persistent transcripts, memory, checkpoints
│ ├── tools/ # 43+ tools (FS, Terminal, AST, Web, Diagnostics)
│ │ ├── tool.go # Tool interface definition
│ │ ├── agent/ # Sub-agent tools (task, batch_task, read_agent)
│ │ ├── filesystem/ # read, write, edit, glob, search
│ │ └── shell/ # bash, async shells, process management
│ ├── tui/ # Bubble Tea v2 TUI
│ └── workflow/ # Artifact system, plan management
└── Makefile # Build, test, lint, release
2. Four-Phase Agent Loop
Every user message flows through a structured 4-phase state machine. Each phase has its own tool set, step budget, prompt style, and behavioral rules. The PhaseManager controls transitions; the PhaseClassifier determines initial routing.
INIT Phase — Classifier & Router
Persona: Serious, decisive. Minimal output.
Tools: bash, read_file, glob, grep, edit_file, task — no todo tools
Step budget: 3
INIT decomposes the user message into structured intents using an LLM call. It:
- Classifies intent type (
explore,implement,fix,question, etc.) - Routes tasks to the appropriate phase and sub-agent type
- Answers direct questions inline without entering the loop
- Confirms violations via the safety ladder (warn → block_imminent → blocked)
- Produces
TaskSpecobjects with phase assignment and agent routing
// internal/shared/types.go
AgentPhase = "init" | "explore" | "plan" | "build"
// TaskSpec carries routing metadata
TaskSpec {
Agent string // "main", "explore", "general-purpose", "review", ...
Phase AgentPhase // explore | plan | build
Type string // "explore", "implement", "fix", "plan", ...
...
}
EXPLORE Phase — Read-Only Investigation
Persona: Thorough, investigative. File:line reporting.
Tools: glob, grep, read_file, bash, list_directory — read-only only
Step budget: 10
EXPLORE is strictly read-only. It performs codebase reconnaissance:
- File discovery and code search (glob, grep, tree-sitter AST)
- Dependency analysis and import graph walking
- Violation confirmation when flagged by INIT
- Issue-suspect investigation with file:line references
- Produces findings for the PLAN phase or BUILD phase
PLAN Phase — Architect Mode
Persona: Structured, analytical. Produces actionable plans.
Tools: read_file, write_file, bash, task, grep, glob
Step budget: 5
PLAN consumes INIT's constraints and EXPLORE's findings to produce a structured implementation plan:
- Ordered task graph with dependencies
- File change manifest (which files to modify, create, delete)
- Risk assessment and rollback points
- Can delegate research sub-tasks via
tasktool
BUILD Phase — Implementer + Tester + Todo Manager
Persona: Focused, pragmatic. Verify after every change.
Tools: Full set — edit_file, write_file, bash, read_file, task, glob, grep, multi_edit
Step budget: 20
BUILD is the implementation workhorse:
- Edits files using AST-aware diffs (tree-sitter)
- Runs tests after every change — verify before moving on
- Manages a todo list of sub-tasks
- When blocked, transitions back to EXPLORE for more context
- Emits task notifications via the steering channel
Phase Transitions & Step Budgets
| From | Allowed Transitions | Step Budget | Trigger |
|---|---|---|---|
| INIT | EXPLORE, PLAN, BUILD, INIT (self) | 3 | User message classification |
| EXPLORE | PLAN, BUILD, INIT | 10 | Findings complete, blocked, or clarified |
| PLAN | BUILD, EXPLORE, INIT | 5 | Plan ready, needs more info, or done |
| BUILD | EXPLORE, PLAN, INIT | 20 | Implementation complete, blocked, or needs plan revision |
// internal/prompt/phase_manager.go
PhaseManager {
currentPhase AgentPhase
phaseHistory []PhaseTransition
taskQueue *TaskQueue
violationCount map[ViolationType]int
}
// Valid transitions are enforced:
CanTransition(from, to) → bool
Transition(to, reason, trigger) → error
3. Prompt System Architecture
The system prompt is assembled from 11+ composable layers via the PromptComposer. Each layer is independent, cached where possible, and injected in a fixed order.
Layered Composition
Composer Pipeline
// internal/prompt/composer.go
PromptComposer {
layers map[PromptLayer][]string
layerOrder []PromptLayer // fixed assembly order
model ModelInfo
agent *AgentDefinition
phase AgentPhase
registry *tools.Registry // live tool metadata
}
// Assembly order — each layer appends to its slot
buildAllLayers() → buildBaseModel → buildPlatform → buildEnvironment →
buildInstructions → buildSkills → buildMCP → buildAgentCustom →
buildPhase → buildBehavioral → buildTool → buildDynamic
// Final render — joins layers with "---" separators
Compose() → string
The composer's SetRegistry() method attaches the live tool registry so the tool layer renders ToolMeta self-documentation for each offered tool — tools document themselves.
Behavioral Rules
Behavioral prompts are injected by RenderBehavioralPrompts() and grouped by dimension:
| Dimension | Rules | Triggers |
|---|---|---|
| Phase Discipline | Stay in current phase. Don't edit during EXPLORE. Don't explore during BUILD. | always |
| Context Hygiene | Don't re-read known files. Use file:line references. Micro-compact tool outputs. | always |
| Verification | Run tests after every edit. Confirm build passes. Check for regressions. | tool_use, phase_change |
| Safety & Honesty | Don't fabricate. Don't commit secrets. Confirm destructive ops. Escalate violations. | always, violation |
4. Context Window Management
Automergent uses a progressive degradation ladder to keep conversations within the model's context window. The CompactionPipeline operates across 6 tiers, each triggered at a different threshold.
The Compaction Ladder
Token Budgets & Thresholds
| Tier | Strategy | Threshold | Description |
|---|---|---|---|
| 1 | ghost | 50% | Persist large tool outputs (>32KB) to disk; replace with preview stub |
| 2 | truncate_middle | 65% | Keep 40% head + 40% tail of tool results; insert truncation marker |
| 3 | distill | 75% | LLM-summarize old file reads (>2 turns old, >2KB) |
| 4 | snapshot | Emit Master State JSON with active tasks, facts, constraints, recent arc | |
| 5 | microcompact | 90% | Non-LLM compact of all but last 2 tool results |
| 6 | full_compact | 95% | Full history summarization + probe-verify for completeness |
Token Estimation
Automergent uses anchor-plus-estimate token counting:
- Anchor: After each LLM call, the provider-reported usage anchors the count
- Estimate: Between calls,
len(content) / 4provides a fast heuristic - Adaptive:
AdaptiveTokenCalculatorlearns model-specific drift and corrects estimates over time
Compaction Boundaries & Metadata Markers
Compacted sections are delimited by metadata markers that the agent can detect:
[DISTILLED]— LLM-summarized tool output[truncated N chars]— head/tail truncation[Output persisted to path]— ghosted large output# Neural Context Summary— full compact summary# Context Snapshot— Master State JSON injection
Post-Compact File Restore
Ghosted outputs can be restored on demand via read_file — the stub includes the persistence path. The ToolResultStore manages the on-disk ghost files and cleans up after session end.
5. Tool System
Tool Interface & Registry
// internal/tools/tool.go
Tool interface {
Name() string
Description() string
Schema() map[string]any
Execute(ctx, args) → (Result, error)
RequiresConfirmation(mode) → bool
IsConcurrencySafe(args) → bool
IsReadOnly(args) → bool
IsDestructive(args) → bool
EstimatedCost() → ToolCost
}
ToolCost {
TokensApprox int // estimated tokens consumed
LatencyMs int // typical latency in ms
RiskLevel string // "low" | "medium" | "high"
}
BaseTool // embeddable defaults: conservative (not concurrency-safe, not read-only)
The Tool Registry supports:
- Clone for sub-agents: Each child agent gets a filtered copy of the registry
- Per-phase filtering: Only tools allowed in the current phase are offered to the model
- ToolMeta self-documentation: Tools describe themselves via
Meta()for prompt injection - Cost tracking: Each tool reports estimated tokens, latency, and risk level
Tool Orchestrator & Security
- Confirmation: Destructive tools require user approval (configurable: manual / accept-edits / auto)
- Path Boundary:
PathScopeenforces that file operations stay within the working directory - Concurrency: Tools declare
IsConcurrencySafe()— the orchestrator serializes unsafe tools - Ghosting: Large tool outputs (>32KB) are automatically ghosted to disk to protect the context window
6. Sub-agent Architecture
Agent Types
| Type | Role | Tools | Use Case |
|---|---|---|---|
| general-purpose | Full coding agent | Full toolset | Implementation, debugging, refactoring |
| explore | Read-only researcher | Read-only tools | File discovery, code understanding |
| review | Code reviewer | Read-only + analysis | Bug detection, security analysis |
| code-review | Deep code review | Read-only + diagnostics | PR review, style compliance |
| contexter | Context manager | Context tools | Compaction, memory optimization |
| coordinator | Orchestrator | Task spawning | Spawns and manages child agents |
| task | Command executor | Shell + file tools | Execute commands with verbose output |
Agent Lifecycle
Key architectural details:
- Per-child registry clone: Each sub-agent gets its own filtered copy of the tool registry, ensuring it can only use tools appropriate for its type
- Own todo store: Sub-agents maintain independent todo lists
- Sidechain transcripts: Child conversations are persisted to separate files, not mixed with the parent
- Steering channel: Parent is notified of child completion via
AgentNotificationhooks - Background execution:
mode="background"runs the agent async;read_agentpolls results with optional blocking - Fork context:
inherit_context=truecopies the parent's conversation as the child's starting context - Resume:
resume_agent_idcontinues a previously spawned agent's conversation
7. Session Management
Persistent Transcripts
Every conversation is persisted as a JSONL transcript. Sessions survive process restarts and can be resumed.
// internal/session/session.go
Session {
ID string
Title string
WorkDir string
Messages []ai.Message
Metadata map[string]string
AllowedTools []ToolApproval // survives restarts
TotalInputTokens int
TotalOutputTokens int
}
Checkpoint / Recovery
Checkpoints are atomic snapshots written to disk at configurable intervals:
- Written atomically via temp file + rename
- Restricted permissions (0600)
- Index-based naming:
{session_id}_cp{index}.json - Recovery loads the most recent checkpoint and replays from there
Undo Stack
File edits are tracked in an undo stack. The agent can revert changes on failure or user request.
Memory System
// internal/session/memory.go
ProjectMemory {
Conventions map[string]Convention // learned coding conventions
CodePatterns []CodePattern // reusable patterns
Architecture *ArchitectureDecisions // project architecture
FilePatterns map[string]FilePattern // per-filetype conventions
Preferences map[string]string // user preferences
}
Convention {
Name string
Description string
Confidence float64 // 0.0–1.0
Source string // where learned from
}
// /dream command consolidates session observations into memory
// Memory is injected into context via GetContextWithMemory()
Session Search & Compaction
- Search: Full-text search across session transcripts via
SearchTranscripts() - Compaction: Old sessions are compacted via the same 6-tier pipeline
- Migration: Schema versioning ensures backward compatibility across upgrades
- Redaction: Sensitive content is automatically redacted in logs and exports
8. Sandboxing
Automergent wraps every shell command in an OS-level sandbox to contain damage from runaway or malicious tool executions.
| Platform | Mechanism | Capabilities | Availability |
|---|---|---|---|
| Linux | bubblewrap (bwrap) |
Namespace isolation, mount sandboxing, seccomp, network isolation, resource limits | Requires bubblewrap installed |
| macOS | sandbox-exec (seatbelt) |
Filesystem restriction, network filtering,itlement-based isolation | Built-in on macOS |
| Windows | noop |
No sandboxing — commands run without restrictions | Fallback only |
// internal/sandbox/sandbox.go
Sandbox interface {
Wrap(ctx, name, args) → (string, []string)
WrapWithPolicy(ctx, name, args, policy) → (string, []string)
IsAvailable() → bool
Name() → string
Capabilities() → Capabilities
}
Capabilities {
SupportsNamespaces bool
SupportsSeccomp bool
SupportsCgroups bool
SupportsNetworkIsolation bool
SupportsFSIsolation bool
SupportsResourceLimits bool
RequiresRoot bool
}
Path Boundary Enforcement
The PathScope component ensures all file operations stay within the working directory:
- Validates every file path before tool execution
- Prevents
../../../etc/passwdstyle escapes - Checked at the orchestrator level before sandbox wrapping
- Configurable scope (strict: working dir only; relaxed: allow parent dirs)
Automergent Architecture Documentation — Generated from source code analysis
internal/agent/agent.go · internal/prompt/composer.go · internal/context/compaction.go · internal/tools/tool.go · internal/tools/agent/tools.go · internal/sandbox/sandbox.go · internal/session/