A lightweight, modular AI agent framework written in Go.
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-10-27 19:06:22 -07:00
.github/workflows ci: fix rolling release publishing 2026-08-07 09:06:13 -07:00
agent Preserve context and recover full tool output with budget-aware compaction 2026-10-27 19:06:22 -07:00
attachment feat: unify CLI and IM incoming attachment processing 2026-10-22 21:45:40 -07:00
channel Preserve Matrix input admission order and associate follow-up attachments 2026-10-27 17:11:49 -07:00
config Preserve context and recover full tool output with budget-aware compaction 2026-10-27 19:06:22 -07:00
conversation refactor: unify IM conversation surfaces and rendering 2026-10-22 23:41:11 -07:00
creds test: add core regression suite and CI 2026-08-03 00:24:53 -07:00
docs Preserve context and recover full tool output with budget-aware compaction 2026-10-27 19:06:22 -07:00
event Improve IM progress cards with tool arguments and recent activity 2026-10-27 17:05:35 -07:00
input feat: support active-run steering across IM and CLI 2026-10-23 16:03:04 -07:00
internal/agentdocs Preserve context and recover full tool output with budget-aware compaction 2026-10-27 19:06:22 -07:00
jsonutil test: add core regression suite and CI 2026-08-03 00:24:53 -07:00
llm Track cache usage and scoped request totals with streaming compatibility 2026-10-27 19:05:43 -07:00
rendering Improve IM progress cards with tool arguments and recent activity 2026-10-27 17:05:35 -07:00
security feat: segmented skills, cron rewrite, and safety controls 2026-08-07 00:09:53 -07:00
server Stage attachments as references and read multimodal content on demand 2026-10-27 17:19:53 -07:00
skills feat: segmented skills, cron rewrite, and safety controls 2026-08-07 00:09:53 -07:00
tools Preserve context and recover full tool output with budget-aware compaction 2026-10-27 19:06:22 -07:00
.gitattributes test: add core regression suite and CI 2026-08-03 00:24:53 -07:00
.gitignore fix bugs 2026-04-08 22:22:58 -07:00
cli_active_input_test.go Stage attachments as references and read multimodal content on demand 2026-10-27 17:19:53 -07:00
cliui.go Track cache usage and scoped request totals with streaming compatibility 2026-10-27 19:05:43 -07:00
config_cli.go feat: add interactive config editor 2026-09-29 23:11:02 -07:00
config_cli_test.go feat: add interactive config editor 2026-09-29 23:11:02 -07:00
CURRENT_STAGE_TASKS.md feat: harden agent interactions and scheduling 2026-09-28 21:34:46 -07:00
example.config.toml Preserve context and recover full tool output with budget-aware compaction 2026-10-27 19:06:22 -07:00
go.mod feat: add interactive config editor 2026-09-29 23:11:02 -07:00
go.sum feat: add interactive config editor 2026-09-29 23:11:02 -07:00
im_commands.go feat: expose owned tool and config metadata 2026-09-29 22:58:49 -07:00
im_commands_test.go feat: harden agent interactions and scheduling 2026-09-28 21:34:46 -07:00
im_failed_inbox.go feat: support active-run steering across IM and CLI 2026-10-23 16:03:04 -07:00
im_failed_inbox_test.go feat: support active-run steering across IM and CLI 2026-10-23 16:03:04 -07:00
im_input.go feat: support active-run steering across IM and CLI 2026-10-23 16:03:04 -07:00
im_input_test.go Stage attachments as references and read multimodal content on demand 2026-10-27 17:19:53 -07:00
im_pending_input_test.go Stage attachments as references and read multimodal content on demand 2026-10-27 17:19:53 -07:00
im_progress.go refactor: unify IM conversation surfaces and rendering 2026-10-22 23:41:11 -07:00
im_progress_test.go feat: harden agent interactions and scheduling 2026-09-28 21:34:46 -07:00
im_runtime.go Preserve Matrix input admission order and associate follow-up attachments 2026-10-27 17:11:49 -07:00
im_runtime_test.go Stage attachments as references and read multimodal content on demand 2026-10-27 17:19:53 -07:00
im_session_commands.go refactor: unify IM conversation surfaces and rendering 2026-10-22 23:41:11 -07:00
im_session_commands_test.go refactor: unify IM conversation surfaces and rendering 2026-10-22 23:41:11 -07:00
im_sessions.go refactor: unify IM conversation surfaces and rendering 2026-10-22 23:41:11 -07:00
im_sessions_test.go refactor: unify IM conversation surfaces and rendering 2026-10-22 23:41:11 -07:00
init_cli.go feat: make init keyboard driven 2026-09-29 23:12:13 -07:00
INTERACTIVE_CLI_TASKS.md docs: document interactive administration 2026-09-29 23:12:20 -07:00
LICENSE update docs 2026-04-16 15:41:08 -07:00
main.go Preserve context and recover full tool output with budget-aware compaction 2026-10-27 19:06:22 -07:00
main_test.go fix: preserve attachment routing and pending user answers 2026-10-22 22:07:58 -07:00
memory_cli.go feat: add memory administration commands 2026-09-29 23:12:20 -07:00
memory_cli_test.go feat: add memory administration commands 2026-09-29 23:12:20 -07:00
readline.go feat: support active-run steering across IM and CLI 2026-10-23 16:03:04 -07:00
readline_test.go feat: support active-run steering across IM and CLI 2026-10-23 16:03:04 -07:00
README.md Preserve context and recover full tool output with budget-aware compaction 2026-10-27 19:06:22 -07:00
SECURITY_BASELINE_TASKS.md feat: harden agent interactions and scheduling 2026-09-28 21:34:46 -07:00

AgeAge

A self-hosted AI agent framework in Go. Wire any OpenAI-compatible LLM to a tool loop, teach it custom workflows with Markdown skill files, and reach it from a terminal, a chat room, or your own client — all from a single binary with a single config file.


Why AgeAge

Most AI agent frameworks are heavy, unpredictable black boxes that blindly consume tokens and require massive Python environments. AgeAge takes a different approach: maximum token efficiency, strict predictability, and zero deployment friction.

Core Philosophy

1. Context Economy & Token Efficiency — AgeAge aggressively protects your context window. An intelligent Router intercepts user intents, directing simple queries to cheap models and reserving strong models for complex reasoning. Deep tasks are offloaded to isolated sub-agents via delegation, keeping the main session clean and strictly scoped.

2. Semi-Automated Predictability (Pipelines) — LLMs are inherently non-deterministic, making repetitive workflows fragile. AgeAge introduces YAML Pipelines as an escape hatch: chains of LLM-driven agent nodes and deterministic auto nodes (direct tool calls, no reasoning). You only invoke the AI when cognitive work is actually required, eliminating infinite loops and guaranteeing reproducibility.

3. Transparent Execution & Zero-Leak Security — In supervised mode, destructive actions pause for explicit confirmation. More importantly, your secrets never touch the cloud. Through an encrypted local vault and {{cred:name}} placeholders, the LLM requests authenticated actions without ever seeing the actual passwords or API keys.

4. First-Class Multi-Platform UX — Whether you are in the terminal (ageage cli) or a group chat (Telegram, Discord, Matrix), the experience is tailored. Multi-session management works out of the box — every Matrix thread automatically spawns an isolated, persistent agent session. The CLI features live reasoning streams, inline file attachments (@file), and conversational rollbacks (/undo).

5. Ultra-Lightweight Single Binary — Written in Go, AgeAge deploys as a single standalone executable. It consumes less than 10 MB of RAM at idle, starts instantly, and requires zero heavy dependencies to run the core engine.


Delivery modes

Mode Command Description
Interactive CLI ageage cli Development, debugging, power-user tasks. /session, /clear, /build, /retry, @file attachments.
IM channels ageage connect Telegram, Discord, Matrix. Per-room session management, typing indicators, ⏳/✅ reactions, thread sessions.
HTTP API ageage serve OpenAI-compatible /v1/chat/completions. Drop-in backend for SillyTavern, OpenWebUI, and similar clients.
MCP server ageage mcp Expose all registered tools as MCP tools over stdio so other AI systems can call them.

Quick start

# 1. Clone and build
git clone https://github.com/your-org/ageage
cd ageage
go build -o ageage .

# 2. Create a workspace with a starter config
./ageage init
# For pipes and automation, use line-oriented prompts and an explicit target:
./ageage init --plain --dir ./ageage
# or copy the fully commented example and fill it in:
cp example.config.toml workspace/config.toml

# 3. Start chatting
./ageage cli

Minimum config:

[llm]
api_key  = "sk-..."
base_url = "https://api.openai.com/v1"
model    = "gpt-4o-mini"

Full configuration reference: docs/config.md


Basic usage

After ./ageage init, start a session with:

./ageage cli              # interactive terminal session
./ageage cli -c path/to/config.toml
./ageage config            # staged keyboard configuration editor
./ageage config tools      # edit the default or custom tool allowlist
./ageage config edit       # open the resolved TOML file in $VISUAL / $EDITOR
./ageage config validate   # validate config.toml (script friendly)
./ageage memory            # interactive memory manager
./ageage memory list       # script-friendly memory listing
./ageage memory search "project preference"
./ageage memory export ./memory-backup.jsonl

ageage tools is retained as a compatibility alias for ageage config tools. An empty agent.tools value means the default tool set; skill-only tools are added only when a skill requests them. A non-empty custom allowlist can make selected skill-only tools available globally, and cannot be empty. See the configuration reference for editor and non-terminal behavior.

Slash commands

Command What it does
/help Show this command list
/build [description] Ask the Planner to author a reusable skill or pipeline
/clear Clear the current conversation (keeps the session)
/stop Interrupt the running task
/summarize Compress conversation history with an LLM summary
/undo Remove the last user→assistant turn
/retry [text] Re-run the last message, optionally with extra instructions
/think Show the last captured reasoning think-block
/skills List loaded skills (/name invokes one directly)
/session or /s Session management (see below)

Type exit or quit to leave the CLI.

Sessions

Each conversation lives in a session under .ageage/sessions/. Session history persists across runs — the same session can be resumed with /s switch.

/s              List sessions
/s new [name]   Create and switch to a new session
/s switch <name>   Switch sessions (prefix matching supported)
/s rename <new>     Rename the current session
/s delete <name>    Delete a session (moves it to the trash)

File attachments

Attach files inline with @path. Uploads provide metadata and a stable reference; the agent decides when to call file_read to view an image or parse a document:

You ▸ Summarise @report.pdf and check @screenshot.png for issues

Other entry points

Mode Command
IM channels (Telegram/Discord/Matrix) ./ageage connect
HTTP API (OpenAI-compatible) ./ageage serve <data-dir>
MCP server over stdio ./ageage mcp
Configure / inspect tools ./ageage config, ./ageage config tools, ./ageage tools (compatibility alias)
Manage memory ./ageage memory, ./ageage memory list, ./ageage memory search <query>, ./ageage memory export <path>
Inspect skills / credentials / scheduled tasks ./ageage skills, ./ageage cred, ./ageage cron

In IM mode, Matrix AgeAge commands use ! (for example !help and !session new); Telegram and Discord commands continue to use /. IM progress visibility is configured with [notifications], including per-channel overrides. See the channel configuration for details.

IM conversations share a target/surface abstraction for replies, topics, status cards, questions and notifications. Matrix !session new [name] creates a named thread root; session lists include previews, turn counts and links. Existing threads keep their session when navigating with session switch. See the IM conversation architecture for behavior and fallbacks.


Architecture overview

flowchart TD
    subgraph entry [Entry Points]
        direction LR
        cli[CLI]
        http[HTTP API]
        im[IM Channels]
        mcp[MCP Server]
    end

    fac[AgentFactory]

    subgraph agt [Per-Request Agent]
        rtr[Router] --> pln[Planner] --> loop[Execution Loop]
        loop --> sum[Summarizer]
        loop -.->|background| ev[Evaluator]
    end

    tr[(Tool Registry)]
    lm[(LLM Client)]
    sk[(Skills)]

    entry --> fac
    fac -->|CreateAgent| agt
    agt --- tr
    agt --- lm
    agt --- sk

Key components

AgentFactory is the single initialization point. It loads config, establishes the LLM client, compiles skills, and wires up all shared resources (cron store, MCP sessions, security checker, credential manager). Every agent instance comes from the factory and shares underlying config without duplicating state. A skill watcher goroutine polls the skills directory every 2 seconds and hot-reloads .md and .yaml files on any change.

Agent is the conversation engine. It holds the full message history, the tool registry, the router, and the summarizer. One instance persists across turns; history grows with each exchange and is compressed or summarized when it exceeds configurable thresholds.

Router is an optional lightweight classifier that runs before each turn. It answers three factual yes/no questions about the request (needs_tools, needs_multiple_calls, needs_synthesis) and lists the required tools. The engine derives the model tier (base/medium/strong) from the checklist — eliminating semantic bias from manual labelling. The router also selects the active skill.

Planner & Evaluator work together for complex tasks with no existing skill. The Planner runs a sandboxed agent to author a new .md or .yaml skill file. Afterwards, the Evaluator runs asynchronously in the background to review the execution and potentially patch the generated skill. The /build command also triggers the Planner explicitly, passing recent conversation context.

Tool Registry is a simple map with thread-safe register/execute methods. Global tools are always available; skill-only tools (grep, glob, browser_*, update_todos, escalate) are injected at the start of a matched turn and removed when it returns.


Request flow (one user turn)

User message
    │
    ▼
① Skill matching — word-boundary name match against loaded skills
② Skill-only tool injection — add declared tools; remove on turn exit (defer)
③ Router (optional) — answer 3 checklist questions; derive model tier; select tools & model
④ Planner (optional) — if tier=strong and no skill exists, auto-generate one
⑤ Model & tool selection
    base   → [llm] model, no delegate
    medium → [router.medium] model if configured
    strong → [router.strong] model, delegate injected
⑥ Execution loop (up to max_iterations)
    LLM call → tool calls → LLM call → …
    <think> blocks stripped from streamed output
    Budget pressure compacts older tool turns
    finish_task(status, summary) called → save history → return
    [Guard] finish_task(status="success") blocked if any todo is still pending
⑦ Post-turn summarization & Evaluator
    Background summarization (optional)
    Background evaluator for auto-generated skills

Token efficiency

Strategy Effect
KV cache stability Dynamic time, todos and session notes stay in a trailing context block. Raw history remains unchanged below the soft budget; skills and configured directives can intentionally change the prefix.
In-place compression Under budget pressure, older tool exchanges become compact narratives while at least the latest 2 stay raw. Full-output references remain readable.
Tool result capping Tool outputs in history are capped at 4 000 runes; the full result is used by the current iteration.
Conversation summarization After a threshold, old pairs are replaced by an LLM-generated summary.
Router model tiering Simple tasks stay on the base model; only complex multi-step tasks escalate to the strong model. The router itself runs on a cheap classifier model.

Skills

Skills live in {workspace}/skills/. Any .md file with valid frontmatter is picked up automatically.

---
name: code-review
description: "Review code for bugs, style, and security."
tier: medium
required_tools:
  - file_read
  - grep
---

Read the given file(s) and return a structured review with severity ratings.
Frontmatter field Purpose
name Word-boundary matched against user input (case-insensitive, spaces/underscores/hyphens equivalent).
description Shown to the router for skill selection.
tier base / medium / strong — bypasses the router LLM call when set.
required_tools Tool allowlist for this turn.

Model tiers

Value Model used Delegate tool
base Base [llm] model No
medium [router.medium] model (if set) No
strong [router.strong] model (if set) Yes

Multiple skills can match simultaneously. Their tool lists merge and the highest tier wins. Skills are hot-reloaded — the change applies within 2 seconds.

Pipeline skills

Skills can define structured multi-step pipelines in YAML — chains of sub-agent nodes, direct tool calls, and foreach loops — instead of a single prompt body. See docs/skills.md for the full pipeline reference.

/build command

Type /build [description] to explicitly trigger the Planner from any mode. The Planner receives the last 8 turns of conversation as context and authors a new skill or pipeline file — without adding any of the build process to the main conversation history.

/build create a daily GitHub notification summarizer

Sub-agents and delegation

The delegate global tool and escalate skill-only tool each spawn an isolated sub-agent with its own registry and iteration budget. Sub-agents do not run the router and do not inherit personality files, giving clean isolation for complex tasks.

delegate uses [subagent.model]; escalate always uses [router.strong].


Multimodal attachments

CLI — attach files inline with @path syntax:

You ▸ Summarise @report.pdf and check @screenshot.png for issues

HTTP API — multimodal content arrays in request bodies are forwarded directly to the agent.

Matrix, Telegram, Discord — send files with or without a caption. Matrix m.image and m.file, Telegram photos and documents, and Discord attachments use the same staging pipeline as CLI files. Uploads supply filename, MIME, size and an opaque attachment:att-… reference; file contents are not injected into the model automatically. The agent chooses whether and when to use file_read with that reference. A reference may also be passed in a delegated task.

Matrix preserves admission order for separate text and attachment events without waiting for the active run to finish. In group rooms, an explicit bot mention in a text message allows follow-up attachments from the same sender in the same conversation for 15 seconds. Existing thread routing remains supported.

multimodal.max_attachment_bytes limits each CLI/IM file to 10 MiB by default; multimodal.max_turn_attachment_bytes limits the total successfully downloaded bytes in one input to 40 MiB by default. At most 16 files are accepted per input. Bodies are streamed into private files under the config directory's data/tmp/staged/; original files remain available while history references them. References are retained through history summarization and can be restored after restart. Clearing history reclaims managed originals.

file_read reads text by line (up to 500 lines per call), parses documents with the existing [multimodal.converters] settings, or returns images as vision parts when multimodal.vision = true. Parsed document text is capped at 512 KiB and cached by content and converter settings. Converters run for at most 60 seconds; an output file exceeding the 1 MiB hard limit terminates conversion. Images with vision disabled can use a configured text converter; otherwise reading reports an explicit error. Upload/download failures are reported immediately, while unsupported formats and conversion failures are reported when the agent reads. Matrix audio/video and encrypted attachments remain unsupported.

The runtime returns a normalized agent.UserInput without open download streams. New turns, active-run steering, and scoped ask_user answers all carry the same attachment references. A successful image read appends a framework-generated multimodal message after the entire tool-result batch, preserving provider message ordering. These resource messages do not count as external user turns. The MCP server also preserves image results as native MCP image content.


LLM compatibility

llm.Client wraps any OpenAI-compatible endpoint.

Google Gemini — when base_url contains generativelanguage.googleapis.com, a compatibility layer:

  • Merges multiple system messages into one (Gemini requires exactly one)
  • Merges consecutive same-role plain-text messages
  • Strips reasoning_content from outbound messages
  • Preserves thought_signature on tool calls for Gemini's verification round-trip

Security model

  • blocked_commands — substring blocklist on every bash invocation
  • allowed_roots / forbidden_roots — path allowlist/denylist for all file tools; symlinks are fully resolved before checking
  • Supervised mode — destructive tools pause for y / n / a confirmation; bash.auto_allow_commands defines prefix patterns that skip confirmation in that directory
  • credentials.toml — unconditionally blocked from all file tools regardless of config; cannot be overridden
  • IM group chats — bot only responds when @mentioned or replied to; allowed_users must be configured or group messages are denied
  • Scoped interaction replies — confirmations and ask_user answers are bound to their channel, thread, session, and sender instead of being selected by arrival order
  • Network egress — web and browser tools reject local, private, link-local, multicast, and cloud-metadata targets by default and recheck native HTTP redirects
  • Private memory storage — memory files use mode 0600; recall output is bounded and updates are synchronized and atomically replaced

Security migration notes

  • Existing local API setups remain unauthenticated by default and continue to bind to 127.0.0.1. Before exposing the API beyond localhost, configure server.api_key, explicit server.cors_origins, request limits, and optionally server.health_auth.
  • Private web endpoints that previously worked are now blocked. Enable allow_private only for the specific web or browser tool that needs trusted internal access, or use allowed_domains to narrow its destinations.
  • IM approvals now require the same conversation scope and an authorized sender. Integrations that inject replies directly should use the scoped confirmation and user-input APIs.
  • Tool extensions may implement MetadataProvider to declare execution properties. Extensions without metadata remain compatible but are conservatively treated as side-effecting and medium risk.

See docs/config.md for the server and network settings, and docs/tools.md for tool behavior.

Credential system

Named secrets are stored AES-256-GCM encrypted. The master key lives at os.UserConfigDir()/ageage/master.key — separate from the workspace so the credential file cannot be decrypted even if the workspace is leaked.

ageage cred add <name>     # prompt for value (no terminal echo)
ageage cred list           # list names (never values)
ageage cred remove <name>

The agent uses {{cred:name}} placeholders in tool arguments. Values are substituted in-memory just before execution and scrubbed from all results before they enter conversation history.


Configuration quick-start

[llm]
api_key  = "sk-..."
base_url = "https://api.openai.com/v1"
model    = "gpt-4o-mini"

[agent]
max_iterations = 20
mode           = "supervised"   # "full" skips all confirmations

[router]
enabled = true

[router.classifier]             # Fast cheap model for classification
model    = "gemini-2.0-flash"
api_key  = "AIza..."
base_url = "https://generativelanguage.googleapis.com/v1beta/openai/"

[router.strong]                 # Best model for complex/workflow tasks
model = "gpt-4o"

[eval]                          # Auto-generated skill quality checker
success_threshold = 3

[pipeline]                      # Pipeline execution overrides
foreach_concurrency = 4

Run ageage init for an interactive setup wizard.

Full reference: docs/config.md · docs/skills.md · docs/tools.md


Directory layout

ageage/
├── agent/          # Agent loop, factory, router, planner, evaluator, sessions
├── channel/        # IM connectors (Telegram, Discord, Matrix)
├── config/         # TOML config structs and loader
├── creds/          # AES-256-GCM credential store
├── docs/           # config.md, skills.md, tools.md, pipeline.md
├── llm/            # OpenAI-compatible HTTP client + Gemini compatibility layer
├── security/       # Command and path safety checker
├── server/         # HTTP API (OpenAI-compatible) and MCP server
├── skills/         # Skill loader and hot-reload watcher
├── tools/          # All tool implementations
├── cliui.go        # Lipgloss CLI UI
└── main.go         # CLI entrypoint (cobra)

Development

Run the local verification suite before submitting changes:

gofmt -w $(git ls-files '*.go')
go vet ./...
go test ./...
go test -race ./...
go build ./...

The GitHub Actions Test workflow runs formatting checks, vet, race-enabled tests, and the build automatically for pushes to public and for pull requests. Tests use temporary directories and local httptest servers; they do not require API keys, model access, a browser installation, or public network access.


Dependencies

Package Purpose
github.com/BurntSushi/toml Config file parsing
github.com/PuerkitoBio/goquery HTML extraction fallback
codeberg.org/readeck/go-readability/v2 Mozilla Readability content extraction
github.com/modelcontextprotocol/go-sdk MCP client and server
github.com/playwright-community/playwright-go Browser automation
github.com/charmbracelet/lipgloss Terminal UI styling
github.com/spf13/cobra CLI command structure
gopkg.in/yaml.v3 Skill frontmatter parsing

License

MIT

Context integrity and cache telemetry

Generated history summaries persist across turns and reloads. Compression checks run between complete tool batches, including within long single tasks. The shared soft input budget defaults to summarize.max_context_tokens = 24000; exact token counts depend on the provider. Model summarization remains opt-in via summarize.enabled.

Long tool outputs are saved in full and surfaced through recoverable references. file_read supports line ranges and byte pages (start_byte, max_bytes) for large logs and single-line JSON. Session notes use the active session's exact path and refresh at each call without changing the stored prompt prefix.

CLI usage includes cache-read tokens and an observed-request hit ratio when the provider reports it, labels partial coverage, and shows unavailable telemetry explicitly. Run totals include synchronous router, summary and child-agent calls.