Architecture
Package map
Section titled “Package map”Core runtime
Section titled “Core runtime”| Package | Role |
|---|---|
cmd/moa/ | CLI entrypoint, flag parsing, runtime wiring |
pkg/agent/ | Agent loop, state machine, steering, budget enforcement |
pkg/bus/ | Typed event bus — commands, queries, events between components |
pkg/core/ | Shared abstractions: provider, message, tool, config, models |
pkg/bootstrap/ | Runtime config assembly and startup helpers |
Agent capabilities
Section titled “Agent capabilities”| Package | Role |
|---|---|
pkg/tool/ | Built-in tools (bash, read, write, edit, grep, find, etc.) |
pkg/compaction/ | Context summarization when token window fills up |
pkg/context/ | System prompt builder + AGENTS.md discovery |
pkg/permission/ | Gate for yolo/ask/auto tool approvals |
pkg/checkpoint/ | File-level undo — snapshots before writes, pop to revert |
pkg/memory/ | Cross-session persistent project memory |
pkg/tasks/ | Task store and tool for plan execution |
pkg/subagent/ | Child agent execution and async job management |
pkg/verify/ | Run project verification checks |
pkg/skill/ | Skill file loading |
pkg/askuser/ | ask_user tool bridge to UI |
pkg/goal/ | Autonomous maker→verifier loop toward an objective (/goal). The verifier is a read-only mini-agent (read/grep/find/ls, luna by default, 25 inspection turns) that inspects the repo to judge completion, and carries memory of its earlier verdicts across iterations; --verify-oneshot falls back to the legacy tool-less check. Exhausting its turns or time is not a verdict: it retries once with double the turns and, failing that, pauses the goal instead of relaying a non-verdict to the maker. When the project defines .moa/verify.json, red checks are a hard gate — the goal can’t be declared done while they fail, whatever the verdict says |
pkg/autotitle/ | Generates short session titles from the conversation via a cheap LLM call |
pkg/pulsebrief/ | Generates a per-session status brief (attempting/progress) via a cheap same-vendor LLM call; feeds the web dashboard and Pulse voice (web/Pulse-only) |
Providers
Section titled “Providers”| Package | Role |
|---|---|
pkg/provider/ | Factory: resolve model → provider adapter |
pkg/provider/anthropic/ | Anthropic Messages API (streaming SSE) |
pkg/provider/openai/ | OpenAI API transport and transcription |
pkg/provider/responses/ | Shared stateless Responses request/replay codec for OpenAI-compatible Responses transports (store: false) |
pkg/provider/xai/ | xAI Responses transport: api.x.ai API keys or the separate Grok consumer OAuth proxy |
pkg/provider/meta/ | Meta Model API Responses transport (Muse Spark): META_API_KEY or a key minted from a Muse subscription login |
pkg/provider/retry/ | Retry wrapper with backoff |
pkg/provider/sseutil/ | SSE timeout reader |
Interfaces
Section titled “Interfaces”| Package | Role |
|---|---|
pkg/serve/ | HTTP/WebSocket server + web UI session manager; also hosts the Pulse backend (device pairing, guardian WebSocket, Realtime client-secret broker, session brief) |
Infrastructure
Section titled “Infrastructure”| Package | Role |
|---|---|
pkg/auth/ | Credential store + OAuth flows, including xAI’s OIDC device grant |
pkg/session/ | Session persistence (file-backed, atomic writes) |
pkg/attachment/ | Content-addressed store for image/document bytes referenced by sessions |
pkg/extension/ | Extension host + typed hooks (internal; fired every turn but no user-facing loader/config to register extensions yet) |
pkg/mcp/ | MCP manager — local tool servers over stdio, remote ones over streamable HTTP |
pkg/git/ | Git context detection |
pkg/files/ | File utilities |
pkg/jsonutil/ | JSON parsing utilities |
pkg/push/ | Web Push notifications (VAPID keys, subscription store, dispatch) |
pkg/usage/ | Provider-qualified plan-usage pollers; Claude and xAI consumer data come from private, best-effort endpoints |
pkg/attention/ | Attention Service: consumes every session’s event bus and produces a priority-ordered attention queue |
pkg/schedule/ | Durable one-shot schedule records (backs the web /schedule command) |
pkg/release/ | Build metadata and best-effort release update checks |
pkg/ansi/ | Strips terminal control sequences from untrusted text before rendering |
Execution model
Section titled “Execution model”The same agent core is reused across all interfaces:
- CLI calls the agent directly, streams events to stdout
- Serve wraps the agent in HTTP/WebSocket session management
Event bus
Section titled “Event bus”pkg/bus is the central nervous system. Components communicate through typed messages:
- Events — async, fan-out (e.g.
ToolExecStarted,GoalChanged) - Commands — sync, one handler (e.g.
AbortRun) - Queries — sync, request-response (e.g.
GetSessionState)
The serve layer subscribes to events for rendering. The agent loop publishes events and handles commands.
Agent loop
Section titled “Agent loop”- Fire lifecycle hooks.
- Check budget and turn limits.
- Optionally compact context.
- Build the provider request.
- Stream assistant events.
- Extract tool calls.
- Validate and permission-check them.
- Execute approved tools (checkpoint files first).
- Append tool results and continue.
- End when the assistant stops without more tool calls.
Sessions
Section titled “Sessions”Sessions persist full message history plus metadata using atomic file writes. Serve uses that store for persistence and resume.
Attachments
Section titled “Attachments”Images and documents are not stored inline in the session file. When a tool
produces one, the bytes go to a content-addressed store under
~/.config/moa/attachments/v1 and the transcript keeps only a small reference;
the bytes are put back just before the provider request. Identical content is
stored once no matter how many sessions or subagents read it.
The reason is that a session file is rewritten whole on every save, so inlining base64 made the cost of a conversation grow with every screenshot it had ever seen — in both disk and memory — even when nothing referred to them anymore.
Producing a reference and being able to resolve it is a single capability
tied to the owning session (attachment.Scope), resolved per tool invocation
rather than captured when a tool is built: tools are shared between a parent and
its children, and an ephemeral agent that does not persist its transcript must
not mint references nobody will ever resolve. Subagent attachments are owned by
the parent session, so they survive a restart that ends the child’s job.
A request carrying an unresolved reference is refused before it reaches the provider: silently sending an empty image would make the model answer about something it cannot see.
Compaction
Section titled “Compaction”When a session approaches its context window it is summarized: an older prefix
of the conversation is replaced by a single summary message, and a recent tail
is kept verbatim. FindCutPoint picks the boundary, SerializeForSummary
renders the prefix for the summarizer, and Compact performs the swap.
What survives compaction is whatever the summary records, so the design goal is not compression but retention of what cannot be recovered by other means. File contents, command output and code can be read again; decisions, rejected approaches, constraints and user intent cannot. Three properties follow:
- Recency is protected. On overflow the serializer drops the oldest messages, never the newest, and never the final message — an empty transcript would let a summary of nothing replace real history.
- Tool results keep their ending. Their budget is spent newest-first, and anything that must be shortened keeps its head and its tail: the outcome of a tool call (the failing assertion, the final error) lives at the end. Tool output is capped at a share of the transcript so it cannot evict the dialogue, which is the part carrying intent.
- The output budget is explicit. The summary has a declared token budget that scales with the context window, and the cut point reserves that same figure so the two cannot drift apart. Thinking is disabled for the call, since it draws from the same output budget.
The session checkpoint (pkg/sessioncheckpoint) is an escape hatch from the
summarizer: a small slot the model can write before compaction, appended to the
summary mechanically so it cannot be omitted or paraphrased. Both the manual and
automatic paths append it. It is ephemeral — consumed once, cleared by
generation so a checkpoint written mid-compaction is not lost, and never
consumed by a failed or cancelled compaction.
An automatic compaction arrives mid-task with no warning, so the agent can be
holding findings it never wrote down. compact_strategy decides what it gets
first: plain summarizes silently, notify (the default) reminds it that the
threshold is near and to persist anything that only exists in the conversation,
and prepare gives it a full turn to do so. The notice costs nothing — it rides
on the next request — while prepare costs a request. Both apply only to
automatic compaction: /compact is a deliberate act and is left alone, and
/prepare-compact is the manual form of the same turn. Subagents are excluded
under every setting.
Compaction is lossy by construction. The full transcript remains on disk: the session log is append-only, so a summarized session can still be inspected in full after the fact.
Design constraints
Section titled “Design constraints”- The agent loop depends on a hook interface, not a concrete extension host.
- Errors are returned, not panics.
pkg/corestays dependency-light.- Interface layers reuse the core runtime — they don’t reimplement it.