axi

package module
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Apr 16, 2026 License: MIT Imports: 5 Imported by: 0

README

axi-go

A domain-driven execution kernel for AI agent tools — a Go library you embed, not a service you run.

CI Go Report Card Go Reference

Zero external dependencies. Standard library only.


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 with a two-layer model:

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? writes? external?), pauses for human approval when required, runs within execution budgets, and produces a structured audit trail.

You embed axi-go in your Go program. It has no HTTP API, no daemon, no protocol assumptions — those are delivery concerns for you to choose (HTTP, gRPC, CLI, MCP, whatever fits your stack).

Install

go get github.com/felixgeelhaar/axi-go

60-Second Tour

package main

import (
    "context"
    "fmt"
    "github.com/felixgeelhaar/axi-go"
)

func main() {
    // 1. Build a kernel with fluent configuration.
    kernel := axi.New().
        WithBudget(axi.Budget{MaxCapabilityInvocations: 100})

    // 2. Wire executors and register your plugin.
    kernel.RegisterActionExecutor("exec.greet", &greetExecutor{})
    kernel.RegisterCapabilityExecutor("exec.upper", &upperExecutor{})
    _ = kernel.RegisterPlugin(&greeterPlugin{})

    // 3. Execute an action.
    result, _ := kernel.Execute(context.Background(), axi.Invocation{
        Action: "greet",
        Input:  map[string]any{"name": "world"},
    })
    fmt.Println(result.Result.Data)  // → {"message": "Hello, WORLD!"}
}

See example/main.go for a complete runnable example.

Core Concepts

Actions express intent
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:

result, _ := kernel.Execute(ctx, axi.Invocation{Action: "send-email", Input: ...})
// result.Status == "awaiting_approval"

// A supervisor approves (or rejects):
final, _ := kernel.Approve(ctx, string(result.SessionID))
// final.Status == "succeeded"
Capabilities express mechanics
smtpCap, _ := domain.NewCapabilityDefinition(
    "smtp.send",
    "Sends an email via SMTP",
    inputContract, outputContract,
)

Capabilities are building blocks. Actions compose them. Capabilities themselves have no effect profile — the action's profile governs safety.

Plugins bundle actions + capabilities
type emailPlugin struct{}

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

kernel.RegisterPlugin(&emailPlugin{})
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. Poll it anytime:

session, _ := kernel.GetSession(sessionID)
fmt.Println(session.Status(), session.Evidence())

The SDK

The root axi package provides a fluent, descriptive API:

kernel := axi.New().
    WithLogger(logger).
    WithBudget(axi.Budget{MaxDuration: 5*time.Minute, MaxCapabilityInvocations: 100}).
    WithRateLimiter(myRateLimiter).
    WithIDGenerator(uuidGen)

// Register
kernel.RegisterPlugin(plugin)
kernel.RegisterPluginWithConfig(plugin, map[string]any{"api_key": "..."})
kernel.RegisterBundle(bundle)  // atomic: metadata + executors
kernel.DeregisterPlugin("my.plugin")

// Execute
result, _ := kernel.Execute(ctx, axi.Invocation{Action: "x", Input: ...})
result, _ := kernel.ExecuteAsync(ctx, axi.Invocation{Action: "x", Input: ...})

// Approval flow
result, _ := kernel.Approve(ctx, sessionID)
result, _ := kernel.Reject(sessionID, "too risky")

// Introspection
actions := kernel.ListActions()
caps    := kernel.ListCapabilities()
session, _ := kernel.GetSession(sessionID)

Safety & Control

Feature What it does
Effect profiles none, read-local, write-local, read-external, write-external
Approval gate write-external actions pause at awaiting_approval — call kernel.Approve(...)
Execution budgets 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
Evidence trail Append-only EvidenceRecords with timestamps — full audit log

Persistence

Two adapters included. Pick one, or implement the repository interfaces in domain/ for Postgres, SQLite, Redis, etc.

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

By default, axi.New() uses inmemory/. Swap the repositories by implementing the 4 ports in domain/: ActionRepository, CapabilityRepository, PluginRepository, SessionRepository.

Architecture

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

axi (root)       Fluent SDK facade — what you import.
domain/          Aggregates, services, port interfaces. Zero deps.
application/     Use cases that orchestrate the domain.
inmemory/        In-memory adapters + StdLogger.
jsonstore/       File-based JSON persistence adapter.
example/         Working sample plugin.

Dependency direction: domainapplicationinmemory/jsonstoreaxi ← your code

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

Building a delivery adapter

axi-go is a kernel. If you need HTTP, gRPC, MCP, or a CLI, build it as a thin adapter on top:

// Your HTTP handler (you own this, it's not in axi-go)
func executeHandler(kernel *axi.Kernel) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        var req ExecuteRequest
        _ = json.NewDecoder(r.Body).Decode(&req)

        result, err := kernel.Execute(r.Context(), axi.Invocation{
            Action: req.Action, Input: req.Input,
        })
        if err != nil {
            http.Error(w, err.Error(), http.StatusBadRequest)
            return
        }
        _ = json.NewEncoder(w).Encode(result)
    }
}

An MCP server adapter, a gRPC service, or a Cobra CLI would all follow the same pattern: translate protocol → kernel calls → translate response.

Development

make check          # Full suite: fmt + lint + test + security
make test           # Run tests
make lint           # golangci-lint
make fmt            # Auto-fix formatting
make install-hooks  # Install pre-commit git hook
go test ./... -race # Race detector

See CONTRIBUTING.md for contribution guidelines and CLAUDE.md for a deeper architecture reference.

License

MIT — see LICENSE.

Documentation

Overview

Package axi is the entry point for axi-go — a domain-driven execution kernel for semantic actions. It provides a fluent, descriptive SDK for registering plugins and executing actions with built-in safety controls.

Example:

kernel := axi.New().
    WithLogger(logger).
    WithBudget(axi.Budget{MaxInvocations: 100})

if err := kernel.RegisterPlugin(myPlugin); err != nil {
    return err
}

result, err := kernel.Execute(ctx, axi.Invocation{
    Action: "greet",
    Input:  map[string]any{"name": "world"},
})

axi-go is a library you embed, not a service you run. There is no HTTP API. Delivery mechanisms (HTTP, gRPC, CLI, MCP) are the caller's choice; build your own adapter around this kernel.

Example

Demonstrate the example from the package doc works.

package main

import (
	"context"
	"strings"

	"github.com/felixgeelhaar/axi-go"
	"github.com/felixgeelhaar/axi-go/domain"
)

func main() {
	kernel := axi.New()
	kernel.RegisterActionExecutor("exec.greet", &greetDocExecutor{})
	_ = kernel.RegisterPlugin(&docPlugin{})

	result, _ := kernel.Execute(context.Background(), axi.Invocation{
		Action: "greet",
		Input:  map[string]any{"name": "world"},
	})
	data, _ := result.Result.Data.(map[string]any)
	_ = strings.Contains(data["message"].(string), "world")
}

type docPlugin struct{}

func (p *docPlugin) Contribute() (*domain.PluginContribution, error) {
	action, _ := domain.NewActionDefinition("greet", "",
		domain.EmptyContract(), domain.EmptyContract(), nil,
		domain.EffectProfile{}, domain.IdempotencyProfile{})
	_ = action.BindExecutor("exec.greet")
	return domain.NewPluginContribution("doc.plugin",
		[]*domain.ActionDefinition{action}, nil)
}

type greetDocExecutor struct{}

func (e *greetDocExecutor) Execute(_ context.Context, input any, _ domain.CapabilityInvoker) (domain.ExecutionResult, []domain.EvidenceRecord, error) {
	m := input.(map[string]any)
	return domain.ExecutionResult{Data: map[string]any{"message": "Hello, " + m["name"].(string)}}, nil, nil
}

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Budget

type Budget = domain.ExecutionBudget

Budget is an alias for domain.ExecutionBudget for ergonomic SDK usage.

type Invocation

type Invocation struct {
	Action string
	Input  map[string]any
}

Invocation is the input to Execute — an action name plus its input data.

type Kernel

type Kernel struct {
	// contains filtered or unexported fields
}

Kernel is the fluent entry point for axi-go. Build it with New(), configure it with With* methods, register plugins, then Execute actions.

A Kernel is NOT safe to configure concurrently. Call With* before the first Execute. Execute itself is safe for concurrent use.

func New

func New() *Kernel

New creates a Kernel with default in-memory adapters. Further configuration is done via chainable With* methods.

func (*Kernel) Approve

func (k *Kernel) Approve(ctx context.Context, sessionID string) (*Result, error)

Approve approves a session in AwaitingApproval state and resumes execution.

func (*Kernel) DeregisterPlugin

func (k *Kernel) DeregisterPlugin(id string) error

DeregisterPlugin removes a plugin and all its contributed actions/capabilities.

func (*Kernel) Execute

func (k *Kernel) Execute(ctx context.Context, inv Invocation) (*Result, error)

Execute runs an action synchronously and returns the full result. If the action has effect_level "write-external", execution pauses at AwaitingApproval and the caller must Approve() or Reject() before completion.

func (*Kernel) ExecuteAsync

func (k *Kernel) ExecuteAsync(ctx context.Context, inv Invocation) (*Result, error)

ExecuteAsync submits an action for background execution and returns immediately. Poll via GetSession(sessionID) to check status.

func (*Kernel) GetAction

func (k *Kernel) GetAction(name string) (*domain.ActionDefinition, error)

GetAction returns an action definition by name.

func (*Kernel) GetSession

func (k *Kernel) GetSession(sessionID string) (*domain.ExecutionSession, error)

GetSession returns the current state of an execution session by ID.

func (*Kernel) ListActions

func (k *Kernel) ListActions() []*domain.ActionDefinition

ListActions returns all registered actions.

func (*Kernel) ListCapabilities

func (k *Kernel) ListCapabilities() []*domain.CapabilityDefinition

ListCapabilities returns all registered capabilities.

func (*Kernel) RegisterActionExecutor

func (k *Kernel) RegisterActionExecutor(ref string, executor domain.ActionExecutor)

RegisterActionExecutor wires an executor ref to an implementation. Use this when registering actions without a PluginBundle.

func (*Kernel) RegisterBundle

func (k *Kernel) RegisterBundle(bundle *domain.PluginBundle) error

RegisterBundle atomically registers a plugin contribution along with its executor implementations. Preferred over RegisterPlugin when you want to validate executor refs match implementations before registration.

func (*Kernel) RegisterCapabilityExecutor

func (k *Kernel) RegisterCapabilityExecutor(ref string, executor domain.CapabilityExecutor)

RegisterCapabilityExecutor wires a capability executor ref to an implementation.

func (*Kernel) RegisterPlugin

func (k *Kernel) RegisterPlugin(plugin domain.Plugin) error

RegisterPlugin registers a Plugin by calling Contribute() and activating it. If the plugin implements domain.LifecyclePlugin, Init() is called first.

func (*Kernel) RegisterPluginWithConfig

func (k *Kernel) RegisterPluginWithConfig(plugin domain.Plugin, config domain.PluginConfig) error

RegisterPluginWithConfig registers a LifecyclePlugin with configuration.

func (*Kernel) Reject

func (k *Kernel) Reject(sessionID, reason string) (*Result, error)

Reject rejects a session in AwaitingApproval state with a reason.

func (*Kernel) WithBudget

func (k *Kernel) WithBudget(budget Budget) *Kernel

WithBudget sets the default execution budget (max duration, max invocations). Returns the kernel for chaining.

func (*Kernel) WithIDGenerator

func (k *Kernel) WithIDGenerator(gen application.IDGenerator) *Kernel

WithIDGenerator overrides the default session ID generator.

func (*Kernel) WithLogger

func (k *Kernel) WithLogger(logger domain.Logger) *Kernel

WithLogger sets a structured logger for the kernel. Returns the kernel for chaining.

func (*Kernel) WithRateLimiter

func (k *Kernel) WithRateLimiter(rl domain.RateLimiter) *Kernel

WithRateLimiter sets a rate limiter checked before each execution. Returns the kernel for chaining.

func (*Kernel) WithTimeout

func (k *Kernel) WithTimeout(d time.Duration) *Kernel

WithTimeout configures a default execution timeout via the budget's MaxDuration. Returns the kernel for chaining.

type Result

Result is the output of Execute — session state, result data, evidence.

Directories

Path Synopsis
Package application contains the use cases for axi-go.
Package application contains the use cases for axi-go.
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 axi-go embedded in a Go program.
Package main demonstrates axi-go embedded in a Go program.
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