An agent SDK
you can read
Ordinary Go packages and explicit types, with no framework to learn. Start at the top with one call, and move down a layer when you need to.
go get github.com/sonnes/pi-go/pkg/piprompt := ai.Prompt{ Messages: []ai.Message{ ai.UserMessage("Say hello in one sentence."), },}
msg, err := pi.GenerateText(ctx, spec, prompt)
fmt.Println(msg.JoinText(""))$ go run ./first_call.go
Hello! How can I help you today?pkg/pi
Start with one durable agent
pi.DurableAgent is the top of the stack. One call gives you an agent with tools and a transcript on disk.
pi-go reads the credentials from your environment. The spec string names the provider and the model. Next week, the same session ID resumes the same conversation.
How pi-go wires a providerchat, err := pi.DurableAgent(ctx, "anthropic-messages/claude-opus-4-7", durable.WithStore(store), durable.WithSessionID("ticket-8472"), pi.WithTools(lookupOrder),)defer chat.Close()
msgs, err := chat.Run(ctx, durable.Text("Where is order 1234?"),).Wait()pkg/durable
A session is an append-only tree
Each entry is written once and never changed, so the state of a session is a replay of its log. A branch moves the leaf to an earlier entry and the next run grows a sibling path beside the first. Both paths stay, and one JSONL file holds them.
Sessions, branches, compaction- entryrolecontentnote
- e1userMy name is Ravi.
- e2assistantNoted.
- e3userWhat is my name?
- e4assistantRavi.leaf
- e3′userCall me R.Branch(e2)
- e4′assistantGot it, R.leaf
pkg/agent · pkg/ai
Tools are functions, runs are iterators
ai.DefineTool takes a Go function and derives the JSON schema from its input type, so the schema and the handler cannot differ.
A run returns a Go iterator of typed events. The cancel function of the context stops it — there is no second stop API and no goroutine to signal.
Retries, rate limits, sub-agents, and planning are functions you compose.
Inside the run loopweather := ai.DefineTool( "get_weather", "Get the current weather for a city", func(ctx context.Context, in WeatherIn) (WeatherOut, error) { return WeatherOut{Temp: "22°C"}, nil },)
a, err := pi.Agent(spec, pi.WithTools(weather))
for e, err := range a.Run(ctx, msg).Events() { switch e.Type { case agent.EventMessageUpdate: io.WriteString(w, e.AssistantEvent.Delta) }}pkg/ai/provider
The provider contract is one method
ai.TextProvider has one method, and every provider in the repository is that interface and nothing more. Four talk to an HTTP API; four drive an installed CLI as a subprocess. Each is a separate Go module, so you can import Anthropic without the Google SDK.
| provider | transport | auth | it drives | go module |
|---|---|---|---|---|
| anthropic-messages | http | key · oauth | the Messages API | provider/anthropic |
| openai-completions | http | key · oauth | the Completions API | provider/openai |
| openai-responses | http | key · oauth | the Responses API | provider/openairesponses |
| google-generative | http | key | the Gemini API | provider/google |
| claude-cli | subprocess | cli | the Claude Code CLI | provider/claudecli |
| codex-cli | subprocess | cli | the Codex CLI | provider/codexcli |
| cursor-cli | subprocess | cli | the Cursor CLI | provider/cursorcli |
| antigravity-cli | subprocess | cli | the Antigravity CLI | provider/antigravitycli |
cmd/pi
Run it four ways
The same core runs as a library in your service, as a chat on your terminal, around an installed coding CLI, and inside a browser.
Driving an installed CLIlibrary
Import the packages into your own service. This is the main way.
pkg/pi
cli
The pi command logs in, picks a model, and chats over the same core.
pi login anthropic
subprocess
A spec prefix routes a run to an installed coding CLI.
pi -m codex/gpt-5
wasm
The core packages build for the browser and run the same loop.
GOOS=js GOARCH=wasm
the documentation
Every layer has a page of its ownStart at the top, or go straight to the one you need.
- Overviewthe stack in one page
- The layersone page for each
- How totask-first guides
- Capabilitiesfeature by feature