The language codai apps speak to each other, so a task you start on your phone can be watched, steered or finished from your laptop.
codai is one model name — codai — available on every device you own: phone, desktop, the console, the CLI and any agent you write against the gateway. This repository describes how those clients share a session: the ordered log of what an agent did, who is currently running it, and how anyone with access can send it instructions.
- A session lives on the gateway. It is an append-only list of events (
turn_start,tool_call,assistant, …) with a dense sequence number. - Exactly one device — the executor — holds a short lease on the session and is the only one allowed to append events. Everyone else is a viewer or editor.
- Viewers follow the session live over SSE or WebSocket and can resume from any
seqafter a disconnect. - Editors send controls (
send,answer,approve,cancel,steer, …). The gateway stores them and echoes them into the log so the executor can apply them — even if it was offline when they were sent. - Dispatch starts a task on a named device: the control is queued for that device and a push notification wakes it up to claim the lease.
sequenceDiagram
participant Phone as Phone (executor)
participant GW as Gateway ai.codai.ro
participant Laptop as Laptop (viewer/editor)
Phone->>GW: POST /v1/sessions {session_key}
Phone->>GW: POST /v1/sessions/:id/lease
GW-->>Phone: 200 lease {device_id, expires_at}
loop every ≤200 ms / 20 events
Phone->>GW: POST /v1/sessions/:id/events {events[]}
GW-->>Phone: {last_seq, events[{client_event_id, seq}]}
end
Laptop->>GW: GET /v1/sessions/:id/stream?after=0
GW-->>Laptop: event: event (replay) … event: lease … event: presence
Laptop->>GW: POST /v1/sessions/:id/control {id, kind:"steer", text}
GW-->>Laptop: 202 {accepted, seq}
GW-->>Phone: frame control (WS) / event kind:"control" (SSE)
Phone->>GW: POST /v1/sessions/:id/control/:cid/applied
loop every 10 s
Phone->>GW: PUT /v1/sessions/:id/lease (heartbeat, TTL 30 s)
end
- App builders — you want your own viewer, executor or dashboard to interoperate with the codai phone and desktop apps. Read the spec and validate your payloads against
schemas/. - Agent builders — you have an agent (a script, a CI job, an MCP tool) and want to hand tasks to a user's device or follow what it does. Start with
examples/probe.mjsand the Dispatch section of the spec. - Curious users — you want to know exactly what leaves your phone when "session sync" is on. The answer is the Event object and nothing else; see the privacy notes.
You need a codai API key (codai_…, from the console) and a device id. The device id is any UUID you pick — the gateway registers it under your account the first time it sees it.
The three calls below create a session, claim the executor lease, append one event, then read it back.
PowerShell
$CODAI_KEY = "codai_…" # your key
$DEVICE = [guid]::NewGuid().ToString() # any UUID, reuse it for this device
$H = @{ Authorization = "Bearer $CODAI_KEY"; 'x-codai-device' = $DEVICE
'x-codai-device-platform' = 'cli'; 'x-codai-device-name' = 'try-it' }
$GW = 'https://ai.codai.ro'
$s = Invoke-RestMethod -Method Post -Uri "$GW/v1/sessions" -Headers $H -ContentType 'application/json' `
-Body '{"title":"hello from curl"}'
Invoke-RestMethod -Method Post -Uri "$GW/v1/sessions/$($s.id)/lease" -Headers $H -ContentType 'application/json' -Body '{}'
Invoke-RestMethod -Method Post -Uri "$GW/v1/sessions/$($s.id)/events" -Headers $H -ContentType 'application/json' `
-Body '{"events":[{"kind":"assistant","client_event_id":"e1","payload":{"text":"hi"}}]}'
Invoke-RestMethod -Uri "$GW/v1/sessions/$($s.id)/events?after=0" -Headers $H | ConvertTo-Json -Depth 5bash
export CODAI_KEY="codai_…" # your key
export DEVICE="$(uuidgen | tr 'A-Z' 'a-z')" # any UUID, reuse it for this device
GW=https://ai.codai.ro
H=(-H "Authorization: Bearer $CODAI_KEY" -H "x-codai-device: $DEVICE"
-H "x-codai-device-platform: cli" -H "x-codai-device-name: try-it" -H "content-type: application/json")
SID=$(curl -s "${H[@]}" -X POST $GW/v1/sessions -d '{"title":"hello from curl"}' | jq -r .id)
curl -s "${H[@]}" -X POST $GW/v1/sessions/$SID/lease -d '{}'
curl -s "${H[@]}" -X POST $GW/v1/sessions/$SID/events \
-d '{"events":[{"kind":"assistant","client_event_id":"e1","payload":{"text":"hi"}}]}'
curl -s "${H[@]}" "$GW/v1/sessions/$SID/events?after=0" | jqExpected: 201 for the session, 200 with {device_id, expires_at} for the lease, {last_seq: 1, accepted: 1, …} for the append, and one event with seq: 1 on the read. Clean up with DELETE /v1/sessions/$SID and DELETE /v1/devices/$DEVICE. More recipes in examples/curl.md.
| Term | In one sentence |
|---|---|
| Session | A server-side conversation with an ordered event log, owned by one user, addressed by its server id (UUID) or your own session_key. |
| Event | One append-only line in the log — {seq, kind, ts, sender_device_id, turn_id?, payload} — written only by the executor; kind is free-form text. |
| Control | A request to the executor (send, answer, approve, deny, cancel, steer, inject) with a client-chosen idempotency id, stored and echoed as an event. |
| Lease | The 30-second, heartbeat-renewed right to execute a session; exactly one device holds it, and only the owner's devices can claim it. |
| Device | A UUID you choose, registered lazily from the x-codai-device header, with a platform (android, ios, web, desktop, cli, agent) and a name. |
| Role | What a caller may do on a session — viewer (read/follow), editor (also send controls), owner (everything, including leases and shares). |
| Share | A grant of a role on one session to a user, an org, or anyone holding a link token (shown once, stored only as a SHA-256 hash). |
| Dispatch | Queue a send control for one specific device of the owner and wake it with a data-only push, so a task can be started remotely. |
| Presence | Who is connected right now (device_id, role, executor, driving), streamed as frames and never persisted. |
spec/shared-sessions.md— the normative specification: objects, routes, headers, SSE/WS frames, lease and idempotency rules, dispatch, errors, limits, conformance checklist.schemas/— JSON Schema (draft 2020-12) for every wire object; enums are copied verbatim from the reference implementation.examples/—probe.mjs(end-to-end round trip in plain Node),viewer.html(a single-file live transcript viewer, no build step),curl.md.CHANGELOG.md— every change to the protocol, with deprecations announced ahead of time.
| Project | Role | Language |
|---|---|---|
| codai-ro/codai-phone | reference executor + viewer | Kotlin |
| codai-ro/codai-desktop | executor + viewer | Tauri / TS |
| codai-ro/codai-sdk | client library (npm i codai-sdk) |
TypeScript |
| codai-ro/codai-sdk-python | client library (pip install codai-sdk) |
Python |
| codai.ro/console | viewer / editor / device manager | — |
This is protocol v1. Every realtime frame carries "v": 1.
- Additive only. New optional fields, new event
kinds and new routes may appear in any release. Clients must ignore fields and kinds they do not know. - Never removed or renamed within v1. A field, route, header or enum value listed in the v1 spec stays valid for the lifetime of v1.
- Deprecations are announced in
CHANGELOG.mdat least one minor release before behaviour changes, and the old form keeps working until v2. - The spec, schemas and the gateway change in the same commit; if you find a mismatch it is a bug — please report it.
Issues and pull requests are welcome — a typo in the spec, a schema that rejects a valid payload, a client you built. See CONTRIBUTING.md (DCO sign-off, conventional commits) and SECURITY.md for anything security-sensitive.
- Code, schemas and examples: Apache License 2.0 — © 2026 Dragos Catalin Vladulescu.
- Prose (the specification and this README): Creative Commons Attribution 4.0.
"codai" is a trademark of Dragos Catalin Vladulescu. Implementing this protocol does not require permission; calling your product "codai" does.