Chio/Docs
LOGIN · JOIN

ReferenceSDKs

JVM SDK

Run agents on a computer from Kotlin or Java, bring services under Chio policy, and carry execution evidence into streaming systems.

Choose your integration

Chio’s JVM SDK runs agents on a computer through the Computer API, and connects existing services to the kernel through its HTTP sidecar. Keep your application in Java or Kotlin while the kernel evaluates requests and signs decisions. Choose the Computer API for agent work, the core client for explicit calls, the Spring filter for servlet requests, or the Flink module for streaming integration.

Your taskStart hereAuthority boundary
Run agents on a computer
Your kernel issues each grant and signs every decision in the run.
Protect a Spring HTTP API
An allowed, verified decision is required before the selected servlet routes run.
Evaluate an HTTP request explicitly
The client verifies allow-shaped responses; your application owns whether it executes the operation.
Inspect a policy outcome
Returns advisory evidence, which does not authorize tool execution.
Integrate a streaming job
Receipt, refusal, and dead-letter records retain their distinct meanings.

Sidecar calls use blocking HTTP. For a hosted MCP session client, see SDK selection. For Android with an embedded kernel, see Portable Kernel.

Modules

Every module publishes to Maven Central under the group world.chio at version 0.1.0. Each module sets its own Java target, and the streaming module targets a higher one than the other two.

ModuleJavaWhat it holds
chio-sdk-jvm
17
The Computer API, the blocking sidecar client, canonical JSON, hashing, the receipt types, and the streaming envelope builders. Its only api dependencies are the Kotlin standard library and Jackson.
chio-spring-boot
17
The servlet filter and its auto-configuration. Carries the core as an api dependency, so the starter also exposes the core APIs.
chio-streaming-flink
21
The Flink operator. Takes the core as an api dependency and expects Flink on the job's classpath.

Install

build.gradle.ktskotlin
dependencies {
    implementation("world.chio:chio-sdk-jvm:0.1.0")
}

Maven uses the same coordinates. A Spring service depends on the starter instead, which carries the core:

build.gradle.ktskotlin
dependencies {
    implementation("world.chio:chio-spring-boot:0.1.0")
}

HTTP evaluation needs a running Chio sidecar configured for your service. SidecarPaths.DEFAULT_BASE_URL is http://127.0.0.1:9090.

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.

Calls block until your kernel has decided, which suits virtual threads and coroutines on Dispatchers.IO alike. Event streams are Sequences. Options are named arguments with defaults, and durations are kotlin.time.Duration. Java callers use the same classes; every option has a builder overload.

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

Quickstart.ktkotlin
import kotlin.time.Duration.Companion.minutes
import world.chio.sdk.Computer
import world.chio.sdk.Outcome
import world.chio.sdk.USD

fun main() {
    val project = Computer.open("quickstart")
    val fixer = project.agent("fixer")
    val fork = project.fork()
    val authority = fork.grant(
        to = fixer,
        resources = mapOf(
            "/workspace" to listOf("read", "write"),
            "/models/default" to listOf("invoke"),
        ),
        budget = USD(2),
        expiresIn = 20.minutes,
    )

    val run = fixer.run("fix issue #412", on = fork, authority = authority)
    run.events().forEach { println(it.summary) }

    val refusal = project.history.receipts(outcome = Outcome.DENY).first()
    println(refusal.describe())
}
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.

Computer.open

Computer.open(name: String): Computer

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

kotlin
import world.chio.sdk.Computer

val project = Computer.open("quickstart")
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

Computer.connect

Computer.connect(name: String): Partner

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

kotlin
val partner = Computer.connect("partner")
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

Computer.partners

Computer.partners(): List<Partner>

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

kotlin
Computer.partners().forEach { println(it.summary) }
Output
partner    did:chio:z6MkpA7…c2T   Halden   kernel 9b07…d31c   treaty trt-01aa…4c19

computer.agent

fun agent(name: String): Agent

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.

kotlin
val fixer = project.agent("fixer")
println(fixer.describe())
Output
agent      fixer, did:chio:z6MkrT2…8wd
issued by  your kernel 4f1c…a2e9
grants     none

computer.agents

fun agents(role: String, count: Int, reads: Mailbox? = null): List<Agent>

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

kotlin
val reviewers = project.agents("review", count = 2, reads = reviews)
reviewers.forEach { println(it.summary) }
Output
reviewer-1   did:chio:z6MkvR1…3mc   reads reviews
reviewer-2   did:chio:z6MkvR2…9tf   reads reviews

computer.fork

fun fork(boot: Program? = null): Fork

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.

kotlin
val fork = project.fork()
println(fork.describe())
Output
fork       51c07ae of quickstart, base main 7c2e1d0
writes     stay in the fork until you apply them

fork.grant

fun grant(to: Principal, resources: Map<String, List<String>>, budget: Money? = null, expiresIn: Duration, under: Treaty? = null, delegation: Delegation? = null): Authority

Issues a capability token over part of the fork: resources and their rights, a budget charged at your door, and an expiry. 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.

kotlin
val authority = fork.grant(
    to = fixer,
    resources = mapOf(
        "/workspace" to listOf("read", "write"),
        "/models/default" to listOf("invoke"),
    ),
    budget = USD(2),
    expiresIn = 20.minutes,
)
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

fun reissue(on: Fork): Authority

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

kotlin
val next = project.fork()
val again = authority.reissue(on = next)
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

fun run(task: String, on: Fork, authority: Authority): Run

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

kotlin
val run = fixer.run("fix issue #412", on = fork, authority = authority)
println(run.describe())
Output
run        run-01a8…e7c0
agent      fixer, on fork 51c07ae
authority  cap-01a8…31f2

run.events

fun events(include: List<Agent> = emptyList()): Sequence<RunEvent>

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.

kotlin
run.events().forEach { println(it.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

fun exec(task: String, on: Fork, authority: Authority, reportTo: Mailbox? = null): Run

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.

kotlin
val run = partner.exec("fix issue #414", on = fork, authority = authority, reportTo = reviews)
run.events().forEach { println(it.summary) }
run.joinTree()
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

fun diff(): String

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

kotlin
println(fork.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

fun apply(changes: ChangeSet, treaty: Treaty? = null): Applied

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.

kotlin
val merged = project.apply(fork.changes)
println(merged.describe())
Output
applied    fork 51c07ae onto quickstart main
revision   main 8d4b2e7, base 7c2e1d0
receipt    rcpt-01a8…d51e, signed by your kernel

computer.history.receipts

fun receipts(outcome: Outcome? = null, agent: Agent? = null, since: Duration? = null): List<Receipt>

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

kotlin
val refusal = project.history.receipts(outcome = Outcome.DENY).first()
println(refusal.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

computer.history.export

fun export(dir: Path): Evidence

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

kotlin
val evidence = project.history.export(Path("audit"))
println(evidence.verify())
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

computer.policy.add

fun add(name: String, allow: Match? = null, deny: Match? = null): Rule

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

kotlin
val rule = project.policy.add(
    name = "tests-are-evidence",
    deny = Match(write = "/workspace/tests/**"),
)
println(rule.describe())
Output
rule       tests-are-evidence
denies     write /workspace/tests/**
policy     sha256 9a41…e6f2, narrows every grant on quickstart

computer.service

fun service(name: String, tools: Map<String, String>): Service

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

kotlin
val pricing = project.service(
    "pricing",
    tools = mapOf("quote" to "src/pricing.py:quote", "refund" to "src/pricing.py:refund"),
)
val endpoint = pricing.publish(Protocol.MCP, Protocol.HTTP)
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

fun grant(to: String, tools: List<String>, limits: Limit? = null, budget: Money? = null, expiresIn: Duration): Authority

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

kotlin
val access = pricing.grant(
    to = "did:chio:z6MkbY4…e1R",
    tools = listOf("quote"),
    limits = Limit(calls = 100, per = 1.hours),
    budget = USD(2),
    expiresIn = 24.hours,
)
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

fun require(approval: Approver, condition: String): Rule

Pauses each matching call until the approver signs for that exact request. Kotlin reserves when, so the condition parameter is named condition.

kotlin
val rule = pricing.require(approval = Approver.YOU, condition = "quote.percent > 50")
println(rule.describe())
Output
rule       quote.percent > 50 waits for did:chio:z6Mkf3…Qp9 (you)
binds      one signature to one request

service.events

fun events(): Sequence<CallEvent>

Streams calls to the service as your kernel decides them.

kotlin
pricing.events().forEach { println(it.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

fun usage(since: Duration): Usage

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

kotlin
println(pricing.usage(since = 1.hours))
Output
caller             calls   refused   approvals   spent
z6MkbY4…e1R        100     2         1           USD 1.00
z6MkQx8…7Ha        0       1         0           USD 0.00

computer.treaty

fun treaty(partner: Partner, actions: List<String>, expiresIn: Duration): Treaty

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.

kotlin
val treaty = project.treaty(
    partner = partner,
    actions = listOf("workspace.read", "workspace.write", "model.invoke", "merge"),
    expiresIn = 7.days,
)
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

fun revoke()

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.

kotlin
treaty.revoke()
run2.events().forEach { println(it.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

fun mailbox(name: String, partner: Partner, treaty: Treaty): Mailbox

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.

kotlin
val reviews = project.mailbox("reviews", partner = partner, treaty = treaty)
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

firstPassing

fun firstPassing(runs: List<Run>, check: String): Run

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.

kotlin
import world.chio.sdk.firstPassing

val ours = fixer.run("fix issue #414", on = forkA, authority = grantA)
val theirs = partner.exec("fix issue #414", on = forkB, authority = grantB)
val winner = firstPassing(listOf(ours, theirs), check = "pytest")
println(winner.describe())
project.apply(winner.changes)
Output
winner     run-01ab…0c7d, partner on fork c4e81b7
check      pytest: 14 passed
others     cancelled, receipts kept in history

parallel and pipe

fun parallel(vararg programs: Program): Program; infix fun Program.pipe(next: Program): Program

Composes boot programs, the pinned descriptors your project exports. parallel runs programs at once; pipe runs them in sequence and reads as an infix operator. A fork started with a boot program runs it under the fork's grant.

kotlin
import world.chio.sdk.parallel

val boot = parallel(Programs.reproduce, Programs.survey) pipe Programs.patch pipe Programs.verify
val fork = project.fork(boot = boot)
println(fork.describe())
Output
boot       (reproduce & survey) | patch | verify
fork       a3d90f1 of quickstart, base main 2f60a91

USD

fun USD(dollars: Long): Money; fun USD(amount: String): Money

Constructs an exact dollar amount for budgets and prices. Money wraps a BigDecimal, so pass a string for cents and arithmetic stays exact.

kotlin
println(USD(2))
println(USD("0.01") * 100)
Output
USD 2.00
USD 1.00

ApplyConflictError

class ApplyConflictError(val base: String, val current: String) : ChioError

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

kotlin
try {
    project.apply(fork.changes)
} catch (conflict: ApplyConflictError) {
    println(conflict.message)
}
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 sidecar client

Beneath the Computer API, the core module exports the blocking sidecar client a service uses to put its own operations under kernel policy: HTTP request evaluation, advisory tool-call evaluation, and receipt verification. The Spring starter and the Flink operator build on it.

ChioClient

world.chio.sdk.ChioClient (ChioClient.kt:27-33) is the blocking HTTP client. It takes a base URL defaulting to SidecarPaths.DEFAULT_BASE_URL and a java.time.Duration timeout defaulting to five seconds. It implements AutoCloseable, though close() is a no-op: java.net.http.HttpClient has no close() before JDK 21 (ChioClient.kt:52-58).

It also implements ChioClientLike (ChioClientLike.kt:8-17), the two-method interface the Flink operator calls, so a test double replaces the client without an HTTP server. That interface's default verifyReceipt returns false (ChioClientLike.kt:16), so an implementation that forgets to override it denies rather than allows.

MethodEndpointReturns
health and isHealthy
GET /chio/health
Map, Boolean
evaluateToolCall
POST /v1/evaluate/advisory
Always throws for the advisory route
evaluateToolCallAdvisory
POST /v1/evaluate/advisory
ChioReceipt
evaluateHttpRequest(request, capabilityToken)
POST /chio/evaluate
EvaluateResponse
evaluateHttpRequest(requestId, method, …)
POST /chio/evaluate
EvaluateResponse
verifyReceipt(receipt: ChioReceipt)
POST /v1/receipts/verify
Boolean
verifyHttpReceipt(receipt: HttpReceipt)
POST /chio/verify
VerifyReceiptResponse
verifyReceipt(receipt: HttpReceipt)
POST /chio/verify
VerifyReceiptResponse
verifyReceiptChain(receipts)
none
Boolean
ChioClient.collectEvidence(receipts)
none
List<GuardEvidence>

The two verifyReceipt overloads

The name verifyReceipt is overloaded on its argument type, and the two overloads reach different endpoints and return different types. Read the argument, not the name.

  • verifyReceipt(receipt: ChioReceipt): Boolean (ChioClient.kt:223) posts to /v1/receipts/verify and collapses the response to one boolean. It requires ok, authorized, signer_trusted, receipt_id_valid, signature_valid, and parameter_hash_valid all true, receipt_kind mediated_decision, boundary_class prevent, trust_level mediated, and result in the set allow, authorized, Authorized. Any missing field reads as false.
  • verifyReceipt(receipt: HttpReceipt): VerifyReceiptResponse (ChioClient.kt:254) is a one-line alias for verifyHttpReceipt, which posts to /chio/verify and returns the parsed structure rather than a verdict.

HTTP request evaluation

evaluateHttpRequest is the API the Spring Boot filter uses. It has two overloads: one takes a built ChioHttpRequest plus an optional capability token, the other takes the fields and builds the request for a Java caller, defaulting the timestamp to the current second (ChioClient.kt:184-215). A capability token, when present, rides in the X-Chio-Capability header.

kotlin
import world.chio.sdk.CallerIdentity
import world.chio.sdk.ChioClient
import world.chio.sdk.ChioHttpRequest

val client = ChioClient("http://127.0.0.1:9090")

val response = client.evaluateHttpRequest(
    ChioHttpRequest(
        requestId = "req-1",
        method = "GET",
        routePattern = "/pets",
        path = "/pets",
        caller = CallerIdentity.anonymous(),
        timestamp = System.currentTimeMillis() / 1000,
    ),
)

Any allow-shaped response is integrity-checked before the client hands it back: the embedded receipt must be a mediated authorization, and verifyHttpReceipt must authorize it. A non-authorizing allow raises ChioError with code chio_invalid_receipt rather than letting a forged allow through (ChioClient.kt:164-178).

Advisory tool-call evaluation

evaluateToolCallAdvisory posts to /v1/evaluate/advisory, checks the advisory receipt's integrity, and returns it. The response is wrapped in the chio.sidecar.advisory-evaluation.v1 envelope, which sets authorization to false and authorizationBasis to advisory_only. A response that does not identify that basis raises ChioError (ChioClient.kt:300-305).

kotlin
val advisory = client.evaluateToolCallAdvisory(
    capabilityId = "cap-fraud",
    toolServer = "flink://fraud-job",
    toolName = "events:consume:transactions",
    parameters = mapOf("body_length" to 42L, "body_hash" to "..."),
)
println("advisory receipt: " + advisory.id)

evaluateToolCall (ChioClient.kt:83) is the ChioClientLike implementation. It calls the advisory route and then throws ChioDeniedError on both paths: one message for an observation outcome of dropped, another for an advisory evaluation that is not an authoritative authorization decision (ChioClient.kt:96-102). Against the advisory-only sidecar route it therefore never returns. Use evaluateToolCallAdvisory when you need the observation. Neither method dispatches a tool.

Check predecessor hash links

verifyReceiptChain compares each receipt’s contentHash with the SHA-256 of its predecessor’s canonical JSON. It makes no network call and does not verify signatures, trusted signers, or authorization. Empty and single-receipt lists return true because they contain no links to check.

ChioClient.kt · hash linkagekotlin
val linksMatch: Boolean = client.verifyReceiptChain(receipts)

Use the receipt verification methods to inspect authority. Matching predecessor hashes alone do not establish that the records came from a trusted kernel.

Canonical JSON

CanonicalJson (CanonicalJson.kt) is the Jackson-backed canonicalizer every receipt hash flows through. It matches Python's json.dumps(sort_keys=True, separators=(",", ":"), ensure_ascii=True) byte for byte: map keys and object properties sorted in code-point order, no insignificant whitespace, non-ASCII escaped as lowercase \uXXXX, and null map values preserved rather than dropped.

kotlin
import world.chio.sdk.CanonicalJson

val bytes: ByteArray = CanonicalJson.writeBytes(mapOf("b" to 2, "a" to 1))
val text: String = CanonicalJson.writeString(mapOf("b" to 2, "a" to 1))
// text == {"a":1,"b":2}

Prefer writeBytes when the consumer hashes the output. world.chio.sdk.Hashing.sha256Hex accepts either a String or a ByteArray and emits lowercase hex.

Receipt types

The receipt object graph lives in world.chio.sdk. Every type carries Jackson bindings to the snake_case wire names and implements java.io.Serializable, so it crosses Flink operator boundaries.

TypeSourceRole
ChioHttpRequest
ChioTypes.kt:168
Request posted to /chio/evaluate.
CallerIdentity, AuthMethod
ChioTypes.kt:56, :19
Caller and the kind of credential presented (bearer, api_key, cookie, anonymous). Authentication requires a separate check.
Verdict
ChioTypes.kt:77
Kernel HTTP verdict: allow or deny, plus reason, guard, and httpStatus.
HttpReceipt
ChioTypes.kt:128
Signed receipt for an HTTP evaluation.
EvaluateResponse
ChioTypes.kt:188
Verdict, receipt, and evidence.
VerifyReceiptResponse
ChioTypes.kt:199
Structured authority result from /chio/verify.
ChioPassthrough
ChioTypes.kt:233
Compatibility type for an unevaluated request. The Spring filter does not emit it; sidecar failures return HTTP 502.
ChioErrorResponse
ChioTypes.kt:245
The JSON body the filter writes on a deny or a sidecar failure.
ChioErrorCodes
ChioTypes.kt:257
The five error-code constants, listed under Errors.
GuardEvidence
ChioTypes.kt:116
Per-guard evaluation evidence.
ChioReceipt
ChioReceipt.kt:15
Signed tool-call receipt: action, decision, evidence.
Decision, ToolCallAction
Decision.kt:13, ToolCallAction.kt:10
Tool-call verdict and the hashed action parameters.

HttpReceipt.isAuthorized() checks field semantics, not a signature. The HTTP client also asks the sidecar to verify allow-shaped responses before returning them. HttpReceipt.isAuthorized() requires receiptKind mediated_decision, boundaryClass prevent, trustLevel mediated, a null observationOutcome, and an allow verdict. The Receipt format page carries the full field set.

Spring Boot starter

Register ChioFilter explicitly with FilterRegistrationBean, as the worked application does. Choose the servlet URL patterns that Chio will govern. Adding the starter dependency alone does not establish that a filter runs.

Application configuration · explicit registrationkotlin
import org.springframework.boot.web.servlet.FilterRegistrationBean
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import world.chio.ChioFilter
import world.chio.ChioFilterConfig

@Configuration
open class ChioProtection {
    @Bean
    open fun chioFilterRegistration(): FilterRegistrationBean<ChioFilter> =
        FilterRegistrationBean(
            ChioFilter(
                ChioFilterConfig(sidecarUrl = "http://127.0.0.1:9090"),
            ),
        ).apply {
            addUrlPatterns("/pets", "/pets/*")
            order = 1
        }
}

The filter fails closed. A sidecar it cannot reach produces a 502 with a structured JSON body (ChioFilter.kt:142 and :177). A denied evaluation returns the verdict's httpStatus, falling back to 403 (ChioFilter.kt:154). An allowed request reaches the chain only after the receipt passes the semantic check and the sidecar’s verification check, and the filter then sets X-Chio-Receipt-Id to the receipt id (ChioFilter.kt:188). This records authorization before the handler runs; it does not record whether the handler succeeded. The filter reads a capability token from the X-Chio-Capability header or the chio_capability query parameter (ChioFilter.kt:40).

What the starter adds

  • ChioIdentityExtractor.kt defines the IdentityExtractorFn type alias and defaultIdentityExtractor, which reads an Authorization: Bearer token, then X-API-Key, then the cookie header, then falls back to an anonymous identity. It fingerprints credentials without verifying them; verified remains false. Integrate a custom extractor with your authentication layer when policy requires an authenticated caller.
  • CachedBodyHttpServletRequest.kt wraps the request so the filter hashes the full body without consuming the stream that downstream filters and controllers read.
  • compat/ChioSdkAliases.kt declares package-level typealiases so world.chio.* resolves the world.chio.sdk.* types the filter uses.
  • compat/ChioSidecarClient.kt wraps ChioClient so a starter consumer calls evaluate and verifyReceipt without duplicating the HTTP logic. Its verifyReceipt returns whether the verification authorizes the receipt.

Configuration

These chio properties apply when ChioAutoConfiguration is explicitly registered. The manually constructed ChioFilter above uses ChioFilterConfig directly; changing this YAML does not configure that instance. Choose one registration approach to avoid duplicate filter beans.

ChioProperties binds the keys below. Its configuration class runs when chio.enabled is true or absent.

yaml
chio:
  sidecar-url: http://127.0.0.1:9090
  timeout-seconds: 5
  on-sidecar-error: deny   # reserved: the filter always denies sidecar errors
  enabled: true
  url-patterns:
    - "/*"
  filter-order: 1
KeyDefaultPurpose
sidecar-url
CHIO_SIDECAR_URL env, else http://127.0.0.1:9090
Base URL of the sidecar kernel.
timeout-seconds
5
HTTP timeout for sidecar calls.
on-sidecar-error
deny
Reserved. The filter fails closed on sidecar errors regardless of this value.
enabled
true
Toggle the auto-configured filter.
url-patterns
["/*"]
Servlet URL patterns the filter guards.
filter-order
1
Filter order; lower runs first.

For header-driven caller lookup and pattern-based routing, supply a ChioFilterConfig with custom identityExtractor and routeResolver functions. The default resolver returns the raw path.

kotlin
import world.chio.ChioFilterConfig

val config = ChioFilterConfig(
    sidecarUrl = "http://127.0.0.1:9090",
    routeResolver = { _, path ->
        if (path.startsWith("/pets/")) "/pets/{petId}" else path
    },
)

A worked application

Your pricing service, written as a Spring application, registers the filter explicitly rather than relying on auto-configuration, which is what a service wants when only some routes are governed. It guards /v1/quote and /v1/refund and leaves /healthz alone.

PricingApplication.ktkotlin
@SpringBootApplication
class PricingApplication {
    @Bean
    fun chioFilterRegistration(): FilterRegistrationBean<ChioFilter> {
        val filter = ChioFilter(
            ChioFilterConfig(
                sidecarUrl = System.getenv("CHIO_SIDECAR_URL") ?: "http://127.0.0.1:9090",
            ),
        )

        return FilterRegistrationBean<ChioFilter>().apply {
            setFilter(filter)
            addUrlPatterns("/v1/quote", "/v1/refund")
            order = Ordered.HIGHEST_PRECEDENCE
        }
    }
}

fun main(args: Array<String>) {
    runApplication<PricingApplication>(*args)
}

An allowed request carries X-Chio-Receipt-Id; a POST /v1/refund from a caller whose grant covers only quotes returns 403 and a ChioErrorResponse body carrying chio_access_denied, the deny reason, the receipt id, and the suggestion provide a valid capability token in the X-Chio-Capability header or chio_capability query parameter (ChioFilter.kt:162). The kernel records the denial in a receipt before the controller runs.

Flink and streaming envelopes

The Flink evaluator requires a ChioClientLike implementation that can return a mediated authorization and verify it. Supplying ChioClient routes tool evaluation through its advisory endpoint, which cannot provide that authorization. The evaluator follows its refusal path and creates a synthetic deny record. HTTP servlet protection uses the separate /chio/evaluate API.

The core carries the envelope builders that chio-streaming-flink emits to Kafka. Each pins a schema version and a fixed header set.

  • ReceiptEnvelope. The receipt side-output envelope, version chio-streaming/v1, with the X-Chio-Receipt and X-Chio-Verdict headers (ReceiptEnvelope.kt:46-52).
  • DlqRouter. Builds the dead-letter record, version chio-streaming/dlq/v1 (DlqRouter.kt:131). It rejects non-deny receipts with ChioValidationError and routes on an exact topic map with a default fallback.
  • SyntheticDenyReceipt. Stamps a deny receipt carrying the chio-streaming/synthetic-deny/v1 marker (SyntheticDenyReceipt.kt:20) when the sidecar is unreachable in fail-closed mode. Its reason is prefixed [unsigned], and kernelKey and signature are empty.

Errors

Errors live in world.chio.sdk.errors and all extend ChioError, which carries a code string.

  • ChioError: the base exception, with message, code, and cause.
  • ChioDeniedError: structured deny with guard, reason, receipt id, hint, and the rest of the deny payload; fromWire and toWire round-trip the Python shape.
  • ChioConnectionError and ChioTimeoutError: transport failures.
  • ChioValidationError and ChioStreamingError: envelope and streaming invariants.
kotlin
import world.chio.sdk.errors.ChioDeniedError

try {
    client.evaluateToolCall(
        capabilityId = "cap-fraud",
        toolServer = "flink://fraud-job",
        toolName = "events:consume:transactions",
        parameters = mapOf("body_length" to 42L),
    )
} catch (error: ChioDeniedError) {
    println("not authorized: " + error.message)
}

ChioErrorCodes (ChioTypes.kt:257-263) holds the codes: chio_access_denied, chio_sidecar_unreachable, chio_evaluation_failed, chio_invalid_receipt, and chio_timeout. Each is used by the client or Spring filter. These include locally generated failures; they are not all copied from a sidecar response. See Schemas and errors taxonomy.

Parity

Canonical JSON, synthetic-deny, and the DLQ builders each carry a parity test tag and assert byte equality against Python vectors. The Bindings API page carries the cross-language invariants every binding conforms against.

Source and verification

Module coordinates and Java targets come from the JVM sources. ChioClient.kt defines the blocking calls; ChioTypes.kt and ChioReceipt.kt define the wire types. The servlet integration is in chio-spring-boot.