axi-go

module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 16, 2026 License: MIT

README

axi-go

A safe, auditable execution kernel for AI agent tools — built in Go with zero dependencies.

CI Go Report Card


Why axi-go?

When you give an AI agent a bag of tools (search, send_email, run_sql), you quickly hit these problems:

  • No safety — the agent can call send_email a thousand times before you know it
  • No audit trail — you can't explain why the agent did what it did
  • Tool sprawl — 200 raw functions, no grouping, no dependencies, no lifecycle
  • No type information — the agent has to guess what inputs each tool accepts
  • No approval gates — the agent can take irreversible actions autonomously

axi-go solves this by splitting the world into two layers:

Layer Example Answers
Actions greet, send-email, search-docs What the agent wants to do (intent)
Capabilities string.upper, http.get, db.query How it gets done (mechanics)

An action declares the capabilities it needs. axi-go resolves them, validates inputs against typed contracts, enforces effect profiles (read-only? external? writes?), pauses for human approval when required, runs with execution budgets, and produces a structured audit trail (evidence).

Think of it as "Kubernetes for agent tools": you register capabilities once, and agents invoke high-level actions that compose them safely.

Installation

go get github.com/felixgeelhaar/axi-go

Or run the example server:

git clone https://github.com/felixgeelhaar/axi-go.git
cd axi-go
go run example/main.go

60-Second Tour

Start the example server and try it:

# 1. See what actions are registered
curl localhost:8080/api/v1/actions

You'll see a greet action with a typed input contract:

{
  "items": [{
    "name": "greet",
    "description": "Greet someone by name, with their name uppercased",
    "input_contract": {
      "fields": [{
        "name": "name", "type": "string",
        "description": "Person to greet", "required": true, "example": "world"
      }]
    },
    "effect_level": "none",
    "is_idempotent": true,
    "requirements": ["string.upper"]
  }],
  "count": 1
}

Note: the agent can see the input type, a description, an example, the side-effect level, and what capabilities this action composes.

# 2. Execute it
curl -X POST localhost:8080/api/v1/actions/execute \
  -H 'Content-Type: application/json' \
  -d '{"action_name":"greet","input":{"name":"world"}}'

Response includes a session ID, the result, and an evidence trail:

{
  "session_id": "session-1",
  "status": "succeeded",
  "result": {
    "data": {"message": "Hello, WORLD!"},
    "summary": "Greeted world",
    "content_type": "application/json"
  },
  "evidence": [{
    "kind": "invocation",
    "source": "greet",
    "value": {"name": "world", "upper": "WORLD"}
  }]
}

The evidence array is your audit trail — every capability invocation, input, and output is captured.

Key Concepts

Actions express intent

An Action is what an agent wants to accomplish:

action, _ := domain.NewActionDefinition(
    "send-email",
    "Send an email notification",
    inputContract,   // { to: string, subject: string, body: string }
    outputContract,  // { message_id: string }
    requirements,    // needs smtp.send capability
    domain.EffectProfile{Level: domain.EffectWriteExternal}, // !! external write !!
    domain.IdempotencyProfile{IsIdempotent: false},
)

Because this is write-external, axi-go pauses for approval before sending. A supervisor (human or another system) must call POST /sessions/{id}/approve first.

Capabilities express mechanics

A Capability is a low-level building block:

smtpCap, _ := domain.NewCapabilityDefinition(
    "smtp.send",
    "Sends an email via SMTP",
    inputContract,
    outputContract,
)

Capabilities have no side-effect profile of their own — they're invoked by actions, and the action's effect profile governs safety.

Plugins bundle actions + capabilities

A Plugin contributes a set of actions and capabilities. Plugins are the unit of deployment:

type emailPlugin struct{}

func (p *emailPlugin) Contribute() (*domain.PluginContribution, error) {
    return domain.NewPluginContribution("email.plugin",
        []*domain.ActionDefinition{sendEmailAction},
        []*domain.CapabilityDefinition{smtpCap},
    )
}
Sessions track each execution

Every execution gets a Session with a strict state machine:

Pending → Validated → Resolved → [AwaitingApproval] → Running → Succeeded | Failed | Rejected

The session persists input, resolved capabilities, evidence, and result/failure. You can poll it anytime via GET /sessions/{id}.

Safety Features

Feature What it does
Effect profiles none, read-local, write-local, read-external, write-external — actions declare their blast radius
Approval gate write-external actions pause at awaiting_approval; require POST /sessions/{id}/approve
Execution budget Max duration and max capability invocations per session
Rate limiting Pluggable RateLimiter checked before each execution
Output validation Results validated against output contracts before succeeded
Idempotency profile Actions declare whether they're safe to retry

Writing a Plugin

Complete example — see example/main.go for a runnable version.

// 1. Implement domain.Plugin with actions and capabilities
type greeterPlugin struct{}

func (p *greeterPlugin) Contribute() (*domain.PluginContribution, error) {
    cap, _ := domain.NewCapabilityDefinition("string.upper", "Uppercases",
        domain.EmptyContract(), domain.EmptyContract())
    _ = cap.BindExecutor("exec.upper")

    reqs, _ := domain.NewRequirementSet(domain.Requirement{Capability: "string.upper"})
    action, _ := domain.NewActionDefinition("greet", "Greets someone",
        domain.NewContract([]domain.ContractField{
            {Name: "name", Type: "string", Required: true, Example: "world"},
        }),
        domain.NewContract([]domain.ContractField{
            {Name: "message", Type: "string", Required: true},
        }),
        reqs,
        domain.EffectProfile{Level: domain.EffectNone},
        domain.IdempotencyProfile{IsIdempotent: true},
    )
    _ = action.BindExecutor("exec.greet")

    return domain.NewPluginContribution("greeter",
        []*domain.ActionDefinition{action},
        []*domain.CapabilityDefinition{cap})
}

// 2. Implement executors (the actual code that runs)
type upperExecutor struct{}
func (e *upperExecutor) Execute(ctx context.Context, input any) (any, error) {
    return strings.ToUpper(input.(string)), nil
}

type greetExecutor struct{}
func (e *greetExecutor) Execute(ctx context.Context, input any, caps domain.CapabilityInvoker) (domain.ExecutionResult, []domain.EvidenceRecord, error) {
    m := input.(map[string]any)
    upper, _ := caps.Invoke("string.upper", m["name"])
    return domain.ExecutionResult{
        Data:        map[string]any{"message": "Hello, " + upper.(string) + "!"},
        ContentType: "application/json",
    }, nil, nil
}

// 3. Register atomically with PluginBundle
bundle, _ := domain.NewPluginBundle(contribution,
    map[domain.ActionExecutorRef]domain.ActionExecutor{"exec.greet": &greetExecutor{}},
    map[domain.CapabilityExecutorRef]domain.CapabilityExecutor{"exec.upper": &upperExecutor{}},
)
compositionService.RegisterBundle(bundle, actionExecReg, capExecReg)

API Reference

Method Path Description
GET /health Health check
GET /api/v1/actions List actions with contracts
GET /api/v1/actions/{name} Get action details
POST /api/v1/actions/execute Execute action (sync or async:true)
GET /api/v1/sessions/{id} Get/poll session state
POST /api/v1/sessions/{id}/approve Approve pending session
POST /api/v1/sessions/{id}/reject Reject pending session
GET /api/v1/capabilities List capabilities
POST /api/v1/plugins Register plugin metadata
DELETE /api/v1/plugins/{id} Deregister plugin

Error responses include an error_code field: not_found, conflict, validation_error, unauthorized, internal_error.

Configuration

Environment variables (all optional):

Variable Default Description
AXI_ADDR :8080 Listen address
AXI_READ_TIMEOUT_SECS 15 HTTP read timeout
AXI_WRITE_TIMEOUT_SECS 30 HTTP write timeout
AXI_IDLE_TIMEOUT_SECS 60 HTTP idle timeout
AXI_MAX_DURATION_SECS 300 Max execution duration
AXI_MAX_INVOCATIONS 100 Max capability invocations per session

Authentication

Bearer-token auth is included; implement api.Authenticator for JWT/OAuth/etc.

auth := api.NewBearerTokenAuth("my-secret-token")
srv.Engine().Use(api.AuthMiddleware(auth, "/health")) // skip auth on /health

Persistence

Two adapters included, pick the one that fits:

Adapter Package Use for
In-memory inmemory/ Tests, ephemeral workloads, single-process
JSON files jsonstore/ Small deployments, dev/staging, simple persistence

For Postgres, SQLite, Redis, etc. implement the 4 repository interfaces in domain/: ActionRepository, CapabilityRepository, PluginRepository, SessionRepository.

actionRepo, _ := jsonstore.NewActionStore("./data")
capRepo, _ := jsonstore.NewCapabilityStore("./data")
pluginRepo, _ := jsonstore.NewPluginStore("./data")
sessionRepo, _ := jsonstore.NewSessionStore("./data")

Architecture

axi-go is built with strict Domain-Driven Design:

domain/          Core business logic. Zero dependencies.
application/     Use cases that orchestrate the domain.
api/             HTTP delivery, config, middleware.
inmemory/        In-memory adapters + structured logger.
jsonstore/       File-based JSON persistence.
cmd/server/      Entrypoint with graceful shutdown.
example/         Working sample plugin.

Dependency direction: domainapplicationapi/inmemory/jsonstorecmd/server

The domain has no external imports and no knowledge of HTTP, JSON, or storage. All port interfaces live in domain/.

Development

make check          # Full suite: fmt + lint + test + security
make test           # Run tests
make lint           # golangci-lint (gocritic, errcheck, staticcheck, unused)
make fmt            # Auto-fix formatting
make cover          # Tests with coverage
make security       # nox security scan
make install-hooks  # Install pre-commit git hook
go test ./... -race # Race detector (important for async execution)

Every commit passes through a pre-commit hook that runs fmt, lint, vet, and all 101+ tests.

Status

Production-ready core — domain, API, safety, persistence, observability all shipped. Used as an execution kernel behind agent systems.

See CONTRIBUTING.md for contributing, CLAUDE.md for a deeper architecture reference.

License

MIT — see LICENSE.

Directories

Path Synopsis
Package api provides a Gin-like HTTP API layer for axi-go, built on net/http.
Package api provides a Gin-like HTTP API layer for axi-go, built on net/http.
Package application contains the use cases for axi-go.
Package application contains the use cases for axi-go.
cmd
server command
Package main is the entrypoint for the axi-go HTTP server.
Package main is the entrypoint for the axi-go HTTP server.
Package domain defines the core domain model for axi-go, a domain-driven execution kernel for semantic actions.
Package domain defines the core domain model for axi-go, a domain-driven execution kernel for semantic actions.
Package main demonstrates an end-to-end axi-go setup with a sample plugin.
Package main demonstrates an end-to-end axi-go setup with a sample plugin.
Package inmemory provides in-memory implementations of all repository ports.
Package inmemory provides in-memory implementations of all repository ports.
Package jsonstore provides file-based JSON persistence adapters for all axi-go repository interfaces.
Package jsonstore provides file-based JSON persistence adapters for all axi-go repository interfaces.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL