io is a collection of agent and world-building primitives written in Go.
agent: conversation loop, with streaming, batching, and cancellationtool: tools, schemas, middleware, and concurrencytoolbox: implementation of standard tools, plus extrassession: session saving and resumption, as an append-only journalwire/anthropic/messages: the Anthropic Messages protocolwire/openai/chatcompletions: the OpenAI-compatible Chat Completions protocolwire/openai/responses: the OpenAI Responses protocolprovider/anthropic: the Claude subscription serviceprovider/codex: the ChatGPT Codex serviceprovider/opencodego: the OpenCode Go serviceprovider/ollama: local and network Ollama servers- ... and more, soon
go get crdx.org/ioImplementors glue the auth flow together themselves. The oh implementation handles it with -L:
go run ./cmd/oh -L codex
go run ./cmd/oh -L opencode-go
go run ./cmd/oh -L anthropicOmit the provider to choose interactively.
typeWeatherParamsstruct {
Citystring
}
weather:=tool.Implement(
tool.Definition{
Name: "weather",
Description: "report weather in a city",
Schema: tool.Schema{tool.String("city", "the city to look up")},
},
func(argsWeatherParams) (string, string) { returnargs.City, "" },
).Plain(func(_ context.Context, _WeatherParams) (string, error) {
return"Take a guess.", nil
})
client, _:=codex.Auth("gpt-5.6-sol", "high")
assistant:=agent.New("You are a helpful weatherperson", client, []tool.Tool{weather})
answer, _:=assistant.Send(ctx, "what is the weather in London?")
fmt.Println(answer) // => "It's probably raining."Send blocks until the turn is over. Cancelling the context ends the request the turn is waiting on.
Stream is the same turn as transient prose deltas and completed events. Deltas are suitable for live rendering; only events belong in durable history.
forupdate, err:=rangeassistant.Stream(ctx, "what is the weather in London?", nil) {
iferr!=nil {
returnerr
}
switch {
caseupdate.Delta!=nil:
ifupdate.Delta.Kind==agent.ModelMessageEvent {
fmt.Print(update.Delta.Text)
}
caseupdate.Event!=nil&&update.Event.Kind==agent.ToolCallRequestEvent:
fmt.Println("call", update.Event.Name, update.Event.Arguments)
caseupdate.Event!=nil&&update.Event.Kind==agent.ToolCallResultEvent:
fmt.Println("result", update.Event.Text)
}
}Send collects the completed ModelMessageEvent blocks and returns their text.
A coding harness.
go run ./cmd/ohStand in for every provider endpoint at once, playing scenarios defined in TOML files.
go run ./cmd/simulate --scenario internal/sim/scenarios/success.tomlThe simulator deals in wire formats, not providers. A provider speaks one of them, and more than one provider can speak the same one.
| Wire format | Served at | Spoken by |
|---|---|---|
| Responses | /v1/codex/responses | codex |
| Chat Completions | /v1/chat/completions | opencode-go, ollama |
| Messages | /v1/messages | anthropic |
| Command | Shows |
|---|---|
cmd/weather | A tool that answers questions about the weather |
cmd/streaming | A prompt loop printing each event as it arrives |
cmd/simple | The same loop, with text fragments glued together |
cmd/ohctl | Control of stored sessions, transcripts, formats |
Open an issue or send a pull request.