Skip to content
alaninnovatesPublic

About

An open-source agent system built in Go, inspired by the ReAct agent pattern, Anthropic's MCP protocol, Google's ACP spec, and the broader agent tooling ecosystem.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

Maia

An open-source agent system built in Go, inspired by the ReAct agent pattern, Anthropic's MCP protocol, Google's ACP spec, and the broader agent tooling ecosystem.

Maia runs a composable ReAct loop with streaming model calls, concurrent tool execution, 24 middleware hooks, and SQLite persistence — all in a single 30 MB static binary with no Python needed.

Quick Start

# Copy and edit configuration.
make config          # Creates config.yaml, .env, extensions_config.json

# Set your API key in .env.
# LLM_AUTH_TOKEN=sk-your-key-here

# Run gateway + frontend locally.
make dev-stop ; make dev-full        # Gateway :8002, Frontend :3000

# Open browser localhost:3000

Architecture

┌──────────────────────────────────────────────────────────────────────┐
│  Gateway (HTTP/SSE) — :8002                                         │
│  ┌──────────────────┐  ┌──────────────┐  ┌───────────────────────┐  │
│  │ Thread/Run API   │  │ Auth API     │  │ Channel Webhooks      │  │
│  │ Memory / Models  │  │ OIDC Login   │  │ Feishu/Slack/Telegram │  │
│  │ Skills / MCP     │  │ JWT Tokens   │  │                       │  │
│  └──────────────────┘  └──────────────┘  └───────────────────────┘  │
├──────────────────────────────────────────────────────────────────────┤
│  Middleware Hooks (24 layers)                                        │
│  ┌─────────────────────────────────────────────────────────────────┐│
│  │ WrapModelCall: input_sanitization → syscoal → dangling_tc →    ││
│  │   llm_error → loop_detection → token_budget                    ││
│  │ WrapToolCall: tool_budget → tool_error → clarification         ││
│  │ BeforeAgent: dynamic_context (date + memory injection)         ││
│  │ AfterModel:  token_usage, todo, title                          ││
│  │ AfterAgent:  memory (async LLM updates)                        ││
│  │ Other: guardrails, thread_data, tool_metadata, uploads,        ││
│  │   view_image, deferred_tool_filter, skill_activation,          ││
│  │   sandbox_audit, subagent_limit, safety_finish                 ││
│  └─────────────────────────────────────────────────────────────────┘│
├──────────────────────────────────────────────────────────────────────┤
│  Agent (ReAct Loop)                                                 │
│  ┌──────────────────┐   ┌───────────────┐   ┌──────────────────┐   │
│  │  Model Provider   │──▶│ AfterModel    │──▶│  Tool Registry   │   │
│  │  OpenAI / Anthropic│  │  hooks        │   │                  │   │
│  │  vLLM / DeepSeek  │   └───────────────┘   │  shell           │   │
│  │  Ollama / MiniMax │                       │  read_file       │   │
│  │  StepFun / Mimo   │                       │  write_file      │   │
│  └──────────────────┘                        │  list_files      │   │
│                                              │  present_files   │   │
│  ┌──────────────────┐                        │  web_search      │   │
│  │  Subagent Executor│                       │  web_fetch       │   │
│  │  delegate_task   │                        │  skill_manage    │   │
│  │  (general-purpose│                        │  tool_search     │   │
│                                              │  invoke_acp_agent│   │
│  │   bash subagents)│                        │  + MCP tools     │   │
│  └──────────────────┘                        └──────────────────┘   │
├──────────────────────────────────────────────────────────────────────┤
│  Sandbox                                                            │
│  ┌────────────────────────────┐  ┌───────────────────────────────┐  │
│  │ Local sandbox              │  │ Host bash mode                │  │
│  │ (scoped working directory) │  │ (direct host execution for    │  │
│  │                            │  │  host tools, SSO, host network) │  │
│  └────────────────────────────┘  └───────────────────────────────┘  │
├──────────────────────────────────────────────────────────────────────┤
│  Store (SQLite)          Memory (JSON)        Skills (filesystem)    │
└──────────────────────────────────────────────────────────────────────┘

Model Providers

Provider Config Value Protocol
OpenAI openai Chat Completions
Anthropic / Claude anthropic Messages API
vLLM vllm Chat Completions + Quirks
DeepSeek deepseek Chat Completions + Quirks
Ollama ollama Chat Completions + Quirks
MiniMax minimax Chat Completions
StepFun stepfun Chat Completions
Mimo mimo Chat Completions

Provider-specific behavioral differences are handled via a Quirks struct (e.g., DeepSeek returns finish_reason: "stop" for tool calls; vLLM requires explicit tool_choice: "auto"). No per-provider subclass hierarchy needed.

Built-in Tools

Tool Purpose
shell Execute shell commands (sandbox or host mode)
read_file Read file contents with optional line offset/limit
write_file Write or append content to files
list_files List directory contents with sizes and types
present_files Mark files for presentation in the client UI
web_search Search the web (7 backends: DuckDuckGo, Serper, Tavily, Brave, Exa, SearXNG, Google)
web_fetch Fetch web page content (2 backends: basic HTTP, Jina AI Reader)
skill_manage Install, list, enable/disable skills
tool_search Search available tools by keyword
invoke_acp_agent Invoke external ACP-compatible agents (e.g. Codex)
invoke_claude Invoke Claude Code CLI for coding tasks

Additional tools can be loaded from MCP servers configured in extensions_config.json.

Web Search Backends

The web_search tool supports 7 backends, configured in config.yaml:

web_search:
  enabled: true
  backend: "duckduckgo"    # choose one
  api_key: ""              # required for all except duckduckgo and searxng
  max_results: 5
  # base_url: ""           # required for searxng
  # cx: ""                 # required for google
Backend API Key Free tier Best for
duckduckgo Not needed Unlimited Quick setup, no account required
serper serper.dev 2,500 queries/mo Google-quality results
tavily tavily.com 1,000 queries/mo AI-optimized results, built for agents
brave brave.com/search/api 2,000 queries/mo Privacy-focused search
exa exa.ai 1,000 queries/mo Semantic/neural search, finds by meaning
searxng Not needed (self-hosted) Unlimited Aggregates many engines, full control
google Google Cloud 100 queries/day Official Google API

Examples:

# Free search — no API key needed
web_search:
  enabled: true
  backend: "duckduckgo"

# Tavily — best for AI agents
web_search:
  enabled: true
  backend: "tavily"
  api_key: "tvly-xxxxxxxxxxxxxxxxxxxxxxxx"

# Brave — privacy-focused
web_search:
  enabled: true
  backend: "brave"
  api_key: "BSA-xxxxxxxxxxxxxxxxxxxxxxxx"

# Exa — semantic search (finds pages by meaning, not just keywords)
web_search:
  enabled: true
  backend: "exa"
  api_key: "exa-xxxxxxxxxxxxxxxxxxxxxxxx"

# Serper — Google results via API
web_search:
  enabled: true
  backend: "serper"
  api_key: "xxxxxxxxxxxxxxxxxxxxxxxx"

# SearXNG — self-hosted meta-search (aggregates Google, Bing, DuckDuckGo, etc.)
web_search:
  enabled: true
  backend: "searxng"
  base_url: "http://localhost:8080"

# Google Custom Search — official Google API
web_search:
  enabled: true
  backend: "google"
  api_key: "$GOOGLE_API_KEY"
  cx: "xxxxxxxxxxxxxxxxxxxxxxxx"

# Use env var to keep API key out of config
web_search:
  enabled: true
  backend: "tavily"
  api_key: "$TAVILY_API_KEY"

The web_fetch tool retrieves full page content and supports 2 backends:

web_fetch:
  enabled: true
  backend: "basic"           # "basic" (raw HTTP) or "jina" (clean markdown)
  max_content_length: 4096   # max bytes to return per page
  # api_key: ""              # optional — Jina free tier works without it
Backend API Key Best for
basic Not needed Simple pages, raw HTTP + HTML tag stripping
jina Optional (jina.ai/reader) Any URL → clean markdown, handles JS-rendered pages
# Jina AI Reader — URL to clean markdown (recommended)
web_fetch:
  enabled: true
  backend: "jina"
  api_key: "$JINA_API_KEY"   # optional — free tier works without key

Middleware

Middleware Phase Purpose
input_sanitization WrapModelCall Sanitize user input
syscoal WrapModelCall Coalesce consecutive system messages
dangling_tool_call WrapModelCall Repair incomplete tool call/result pairs
llm_error WrapModelCall Retry transient LLM errors
loop_detection AfterModel + WrapModelCall Detect and break repetitive loops
token_budget AfterModel + WrapModelCall Warn/stop on token budget exceeded
safety_finish AfterModel Strip tool calls on safety termination
tool_budget WrapToolCall Truncate large tool output
tool_error WrapToolCall Catch tool execution errors
clarification WrapToolCall Intercept ask_clarification calls
dynamic_context BeforeAgent Inject date + memory as system reminder
token_usage AfterModel Track token consumption
todo AfterModel Force-model for incomplete todo items
title AfterAgent Generate thread title after first exchange
subagent_limit — Enforce subagent invocation limits
guardrails — Content safety word filter
thread_data — Inject thread metadata
tool_metadata WrapToolCall Add server name to MCP tool results
uploads — Resolve uploaded file references
view_image — Convert image paths to content blocks
deferred_tool_filter — Defer low-priority tool calls
skill_activation — Activate skills based on slash commands
sandbox_audit — Audit sandbox operations
summarization BeforeModel Truncate old messages when limits approached
memory AfterAgent Async LLM-based memory updates

Host Bash Mode

When allow_host_bash: true is set in config, the shell tool executes commands directly on the host machine. This is needed for tools that require host network access and local credentials.

sandbox:
  # Local: use "." so the agent can see project files
  dir: "."
  # Docker: use ".maia/sandbox" for isolation
  # dir: ".maia/sandbox"
  allow_host_bash: true
  # No host_shell_command needed for local execution.
  # For Docker: host_shell_command: "docker exec host-shell bash -c {command}"

IM Channels

Maia connects to popular messaging platforms so users can chat with the agent from their preferred app. All channels support the same slash commands (/help, /new, /status).

Channel Direction Editing Auth
Telegram Bidirectional ✅ Bot token from @BotFather
Slack Bidirectional ✅ Bot token + signing secret
Discord Bidirectional ✅ Bot token + application ID
Feishu Bidirectional ✅ App ID + app secret
DingTalk Bidirectional ❌ Webhook URL + secret
WeChat Work Bidirectional ❌ App credentials or webhook
WhatsApp Bidirectional ❌ QR code scan

👉 Full setup guides: docs/CHANNELS.md

WhatsApp Quick Setup

Maia supports WhatsApp via the whatsmeow library (WhatsApp Web protocol). Unlike webhook-based channels, WhatsApp maintains a persistent WebSocket connection.

  1. Enable WhatsApp in config.yaml:

    channels:
      whatsapp:
        enabled: true
        allow_from: ["+15551234567"]
        dm_policy: "allowlist"
  2. Start Maia and click Connect next to WhatsApp in the web UI, or retrieve the QR code directly:

    curl http://localhost:8002/api/channels/whatsapp/qr
  3. Scan the QR code with your phone: WhatsApp → Settings → Linked Devices → Link a device

  4. After pairing, the session is saved in .maia/whatsapp/. Subsequent starts reconnect automatically — no need to scan again.

DM Policy

Policy Behavior
allowlist Only numbers in allow_from can chat (default)
open Anyone can chat
disabled No DMs processed

Group Chats

Set group_enabled: true to allow the bot in group chats. Use group_allow_from to restrict to specific groups by their JID (e.g. 120363xxx@g.us).

Recommended Setup

Use a dedicated phone number for the bot when possible. Personal-number setups work but may cause confusion with self-chat messages.

Configuration

Maia loads configuration from config.yaml (YAML) and .env (environment variables). The .env file is loaded at startup, so $VARIABLE references in config.yaml work even if the variable isn't exported in your shell.

Key config sections: server, agent, model, database, auth, sandbox, title, summarize, dynamic_context, memory, web_search, web_fetch, skills, channels, subagent_limit, guardrails.

Project Structure

maia/
├── cmd/maia/              # Main entry point
├── internal/
│   ├── agent/             # Agent runtime (ReAct loop)
│   │   └── state/         # Immutable state model
│   ├── auth/              # JWT + OIDC authentication
│   ├── channel/           # IM channel registry
│   │   ├── feishu/        # Feishu/Lark integration
│   │   ├── slack/         # Slack integration
│   │   ├── telegram/      # Telegram integration
│   │   ├── discord/       # Discord integration
│   │   ├── dingtalk/      # DingTalk integration
│   │   ├── wechat/        # WeChat Work / WeCom integration
│   │   └── whatsapp/      # WhatsApp integration (whatsmeow)
│   ├── config/            # Configuration loading + env expansion
│   ├── gateway/           # HTTP API server
│   ├── hook/              # Middleware hook interfaces
│   ├── journal/           # Run event journal
│   ├── mcp/               # Model Context Protocol client
│   ├── memory/            # Memory storage + async updater
│   ├── message/           # Message types + merge logic
│   ├── middleware/        # 24 middleware implementations
│   ├── model/             # Provider interface
│   │   ├── anthropic/     # Anthropic Messages API provider
│   │   ├── fake/          # Fake provider for testing
│   │   └── openai/        # OpenAI-compatible provider (+ quirks)
│   ├── skill/             # Skill storage + SKILL.md parser
│   ├── store/sqlite/      # SQLite persistence + checkpoints
│   ├── subagent/          # Subagent executor
│   └── tool/              # Tool interface + registry
│       └── builtin/       # Built-in tools + sandbox
├── frontend/              # Next.js web interface
├── skills/                # Agent skills (public/ + custom/)
├── scripts/               # Setup and deployment scripts
├── docker/                # Docker Compose + nginx config
└── pkg/types/             # Shared domain types

Key Design Decisions

  1. ReAct loop, not a graph engine — Simple for-loop over model calls and tool execution. No DAG, no conditional edges.

  2. Immutable state — Every state transition produces a new State value. The original is never mutated.

  3. Interface-segregated hooks — Middleware implements only the phases it needs (BeforeAgent, AfterModel, WrapModelCall, WrapToolCall, etc.).

  4. Fakes over mocks — Test doubles are real implementations with deterministic behaviour.

  5. Multi-protocol providers — OpenAI Chat Completions and Anthropic Messages are both supported. Provider-specific quirks are handled via a Quirks struct instead of a class hierarchy.

  6. Sandboxed execution — All file/command operations are scoped to a sandbox directory with path traversal protection. Host bash mode is opt-in for tools requiring host access.

  7. Streaming tool call accumulation — OpenAI streaming delivers tool call arguments as JSON string fragments. Each fragment is json.Unmarshal'd as a Go string to resolve escape sequences, then concatenated. The final string is a valid JSON object.

  8. .env file loading — The .env file is loaded at startup before config parsing, so $VARIABLE references in config.yaml work without requiring the user to export variables in their shell.

Testing

go test ./... -v        # Run all tests (588 tests)
go test ./... -cover    # With coverage
go vet ./...            # Static analysis

Make Targets

  build          Build the Maia binary
  run            Run Maia gateway only (port 8002)
  dev            Run gateway with auto-reload (requires air)
  dev-full       Run gateway + frontend (gateway :8002, frontend :3000)
  dev-stop       Stop locally-running gateway and frontend
  test           Run all tests
  test-race      Run tests with race detector
  lint           Run go vet
  config         Create config files from templates (first-time)
  setup          First-time setup
  clean          Remove binary and runtime data
  help           Show this help

What's Next

  • PostgreSQL store
  • Remote K8s sandbox / provisioner
  • CSRF protection
  • Run cancel / join / events endpoints
  • Skill CRUD API + rollback + history + security scanner
  • Skill evolution (agent-managed skill creation)
  • Tracing / OpenTelemetry

About

An open-source agent system built in Go, inspired by the ReAct agent pattern, Anthropic's MCP protocol, Google's ACP spec, and the broader agent tooling ecosystem.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages