Typed Go access to Claude Code, OpenAI Codex and other coding-agent CLIs.
Find every install and home, read settings, MCP servers, skills, plugins, logins, plan usage, conversations and live sessions, and build the command that launches or resumes the agent, from one type-safe Go library.
agentio is an open-source Go library that tells a program everything it can know about the coding-agent CLIs on a machine: whether Claude Code, OpenAI Codex, Gemini CLI and pi are installed and at which version, where their homes (config directories) are, what is in their settings, CLAUDE.md / AGENTS.md instruction files, MCP servers, skills and plugins, which account is logged in, how much of the plan's usage is left, which conversations exist and which sessions are running right now. It also builds the exact Command that launches, resumes or runs an agent headless, with the right environment for each home. It reads local files through typed structs modelled on real vendor files, never runs a vendor binary or touches the network unless you ask, and never refreshes or prints a login token.
- Why agentio?
- Architecture
- Install
- Quick start
- What you can read and build
- Platforms
- Documentation
- Examples
- FAQ
- Contributing
- Security
- License
Every tool that sits next to a coding agent (a launcher, a usage meter, a session switcher, a dashboard, a backup script) ends up re-learning the same vendor facts: where ~/.claude and ~/.codex live and how a second home is selected, how Claude Code names its per-folder history directory, which keychain entry holds the login, which file holds cached usage, how a Codex rollout is laid out, which flags exist in which version. Each tool gets a few of them wrong (a hard-coded home path, a token on argv, a whole transcript read into memory) and each breaks separately when the vendor ships a change.
agentio puts those facts in one tested library, with each vendor fact backed by a fixture home copied from a real install:
- Type-safe all the way down. Concrete vendor types (
*claude.Agent,*codex.Agent) when you know the agent; one typed accessor per shared capability (agentio.UsageOf(a),agentio.ConversationsOf(a)) when you loop over agents. Noany, no string-keyed features, no type assertions in your code. - Every call states its cost. Pure, disk read, process run, network read or quota-spending call. Running a vendor binary, spending quota and writing sit behind separate doors (
Runner,Prober,Writer), so a read-only loop cannot do any of them by accident. - Safe with credentials. Tokens are a redacting
Tokentype that prints[redacted]everywhere, never go on argv, and are never refreshed or written. Emails and account ids are redacted too unless you callReveal(). - Read-only by default. Writes are explicit
Writermethods that back the target up first and return what they copied. - No hidden state. Everything (home dir, environment, clock, HTTP, keychain, process table, exec) comes from one
Hostvalue, so tests point it at fixture homes and one process can inspect several homes side by side. - Multiple homes are first class. Default home,
CLAUDE_CONFIG_DIR/CODEX_HOMEhomes and any extra home directory, with the vendor's own env rule applied when launching. - Pay only for what you import. One Go module per vendor: a Claude-only program never downloads Codex's TOML or SQLite dependencies.
- Version drift is visible. Each vendor module declares the CLI versions it was tested against; anything outside the range still works and returns a warning. A drift report lists unknown keys, enum values and flags on a real machine.
- Agents as data. A simple CLI agent (binary, home variable, resume syntax) is an
agentio.Specvalue, no Go code needed.
| agentio | Parsing vendor files yourself | Shelling out to vendor subcommands | |
|---|---|---|---|
| Typed structs for settings, MCP, usage, conversations | ✅ | ||
| Same code loops over Claude Code and Codex | ✅ typed accessors | ❌ | ❌ |
| Never runs a vendor binary unless asked | ✅ | ✅ | ❌ |
| Never refreshes or rewrites a login | ✅ invariant with tests | ❌ some subcommands refresh tokens | |
| Tokens kept off argv, logs and JSON | ✅ redacting types | ||
| Multiple homes per agent | ✅ | ||
| Testable without a real account | ✅ Host + fixture homes + fakes |
❌ | |
| Tracks vendor version drift | ✅ tested ranges, warnings, drift report | ❌ | ❌ |
If you only need one value from one file once, reading it yourself is fine. If your program needs to understand several agents, several homes or several vendor versions, and must not leak or break a login, use agentio.
The library has three levels:
- L1 core (
agentio.Agent): what every agent has, including one described only by data: identity, homes, binary lookup and building a launchCommand. - L2 shared capabilities: concepts both vendors share (install, identity, usage, cached usage, subscription, conversations, prompt history, live sessions, turns, instruction files, MCP servers, skills, plugins, config layers, run events, doctor checks), each a small interface with a typed accessor such as
agentio.UsageOf(a). - L3 vendor API: everything else, typed per vendor: Claude Code settings, hooks, subagents, transcripts and stream-json events; Codex
config.toml, rollouts, SQLite threads and exec events.
agentio is a set of Go modules. Import only the ones you need:
| Module | Import when you need | Extra dependencies |
|---|---|---|
github.com/ubgo/agentio |
the neutral core: Host, Agent, Command, Token, capability accessors, agenttest |
none (stdlib only) |
github.com/ubgo/agentio/claude |
Claude Code | none |
github.com/ubgo/agentio/codex |
Codex: config, rollouts, usage, sessions, launch | github.com/pelletier/go-toml/v2 |
github.com/ubgo/agentio/codex/state |
Codex's SQLite stores (threads, turns, goals, memories, logs), read-only | modernc.org/sqlite (pure Go, no CGO) |
github.com/ubgo/agentio/gemini |
Gemini CLI: homes, install, settings, conversations, MCP, extensions, launch | none |
github.com/ubgo/agentio/pi |
pi: agent dir, install, sessions, settings, trust, MCP, skills, launch | none |
github.com/ubgo/agentio/builtin |
every built-in agent at once, plus config-defined agents | whatever claude, codex, gemini and pi pull |
# Claude Code only
go get github.com/ubgo/agentio/claude
# Codex only
go get github.com/ubgo/agentio/codex
# Every built-in agent in one typed set
go get github.com/ubgo/agentio/builtin
# Codex SQLite stores: only if you read threads or logs from SQLite
go get github.com/ubgo/agentio/codex/statecodex/state is its own module because it pulls a full SQLite driver. Most programs never need it: codex already reads Codex's rollouts, config and usage. builtin does not import codex/state either.
agentio needs Go 1.26.4 or newer and builds with CGO_ENABLED=0 on linux, macOS and windows (amd64 and arm64).
This program lists every Claude Code and Codex home on the machine, the three newest conversations in each, the cached plan usage, and the command that would start Claude Code in plan mode. It only reads disk; it runs no vendor binary and makes no network call.
package main
import (
"context"
"fmt"
"log"
"github.com/ubgo/agentio"
"github.com/ubgo/agentio/builtin"
"github.com/ubgo/agentio/claude"
)
func main() {
ctx := context.Background()
// The only call that touches the real machine.
host, err := agentio.HostFromOS()
if err != nil {
log.Fatal(err)
}
agents, err := builtin.New(host) // Claude Code + Codex; nothing read yet
if err != nil {
log.Fatal(err)
}
// Neutral loop: every agent, every home, capabilities by typed accessor.
for _, a := range agents.All() {
homes, err := a.Homes() // disk read
if err != nil {
log.Fatal(err)
}
for _, h := range homes {
fmt.Println(a.Name(), h.Dir())
if conv, ok := agentio.ConversationsOf(a); ok {
list, err := conv.Conversations(ctx, h, agentio.ConversationQuery{Limit: 3})
if err != nil {
log.Fatal(err)
}
for _, c := range list {
fmt.Printf(" %s %s\n", c.Updated.Format("2006-01-02"), c.Title)
}
}
if cu, ok := agentio.CachedUsageOf(a); ok {
u, found, err := cu.CachedUsage(h) // disk only, safe in a loop
if err == nil && found {
fmt.Printf(" plan usage: %.0f%% (as of %s)\n", u.MaxPercent(), u.ObservedAt.Format("15:04"))
}
}
}
}
// Vendor API: code that knows it is Claude Code gets typed options.
cmd, err := agents.Claude.Launch(agents.Claude.DefaultHome().Target(), claude.LaunchOptions{
Model: "opus",
PermissionMode: claude.ModePlan,
})
if err != nil {
log.Fatal(err)
}
fmt.Println(cmd.Redacted()) // building is pure; you run it
}Run against the repository's fixture homes (two Claude Code homes and one Codex home), it prints:
Claude Code /home/user/.claude
2026-11-14 Fixture title 185
2026-11-14 Fixture title 166
plan usage: 6% (as of 08:38)
Claude Code /home/user/.claude-work
2026-11-14 Work home fixture
Codex /home/user/.codex
2026-11-14 List the files
2026-11-14 hello
plan usage: 12% (as of 10:00)
claude --model opus --permission-mode plan
A Claude-only program skips builtin and uses claude.New(host) directly; c.Neutral() gives the same neutral view for the L2 accessors.
| Area | Claude Code | Codex | Gemini CLI | pi |
|---|---|---|---|---|
| Install, version, tested range, binary lookup | ✅ | ✅ | ✅ | ✅ |
| Homes: default, env-selected, extra dirs | ✅ | ✅ | ✅ GEMINI_CLI_HOME names the parent |
✅ PI_CODING_AGENT_DIR |
| Settings and config layers | ✅ settings.json scopes, hooks, env, policy limits |
✅ config.toml, profiles |
✅ settings.json layers, every key kept |
✅ settings.json, trust |
| Instruction files | ✅ CLAUDE.md, imports, auto memory |
✅ AGENTS.md |
✅ GEMINI.md |
✅ AGENTS.md, CLAUDE.md, SYSTEM.md |
| MCP servers, skills, plugins | ✅ | ✅ | ✅ (extensions) | ✅ MCP and skills |
| Account identity and subscription | ✅ (personal values redacted) | ✅ (personal values redacted) | identity only | stored logins, never values |
| Plan usage: cached on disk or live from the vendor | ✅ | ✅ | ❌ not recorded yet | ❌ no plan; per-session cost |
Pick the home with the most headroom (agentio/pick, across vendors with usage) |
✅ | ✅ | ❌ | ❌ |
| Conversations, prompt history, turns | ✅ transcripts, read with bounded head and tail | ✅ rollouts, SQLite threads via codex/state |
✅ chats per project | ✅ sessions and turns |
| Live sessions and process classification | ✅ | ✅ | processes only | processes only |
| Launch, resume, headless commands | ✅ typed LaunchOptions, stream-json decoder |
✅ typed options, exec event decoder | ✅ approval modes, stream-json | ✅ typed options, JSON mode |
| Typed launch config: option registry, settings check, live login | ✅ all three | ✅ option registry, -c key table |
❌ | ❌ |
| Explicit writes with backup | ✅ settings, hooks, move history and conversations, link homes | ❌ by design: Codex keeps its own stores consistent, so changes go through Codex's own commands | ❌ read-only | ❌ read-only |
Each agent's reason for a capability it lacks is in the capability matrix.
The full list, with the cost of every call, is in the feature index.
Every feature works on each OS below or returns agentio.ErrUnsupported with the reason. The rules follow Host.GOOS, so the tests prove the Windows rules on Linux too. Details and per-OS pitfalls: Platforms.
| macOS | Linux (glibc, musl) | Windows 10/11 | |
|---|---|---|---|
| Install, version, homes, settings, MCP, skills, conversations | ✅ | ✅ | ✅ (PATHEXT, .cmd shims, %USERPROFILE%) |
| Claude login | keychain + .credentials.json |
.credentials.json |
.credentials.json |
| Codex keyring login | keychain | Secret Service (secret-tool) |
ErrUnsupported; auth.json works |
| Managed settings | files + plist | /etc/claude-code, /etc/codex |
files + policy registry |
| Process table, live sessions | ps |
/proc (Alpine included) |
PowerShell; no process cwd |
| Home links | ✅ | ✅ | needs Developer Mode, else a typed error |
| Tested | owner's Mac | Ubuntu, and Debian, Alpine, Fedora, Arch containers (task test:distros) |
compiled and fixture-tested; real runs through the manual platforms workflow |
- Learning path: read the docs in order, from the first
Hostto vendor-specific APIs. - Choosing guides: which module, which level, which door for your program.
- Feature index: every capability and method with its cost.
- All docs: getting started, core, capabilities, Claude Code, Codex, testing, compatibility.
- CHANGELOG: release history per module.
Runnable programs live in examples/, one per area, each with a README and a test that runs it on fixture homes; the full list is in All examples. Two of them report on your own machine:
examples/agentinfo: a text or--jsonreport of every agent and home on the machine: install, compat, homes, paths, settings, MCP servers, instruction files, skills, plugins, identity (redacted), cached usage, recent conversations, live sessions. Disk and process-table reads only unless you pass--runneror--network.examples/drift: lists what the library does not know yet about the vendor versions on this machine (unknown keys, enum values, record types, flags), naming files and keys only, never values.
cd examples
go run ./agentinfoCreate a Host with agentio.HostFromOS(), then claude.New(host) or codex.New(host). The vendor agent has typed readers for Claude Code's settings.json scopes and Codex's config.toml, and agentio.ConfigOf(a) gives a neutral view of both.
No. By default it only reads local files and, for live sessions, the process table. Live usage and subscription calls are separate network methods you call explicitly, and the only quota-spending call sits behind the Prober door.
It is designed not to. agentio never refreshes, writes or copies an OAuth token, and never runs a vendor binary unless you call a Runner method, because some vendor subcommands refresh tokens and rewrite login files. The default home is launched with the home variable removed, not set, so the CLI keeps finding its keychain entry.
agentio.CachedUsageOf(a) reads the usage the CLI last saved on disk, which is free and safe in a loop. agentio.UsageOf(a) or c.Usage(ctx, home) asks the vendor once over the network; a 429 comes back as ErrRateLimited, so cache the result instead of polling.
Yes. a.Homes() finds the default home and other home directories on disk, each launch targets one home, and the vendor's env rule (CLAUDE_CONFIG_DIR, CODEX_HOME) is applied for you.
agentio.ConversationsOf(a) lists conversations per home, newest first, filtered by folder, time or count. Claude Code transcripts are never read whole, only a bounded head and tail. For Codex's SQLite threads, import codex/state.
Each vendor module declares the range it was tested against (claude.TestedMin / TestedMax, codex.TestedMin / TestedMax) with a fixture home per version. Other versions still work: unknown keys are kept in typed Unknown fields and the result carries a warning instead of failing.
A simple CLI agent can be described as data with an agentio.Spec and passed to builtin.New(host, spec); it gets homes, binary lookup, the install version from package metadata, launch (with autonomy, headless and token templates when the Spec describes them) and doctor checks. Full vendor support (usage, conversations) needs a vendor module modelled on a real install, as Gemini CLI and pi now have.
Build a Host with agenttest.NewHost and point it at a copy of a fixture home, with fake keychain, process table and exec runner, and an httptest server for network calls. agenttest.Conformance checks any agentio.Agent against the library's invariants.
Contributions are welcome. Read CONTRIBUTING.md for the multi-module setup, task check, and the rules every change keeps (real vendor shapes only, type-safe APIs, no hidden state, fixtures instead of real accounts).
Report vulnerabilities privately as described in SECURITY.md, which also lists the library's guarantees for tokens and other secrets.