{"id":49630079,"url":"https://github.com/felixgeelhaar/axi-go","last_synced_at":"2026-05-05T10:06:10.790Z","repository":{"id":351736398,"uuid":"1209811284","full_name":"felixgeelhaar/axi-go","owner":"felixgeelhaar","description":"Safe, auditable execution kernel for AI agent tools — DDD in Go, zero dependencies, with approval gates, execution budgets, evidence trails, and typed contracts.","archived":false,"fork":false,"pushed_at":"2026-04-16T10:07:54.000Z","size":107,"stargazers_count":0,"open_issues_count":3,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-16T10:08:59.461Z","etag":null,"topics":["agent-tools","ai-agents","ddd","domain-driven-design","execution-kernel","go","golang","mcp","tool-use"],"latest_commit_sha":null,"homepage":"https://github.com/felixgeelhaar/axi-go","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/felixgeelhaar.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":".github/SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-13T20:02:01.000Z","updated_at":"2026-04-16T10:07:57.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/felixgeelhaar/axi-go","commit_stats":null,"previous_names":["felixgeelhaar/axi-go"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/felixgeelhaar/axi-go","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/felixgeelhaar%2Faxi-go","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/felixgeelhaar%2Faxi-go/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/felixgeelhaar%2Faxi-go/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/felixgeelhaar%2Faxi-go/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/felixgeelhaar","download_url":"https://codeload.github.com/felixgeelhaar/axi-go/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/felixgeelhaar%2Faxi-go/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32644233,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-04T10:08:07.713Z","status":"online","status_checked_at":"2026-05-05T02:00:06.033Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agent-tools","ai-agents","ddd","domain-driven-design","execution-kernel","go","golang","mcp","tool-use"],"created_at":"2026-05-05T10:06:07.946Z","updated_at":"2026-05-05T10:06:10.773Z","avatar_url":"https://github.com/felixgeelhaar.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# axi-go\n\n**A domain-driven execution kernel for AI agent tools — a Go library you embed, not a service you run.**\n\n[![CI](https://github.com/felixgeelhaar/axi-go/actions/workflows/ci.yml/badge.svg)](https://github.com/felixgeelhaar/axi-go/actions/workflows/ci.yml)\n[![Go Report Card](https://goreportcard.com/badge/github.com/felixgeelhaar/axi-go)](https://goreportcard.com/report/github.com/felixgeelhaar/axi-go)\n[![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)\n[![Go Reference](https://pkg.go.dev/badge/github.com/felixgeelhaar/axi-go.svg)](https://pkg.go.dev/github.com/felixgeelhaar/axi-go)\n\n**Zero external dependencies.** Standard library only.\n\n---\n\n## Why axi-go?\n\nWhen you give an AI agent a bag of tools (`search`, `send_email`, `run_sql`), you quickly hit these problems:\n\n- **No safety** — the agent can call `send_email` a thousand times before you know it\n- **No audit trail** — you can't explain *why* the agent did what it did\n- **Tool sprawl** — 200 raw functions, no grouping, no dependencies, no lifecycle\n- **No type information** — the agent has to guess what inputs each tool accepts\n- **No approval gates** — the agent can take irreversible actions autonomously\n\n**axi-go solves this** with a two-layer model:\n\n| Layer | Example | Answers |\n|-------|---------|---------|\n| **Actions** | `greet`, `send-email`, `search-docs` | *What* the agent wants to do (intent) |\n| **Capabilities** | `string.upper`, `http.get`, `db.query` | *How* it gets done (mechanics) |\n\nAn 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.\n\nYou 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).\n\n## What you get\n\nEvery capability below is in the kernel today — no optional module, no extra dependency, no vendor lock-in:\n\n- **Effect-gated approval.** Actions declare their side-effect level (`none`, `read-local`, `write-local`, `read-external`, `write-external`). The kernel pauses any `write-external` action at `awaiting_approval` until a human approves via `kernel.Approve`. Typo catching an agent about to mass-email? Caught before the executor runs.\n- **Tamper-evident evidence trail.** Every `EvidenceRecord` appended to a session carries a SHA-256 `Hash` chained to the previous record. `session.VerifyEvidenceChain()` detects any post-emission mutation — your audit log is cryptographically replay-safe for free.\n- **Domain events stream.** Implement `domain.DomainEventPublisher` once and subscribe to every lifecycle transition: session started/completed, capability invoked/retried, budget exceeded, evidence recorded. Fan it out to Prometheus, OpenTelemetry, Kafka, a SIEM — the plugin contract is one method.\n- **Streaming results.** `StreamingActionExecutor` (optional companion to `ActionExecutor`) emits `ResultChunk` value objects progressively — LLM tokens, large-file reads, row-stream queries — while the kernel stamps monotonic indices under its mutex. Your HTTP/SSE or MCP-SSE adapter forwards chunks as they're produced.\n- **Composition via `ActionInvoker`.** Plugin code can invoke other registered actions through `OrchestratorActionExecutor` — the primitive that lets sagas, fan-out/fan-in, and pipeline-of-actions ship as plugins without pulling a durable-log backend into axi-go core.\n- **Budgets, rate limits, idempotency, output contracts, TOON encoding, truncation, help, suggestions.** Table further down.\n\nComposing these, not reinventing them in every agent service, is the whole pitch.\n\n## Install\n\n```bash\ngo get github.com/felixgeelhaar/axi-go\n```\n\n## 60-Second Tour\n\nA single write-external action. The kernel pauses for approval, runs after the human signs off, and exits with a verified-intact evidence trail — while a subscriber prints every lifecycle event. Full runnable source at [`example/quickstart/`](example/quickstart/); `go run ./example/quickstart` reproduces the output below.\n\n```go\npackage main\n\nimport (\n    \"context\"\n    \"fmt\"\n\n    \"github.com/felixgeelhaar/axi-go\"\n    \"github.com/felixgeelhaar/axi-go/domain\"\n)\n\ntype emailPlugin struct{}\n\nfunc (emailPlugin) Contribute() (*domain.PluginContribution, error) {\n    action, _ := domain.NewActionDefinition(\n        \"send-email\", \"Send an email\",\n        domain.NewContract([]domain.ContractField{{\n            Name: \"to\", Type: \"string\", Required: true, Description: \"Recipient\",\n        }}),\n        domain.EmptyContract(), nil,\n        domain.EffectProfile{Level: domain.EffectWriteExternal}, // → approval gate\n        domain.IdempotencyProfile{IsIdempotent: false},\n    )\n    _ = action.BindExecutor(\"exec.email\")\n    return domain.NewPluginContribution(\"email.plugin\",\n        []*domain.ActionDefinition{action}, nil)\n}\n\ntype emailExec struct{}\n\nfunc (emailExec) Execute(_ context.Context, input any, _ domain.CapabilityInvoker) (domain.ExecutionResult, []domain.EvidenceRecord, error) {\n    to := input.(map[string]any)[\"to\"].(string)\n    return domain.ExecutionResult{Summary: \"sent email to \" + to},\n        []domain.EvidenceRecord{{\n            Kind:   \"smtp.delivered\",\n            Source: \"email.plugin\",\n            Value:  map[string]any{\"to\": to, \"message_id\": \"msg-42\"},\n        }}, nil\n}\n\ntype logEvents struct{}\n\nfunc (logEvents) Publish(e domain.DomainEvent) { fmt.Printf(\"  event → %s\\n\", e.EventType()) }\n\nfunc main() {\n    kernel := axi.New().WithDomainEventPublisher(logEvents{})\n    kernel.RegisterActionExecutor(\"exec.email\", emailExec{})\n    _ = kernel.RegisterPlugin(emailPlugin{})\n\n    ctx := context.Background()\n\n    // 1) Execute. write-external → kernel pauses before the executor runs.\n    result, _ := kernel.Execute(ctx, axi.Invocation{\n        Action: \"send-email\",\n        Input:  map[string]any{\"to\": \"alice@example.com\"},\n    })\n    fmt.Println(\"after Execute:\", result.Status) // awaiting_approval\n\n    // 2) Human (or policy bot) approves. Kernel resumes the session.\n    final, _ := kernel.Approve(ctx, string(result.SessionID), domain.ApprovalDecision{\n        Principal: \"ops@example.com\",\n        Rationale: \"recipient verified\",\n    })\n    fmt.Println(\"after Approve:\", final.Status) // succeeded\n\n    // 3) Audit. Each evidence record carries a SHA-256 hash chained to\n    //    the previous. VerifyEvidenceChain proves the trail is intact.\n    session, _ := kernel.GetSession(string(result.SessionID))\n    for _, ev := range session.Evidence() {\n        fmt.Printf(\"  evidence: kind=%s hash=%.10s…\\n\", ev.Kind, string(ev.Hash))\n    }\n    if err := session.VerifyEvidenceChain(); err == nil {\n        fmt.Println(\"  chain: intact\")\n    }\n}\n```\n\nOutput:\n\n```\n  event → session.started\n  event → session.awaiting_approval\nafter Execute: awaiting_approval\n  event → evidence.recorded\n  event → session.completed\nafter Approve: succeeded\n  evidence: kind=smtp.delivered hash=4a70e78708…\n  chain: intact\n```\n\nFour primitives in one program: effect-gated approval, an evidence record with its tamper-evident hash, `VerifyEvidenceChain()` confirming the trail, and a `DomainEventPublisher` subscriber printing every lifecycle transition. That's the whole 1.x value proposition, compressed.\n\n## More examples\n\n- [`example/main.go`](example/main.go) — fuller plugin showing capability composition, suggestions, TOON, retries.\n- [`example/mcp-server/`](example/mcp-server/) — an MCP (Model Context Protocol) adapter in ~250 lines, no external deps.\n- [`example/observability/`](example/observability/) — adoption templates for `DomainEventPublisher` as a strict-DDD subscriber, evidence-chain verification as an operator endpoint, and a per-action token-budget guard that composes `DomainEventPublisher` and `RateLimiter` instead of needing a new kernel feature.\n\nTo understand the *why* — the reasoning that makes actions, capabilities, effect profiles, and evidence inevitable once you accept certain premises — read [`docs/CONCEPTS.md`](docs/CONCEPTS.md). For versioning commitments and deprecation policy, see [`docs/ROADMAP.md`](docs/ROADMAP.md).\n\n## Configuring a kernel\n\nThe fluent builder on `axi.New()` returns a configured `*Kernel`. Chain\nthe `With*` methods as needed:\n\n```go\nkernel := axi.New().\n    WithLogger(logger).\n    WithBudget(axi.Budget{MaxDuration: 5*time.Minute, MaxCapabilityInvocations: 100}).\n    WithRateLimiter(myRateLimiter).\n    WithIDGenerator(uuidGen)\n```\n\nRegister plugins and executors before the first `Execute`:\n\n```go\nkernel.RegisterPlugin(plugin)\nkernel.RegisterBundle(bundle)  // atomic: metadata + executors together\n```\n\nDrive actions from your delivery layer:\n\n```go\nresult, _ := kernel.Execute(ctx, axi.Invocation{Action: \"greet\", Input: inp})\n\n// For write-external actions that paused at awaiting_approval:\nresult, _ := kernel.Approve(ctx, sessionID, decision)\nresult, _ := kernel.Reject(ctx, sessionID, decision)\n```\n\n## Kernel reference (quick)\n\n| Method | Purpose |\n|---|---|\n| `New()` | Build a kernel with default in-memory adapters |\n| `WithLogger`, `WithBudget`, `WithRateLimiter`, `WithIDGenerator`, `WithTimeout` | Fluent configuration |\n| `RegisterPlugin`, `RegisterPluginWithConfig`, `RegisterBundle` | Add actions + capabilities |\n| `RegisterActionExecutor`, `RegisterCapabilityExecutor` | Bind refs to implementations |\n| `DeregisterPlugin` | Remove a plugin and everything it contributed |\n| `Execute`, `ExecuteAsync` | Invoke an action synchronously or in the background |\n| `Approve`, `Reject` | Resolve a session awaiting approval |\n| `GetSession` | Look up a session by id |\n| `ListActions`, `ListCapabilities` | Full aggregates |\n| `ListActionsResult`, `ListCapabilitiesResult` | Aggregates wrapped with `TotalCount` + `IsEmpty()` |\n| `ListActionSummaries`, `ListCapabilitySummaries` | Minimal-schema projections (axi.md #2) |\n| `GetAction`, `Help` | Introspect one action or any name (axi.md #10) |\n\nSee the godoc on [pkg.go.dev](https://pkg.go.dev/github.com/felixgeelhaar/axi-go)\nfor full signatures and runnable examples.\n\n## Safety \u0026 Control\n\n| Feature | What it does |\n|---------|--------------|\n| **Effect profiles** | `none`, `read-local`, `write-local`, `read-external`, `write-external` |\n| **Approval gate** | `write-external` actions pause at `awaiting_approval` — call `kernel.Approve(...)` |\n| **Execution budgets** | Max duration, max capability invocations, max tokens, and idempotency-gated retries per session |\n| **Rate limiting** | Pluggable `RateLimiter` checked before each execution |\n| **Output validation** | Results validated against output contracts before `succeeded` |\n| **Idempotency profile** | Actions declare whether they're safe to retry |\n| **Evidence trail** | Append-only `EvidenceRecord`s with timestamps — full audit log |\n| **Pipeline saga** | Mid-pipeline failures return a `*PipelineFailure` with partial outputs and run any `PipelineStep.Compensate` hooks in reverse order |\n\n## Agent-facing output\n\naxi-go draws design cues from [axi.md](https://axi.md/) — a set of principles\nfor agent-tool interfaces optimized for token efficiency and discoverability.\n\n### Suggestions (axi.md #9)\n\nActions can emit next-step hints in their result. The agent reads them and\navoids guessing what to call next:\n\n```go\nreturn domain.ExecutionResult{\n    Data:    map[string]any{\"id\": \"abc-123\"},\n    Summary: \"created resource abc-123\",\n    Suggestions: []domain.Suggestion{\n        {Action: \"resource.get\", Description: \"Retrieve the created resource\"},\n        {Action: \"resource.list\", Description: \"List all resources\"},\n    },\n}, nil, nil\n```\n\n### TOON encoding (axi.md #1)\n\nThe `toon` package encodes results in Token-Optimized Object Notation —\nbrace-free, quote-free, and ~40% shorter than equivalent JSON on uniform\narrays:\n\n```go\nimport \"github.com/felixgeelhaar/axi-go/toon\"\n\nout, _ := toon.Encode(map[string]any{\n    \"issues\": []any{\n        map[string]any{\"number\": 42, \"state\": \"open\", \"title\": \"Fix login bug\"},\n        map[string]any{\"number\": 43, \"state\": \"open\", \"title\": \"Add dark mode\"},\n    },\n})\n// issues[2]{number,state,title}:\n//   42,open,Fix login bug\n//   43,open,Add dark mode\n```\n\n### Token budget (axi.md #1)\n\nCapabilities report token usage via `EvidenceRecord.TokensUsed`; the kernel\nsums them and fails the session if the budget is exceeded:\n\n```go\nkernel := axi.New().WithBudget(axi.Budget{MaxTokens: 10_000})\n// A session whose evidence sums to more than 10k tokens fails with\n// FailureReason.Code = \"BUDGET_EXCEEDED\".\n```\n\n### Truncation (axi.md #3)\n\n`axi.Truncate` caps strings and appends a size hint so context windows stay\nbounded without silently dropping data:\n\n```go\nout, truncated := axi.Truncate(longBody, 500)\n// \"…first 500 chars… (truncated, 2847 chars total)\"\n```\n\n### Minimal schemas and empty states (axi.md #2, #5)\n\n`Kernel.ListActionSummaries` and `Kernel.ListCapabilitySummaries` return a\ndiscovery-oriented projection (name, description, effect/idempotency for\nactions) instead of full aggregates. All list responses share the\n`ListResult[T]` shape with `TotalCount` and `IsEmpty()` so callers can\ndistinguish \"no results\" from \"not queried\":\n\n```go\nr := kernel.ListActionSummaries()\nif r.IsEmpty() {\n    fmt.Println(\"no actions registered\")\n}\nfor _, s := range r.Items {\n    fmt.Printf(\"  %s  (%s, idempotent=%t) — %s\\n\",\n        s.Name, s.Effect, s.Idempotent, s.Description)\n}\n```\n\n### Help (axi.md #10)\n\n`ActionDefinition.Help()` and `CapabilityDefinition.Help()` return a\nformatted reference with contracts and capability requirements.\n`Kernel.Help(name)` looks up the name as an action first, then as a\ncapability — a consistent fallback when contextual suggestions aren't\nenough:\n\n```go\ntext, _ := kernel.Help(\"greet\")\n// greet — Greet someone by name\n// Effect: none  Idempotent: true\n//\n// Input:\n//   name  (string, required)  Person to greet\n//     example: world\n// ...\n```\n\n## Persistence\n\nTwo adapters included. Pick one, or implement the repository interfaces in `domain/` for Postgres, SQLite, Redis, etc.\n\n| Adapter | Package | Use for |\n|---------|---------|---------|\n| In-memory | `inmemory/` | Tests, single-process, ephemeral |\n| JSON files | `jsonstore/` | Small deployments, simple persistence |\n\nBy default, `axi.New()` uses `inmemory/`. Swap the repositories by implementing the 4 ports in `domain/`: `ActionRepository`, `CapabilityRepository`, `PluginRepository`, `SessionRepository`.\n\n## Architecture\n\naxi-go is built with strict [Domain-Driven Design](https://www.domainlanguage.com/ddd/):\n\n```\naxi (root)       Fluent SDK facade — what you import.\ndomain/          Aggregates, services, port interfaces. Zero deps.\napplication/     Use cases that orchestrate the domain.\ninmemory/        In-memory adapters + StdLogger.\njsonstore/       File-based JSON persistence adapter.\nexample/         Working sample plugin.\n```\n\n**Dependency direction**: `domain` ← `application` ← `inmemory`/`jsonstore` ← `axi` ← your code\n\nThe domain has no external imports and no knowledge of JSON, HTTP, or any delivery mechanism. All port interfaces live in `domain/`.\n\n## Building a delivery adapter\n\naxi-go is a kernel. If you need HTTP, gRPC, MCP, or a CLI, build it as a thin adapter on top:\n\n```go\n// Your HTTP handler (you own this, it's not in axi-go)\nfunc executeHandler(kernel *axi.Kernel) http.HandlerFunc {\n    return func(w http.ResponseWriter, r *http.Request) {\n        var req ExecuteRequest\n        _ = json.NewDecoder(r.Body).Decode(\u0026req)\n\n        result, err := kernel.Execute(r.Context(), axi.Invocation{\n            Action: req.Action, Input: req.Input,\n        })\n        if err != nil {\n            http.Error(w, err.Error(), http.StatusBadRequest)\n            return\n        }\n        _ = json.NewEncoder(w).Encode(result)\n    }\n}\n```\n\nAn MCP server adapter, a gRPC service, or a Cobra CLI would all follow the same pattern: translate protocol → kernel calls → translate response.\n\n## Development\n\n```bash\nmake check          # Full suite: fmt + lint + test + security\nmake test           # Run tests\nmake lint           # golangci-lint\nmake fmt            # Auto-fix formatting\nmake install-hooks  # Install pre-commit git hook\ngo test ./... -race # Race detector\n```\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for contribution guidelines and [CLAUDE.md](CLAUDE.md) for a deeper architecture reference.\n\n## License\n\nApache License 2.0 — see [LICENSE](LICENSE) and [NOTICE](NOTICE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffelixgeelhaar%2Faxi-go","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffelixgeelhaar%2Faxi-go","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffelixgeelhaar%2Faxi-go/lists"}