Documentation v0.30.0

Architecture

A high-level view of how Agenvoy fits together. For per-module diagrams, sequence flows, and the tool-dispatch state machine, jump into the topic-specific pages.

Overview

graph TB
    subgraph Entry["Entry · cmd/app"]
        App["agen · TUI + daemon<br/>agen cli / run · single-shot<br/>agen stop · update · --daemon<br/>non-TTY stdin · MCP server"]
    end

    subgraph Engine["Engine · internal/agents/exec"]
        Run["exec.Run<br/>:name → skill → model: hint"]
        Execute["exec.Execute · ≤128 iterations<br/>3-pass parallel tool calls"]
        Sub["ExecWithSubagent<br/>in-process · ≤3 concurrent"]
        Compact["compact · threshold → tool history<br/>→ old history → trim fallback"]
    end

    subgraph Pending["Pending · internal/runtime"]
        Reg["Prefix-routed listener registry<br/>per-entry buffered=1 reply ch<br/>TUI/CLI listen '' · Telegram listens 'tg-'"]
    end

    subgraph Providers["go-llm-router · 12 providers"]
        P["OpenAI · Codex · Claude · Gemini<br/>Grok · Copilot · DeepSeek · NVIDIA<br/>OpenRouter · Cloudflare · Compat"]
    end

    subgraph Tools["Tool Subsystem · internal/tools"]
        T["File · Web · HTTP · Media · Memory<br/>run_skill · invoke_subagent · ask_user<br/>scheduler · revision · reasoning_guide"]
        MCP["MCP · internal/runtime/mcp<br/>client + server · go-sdk"]
        Adapter["toolAdapter · internal/runtime<br/>script_* · api_* · ext_*"]
    end

    subgraph Security["Security · go-pkg/sandbox"]
        S["bwrap (Linux) / sandbox-exec (macOS)<br/>filesystem.Policy · DeniedMap"]
    end

    subgraph Session["Session · internal/session"]
        SL["bot.md · status.json · action.log<br/>usage.log · fsnotify watch"]
    end

    subgraph Memory["Memory · ToriiDB + SQLite"]
        M["DBSessionHist · DBSessionSummary<br/>error_memory · 90d TTL<br/>FTS5 archive · text-embedding-3-small"]
    end

    subgraph Kura["KuraDB · daemon-managed child"]
        K["/usr/local/bin/kura<br/>endpoint @ ~/.config/kuradb/endpoint<br/>list_rag · search_rag"]
    end

    App --> Run
    Run --> Execute
    Execute -->|invoke_subagent| Sub
    Sub --> Execute
    Execute --> Compact
    Execute -->|Send| Providers
    Execute -->|tool calls| Tools
    Tools --> MCP
    Tools --> Adapter
    Tools --> Security
    Tools -.->|list_rag · search_rag<br/>per-turn excluded if endpoint absent| Kura
    Execute <-->|confirm/ask| Pending
    Sub <-->|subagent ask_user| Pending
    Execute <--> Memory
    Execute <--> Session
    App -.->|spawn + healthcheck| Kura

Layers

Layer Package Responsibility
Entry cmd/app argv dispatch (cli / run / stop / update / --daemon); MCP server on non-TTY stdin; init sandbox, filesystem policy, MCP manager
Runtime singleton internal/runtime server-mode UID lock; SIGTERM prior server on startup
Engine internal/agents/exec iteration loop; tool dispatch; provider routing; subagent host (execWithSubagent.go)
Compaction internal/agents/exec/compact per-model threshold, tool-history and old-history folding, trim and raw-tool fallbacks
Providers go-llm-router (external module) unified Agent.Send() across 12 providers; reasoning and fast-mode normalization
Tools internal/tools + internal/runtime/toolAdapter built-in / API / script / extension tool definitions
MCP internal/runtime/mcp client and server on the official modelcontextprotocol/go-sdk, one package
Sandbox go-pkg/sandbox OS-native isolation, single entry Wrap()
Filesystem go-pkg/filesystem (+ reader/) + internal/filesystem policy-aware writes; ToriiDB pathing
Session internal/session bot.md / status.json / action.log / fsnotify observer
Pending internal/runtime/pending.go prefix-routed confirm/ask listener registry; per-runtime listener via RegisterListener(prefix), claim via PickNextFor(prefix)
Memory ToriiDB (DBSessionHist / DBSessionSummary / error_memory) + go-sqlkit (SQLite FTS5 archive) semantic search + 90-day TTL + full-text persistent archive
Scheduler internal/runtime scheduler + fsnotify watcher cron / one-shot tasks bound to scheduler skills; hot-reload on {tasks,crons}.json change
KuraDB internal/runtime/kuradb/ (kuradb.go / run.go) + internal/runtime/kuradb/tool/ RAG provider child process; daemon-managed spawn + 3-strike health check; per-turn dynamic tool exclusion when endpoint missing
TUI internal/runtime/tui bubbletea inline-chat front-end; single-package by design

Cross-cutting principles

TUI design choices

Per pardn chiu: "bubbletea isn't designed to be split into separate modules that reference each other — splitting it would make the lifecycle a mess. I don't have the bandwidth to handle it right now." This module is intentionally kept undivided.

The TUI lives in a single package (internal/runtime/tui) and is not split into subpackages. Every file under internal/runtime/tui/ follows this principle.

Why bubbletea (not tview / tcell)

The previous TUI used rivo/tview. It was replaced because:

The cost is that bubbletea is a Go port of The Elm Architecture — its tea.Model interface is monolithic by design.

Why a single package

tea.Model requires Update(tea.Msg) (tea.Model, tea.Cmd) to be a method on the model type. Methods must live in the same package as the type. This forces:

A real Go-style TUI would build per-domain widget packages (each owning its state struct, render method, and event handler) with bubbletea acting only as event loop. That refactor is a 600-800 LOC rewrite split into 4 phases. For the current ~1.1k LOC TUI maintained by one developer, the gain doesn't justify the cost.

When to revisit

Switch to per-domain widget packages when any one of:


[!NOTE] This document was auto-generated by Claude after reading the full source code.

中文