Chio/Docs
LOGIN · JOIN

ReferenceSDKs

Go SDK

Run agents on a computer from Go, call governed tools, verify signed records locally, or put an HTTP service behind kernel policy.

Choose your integration

Go applications use Chio without embedding Rust. The Computer API runs agents under grants from your kernel; the hosted client carries tool requests to a kernel edge; the HTTP middleware brings existing services under kernel policy. Local verification helpers let another service inspect the resulting records.

Your taskUsePrepare
chio-go
Go 1.23.0 or later and an operator identity from chio key init.
chio-go/client
Go 1.23.0 or later, a hosted Chio base URL, and a bearer token accepted by that deployment.
chio-go/invariants
Receipt JSON and independently configured trusted signer keys. No network connection is required.
chio-go-http
Go 1.21 or later and a running Chio sidecar configured for the service’s routes.

Both modules use Go’s HTTP stack and need no cgo. The hosted client uses MCP over HTTP; the middleware uses the sidecar’s HTTP evaluation API.

Installation

bash
go get github.com/backbay-labs/chio/sdks/go/chio-go

The client module carries the Computer API at its root and the hosted client, session, and invariants packages beneath it. The middleware is a separate module. Each declares its own minimum Go version:

Module pathImport asGo directive
github.com/backbay-labs/chio/sdks/go/chio-go
chio, plus client, session, invariants
go 1.23.0
github.com/backbay-labs/chio/sdks/go/chio-go-http
chio (middleware)
go 1.21

Computer

Computer is the API the Quickstart uses. A computer is a versioned environment where authority, resources, and work live. You open one, fork it, grant an agent part of the fork, run work, read the signed receipts, and apply the changes you keep. Your kernel enforces every grant and signs every decision.

Every call takes a context.Context first and returns an error last. Event streams are Go 1.23 iterators of type iter.Seq2[Event, error], so a for range loop reads them. Options are plain structs; durations are time.Duration. The fragments below run inside a function that returns error.

This program is Quickstart part 1 in one file. It runs inside the computer that chio new quickstart creates.

main.gogo
package main

import (
	"context"
	"fmt"
	"log"
	"time"

	chio "github.com/backbay-labs/chio/sdks/go/chio-go"
)

func main() {
	if err := quickstart(context.Background()); err != nil {
		log.Fatal(err)
	}
}

func quickstart(ctx context.Context) error {
	project, err := chio.Open(ctx, "quickstart")
	if err != nil {
		return err
	}
	fixer, err := project.Agent(ctx, "fixer")
	if err != nil {
		return err
	}
	fork, err := project.Fork(ctx)
	if err != nil {
		return err
	}
	authority, err := fork.Grant(ctx, chio.Grant{
		To:        fixer,
		Resources: chio.Resources{"/workspace": {"read", "write"}, "/models/default": {"invoke"}},
		Budget:    chio.USD(2),
		ExpiresIn: 20 * time.Minute,
	})
	if err != nil {
		return err
	}

	run, err := fixer.Run(ctx, "fix issue #412", chio.RunOptions{On: fork, Authority: authority})
	if err != nil {
		return err
	}
	for event, err := range run.Events(ctx) {
		if err != nil {
			return err
		}
		fmt.Println(event.Summary)
	}

	refusals, err := project.History().Receipts(ctx, chio.ReceiptFilter{Outcome: chio.Deny})
	if err != nil {
		return err
	}
	fmt.Println(refusals[0].Describe())
	return nil
}
Output
00:00  admitted   fixer runs "fix issue #412" on fork 51c07ae
00:03  read       /workspace/src/pricing.py
00:05  read       /workspace/tests/test_pricing.py
00:09  refused    read /secrets/.env: outside the grant
00:12  invoke     /models/default, USD 0.21
00:41  write      /workspace/src/pricing.py
00:56  test       pytest -k issue_412: passed
spent  USD 0.48 of 2.00

receipt    rcpt-01a8…9c04
request    fixer read /secrets/.env
verdict    deny: outside the grant
grant      cap-01a8…31f2
policy     sha256 3c7b…8010
signed     your kernel 4f1c…a2e9, signature verifies

The examples below continue from it: project is the quickstart computer, fixer is its agent, and fork and authority are the fork and grant above. The service examples use the pricing service from Quickstart part 2, and the partner examples use the partner card, treaty, and reviews mailbox from part 3.

chio.Open

func Open(ctx context.Context, name string) (*Computer, error)

Opens a computer your operator identity owns and returns its handle. The handle reads the computer's current revision; nothing is copied.

go
project, err := chio.Open(ctx, "quickstart")
if err != nil {
	return err
}
fmt.Println(project.Describe())
Output
computer   quickstart
owner      did:chio:z6Mkf3…Qp9 (you)
revision   main @ 7c2e1d0
resources  /workspace         git, 14 files
           /models/default    model route, metered in USD
           /secrets           3 files

chio.Connect

func Connect(ctx context.Context, name string) (*Partner, error)

Resolves a paired partner by the name you gave its card, and authenticates its kernel before returning.

go
partner, err := chio.Connect(ctx, "partner")
if err != nil {
	return err
}
fmt.Println(partner.Describe())
Output
partner    partner, did:chio:z6MkpA7…c2T
operator   Halden, halden.example
kernel     9b07…d31c, pinned from its card
transport  iroh, authenticated at handshake

chio.Partners

func Partners(ctx context.Context) ([]*Partner, error)

Lists every partner you have paired with, each with its pinned kernel key and the treaty in force.

go
partners, err := chio.Partners(ctx)
if err != nil {
	return err
}
for _, partner := range partners {
	fmt.Println(partner.Summary)
}
Output
partner    did:chio:z6MkpA7…c2T   Halden   kernel 9b07…d31c   treaty trt-01aa…4c19

Computer.Agent

func (c *Computer) Agent(ctx context.Context, name string) (*Agent, error)

Returns the agent with this name, issuing it an identity from your kernel the first time. An agent holds no authority until you grant it some.

go
fixer, err := project.Agent(ctx, "fixer")
if err != nil {
	return err
}
fmt.Println(fixer.Describe())
Output
agent      fixer, did:chio:z6MkrT2…8wd
issued by  your kernel 4f1c…a2e9
grants     none

Computer.Agents

func (c *Computer) Agents(ctx context.Context, role string, opts AgentsOptions) ([]*Agent, error)

Starts several agents of one role, numbered from 1, optionally reading a shared mailbox.

go
reviewers, err := project.Agents(ctx, "review", chio.AgentsOptions{Count: 2, Reads: reviews})
if err != nil {
	return err
}
for _, reviewer := range reviewers {
	fmt.Println(reviewer.Summary)
}
Output
reviewer-1   did:chio:z6MkvR1…3mc   reads reviews
reviewer-2   did:chio:z6MkvR2…9tf   reads reviews

Computer.Fork

func (c *Computer) Fork(ctx context.Context, opts ...ForkOption) (*Fork, error)

Branches the computer's managed state at its current revision. Work on the fork stays there until you apply it. A fork does not reset authority, history, or spending.

go
fork, err := project.Fork(ctx)
if err != nil {
	return err
}
fmt.Println(fork.Describe())
Output
fork       51c07ae of quickstart, base main 7c2e1d0
writes     stay in the fork until you apply them

Fork.Grant

func (f *Fork) Grant(ctx context.Context, g Grant) (*Authority, error)

Issues a capability token over part of the fork: resources and their rights, a budget charged at your door, and an expiry. Grant.To takes an agent, a partner, or a DID; Delegation lets the holder delegate narrower grants onward, and Under issues a grant within a treaty. Issuance completes durably before the call returns.

go
authority, err := fork.Grant(ctx, chio.Grant{
	To:        fixer,
	Resources: chio.Resources{"/workspace": {"read", "write"}, "/models/default": {"invoke"}},
	Budget:    chio.USD(2),
	ExpiresIn: 20 * time.Minute,
})
if err != nil {
	return err
}
fmt.Println(authority.Describe())
Output
grant      cap-01a8…31f2, signed by your kernel
to         fixer, did:chio:z6MkrT2…8wd
on         quickstart, fork 51c07ae
allows     /workspace         read, write
           /models/default    invoke
budget     USD 2.00, charged at your door
expires    in 20 minutes

Authority.Reissue

func (a *Authority) Reissue(ctx context.Context, on *Fork) (*Authority, error)

Issues a grant with the same terms on another fork. The original grant is unchanged.

go
next, err := project.Fork(ctx)
if err != nil {
	return err
}
again, err := authority.Reissue(ctx, next)
if err != nil {
	return err
}
fmt.Println(again.Describe())
Output
grant      cap-01a8…5b3e, signed by your kernel
to         fixer, did:chio:z6MkrT2…8wd
on         quickstart, fork 7d2e914
allows     /workspace         read, write
           /models/default    invoke
budget     USD 2.00, charged at your door
expires    in 20 minutes

Agent.Run

func (a *Agent) Run(ctx context.Context, task string, opts RunOptions) (*Run, error)

Sends a task to an agent under a grant. Every operation the agent attempts reaches your kernel before it reaches a resource.

go
run, err := fixer.Run(ctx, "fix issue #412", chio.RunOptions{On: fork, Authority: authority})
if err != nil {
	return err
}
fmt.Println(run.Describe())
Output
run        run-01a8…e7c0
agent      fixer, on fork 51c07ae
authority  cap-01a8…31f2

Run.Events

func (r *Run) Events(ctx context.Context, opts ...EventsOption) iter.Seq2[RunEvent, error]

Streams the run's events in order: admissions, reads, writes, model calls, tests, and refusals. A refusal is an event with a receipt, not an exception, and the run continues.

go
for event, err := range run.Events(ctx) {
	if err != nil {
		return err
	}
	fmt.Println(event.Summary)
}
Output
00:00  admitted   fixer runs "fix issue #412" on fork 51c07ae
00:03  read       /workspace/src/pricing.py
00:05  read       /workspace/tests/test_pricing.py
00:09  refused    read /secrets/.env: outside the grant
00:12  invoke     /models/default, USD 0.21
00:41  write      /workspace/src/pricing.py
00:56  test       pytest -k issue_412: passed
spent  USD 0.48 of 2.00

Partner.Exec

func (p *Partner) Exec(ctx context.Context, task string, opts ExecOptions) (*Run, error)

Sends a task to a partner's agents under a grant issued within a treaty. The partner's kernel admits the work and runs it in its own process tree. Run.JoinTree waits for every agent the partner delegated to.

go
run, err := partner.Exec(ctx, "fix issue #414", chio.ExecOptions{On: fork, Authority: authority, ReportTo: reviews})
if err != nil {
	return err
}
for event, err := range run.Events(ctx) {
	if err != nil {
		return err
	}
	fmt.Println(event.Summary)
}
return run.JoinTree(ctx)
Output
00:00  them  admitted   "fix issue #414" under trt-01aa…4c19
00:01  them  delegate   coordinator to fixer-1, fixer-2, tester, each narrower
00:31  them  refused    fixer-2 read /secrets/.env: outside the grant, at your door
01:47  them  sealed     fork c4e81b7 at 5b93d10
spent  USD 3.12 of 5.00 across 4 of their agents

Fork.Diff

func (f *Fork) Diff(ctx context.Context) (string, error)

Returns the changes the fork holds against its base, as a unified diff.

go
diff, err := fork.Diff(ctx)
if err != nil {
	return err
}
fmt.Println(diff)
Output
src/pricing.py
@@ def discount(price, percent):
+    if not 0 <= percent <= 100:
+        raise ValueError("percent must be between 0 and 100")
     return price * (1 - percent / 100)

Computer.Apply

func (c *Computer) Apply(ctx context.Context, changes *ChangeSet, opts ...ApplyOption) (*Applied, error)

Publishes the fork's exact changes if main still matches the fork's base, and returns the signed merge. Under a treaty, both kernels co-sign it.

go
merged, err := project.Apply(ctx, fork.Changes())
if err != nil {
	return err
}
fmt.Println(merged.Describe())
Output
applied    fork 51c07ae onto quickstart main
revision   main 8d4b2e7, base 7c2e1d0
receipt    rcpt-01a8…d51e, signed by your kernel

History.Receipts

func (h *History) Receipts(ctx context.Context, filter ReceiptFilter) ([]*Receipt, error)

Reads signed receipts from the computer's history, filtered by outcome, agent, or time.

go
refusals, err := project.History().Receipts(ctx, chio.ReceiptFilter{Outcome: chio.Deny})
if err != nil {
	return err
}
fmt.Println(refusals[0].Describe())
Output
receipt    rcpt-01a8…9c04
request    fixer read /secrets/.env
verdict    deny: outside the grant
grant      cap-01a8…31f2
policy     sha256 3c7b…8010
signed     your kernel 4f1c…a2e9, signature verifies

History.Export

func (h *History) Export(ctx context.Context, dir string) (*Evidence, error)

Writes the computer's history to a directory as an evidence package. Verification checks every signature against pinned kernel keys, offline.

go
evidence, err := project.History().Export(ctx, "audit/")
if err != nil {
	return err
}
verification, err := evidence.Verify(ctx)
if err != nil {
	return err
}
fmt.Println(verification)
Output
receipts   9 signed decisions from your kernel
refusals   1: fixer read /secrets/.env
spend      USD 0.48 of 2.00
verified   offline, against your kernel key 4f1c…a2e9

Policy.Add

func (p *Policy) Add(ctx context.Context, rule Rule) (*RuleRef, error)

Adds a named rule to the computer's policy. Policy narrows every grant on the computer, and no grant widens policy.

go
rule, err := project.Policy().Add(ctx, chio.Rule{
	Name: "tests-are-evidence",
	Deny: chio.Match{Write: "/workspace/tests/**"},
})
if err != nil {
	return err
}
fmt.Println(rule.Describe())
Output
rule       tests-are-evidence
denies     write /workspace/tests/**
policy     sha256 9a41…e6f2, narrows every grant on quickstart

Computer.Service

func (c *Computer) Service(ctx context.Context, name string, opts ServiceOptions) (*Service, error)

Exposes project functions as named tools and publishes them over MCP and HTTP. Every caller presents a grant from your kernel.

go
pricing, err := project.Service(ctx, "pricing", chio.ServiceOptions{
	Tools: map[string]string{"quote": "src/pricing.py:quote", "refund": "src/pricing.py:refund"},
})
if err != nil {
	return err
}
endpoint, err := pricing.Publish(ctx, chio.MCP, chio.HTTP)
if err != nil {
	return err
}
fmt.Println(endpoint.Describe())
Output
service    pricing, from quickstart @ main 2f60a91
tools      quote · refund
mcp        https://127.0.0.1:9440/pricing/mcp
http       https://127.0.0.1:9440/pricing/v1, OpenAPI at /openapi.json
admits     callers that hold a grant from your kernel

Service.Grant

func (s *Service) Grant(ctx context.Context, g ServiceGrant) (*Authority, error)

Grants a caller named tools on the service, with a call rate, a budget spent at each tool's price, and an expiry.

go
access, err := pricing.Grant(ctx, chio.ServiceGrant{
	To:        "did:chio:z6MkbY4…e1R",
	Tools:     []string{"quote"},
	Limit:     chio.Rate(100, time.Hour),
	Budget:    chio.USD(2),
	ExpiresIn: 24 * time.Hour,
})
if err != nil {
	return err
}
fmt.Println(access.Describe())
Output
grant      cap-01ab…77d1, signed by your kernel
to         did:chio:z6MkbY4…e1R
allows     pricing.quote
limits     100 calls per hour
budget     USD 2.00, USD 0.01 per call
expires    in 24 hours

Service.Require

func (s *Service) Require(ctx context.Context, a Approval) (*RuleRef, error)

Pauses each matching call until the approver signs for that exact request.

go
rule, err := pricing.Require(ctx, chio.Approval{By: chio.You, When: "quote.percent > 50"})
if err != nil {
	return err
}
fmt.Println(rule.Describe())
Output
rule       quote.percent > 50 waits for did:chio:z6Mkf3…Qp9 (you)
binds      one signature to one request

Service.Events

func (s *Service) Events(ctx context.Context) iter.Seq2[CallEvent, error]

Streams calls to the service as your kernel decides them.

go
for call, err := range pricing.Events(ctx) {
	if err != nil {
		return err
	}
	fmt.Println(call.Summary)
}
Output
00:00  z6MkbY4  quote   sku-118 at 10%        allow     USD 0.01
00:04  z6MkbY4  refund  order-77              refused   not in the grant
00:05  z6MkQx8  quote   sku-118 at 10%        refused   no grant from your kernel
00:09  z6MkbY4  quote   sku-301 at 80%        pending   approval required
00:41  z6MkbY4  quote   sku-301 at 80%        allow     USD 0.01

Service.Usage

func (s *Service) Usage(ctx context.Context, since time.Duration) (*Usage, error)

Summarizes each caller's calls, refusals, approvals, and spend, computed from that caller's signed receipts.

go
usage, err := pricing.Usage(ctx, time.Hour)
if err != nil {
	return err
}
fmt.Println(usage)
Output
caller             calls   refused   approvals   spent
z6MkbY4…e1R        100     2         1           USD 1.00
z6MkQx8…7Ha        0       1         0           USD 0.00

Computer.Treaty

func (c *Computer) Treaty(ctx context.Context, terms TreatyTerms) (*Treaty, error)

Agrees terms with a partner: the actions you cooperate on and what each requires. Both kernels sign, and each computes the intersection of the two governance ladders, so the stricter requirement wins.

go
treaty, err := project.Treaty(ctx, chio.TreatyTerms{
	Partner:   partner,
	Actions:   []string{"workspace.read", "workspace.write", "model.invoke", "merge"},
	ExpiresIn: 7 * 24 * time.Hour,
})
if err != nil {
	return err
}
fmt.Println(treaty.Describe())
Output
treaty           trt-01aa…4c19, signed by both kernels
participants     you · partner (did:chio:z6MkpA7…c2T)
action           you       them      admitted as
workspace.read   allow     allow     either kernel admits under its own policy
workspace.write  allow     allow     either kernel admits under its own policy
model.invoke     allow     allow     charged at your door
merge            co-sign   co-sign   both kernels sign
expires          in 7 days

Treaty.Revoke

func (t *Treaty) Revoke(ctx context.Context) error

Ends the treaty and every grant issued under it, including grants the partner's agents delegated to each other. The revocation reaches the partner's kernel over iroh, and its next call is refused.

go
if err := treaty.Revoke(ctx); err != nil {
	return err
}
for event, err := range run2.Events(ctx) {
	if err != nil {
		return err
	}
	fmt.Println(event.Summary)
}
Output
00:00  them  admitted   "add banker's rounding as an option" under trt-01aa…4c19
00:04  you   revoked    trt-01aa…4c19, revocation root sent over iroh
00:05  them  refused    fixer-1 write /workspace/src/pricing.py: treaty revoked
00:05  them  stopped    their work closed, no effects after revocation

Computer.Mailbox

func (c *Computer) Mailbox(ctx context.Context, name string, partner *Partner, treaty *Treaty) (*Mailbox, error)

Opens a mailbox shared with a partner under a treaty. Each message is sent and claimed under its sender's or reader's own grant.

go
reviews, err := project.Mailbox(ctx, "reviews", partner, treaty)
if err != nil {
	return err
}
fmt.Println(reviews.Describe())
Output
mailbox    reviews, under trt-01aa…4c19
senders    partner's agents, each under its own grant
readers    your agents that hold a reads grant

chio.FirstPassing

func FirstPassing(ctx context.Context, runs []*Run, check string) (*Run, error)

Waits on several runs and returns the first whose sealed fork passes the check. The other runs are cancelled, and their receipts stay in history.

go
winner, err := chio.FirstPassing(ctx, []*chio.Run{ours, theirs}, "pytest")
if err != nil {
	return err
}
fmt.Println(winner.Describe())
_, err = project.Apply(ctx, winner.Changes())
return err
Output
winner     run-01ab…0c7d, partner on fork c4e81b7
check      pytest: 14 passed
others     cancelled, receipts kept in history

chio.Parallel and Program.Pipe

func Parallel(programs ...Program) Program; func (p Program) Pipe(next Program) Program

Composes boot programs, the pinned descriptors your project exports. Parallel runs programs at once; Pipe runs them in sequence. Go has no operator overloading, so this is the canonical form. A fork started with a boot program runs it under the fork's grant.

go
boot := chio.Parallel(programs.Reproduce, programs.Survey).Pipe(programs.Patch).Pipe(programs.Verify)
fork, err := project.Fork(ctx, chio.Boot(boot))
if err != nil {
	return err
}
fmt.Println(fork.Describe())
Output
boot       (reproduce & survey) | patch | verify
fork       a3d90f1 of quickstart, base main 2f60a91

chio.USD

func USD(dollars int64) Money; func Cents(cents int64) Money

Constructs an exact dollar amount for budgets and prices. Money stores integer cents, so arithmetic is exact.

go
fmt.Println(chio.USD(2))
fmt.Println(chio.Cents(1).Times(100))
Output
USD 2.00
USD 1.00

chio.ApplyConflictError

type ApplyConflictError struct { Base, Current string }

Apply refuses to publish when main moved after the fork was taken. Nothing changes; fork again from the new main and reapply.

go
_, err := project.Apply(ctx, fork.Changes())
var conflict *chio.ApplyConflictError
if errors.As(err, &conflict) {
	fmt.Println(conflict)
	return nil
}
return err
Output
main moved from 7c2e1d0 to 9e03a4b; fork 51c07ae was not applied

How the Computer reaches the kernel

Each grant is a chio.capability.v1 token signed by your kernel. Each operation in a run is a mediated call, and each event carries the id of a chio.receipt.v1 receipt in the store that history reads. Policy rules compile into the HushSpec policy every grant on the computer is checked against. The Computer and the Kernel maps each Computer object onto its kernel mechanism. The low-level clients below speak to the same kernel and the same records without the Computer layer.

Low-level session client

Beneath the Computer API, the module exports the protocol-level packages: client and session for hosted MCP sessions, transport for raw JSON-RPC, and invariants for offline verification. Use them to call a kernel edge you reach over the network, such as the pricing service another operator publishes.

client.Client

Construct client.Client with the hosted base URL, without a trailing /mcp. The transport appends that path. Initialize negotiates the protocol, records the session ID, and sends the initialized notification.

client/client.go · signaturesgo
func WithStaticBearer(baseURL, authToken string, httpClient *http.Client) *Client
func (client *Client) Initialize(ctx context.Context, options InitializeOptions) (*session.Session, error)

This is an outside buyer's agent opening a session on the pricing service from Quickstart part 2. CHIO_TOKEN holds the token for the grant you issued to the buyer. The listing names only the tools the grant covers: quote, not refund.

main.gogo
package main

import (
    "context"
    "fmt"
    "log"
    "net/http"
    "os"
    "time"

    "github.com/backbay-labs/chio/sdks/go/chio-go/client"
)

func main() {
    if err := run(); err != nil {
        log.Fatal(err)
    }
}

func run() error {
    token := os.Getenv("CHIO_TOKEN")
    if token == "" {
        return fmt.Errorf("set CHIO_TOKEN to the token for your grant")
    }
    c := client.WithStaticBearer("https://127.0.0.1:9440/pricing", token, http.DefaultClient)
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()

    sess, err := c.Initialize(ctx, client.InitializeOptions{})
    if err != nil {
        return err
    }
    defer func() {
        closeCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
        defer cancel()
        if _, err := sess.Close(closeCtx); err != nil {
            log.Printf("close session: %v", err)
        }
    }()

    tools, err := sess.ListTools(ctx)
    if err != nil {
        return err
    }
    fmt.Printf("tools: %v\n", tools)
    return nil
}
bash
go mod tidy
go run .

client.WithStaticBearer (client/client.go:43) wraps client.New (client.go:35), which takes an explicit auth.StaticBearer value. Initialize (client.go:47) runs the MCP handshake and returns a *session.Session.

go
import (
    "github.com/backbay-labs/chio/sdks/go/chio-go/auth"
    "github.com/backbay-labs/chio/sdks/go/chio-go/client"
)

c := client.New(
    "https://pricing.larkfield.example",
    auth.StaticBearerToken(os.Getenv("CHIO_TOKEN")),
    http.DefaultClient,
)

session.Session

session.Session carries the Streamable HTTP MCP methods. Each network method takes a context.Context, so deadlines and cancellation reach the transport.

The buyer's grant permits quote. A transport-level success can still contain an MCP tool error, so inspect isError; a refused call, such as refund, returns an error that carries the refusal and its receipt id.

Using an initialized sessiongo
tools, err := sess.ListTools(ctx)
if err != nil {
    return err
}
log.Printf("tools: %v", tools)

result, err := sess.CallTool(ctx, "quote", map[string]any{
    "sku":     "sku-118",
    "percent": 10,
})
if err != nil {
    return err
}
if isError, _ := result["isError"].(bool); isError {
    return fmt.Errorf("tool rejected the call: %v", result["content"])
}
log.Printf("result: %v", result)

The full method set, from session/session.go. The envelope methods return (RPCExchange, error), an alias for the transport type at transport/http.go:11. Every named MCP method returns (map[string]any, error), the result member of the terminal response.

MethodBehavior
SetMessageHandler(MessageHandler)
Installs the callback the session runs for a server-initiated message. Returns nothing.
SendEnvelope
Posts a caller-built JSON-RPC envelope through transport.PostRPC.
Request
Builds an envelope with the next request id and posts it.
RequestResult
Sends a request and returns the result object of the terminal response. Errors when the terminal response carries no object result.
Notification
Posts an envelope with a method and no id.
ListTools
Calls tools/list.
CallTool(ctx, name, arguments)
Calls tools/call.
ListResources
Calls resources/list.
ReadResource(ctx, uri)
Calls resources/read.
SubscribeResource(ctx, uri)
Calls resources/subscribe.
UnsubscribeResource(ctx, uri)
Calls resources/unsubscribe.
ListResourceTemplates
Calls resources/templates/list.
ListPrompts
Calls prompts/list.
GetPrompt(ctx, name, arguments)
Calls prompts/get.
Complete(ctx, params)
Calls completion/complete with the caller's params map.
SetLogLevel(ctx, level)
Calls logging/setLevel.
ListTasks
Calls tasks/list.
GetTask(ctx, taskID)
Calls tasks/get.
GetTaskResult(ctx, taskID)
Calls tasks/result.
CancelTask(ctx, taskID)
Calls tasks/cancel.
Close(ctx)
Deletes the session and returns a DeleteSessionResult.

transport.PostRPC

The transport package drives the HTTP exchange under the session. Call it directly to inspect HTTP status, response headers, and every JSON-RPC message in the exchange. It does not retry automatically. PostRPC (transport/http.go:45) and DeleteSession (:71) each take a context.Context and an *http.Client.

go
import (
    "net/http"

    "github.com/backbay-labs/chio/sdks/go/chio-go/transport"
)

exchange, err := transport.PostRPC(
    ctx,
    http.DefaultClient,
    "https://edge.example.com",
    authToken,
    sessionID,
    protocolVersion,
    map[string]any{
        "jsonrpc": "2.0",
        "id":      1,
        "method":  "tools/list",
        "params":  map[string]any{},
    },
    nil,
)
if err != nil {
    return err
}
for _, msg := range exchange.Messages {
    log.Printf("received: %v", msg["method"])
}

_, err = transport.DeleteSession(ctx, http.DefaultClient, "https://edge.example.com", authToken, sessionID)

Authentication

The auth package ships a static-bearer value plus helpers for the OAuth 2.1 authorization-code flow with PKCE. Feed the resulting token back into client.New or client.WithStaticBearer.

  • Static bearer. auth.StaticBearerToken(token) (auth/auth.go:7) returns an auth.StaticBearer struct carrying the token.
  • OAuth helpers. From auth/oauth.go: GetJSON at line 37, PKCEChallenge at 59, AuthorizationServerMetadataURL at 64, DiscoverOAuthMetadata at 78, PerformAuthorizationCodeFlow at 122, ExchangeAccessToken at 279, and ResolveOAuthAccessToken at 329.
go
import (
    "net/http"

    "github.com/backbay-labs/chio/sdks/go/chio-go/auth"
    "github.com/backbay-labs/chio/sdks/go/chio-go/client"
)

// Use a bearer token accepted by the deployment.
c := client.WithStaticBearer(
    "https://edge.example.com",
    "dev-token",
    http.DefaultClient,
)

// Or run OAuth authorization, then pass the resulting token to the client.
metadata, err := auth.DiscoverOAuthMetadata(ctx, http.DefaultClient, "https://edge.example.com", nil)
if err != nil {
    return err
}
accessToken, err := auth.PerformAuthorizationCodeFlow(
    ctx, http.DefaultClient,
    "https://edge.example.com", "chio.read chio.invoke",
    metadata.AuthorizationServerMetadata, nil,
    &auth.AuthorizationCodeFlowOptions{
        ClientID:    "chio-cli",
        RedirectURI: "http://127.0.0.1:53682/callback",
    },
)
if err != nil {
    return err
}

c2 := client.WithStaticBearer(
    "https://edge.example.com",
    accessToken,
    http.DefaultClient,
)
_ = c2

invariants package

github.com/backbay-labs/chio/sdks/go/chio-go/invariants implements canonical JSON, SHA-256, Ed25519 signing and verification, receipt and capability parsing and verification, and signed manifest verification. It holds no network code.

receipt.go · verification helpergo
package receipts

import (
    "fmt"

    "github.com/backbay-labs/chio/sdks/go/chio-go/invariants"
)

func RequireRecordedAllow(receiptJSON string, trustedKernelKeys []string) error {
    receipt, err := invariants.ParseReceiptJSON(receiptJSON)
    if err != nil {
        return err
    }
    // Configure these keys independently of the receipt you received.
    result, err := invariants.VerifyReceiptWithTrustedSigners(receipt, trustedKernelKeys)
    if err != nil {
        return err
    }
    if !result.Authorized {
        return fmt.Errorf("receipt is not a trusted mediated allow: %+v", result)
    }
    return nil
}

VerifyReceipt checks the record without supplying trusted signers: SignerTrusted, Ok, and Authorized remain false. Use VerifyReceiptWithTrustedSigners when trust is part of the decision. An authorized receipt records a mediated allow; applying it to a new request still requires the appropriate request, policy, and freshness checks.

Canonical JSON and parsing

From invariants/json.go and invariants/errors.go.

  • CanonicalizeJSONString(input string) (string, error), json.go:13. Canonicalizes a JSON document supplied as text.
  • CanonicalizeJSON(value any) (string, error), json.go:21. Canonicalizes an already-decoded value.
  • ParseJSONText(input string) (any, error), errors.go:24. Decodes with UseNumber, so a number keeps its source text rather than becoming a float.

Hashing

SHA256HexUTF8(input string) string (invariants/hashing.go:8) is the only hashing export.

The Rust contract at crates/sdk/chio-binding-helpers/src/hashing.rs exports two functions, sha256_hex_bytes at line 2 and sha256_hex_utf8 at line 7. Go exports no SHA256HexBytes. A caller hashing arbitrary bytes converts them with string(b), which Go passes through unchanged, or calls crypto/sha256 directly. The Bindings API page lists the Rust name.

Signing and key checks

From invariants/signing.go. The three predicates accept an optional 0x prefix and uppercase hex; the decoder normalizes both.

  • IsValidEd25519PublicKeyHex(value string) bool, line 15. True for 32 bytes of hex.
  • IsValidEd25519SignatureHex(value string) bool, line 20. True for 64 bytes of hex.
  • PublicKeyHexMatches(left, right string) bool, line 25. Compares two keys after normalization.
  • SignUTF8MessageEd25519, line 29, and VerifyUTF8MessageEd25519, line 43.
  • SignJSONStringEd25519, line 55, and VerifyJSONStringSignatureEd25519, line 68. Both canonicalize before they touch the key.

Receipts

These receipt helpers support Ed25519 records without BBS material. Their signing projection omits BBS fields; use the Rust verifier for BBS-bound receipts. Validate the complete receipt schema at ingestion; parsing a JSON object alone does not establish its wire shape.

From invariants/receipt.go. The full receipt body and the signing body are different projections. The verifier also reconstructs the content-addressed ID internally.

  • ParseReceiptJSON(input string) (map[string]any, error), line 18.
  • ReceiptBodyCanonicalJSON(receipt map[string]any) (string, error), line 30. Canonicalizes every supplied field except algorithm and signature; this is not the ID preimage.
  • ReceiptSigningBodyCanonicalJSON(receipt map[string]any) (string, error), line 41. Canonicalizes the id and selected body fields the kernel signed.
  • VerifyReceipt(receipt map[string]any) (ReceiptVerification, error), line 56. Calls the next entry with a nil signer list.
  • VerifyReceiptWithTrustedSigners(receipt map[string]any, trustedSigners []string) (ReceiptVerification, error), line 60. Sets SignerTrusted from the supplied key list.
  • VerifyReceiptJSON(input string) (ReceiptVerification, error), line 154.

Capabilities

These helpers inspect signatures, the supplied time, and delegation-chain shape. Kernel admission also checks revocation, request scope, policy, and available budget; local verification alone does not grant execution.

From invariants/capability.go. VerifyCapability and VerifyCapabilityJSON pass a nil depth, so the delegation chain length goes unchecked. The MaxDelegationDepth forms pass the caller's bound, and a chain longer than it fails the check (capability.go:169).

  • ParseCapabilityJSON(input string) (map[string]any, error), line 18.
  • CapabilityBodyCanonicalJSON(capability map[string]any) (string, error), line 50.
  • CapabilitySigningBody(capability map[string]any) map[string]any, line 59. Returns the projection rather than its canonical text.
  • CapabilitySigningBodyCanonicalJSON(capability map[string]any) (string, error), line 87.
  • VerifyCapability(capability map[string]any, now int64) (CapabilityVerification, error), line 91.
  • VerifyCapabilityWithMaxDelegationDepth(capability map[string]any, now int64, maxDelegationDepth int), line 95.
  • VerifyCapabilityJSON(input string, now int64) (CapabilityVerification, error), line 145.
  • VerifyCapabilityJSONWithMaxDelegationDepth(input string, now int64, maxDelegationDepth int), line 153.

Signed manifests

From invariants/manifest.go.

  • ParseSignedManifestJSON(input string) (map[string]any, error), line 82.
  • SignedManifestBodyCanonicalJSON(signedManifest map[string]any) (string, error), line 94.
  • VerifySignedManifest(signedManifest map[string]any) (ManifestVerification, error), line 102.
  • VerifySignedManifestJSON(input string) (ManifestVerification, error), line 124.

Result types

ReceiptVerification (receipt.go:3) carries Decision, Authorized, BoundaryClass, Ok, ParameterHashValid, ReceiptIDValid, ReceiptKind, Result, SignerKeyHex, SignerTrusted, SignatureValid, and TrustLevel. The other result types are CapabilityVerification with CapabilityTimeStatus (capability.go:11 and :3), ManifestVerification (manifest.go:75), and SignedMessage (signing.go:9).

Nested callback router

The nested package routes server-initiated MCP messages to response builders. Register handlers for elicitation, sampling, or roots/list, then invoke the router from the session’s message callback. Responses travel through the same session; the router does not create an HTTP listener. nested.NewRouter (nested/router.go:23) takes a TranscriptHook and the package ships four response builders: RPCResult at line 30, SamplingTextResult at 38, ElicitationAcceptResult at 53, and RootsListResult at 61.

Session callbacks · roots/listgo
import (
    "context"

    "github.com/backbay-labs/chio/sdks/go/chio-go/client"
    "github.com/backbay-labs/chio/sdks/go/chio-go/nested"
    "github.com/backbay-labs/chio/sdks/go/chio-go/session"
)

router := nested.NewRouter(nil)
router.Register("roots/list", "roots", func(message map[string]any, _ *session.Session) map[string]any {
    return nested.RootsListResult(message, []map[string]any{
        {"uri": "file:///workspace", "name": "Workspace"},
    })
})

sess, err := c.Initialize(ctx, client.InitializeOptions{
    OnMessage: func(ctx context.Context, message map[string]any, sess *session.Session) error {
        _, err := router.Handle(ctx, message, sess, "callbacks")
        return err
    },
})

Register routes and callbacks before issuing concurrent requests. The callback owns the response content; the helper only routes and sends it. Configure any required MCP capability declarations through InitializeOptions.Capabilities.

net/http middleware (chio-go-http)

github.com/backbay-labs/chio/sdks/go/chio-go-http wraps any http.Handler, evaluates the incoming request against a Chio sidecar, attaches the receipt id to the response, and short-circuits with a structured JSON error when the verdict denies. It is a separate module with its own go.mod, declaring go 1.21.

bash
go get github.com/backbay-labs/chio/sdks/go/chio-go-http

chio.Protect takes the handler you already have and a list of functional options. Routing does not change.

main.go · protect an existing handlergo
package main

import (
    "fmt"
    "net/http"

    chio "github.com/backbay-labs/chio/sdks/go/chio-go-http"
)

func handlePets(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, `{"pets":[]}`)
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/pets", handlePets)

    protected := chio.Protect(mux, chio.WithSidecarURL("http://127.0.0.1:9090"))
    http.ListenAndServe(":8080", protected)
}

The options, from chio-go-http/config.go.

OptionPurpose
ConfigFile(path)
Stores a path in the middleware configuration. It does not read the file, start the sidecar, or configure sidecar policy; configure the sidecar separately.
WithSidecarURL(url)
Override the sidecar base URL. CHIO_SIDECAR_URL is honored when this is absent.
WithTimeout(seconds)
Sidecar HTTP timeout, default 5.
WithIdentityExtractor(f)
Custom caller extraction. The default hashes Bearer, API key, or cookie credentials and marks them unverified; it does not authenticate the caller.
WithRouteResolver(f)
Map (method, path) to a route pattern, for example /pets/{petId}.

What the middleware writes

The middleware fails closed: chio.go:101 requires both an allowed verdict and an authorized receipt before the request reaches the inner handler. It sets X-Chio-Receipt-Id only after VerifyReceipt agrees with the receipt (chio.go:134). Every other path writes an ErrorResponse (types_helpers.go:212) with the fields error, message, receipt_id, and suggestion, the last two omitted when empty.

Error codeCause
chio_access_denied
The verdict is not an allow, or the receipt is not authorized. Status comes from Verdict.HTTPStatus (types_helpers.go:103) and falls back to 403.
chio_invalid_receipt
Receipt verification failed, or the returned verification does not cover the receipt. Status 502. chio.go:85, :117, :125.
chio_sidecar_unreachable
The evaluation call itself failed for any other reason. Status 502. chio.go:94.
chio_sidecar_unavailable
The standalone sidecar client received 408, 404, 429, or 5xx from verification. Middleware maps verification errors to chio_invalid_receipt (502). sidecar.go:200-212.
chio_evaluation_failed
Middleware rejects an unsupported HTTP method (405) or unreadable body (400). The standalone sidecar client also uses this code for an otherwise unclassified verify response. chio.go:59, :75, sidecar.go:214.

The codes are constants in chio-go-http/types_helpers.go:204-209. A denied response carries the fixed suggestion provide a valid capability token in the X-Chio-Capability header or chio_capability query parameter (chio.go:110).

A worked application

examples/hello-chi puts a chi router behind the middleware and serves three routes: GET /healthz outside evaluation, GET /hello allowed, POST /echo denied without a capability.

examples/hello-chi/main.go:34-49go
func protectedHandler(sidecarURL string) http.Handler {
	return chio.Protect(
		newRouter(),
		chio.WithSidecarURL(sidecarURL),
	)
}

func newRouter() http.Handler {
	router := chi.NewRouter()

	router.Get("/healthz", healthz)
	router.Get("/hello", hello)
	router.Post("/echo", echo)

	return router
}

The example's smoke run starts a trust-control plane, a sidecar, and the application, then checks an allowed read, a denied write without a capability, and an allowed write after capability issuance. The captured read and denial are below.

hello-chi · allowtranscript
$ cat hello.headers hello.json
HTTP/1.1 200 OK
Content-Type: application/json
X-Chio-Receipt-Id: 25fa66e5413d596601b708653d86b3f3a7af20cf12c269b7d5bd2b3c60dba661
Date: Sat, 05 Sep 2026 11:44:09 GMT
Content-Length: 29

{"message":"hello from chi"}
exit 0
The configured application allows a read and returns the verified receipt’s ID. Retrieve and verify the signed receipt to assess the recorded decision; the header alone is a reference.
hello-chi · denytranscript
$ cat deny.headers deny.json
HTTP/1.1 403 Forbidden
Content-Type: application/json
Date: Sat, 05 Sep 2026 11:44:09 GMT
Content-Length: 284

{"error":"chio_access_denied","message":"side-effect route requires a capability token","receipt_id":"04c22cdfd0b4c28d85838f715d6134ac11314294682cb6367330f0ffe0bccdaa","suggestion":"provide a valid capability token in the X-Chio-Capability header or chio_capability query parameter"}
exit 0
The same application refuses a write without a capability. The error names the refusal and receipt ID, and identifies where a valid capability must be supplied.

Errors

The invariants package returns a typed *invariants.InvariantError (invariants/errors.go:8) carrying a Code and a Message. Match it with errors.As and branch on Code.

CodeRaised by
json
Input is not valid JSON, or a receipt, capability, or manifest is not a JSON object. errors.go:29, receipt.go:25, capability.go:25, manifest.go:89.
canonical_json
A value has no canonical form: an unsupported type, an invalid number, or a non-finite number. json.go:84, :101, :108.
invalid_public_key
A public key is not 32 bytes of hex. signing.go:16, :44.
invalid_signature
A signature is not 64 bytes of hex. signing.go:21, :48.
invalid_hex
A signing seed is not 32 bytes of hex. signing.go:30.
receipt
A receipt is missing parameters, or decision is not an object. receipt.go:98, :113.
receipt_id
A field the id preimage covers is absent. receipt.go:192.
go
_, err := invariants.VerifyReceiptJSON(receiptJSON)

var invErr *invariants.InvariantError
if errors.As(err, &invErr) {
    switch invErr.Code {
    case "invalid_signature":
        log.Printf("signature rejected: %s", invErr.Message)
    case "json", "canonical_json":
        log.Printf("malformed input: %s", invErr.Message)
    default:
        log.Printf("invariant %s: %s", invErr.Code, invErr.Message)
    }
}

The client, session, and transport packages return fmt.Errorf-wrapped error values with no sentinels and no typed structs. Check them with err != nil.

go
_, err := sess.CallTool(ctx, "quote", map[string]any{"sku": "sku-118", "percent": 10})
if err != nil {
    log.Printf("call failed: %v", err)
}

Conformance

invariants/vectors_test.go replays the shared binding vectors under tests/bindings/vectors/, the same fixtures used by the Rust helpers and other language bindings. They test agreement on canonical JSON, hashing, signing, and verification for those inputs.

bash
chio conformance run --peer go

Source and package boundaries

The client module is github.com/backbay-labs/chio/sdks/go/chio-go; HTTP middleware is github.com/backbay-labs/chio/sdks/go/chio-go-http. The client has no third-party module dependencies. Middleware depends on github.com/google/uuid and github.com/oapi-codegen/runtime. API details follow the Go source; the cryptographic helper contract is in crates/sdk/chio-binding-helpers.

The client module includes the root chio package, auth, client, session, transport, nested, invariants, and version. It has no DPoP helper or typed receipt-query client. Use the receipt query API directly when querying records from Go.