routex

mcp
Security Audit
Warn
Health Warn
  • License — License: MIT
  • Description — Repository has a description
  • Active repo — Last push 0 days ago
  • Low visibility — Only 6 GitHub stars
Code Pass
  • Code scan — Scanned 11 files during light audit, no dangerous patterns found
Permissions Pass
  • Permissions — No dangerous permissions requested
Purpose
This tool is a lightweight AI agent runtime for Go that allows developers to define and run multi-agent AI crews using YAML, goroutines, and channels. It handles the scheduling, parallelism, retries, and observability of these agents without requiring a Python environment.

Security Assessment
The overall risk is rated as Low. A scan of 11 files found no dangerous code patterns, hardcoded secrets, or requests for dangerous permissions. While the runtime inherently makes network requests to communicate with external LLM APIs (using your own API keys), it operates securely within its stated boundaries. It does not appear to execute hidden or unauthorized shell commands.

Quality Assessment
The project is actively maintained, with its most recent code push occurring today. It uses the permissive MIT license, making it highly accessible for integration. However, it currently has very low community visibility with only 6 GitHub stars. Despite this lack of widespread adoption, the project demonstrates strong professional standards, such as first-class OpenTelemetry support and badges indicating automated testing and code quality reporting.

Verdict
Safe to use, but keep in mind that it is a very new and untested project with minimal community oversight.
SUMMARY

Lightweight AI agent runtime for Go. Define multi-agent crews in YAML, run them with goroutines and channels, and let the runtime handle scheduling, parallelism, retries, and observability — without leaving the Go ecosystem.

README.md
  ██████╗  ██████╗ ██╗   ██╗████████╗███████╗██╗  ██╗
  ██╔══██╗██╔═══██╗██║   ██║╚══██╔══╝██╔════╝╚██╗██╔╝
  ██████╔╝██║   ██║██║   ██║   ██║   █████╗   ╚███╔╝
  ██╔══██╗██║   ██║██║   ██║   ██║   ██╔══╝   ██╔██╗
  ██║  ██║╚██████╔╝╚██████╔╝   ██║   ███████╗██╔╝ ██╗
  ╚═╝  ╚═╝ ╚═════╝  ╚═════╝    ╚═╝   ╚══════╝╚═╝  ╚═╝

  lightweight AI agent runtime for Go

Go Reference
Go Report Card
codecov
GitHub stars
License: MIT
Mentioned in Awesome Go

A lightweight AI agent runtime for Go.

Routex lets you build, run, and supervise multi-agent AI crews using the primitives Go developers already know — goroutines, channels, and interfaces. Define your crew in a YAML file or pure Go code, wire in any LLM provider and tools, and let the runtime handle scheduling, parallelism, retries, memory, and observability.

go install github.com/Ad3bay0c/routex/cmd/routex@latest
routex init my-crew && cd my-crew
cp .env.example .env   # add your API key
routex run agents.yaml

To depend on Routex from your own Go program (not the CLI):

go get github.com/Ad3bay0c/routex@latest

See Using it as a library for imports and examples.


Why Routex?

The AI agent ecosystem is almost entirely Python. Frameworks like LangGraph and CrewAI are powerful but they carry a Python runtime, slow cold starts, and async complexity that does not belong in production Go services.

Routex is built for Go developers who want agentic capabilities without leaving the Go ecosystem.

LangGraph CrewAI Routex
Language Python Python Go
Concurrency model asyncio asyncio goroutines + channels
Agent supervision none none Erlang-style OTP tree
Binary size heavy heavy ~10 MB single binary
Cold start ~2–5 s ~2–5 s ~50 ms
Multi-LLM per agent no no yes
OpenTelemetry partial partial first-class
Deploy target Python env Python env any OS, Docker, K8s

Table of Contents


Concepts

Routex borrows ideas from operating systems and applies them to AI agents:

Agent — a long-lived goroutine with a brain (LLM), a memory scope, and a set of tools. Agents wait on an Inbox channel, process one task at a time, and send results back through typed channels. They never share state.

Crew — a collection of agents that work together on a task. Agents declare depends_on relationships; the scheduler turns these into a DAG and runs independent agents in parallel.

Scheduler — performs topological sort (Kahn's algorithm) on the dependency graph. Agents with no dependencies run immediately in parallel. Each subsequent wave starts only when the previous wave completes. Detects cycles at startup, before any agent runs.

Supervisor — watches agents via a dedicated notify channel. When an agent fails, the scheduler asks the supervisor what to do. The supervisor checks the agent's restart budget and returns a decision: retry or give up. This design means the scheduler never advances a wave past a failed agent — it blocks until the supervisor resolves the failure.

Tool — anything an agent can call: web search, file read/write, HTTP request, translation, image generation. Implement one interface, register once, available to any agent.

Memory — each agent has a namespaced key-value and message history store. In-memory by default; Redis for persistence across runs.

Runtime — the orchestrator. Builds the agent graph, wires in the LLM adapters, starts the supervisor, hands tasks to the scheduler, and collects results.


Quickstart

Using the CLI

The fastest path — no Go code required.

# Install
go install github.com/Ad3bay0c/routex/cmd/routex@latest

# Scaffold a new project
routex init my-research-crew
cd my-research-crew

# Set up environment
cp .env.example .env
# Edit .env — add your ANTHROPIC_API_KEY

# Edit the generated agents.yaml file

# Validate the config
routex validate agents.yaml

# See the execution plan before running
routex run agents.yaml --dry-run

# Run it
routex run agents.yaml

Override the task inline without editing YAML:

routex run agents.yaml --task "Compare the latest releases of Go and Rust"

Use a different env file (e.g. production secrets injected by your platform):

routex run agents.yaml --env-file .env.staging --output ./reports/result.md

Using it as a library

Import Routex into any Go application. Task input can come from an HTTP request, message queue, database — anywhere.

Add the module to your project (this is the library dependency — unlike go install, which only builds the routex CLI):

go get github.com/Ad3bay0c/routex@latest

Then import the root package and any subpackages you need (see below). If you add imports first, go mod tidy will record the module as well.

Goal Command
Use Routex from your Go code go get github.com/Ad3bay0c/routex@version
Install the routex CLI only go install github.com/Ad3bay0c/routex/cmd/routex@version

Tool imports — opt in to what you need

Routex's built-in tools are in separate sub-packages so you only compile what you use. Import tools/all for everything, or pick individual packages for a leaner binary:

// All 11 built-in tools (convenience — larger binary)
import _ "github.com/Ad3bay0c/routex/tools/all"

// Or import only what you need (smaller binary, fewer dependencies)
import (
    _ "github.com/Ad3bay0c/routex/tools/file"   // read_file, write_file
    _ "github.com/Ad3bay0c/routex/tools/search" // web_search, brave_search, wikipedia
    _ "github.com/Ad3bay0c/routex/tools/web"    // http_request, read_url, scrape
    // omit tools/ai   → no OpenAI/Anthropic SDK dependency
    // omit tools/comms → no SendGrid/Resend dependency
)

Option 1 — YAML-driven (recommended for most use cases):

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/Ad3bay0c/routex"
    _ "github.com/Ad3bay0c/routex/tools/all" // register all built-in tools
)

func main() {
    ctx := context.Background()

    // LoadConfig reads agents.yaml, loads .env via env_file:,
    // resolves all env: references, and validates the agent graph.
    rt, err := routex.LoadConfig("agents.yaml")
    if err != nil {
        log.Fatal(err)
    }

    result, err := rt.StartAndRun(ctx)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("Done in %s — %d tokens\n", result.Duration, result.TokensUsed)
    rt.Stop()
}

Option 2 — override task at runtime:

rt, _ := routex.LoadConfig("agents.yaml")

// Task comes from an HTTP request, not from YAML
rt.SetTask(routex.Task{
    Input: r.FormValue("topic"),
})

// Or pass directly to LoadConfig
rt, _ := routex.LoadConfig("agents.yaml",
    routex.WithTaskInput(r.FormValue("topic")),
    routex.WithEnvFile(".env.prod"),
)

result, _ := rt.StartAndRun(ctx)

Option 3 — fully programmatic (no YAML):

import (
    "github.com/Ad3bay0c/routex"
    "github.com/Ad3bay0c/routex/agents"
    "github.com/Ad3bay0c/routex/tools/search"
    "github.com/Ad3bay0c/routex/tools/file"
)

rt, _ := routex.NewRuntime(routex.Config{
    Name: "my-crew",
    LLM: routex.LLMConfig{
        Provider: "anthropic",
        Model:    "claude-sonnet-4-6",
        APIKey:   os.Getenv("ANTHROPIC_API_KEY"),
    },
})

rt.RegisterTool(search.WebSearch())
rt.RegisterTool(file.WriteFileIn("./outputs"))

rt.AddAgent(agents.AgentConfig{
    ID:    "researcher",
    Role:  agents.Researcher,
    Goal:  "Find recent news about Go 1.24",
    Tools: []string{"web_search"},
})

rt.AddAgent(agents.AgentConfig{
    ID:        "writer",
    Role:      agents.Writer,
    Goal:      "Write a summary report and save it",
    Tools:     []string{"write_file"},
    DependsOn: []string{"researcher"},
})

result, _ := rt.StartAndRun(ctx)

YAML reference

runtime:
  name: "my-crew" # identifies this crew in logs and traces
  llm_provider: "anthropic" # anthropic | openai | ollama
  model: "claude-sonnet-4-6" # model name for the chosen provider
  api_key: "env:ANTHROPIC_API_KEY" # env:VAR reads from environment
  log_level: "info" # debug | info | warn | error
  env_file: "." # DEVELOPMENT ONLY — load .env next to this file
  # base_url:   "env:CUSTOM_ENDPOINT"  # override LLM API endpoint

task:
  input: "Research the latest Go releases"
  output_file: "env:OUTPUT_FILE" # env: works in any string field
  max_duration: "5m" # Go duration string — 30s, 5m, 1h

tools:
  # Built-in tools — declare here, auto-registered by the runtime
  - name: "web_search" # DuckDuckGo, free, no key

  - name: "brave_search" # Higher quality, 2,000 free/month
    api_key: "env:BRAVE_API_KEY"
    max_results: 5

  - name: "wikipedia" # Free, no key needed
    extra:
      language: "en"

  - name: "http_request" # Call any REST API
    api_key: "env:MY_API_KEY" # sent as X-Api-Key header
    extra:
      bearer_token: "env:MY_TOKEN" # sent as Authorization: Bearer
      query_api_key: "env:OWM_KEY" # sent as query param (e.g. OpenWeatherMap)
      query_api_key_name: "appid" # the param name (?appid=KEY)
      param_units: "metric" # default query param on every request
      header_Accept: "application/json"

  - name: "write_file"
    base_dir: "./outputs" # agents can only write inside this dir

  - name: "read_file"
    base_dir: "./data"

  - name: "read_url" # Fetch and strip HTML from any URL

  - name: "scrape" # JS-rendered pages via ScrapingBee
    api_key: "env:SCRAPINGBEE_API_KEY"

  - name: "summarise" # LLM-powered text compression
    api_key: "env:ANTHROPIC_API_KEY"

  - name: "translate" # DeepL API, 500k chars/month free
    api_key: "env:DEEPL_API_KEY"

  - name: "generate_image" # DALL-E 3 via OpenAI
    api_key: "env:OPENAI_API_KEY"
    base_dir: "./outputs/images"

  - name: "send_email" # SendGrid or Resend
    api_key: "env:SENDGRID_API_KEY"
    extra:
      provider: "sendgrid" # sendgrid | resend
      from_email: "[email protected]"
      from_name: "Routex Agent"

  - name: "mcp" # MCP server call
    extra:
      server_url: "http://localhost:3000"
      server_name: "github"

  - name: "mcp"
    extra:
      server_url: "http://localhost:3001"
      server_name: "postgres"

agents:
  - id: "researcher" # unique within this crew
    role: "researcher" # planner | researcher | writer | critic | executor
    goal: "Find and summarise recent Go releases"
    tools: ["web_search", "read_url"]
    restart: "one_for_one" # one_for_one | one_for_all | rest_for_one
    max_retries: 2
    timeout: "90s"
    # depends_on: []                   # list of agent IDs that must complete first
    # max_duplicate_tool_calls: 2      # redirect LLM after N identical tool calls (default: 2)
    # max_total_tool_calls: 20         # absolute tool call budget per attempt (default: 20)

    # Per-agent LLM override — uses a different model from the runtime default
    # llm:
    #   provider: "openai"
    #   model:    "gpt-4o"
    #   api_key:  "env:OPENAI_API_KEY"

  - id: "writer"
    role: "writer"
    goal: "Write a markdown report. Save it as report.md"
    tools: ["write_file"]
    depends_on: ["researcher"] # starts only after researcher finishes
    restart: "one_for_one"
    max_retries: 2
    timeout: "120s"

memory:
  backend: "inmem" # inmem | redis
  ttl: "1h"
  # redis_url: "env:REDIS_URL"         # required when backend is "redis"

observability:
  tracing: false
  metrics: false
  # jaeger_endpoint: "env:OTEL_EXPORTER_OTLP_ENDPOINT"  # e.g. http://localhost:4318
  # metrics_addr:    ":9090"           # curl localhost:9090/metrics

The env: prefix

Any string value in the YAML can read from the environment:

api_key: "env:ANTHROPIC_API_KEY" # reads os.Getenv("ANTHROPIC_API_KEY")
output_file: "env:OUTPUT_PATH" # works in any string field
model: "env:LLM_MODEL" # even model names

This means your agents.yaml file can be committed to git with zero secrets in it.


Built-in tools

All tools are registered automatically when listed in the tools: section of agents.yaml. No RegisterTool() call needed for built-ins.

Search & Data

Tool Key needed Free tier Description
web_search none unlimited DuckDuckGo Instant Answers
brave_search BRAVE_API_KEY 2,000/month Structured web results with publication age
wikipedia none unlimited Wikipedia article summaries, 300+ languages
scrape SCRAPINGBEE_API_KEY 1,000 credits/month JS-rendered page content

Web & HTTP

Tool Key needed Description
read_url none Fetch and strip HTML from any URL
http_request configurable Call any REST API — GET, POST, PUT, PATCH, DELETE

http_request supports four authentication patterns:

# 1. Query string key (OpenWeatherMap, Google Maps)
extra:
  query_api_key:      "env:OWM_KEY"
  query_api_key_name: "appid"         # → ?appid=KEY on every request

# 2. Bearer token (GitHub, most REST APIs)
extra:
  bearer_token: "env:GITHUB_TOKEN"   # → Authorization: Bearer TOKEN

# 3. API key header
api_key: "env:MY_KEY"                # → X-Api-Key: KEY

# 4. Custom header
extra:
  header_X-Custom-Header: "value"    # → X-Custom-Header: value
  header_API-KEY: "value"    # → API-KEY: value

Default query params (sent on every request):

extra:
  param_units: "metric" # → ?units=metric on every request
  param_lang: "en"

File

Tool Config Description
write_file base_dir Write files — sandboxed to base_dir
read_file base_dir Read files — sandboxed to base_dir

Path traversal (../) is blocked in both tools. Agents can only read or write inside the configured base_dir.

AI & Generation

Tool Key needed Description
summarise ANTHROPIC_API_KEY LLM-powered text compression (paragraph, bullets, one-line)
translate DEEPL_API_KEY DeepL translation, 30+ languages, 500k chars/month free
generate_image OPENAI_API_KEY DALL-E 3 image generation, saves to disk

Communication

Tool Key needed Providers Description
send_email SENDGRID_API_KEY or RESEND_API_KEY SendGrid, Resend Send emails with plain text or HTML

Swap providers with one line — no code changes:

- name: "send_email"
  api_key: "env:RESEND_API_KEY"
  extra:
    provider: "resend" # was "sendgrid"
    from_email: "[email protected]"

LLM providers

Set the provider at the runtime level — all agents inherit it by default:

runtime:
  llm_provider: "anthropic" # or "openai" or "ollama"
  model: "claude-sonnet-4-6"
  api_key: "env:ANTHROPIC_API_KEY"
Provider Models Notes
anthropic claude-sonnet-4-6, claude-haiku-4-5-20251001, claude-opus-4-6 Default
openai gpt-4o, gpt-4o-mini, o1, o3-mini Also works with OpenAI-compatible APIs
ollama llama3, mistral, phi3, any installed model Local inference, no API key needed

Switch providers by changing two lines in YAML — zero code changes.


Multi-LLM crews

Each agent can use a different LLM provider and model from the runtime default. This lets you assign the right model to each task — use a fast, cheap model for mechanical work and a powerful model only where it matters.

runtime:
  llm_provider: "anthropic"
  model: "claude-haiku-4-5-20251001" # default — fast and cheap
  api_key: "env:ANTHROPIC_API_KEY"

agents:
  # Researcher inherits Haiku — data fetching doesn't need a powerful model
  - id: "researcher"
    role: "researcher"
    goal: "Fetch weather data from the OpenWeatherMap API"
    tools: ["http_request"]

  # Comparator uses GPT-4o — synthesis benefits from a larger model
  - id: "comparator"
    role: "writer"
    goal: "Compare the weather data and write a detailed analysis"
    tools: []
    depends_on: ["researcher"]
    llm:
      provider: "openai"
      model: "gpt-4o"
      api_key: "env:OPENAI_API_KEY"

  # Fact-checker uses Anthropic Sonnet — higher quality critique
  - id: "fact_checker"
    role: "critic"
    goal: "Verify the analysis and write the final report"
    tools: ["write_file"]
    depends_on: ["comparator"]
    llm:
      provider: "anthropic"
      model: "claude-sonnet-4-6"
      api_key: "env:ANTHROPIC_API_KEY"

Memory backends

Agents share a namespaced memory store. Each agent's history and outputs are stored under its own key — agents cannot accidentally read each other's working memory.

# In-memory (default) — no setup, resets between runs
memory:
  backend: "inmem"
  ttl:     "1h"

# Redis — persists across runs, shared between instances
memory:
  backend:   "redis"
  ttl:       "24h"
  redis_url: "env:REDIS_URL"

Start Redis locally:

docker run -p 6379:6379 redis:alpine
export REDIS_URL=redis://localhost:6379

MCP tool servers

Routex connects to any MCP (Model Context Protocol) server at startup and registers all tools it exposes — making them available to agents exactly like built-in tools. Any MCP-compatible server works: the official GitHub MCP server, the Postgres MCP server, a custom server, or any server from the MCP registry.

tools:
  - name: "mcp"
    extra:
      server_url: "http://localhost:3000" # required — your MCP server URL
      server_name: "github" # optional label for logs

  # Multiple servers supported — each is one entry
  - name: "mcp"
    extra:
      server_url: "http://localhost:3001"
      server_name: "postgres"

Tools from the server are discovered automatically via tools/list at startup. Agents use them by name just like built-ins:

agents:
  - id: "researcher"
    role: "researcher"
    goal: "Search GitHub for Go MCP servers"
    tools: ["search_repos", "get_user"] # ← names returned by the MCP server

Authentication — most MCP servers require credentials. Pass them as headers using the header_* prefix, which supports the env: resolver:

tools:
  - name: "mcp"
    extra:
      server_url: "http://localhost:3000"
      server_name: "github"
      header_Authorization: "env:GITHUB_TOKEN" # → Authorization: Bearer ghp_xxx
      header_X-Api-Key: "env:MY_API_KEY" # → X-Api-Key: abc123

Name collisions — if two servers expose a tool with the same name (e.g. both have search), the second is automatically prefixed with its server_name: postgres.search.

routex tools list only shows built-in tools. MCP tools are discovered at runtime — run routex run agents.yaml --dry-run to see all available tools after the server connection is made.


Restart policies

Routex supervision is modelled after Erlang/OTP. Set per agent:

- id: "researcher"
  restart: "one_for_one" # default
Policy When an agent fails... Use when...
one_for_one Restart only that agent Agents are independent
one_for_all Restart the entire crew Agents share state and a partial crew gives wrong results
rest_for_one Restart the failed agent and all agents that depend on it Pipeline — downstream agents need fresh upstream output

The restart budget is controlled at the runtime level:

supervisor.New(agents, policy,
    3,             // max restarts per agent
    time.Minute,   // within this sliding window
)

After maxRestarts failures within the window, the supervisor declares the agent permanently failed and propagates the error to the caller.


Observability

Enable tracing and metrics in agents.yaml:

observability:
  tracing: true
  jaeger_endpoint: "http://localhost:4318" # OTLP HTTP — Jaeger v1.35+

  metrics: true
  metrics_addr: ":9090"

Start Jaeger locally:

docker run \
  -p 16686:16686 \
  -p 4318:4318 \
  jaegertracing/all-in-one

open http://localhost:16686

Every run produces a trace tree:

routex.run                        ← entire crew run
  routex.agent [researcher]       ← one agent's execution
    routex.llm.complete           ← single LLM call
    routex.tool.execute [http_request]
    routex.llm.complete           ← follow-up after tool result
  routex.agent [comparator]
    routex.llm.complete

Prometheus metrics at :9090/metrics:

routex_tokens_total{agent_id,provider}
routex_tool_calls_total{tool_name,status}
routex_tool_duration_seconds{tool_name}
routex_agent_duration_seconds{agent_id,role}
routex_run_duration_seconds{runtime_name}
routex_agent_failures_total{agent_id}

All observe methods are nil-safe — disabling tracing or metrics costs nothing beyond a nil check.


Writing a custom tool

Any struct that implement the Tool interface can be registered:

type Tool interface {
    Name()    string
    Schema()  tools.Schema
    Execute(ctx context.Context, input json.RawMessage) (json.RawMessage, error)
}

Step 1 — implement the interface:

// tools/mytools/db_query.go
package mytools

import (
    "context"
    "database/sql"
    "encoding/json"
    "fmt"

    "github.com/Ad3bay0c/routex/tools"
)

type DBQueryTool struct {
    db *sql.DB
}

func (t *DBQueryTool) Name() string { return "db_query" }

func (t *DBQueryTool) Schema() tools.Schema {
    return tools.Schema{
        Description: "Run a read-only SQL query against the application database. " +
            "Returns results as a JSON array of objects.",
        Parameters: map[string]tools.Parameter{
            "query": {
                Type:        "string",
                Description: "A read-only SQL SELECT query. No INSERT, UPDATE, or DELETE.",
                Required:    true,
            },
        },
    }
}

type dbQueryInput struct {
    Query string `json:"query"`
}

func (t *DBQueryTool) Execute(ctx context.Context, input json.RawMessage) (json.RawMessage, error) {
    var params dbQueryInput
    if err := json.Unmarshal(input, &params); err != nil {
        return nil, fmt.Errorf("db_query: invalid input: %w", err)
    }
    if params.Query == "" {
        return nil, fmt.Errorf("db_query: query is required")
    }

    rows, err := t.db.QueryContext(ctx, params.Query)
    if err != nil {
        return nil, fmt.Errorf("db_query: %w", err)
    }
    defer rows.Close()

    // Convert rows to []map[string]any and marshal to JSON
    results, err := rowsToJSON(rows)
    if err != nil {
        return nil, fmt.Errorf("db_query: %w", err)
    }

    return json.Marshal(results)
}

// compile-time check
var _ tools.Tool = (*DBQueryTool)(nil)

Step 2 — register it:

rt, _ := routex.LoadConfig("agents.yaml")
rt.RegisterTool(&mytools.DBQueryTool{db: db})
result, _ := rt.StartAndRun(ctx)

Step 3 — declare it in agents.yaml:

tools:
  - name: "db_query" # no api_key or extra needed for custom tools

agents:
  - id: "analyst"
    role: "researcher"
    goal: "Query the orders table for last week's top products"
    tools: ["db_query"]

Custom tools are prioritised over built-ins — if you register a tool named web_search, it replaces the built-in.


CLI reference

routex <command> [flags]

Commands:
  run       Run an agent crew
  validate  Validate config without running
  tools     Manage built-in tools
  init      Scaffold a new project
  version   Print version info

routex run

routex run <agents.yaml> [flags]

Flags:
  -e, --env-file   <path>      Load .env file (overrides env_file: in YAML)
  -t, --task       <text>      Override task input
  -o, --output     <path>      Override output file path
  -T, --timeout    <duration>  Override max_duration (e.g. 10m, 30s)
  -l, --log-level  <level>     debug | info | warn | error
      --dry-run                Print execution plan and exit
      --json                   Output result as JSON

Examples:
  routex run agents.yaml
  routex run agents.yaml -t "Latest Go security advisories"
  routex run agents.yaml -e .env.staging -o ./reports/$(date +%Y%m%d).md
  routex run agents.yaml --dry-run
  routex run agents.yaml --json | jq '.agents[] | select(.error != null)'

The --dry-run flag validates the full config and prints the execution plan:

routex dry-run  agents.yaml

  wave 1
    lagos_weather                    ← no deps, runs first (in parallel)
    london_weather                   ← no deps, runs first (in parallel)
  wave 2
    comparator       ← lagos_weather, london_weather  [openai / gpt-4o]
  wave 3
    fact_checker     ← comparator

Config is valid. Run without --dry-run to execute.

routex validate

routex validate <agents.yaml> [flags]

Flags:
  -e, --env-file  <path>   Load env file before validation
      --json               Output result as JSON (exit code 0=valid, 1=invalid)

# CI usage
routex validate agents.yaml --json | jq .

routex tools list

routex tools list [flags]

Flags:
  --json   Machine-readable output

Example output:
  Built-in tools (11)

  NAME                       DESCRIPTION
  ─────────────────────────  ─────────────────────────────────────────────
  brave_search               Search the web using Brave Search for accurat...
  generate_image             Generate an image from a text description usin...
  http_request               Make an HTTP request to any REST API endpoint...
  read_file                  Read the content of a local file...
  read_url                   Fetch and strip HTML from any URL...
  scrape                     Fetch JS-rendered page content via ScrapingBee...
  send_email                 Send an email via SendGrid or Resend...
  summarise                  Compress long text using Claude Haiku...
  translate                  Translate text using DeepL...
  web_search                 Search the web via DuckDuckGo (free, no key)...
  wikipedia                  Fetch a Wikipedia article summary...
  write_file                 Write content to a file safely...

routex init

routex init [dirname]

Scaffolds:
  agents.yaml     — starter config (two agents, web_search + write_file)
  .env.example    — template for required keys
  .gitignore      — ignores .env and output files
  main.go         — minimal Go entrypoint

Example:
  routex init weather-crew
  cd weather-crew
  cp .env.example .env && vim .env
  go mod init github.com/yourname/weather-crew
  go mod tidy
  routex run agents.yaml

routex version

routex version [--json]

Output:
  routex 1.0.0
  go     go1.22.0
  os     linux/amd64

Typo correction — Routex suggests the closest command when you make a mistake:

$ routex runn agents.yaml
routex: unknown command "runn"

Did you mean this?
        routex run

$ routex run agents.yaml --timout 5m
error: unknown flag: --timout

        Did you mean  --timeout  ?

GitHub Actions

You can run Routex in CI the same way you run it locally: check out the repo (with agents.yaml), install the CLI, set API keys as secrets, then invoke routex run. The repository ships a composite action that runs go install and routex run with the same flags as the CLI.

Requirements: use a Linux runner (ubuntu-latest is typical). Set encrypted secrets on the repository or organization for any keys your crew needs (for example ANTHROPIC_API_KEY or OPENAI_API_KEY). Do not put secrets in with: inputs. Fork PRs usually cannot access upstream secrets, so avoid running full agent jobs on untrusted PRs unless you have a deliberate, reviewed policy.

Option A — composite action (uses: …/routex@v…)

Pin the action to a release tag so go install uses the same version as the action (when routex_ref is left empty, install uses github.action_ref).

name: Routex crew
on:
  workflow_dispatch:

jobs:
  run:
    runs-on: ubuntu-latest
    env:
      ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
    steps:
      - uses: actions/checkout@v4

      - uses: Ad3bay0c/routex@main # pin a semver tag (e.g. v1.0.0) for reproducible installs
        with:
          config: agents.yaml
          task: Summarise the README in three bullet points.
          output: report.md
          timeout: 15m

      - uses: actions/upload-artifact@v4
        with:
          name: routex-output
          path: report.md

Inputs map to CLI flags: config (required), optional task, env_file, output, timeout, log_level, working_directory, go_version, json_output, dry_run, and optional routex_ref to override the install revision.

Option B — go install without the composite action

jobs:
  run:
    runs-on: ubuntu-latest
    env:
      ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-go@v5
        with:
          go-version: "1.25"

      - name: Install routex
        run: go install github.com/Ad3bay0c/routex/cmd/routex@latest

      - name: Run
        run: |
          echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH"
          routex run agents.yaml -t "Your task" -o report.md

Safety and cost

Keep timeouts and YAML max_duration reasonable; use dry_run: true (composite) or --dry-run in CI when you only need validation. Restrict tools and tokens in configs that run in CI (least privilege). LLM calls are paid per run—treat workflows like any other production job hitting external APIs.


Environment variables

Variable Used by Description
ANTHROPIC_API_KEY runtime, summarise Anthropic API key
OPENAI_API_KEY openai provider, generate_image OpenAI API key
BRAVE_API_KEY brave_search Brave Search API key
SCRAPINGBEE_API_KEY scrape ScrapingBee API key
DEEPL_API_KEY translate DeepL API key (append :fx for free tier)
SENDGRID_API_KEY send_email SendGrid API key
RESEND_API_KEY send_email Resend API key
OPENWEATHER_API_KEY http_request OpenWeatherMap key (passed via query param)
REDIS_URL memory Redis connection URL
OTEL_EXPORTER_OTLP_ENDPOINT observe OTLP trace endpoint
ROUTEX_METRICS_ADDR observe Prometheus metrics address (default :9090)
ROUTEX_TASK config Overrides task.input in YAML
ROUTEX_ENV_FILE config Overrides env_file: in YAML (set by CLI --env-file)

Development vs Production:

Use env_file: "." in agents.yaml to load a .env file during development:

runtime:
  env_file: "." # DEVELOPMENT ONLY — remove in production

In production, inject secrets through your platform instead:

# Docker
docker run -e ANTHROPIC_API_KEY=sk-ant-... myimage

# Kubernetes
kubectl create secret generic routex-secrets \
  --from-literal=ANTHROPIC_API_KEY=sk-ant-...

Repo layout

routex/
├── runtime.go                  # Runtime — Start, Run, Stop, ExecutionPlan
├── config.go                   # LoadConfig, YAML parsing, env: resolution
├── task.go                     # Task and Result public types
│
├── agents/
│   ├── agent.go                # Agent goroutine — think loop, tool dedup, retry
│   ├── agent_config.go         # AgentConfig, RestartPolicy, per-agent LLM
│   ├── roles.go                # Planner, Researcher, Writer, Critic, Executor
│   ├── memory.go               # Agent memory key helpers
│   └── observe.go              # AgentTracer and AgentMetrics interfaces
│
├── llm/
│   ├── adapter.go              # Adapter interface, Request, Response, Config
│   ├── anthropic.go            # Anthropic provider (claude-*)
│   ├── openai.go               # OpenAI provider + compatible APIs
│   └── ollama.go               # Local Ollama provider
│
├── memory/
│   ├── store.go                # MemoryStore interface
│   ├── inmem.go                # In-memory backend (default)
│   └── redis.go                # Redis backend
│
├── tools/
│   ├── tool.go                 # Tool interface, Registry, Schema, Parameter
│   ├── builtin.go              # RegisterBuiltin, Resolve, ListBuiltins
│   ├── search/
│   │   ├── web_search.go       # DuckDuckGo (free)
│   │   ├── brave_search.go     # Brave Search API
│   │   └── wikipedia.go        # Wikipedia REST API
│   ├── web/
│   │   ├── read_url.go         # HTML fetcher + stripper
│   │   ├── scrape.go           # JS-rendered pages via ScrapingBee
│   │   └── http_request.go     # Generic HTTP client
│   ├── file/
│   │   ├── write_file.go       # Sandboxed file writer
│   │   └── read_file.go        # Sandboxed file reader
│   ├── ai/
│   │   ├── summarise.go        # LLM text compression
│   │   ├── translate.go        # DeepL translation
│   │   └── generate_image.go   # DALL-E 3 image generation
│   └── comms/
│       └── send_email.go       # SendGrid + Resend (two providers, one interface)
│
├── observe/
│   ├── tracer.go               # OpenTelemetry spans — run, agent, LLM, tool
│   └── metrics.go              # Prometheus counters and histograms
│
├── internal/
│   ├── scheduler/
│   │   └── scheduler.go        # Kahn's sort, wave execution, failure cooperation
│   └── supervisor/
│       └── supervisor.go       # Erlang-style restart, FailureReport/Decision protocol
│
├── cmd/routex/
│   ├── main.go                 # CLI entrypoint
│   ├── cli.go                  # Command dispatcher, flag parser, did-you-mean
│   ├── cmd_run.go              # routex run
│   ├── cmd_validate.go         # routex validate
│   ├── cmd_tools.go            # routex tools list
│   ├── cmd_init.go             # routex init
│   ├── cmd_version.go          # routex version
│   └── suggest.go              # Levenshtein distance for typo correction
│
└── examples/
    ├── yaml-driven/            # Minimal YAML-based example
    ├── programmatic/           # Pure Go API example
    ├── search-and-data/        # Group 1 tools — brave_search, wikipedia, scrape
    ├── ai-generation/          # Group 2 tools — summarise, translate, generate_image
    ├── comms-and-storage/      # Group 3 tools — send_email, read_file, http_request
    └── weather-compare/        # Multi-LLM crew — parallel agents, fact-checker

Contributing

See CONTRIBUTING.md for how to add tools, LLM providers, memory backends, and more.


License

MIT — see LICENSE.

Reviews (0)

No results found