Skip to content

Architecture

The agent follows a three-category architecture designed for clarity and extensibility.

The Three Categories

CategoryRoleHas a shape?User-extensible?Examples
CoreWireframe that connects everythingNoNoloop, registry, config, session
ServicesFixed branches the core depends onService typeNo (built-in)api, storage, admin, tokens, guardrails, persona
PluginsSwappable components plugging into core at defined attachment pointsShape per type (Tool, Gateway, Hook, MemoryPlugin)Yes (built-in + user ~/.agent/)tools/, gateways/, hooks/, memory/

Core

The lightweight wireframe — it routes work outward but doesn't do the work itself. Pure orchestration: the event loop, the registry, config loading, session type definition.

Services

Fixed branches the core depends on. Each conforms to a Service shape:

gleam
pub type Service {
  Service(
    name: String,
    supervised: Bool,
    start: fn() -> Result(Nil, String),
    stop: fn() -> Result(Nil, String),
    health: fn() -> Result(String, String),
  )
}

The supervised: Bool flag self-declares whether the service needs OTP process management. Services do actual work (HTTP calls, SQLite, shell execution) but aren't user-swappable.

Plugins

Swappable, shape-conforming, user-extensible. Built-in and user-provided plugins follow the exact same shapes. Users add plugins in ~/.agent/tools/, ~/.agent/hooks/, etc.

Each plugin has a top-level Plugin shape plus a sub-type shape:

gleam
pub type Plugin {
  Plugin(
    name: String,
    description: String,
    plugin_type: PluginType,
    supervised: Bool,
    start: fn() -> Result(Nil, String),
    stop: fn() -> Result(Nil, String),
    health: fn() -> Result(String, String),
  )
}

Plugin types:

  • Tool — things the agent can do (bash, web fetch, browser, memory, cron)
  • Gateway — channels the agent communicates through (Telegram, CLI/TUI)
  • Hook — lifecycle callbacks that fire at specific points (context compression, reflection, tool guardrails)
  • MemoryPlugin — persistence backends for agent memory (file-based, SQLite)

Project Structure

src/
├── core/               — Wireframe (loop, registry, config, session, tool shape)
├── services/           — Fixed branches, Service shape, OTP-supervised
│   ├── api/openai/             — OpenAI completions client
│   ├── storage/                — SQLite persistence
│   ├── admin/admin.gleam       — Inspection and management
│   ├── tokens/tokens.gleam     — Heuristic token estimation (CJK-aware)
│   ├── guardrails/guardrails.gleam — Shell command safety layer
│   ├── persona/persona.gleam   — Persona/SOUL.md file loader
│   ├── context/context.gleam   — System prompt builder
│   ├── titler/titler.gleam     — Auto-titling
│   ├── pulse/pulse.gleam       — Time-driven periodic task execution
│   ├── cron/                   — Traditional cron scheduler
│   ├── harness/harness.gleam   — Deterministic validation gating
│   └── notifications/          — Notification delivery + DND coordination
├── plugins/            — Pluggable, shape-conforming, user-extensible
│   ├── shapes.gleam          — Plugin shape definition
│   ├── tools/              — bash/, browser/, code/, cron/, memory/, session_search/, web/
│   ├── gateways/           — telegram/, tui/
│   ├── hooks/              — context_compressor/, reflection/, tool_guardrails/
│   └── memory/             — file_memory/
├── agent.gleam             — CLI REPL entry point
├── agent_app.gleam         — Daemon entry point
├── agent_admin.gleam       — Admin CLI
└── agent_supervisor.gleam  — Root supervisor

OTP Supervision

The agent uses a structured OTP supervisor tree:

  • Root supervisor (agent_supervisor.gleam) coordinates service and gateway supervisors
  • Service supervisor manages services that declare supervised: true
  • Gateway supervisor manages gateway processes (Telegram polling, admin TCP listener)

Each Service and Plugin self-declares whether it needs supervision via the supervised flag. The supervisor starts and monitors only those that opt in.

Request Flow

  1. A message arrives via a Gateway (Telegram, CLI)
  2. The Loop loads the session, builds the system prompt (via context service), and sends the conversation to the API service
  3. The API response may include tool calls — the loop dispatches them through the Tool Registry
  4. Tool results feed back into the conversation for the next API call
  5. Hooks fire at defined points: reflection after each turn, context compression when the window fills up, tool guardrails on every tool execution
  6. The loop continues until the model responds with a final message or the tool round limit is reached

Extending the Agent

Add custom tools by creating a module in ~/.agent/tools/ that conforms to the Tool shape. The plugin registry auto-discovers it on startup. See the Plugins page for details.

Built with Gleam on the BEAM/Erlang VM.