|
| 1 | +// Copyright 2026 Kakeya contributors. |
| 2 | +// |
| 3 | +// Kakeya local-agent runtime — session-bound inference protocol. |
| 4 | +// |
| 5 | +// This file is the single source of truth for the wire contract |
| 6 | +// between Kakeya runtime instances and their SDKs (Python and |
| 7 | +// TypeScript at v0.3; future Rust / Go SDKs are admitted by the |
| 8 | +// design but not part of v0.3 scope). |
| 9 | +// |
| 10 | +// Servers and clients MUST conform to the bytes of this schema. In |
| 11 | +// particular, the byte-exact KV cache binding contract (ADR 0008 |
| 12 | +// §2.3) is parameterized over the (session_id, history_token_ids, |
| 13 | +// seed) triple defined here. |
| 14 | +// |
| 15 | +// References: |
| 16 | +// - docs/adr/0008-session-bound-runtime-and-grpc-protocol.md |
| 17 | +// - §2.1 wire protocol decision (gRPC bidi primary) |
| 18 | +// - §2.2 session model (server-issued id, raw tokens, append-only) |
| 19 | +// - §2.3 KV binding + determinism contract |
| 20 | +// - §2.4 no chat template at the runtime, ever |
| 21 | +// - §2.6 cache state lifecycle (TTL, no implicit reset) |
| 22 | +// - §2.8 anomaly invariants INV-1 / INV-2 / INV-3 |
| 23 | +// - §2.9 observability surface |
| 24 | +// - §8 OQ-2 (history > sink+window default behavior) |
| 25 | +// - §8 OQ-4 (seed is per-Generate; default while unresolved) |
| 26 | +// |
| 27 | +// This is PR-A1 from ADR 0008 §6.1: schema-only landing, no code-gen |
| 28 | +// targets, no service implementation. The file is documentation in |
| 29 | +// `.proto` form. `buf lint` is the CI gate. |
| 30 | + |
| 31 | +syntax = "proto3"; |
| 32 | + |
| 33 | +package kakeya.v1; |
| 34 | + |
| 35 | +// ----------------------------------------------------------------------------- |
| 36 | +// Service |
| 37 | +// ----------------------------------------------------------------------------- |
| 38 | + |
| 39 | +// RuntimeService is the single service exposed by a Kakeya runtime |
| 40 | +// instance. All session lifecycle and inference RPCs live here. v0.3 |
| 41 | +// is single-tenant; multi-tenant authorization is deferred to v0.4 |
| 42 | +// (ADR 0008 §4.5) and is intentionally not modeled in this schema. |
| 43 | +service RuntimeService { |
| 44 | + // CreateSession allocates a new session and returns its server- |
| 45 | + // issued identifier. Clients cannot fabricate session ids; this RPC |
| 46 | + // is the only way to obtain one (ADR 0008 §2.2 contract item 1). |
| 47 | + rpc CreateSession(CreateSessionRequest) returns (CreateSessionResponse); |
| 48 | + |
| 49 | + // AppendTokens appends raw token ids to a session's history. The |
| 50 | + // history is append-only within a session; clients cannot rewrite |
| 51 | + // prior history mid-session (ADR 0008 §2.2 contract item 3). The |
| 52 | + // server treats token ids as opaque integers and does NOT call any |
| 53 | + // chat template, role marker, or template re-rendering logic |
| 54 | + // (ADR 0008 §2.4). |
| 55 | + rpc AppendTokens(AppendTokensRequest) returns (AppendTokensResponse); |
| 56 | + |
| 57 | + // Generate requests up to GenerateRequest.max_tokens of generation |
| 58 | + // bound to a session_id. The server streams generated token ids |
| 59 | + // back as they commit (server-streaming RPC). The byte-exact KV |
| 60 | + // cache binding contract (ADR 0008 §2.3) applies: for the same |
| 61 | + // (session_id, history_token_ids, seed) tuple, repeated Generate |
| 62 | + // calls produce bit-identical output regardless of how the history |
| 63 | + // was built up (one AppendTokens of N tokens vs. N AppendTokens of |
| 64 | + // 1 token vs. CreateSession+AppendTokens combinations). |
| 65 | + rpc Generate(GenerateRequest) returns (stream GenerateResponse); |
| 66 | + |
| 67 | + // CloseSession releases a session and frees its KV slab. Subsequent |
| 68 | + // RPCs that reference the closed session_id return NOT_FOUND. |
| 69 | + rpc CloseSession(CloseSessionRequest) returns (CloseSessionResponse); |
| 70 | + |
| 71 | + // GetSessionInfo returns diagnostic counters for a session. Used |
| 72 | + // primarily by SDK observability helpers and integration tests |
| 73 | + // (ADR 0008 §2.9). Subset of the metrics also exposed on the |
| 74 | + // Prometheus /metrics endpoint of the deprecated HTTP shim. |
| 75 | + rpc GetSessionInfo(GetSessionInfoRequest) returns (GetSessionInfoResponse); |
| 76 | +} |
| 77 | + |
| 78 | +// ----------------------------------------------------------------------------- |
| 79 | +// Session lifecycle messages |
| 80 | +// ----------------------------------------------------------------------------- |
| 81 | + |
| 82 | +message CreateSessionRequest { |
| 83 | + // Optional client-supplied label, recorded for diagnostics. Has no |
| 84 | + // effect on session identity; the server still issues the |
| 85 | + // session_id. Empty string = no label. |
| 86 | + string client_label = 1; |
| 87 | + |
| 88 | + // Token ids that, when emitted by the verifier sampler during |
| 89 | + // Generate, cause the server to stop emitting and end the stream |
| 90 | + // with GenerateDone.stop_reason = STOP_REASON_EOS. The runtime |
| 91 | + // does NOT interpret these tokens semantically (ADR 0008 §2.4) — |
| 92 | + // they are opaque ids supplied by the SDK / application based on |
| 93 | + // its own knowledge of the model's tokenizer. Empty list = |
| 94 | + // generation only stops on STOP_REASON_MAX_TOKENS, on |
| 95 | + // STOP_REASON_TRUNCATED, or on cancellation. |
| 96 | + repeated uint32 eos_token_ids = 2; |
| 97 | +} |
| 98 | + |
| 99 | +message CreateSessionResponse { |
| 100 | + // Server-issued opaque identifier. Treat as a black box; do not |
| 101 | + // attempt to parse, derive new ids from, or reuse across runtime |
| 102 | + // process restarts. |
| 103 | + string session_id = 1; |
| 104 | +} |
| 105 | + |
| 106 | +message AppendTokensRequest { |
| 107 | + // Target session. Must reference a session previously returned by |
| 108 | + // CreateSession; otherwise returns NOT_FOUND (ADR 0008 §2.6). |
| 109 | + string session_id = 1; |
| 110 | + |
| 111 | + // Raw token ids to append. Each id must be in the verifier's |
| 112 | + // tokenizer vocabulary range; out-of-range ids return |
| 113 | + // INVALID_ARGUMENT and the session's history is unchanged. |
| 114 | + repeated uint32 token_ids = 2; |
| 115 | +} |
| 116 | + |
| 117 | +message AppendTokensResponse { |
| 118 | + // Total number of tokens in the session's history after this |
| 119 | + // append. Useful for SDK-side bookkeeping and history-length |
| 120 | + // assertions. |
| 121 | + uint64 history_length = 1; |
| 122 | +} |
| 123 | + |
| 124 | +message CloseSessionRequest { |
| 125 | + string session_id = 1; |
| 126 | +} |
| 127 | + |
| 128 | +message CloseSessionResponse { |
| 129 | + // Number of tokens that were in the session's history at close |
| 130 | + // time. Diagnostic only; clients should not depend on this for |
| 131 | + // correctness. |
| 132 | + uint64 final_history_length = 1; |
| 133 | +} |
| 134 | + |
| 135 | +// ----------------------------------------------------------------------------- |
| 136 | +// Generate messages |
| 137 | +// ----------------------------------------------------------------------------- |
| 138 | + |
| 139 | +message GenerateRequest { |
| 140 | + string session_id = 1; |
| 141 | + |
| 142 | + // Maximum tokens to emit in this Generate call. Generation may |
| 143 | + // stop earlier if (a) the verifier sampler emits one of the |
| 144 | + // session's eos_token_ids (STOP_REASON_EOS), (b) the session's |
| 145 | + // history would exceed the verifier's sink+window capacity |
| 146 | + // (STOP_REASON_TRUNCATED, default per ADR 0008 §8 OQ-2), or |
| 147 | + // (c) the call is cancelled (STOP_REASON_CANCELLED). |
| 148 | + uint32 max_tokens = 2; |
| 149 | + |
| 150 | + // Deterministic-sampling seed. ADR 0008 §8 OQ-4 default while |
| 151 | + // unresolved: the seed is a per-Generate argument; the byte-exact |
| 152 | + // determinism contract (§2.3) is parameterized over a fixed seed. |
| 153 | + // Field is proto3-optional so the SDK can distinguish "no seed |
| 154 | + // pinned, sampler may use process entropy" from "seed = 0 |
| 155 | + // pinned" (both are valid). |
| 156 | + optional uint64 seed = 3; |
| 157 | + |
| 158 | + // Sampling parameters, all optional. Documented here for the SDK |
| 159 | + // contract; the runtime passes these through to the verifier |
| 160 | + // sampler without further interpretation in v0.3. |
| 161 | + optional float temperature = 4; |
| 162 | + optional float top_p = 5; |
| 163 | + optional uint32 top_k = 6; |
| 164 | +} |
| 165 | + |
| 166 | +// GenerateResponse is one frame of the server-streaming response. |
| 167 | +// Exactly one terminal Done event is emitted per Generate call, |
| 168 | +// after which the stream closes cleanly. Despite the "Response" |
| 169 | +// suffix (per proto3 / buf STANDARD convention), each individual |
| 170 | +// instance is conceptually one *event* in the stream — see the |
| 171 | +// `payload` oneof. |
| 172 | +message GenerateResponse { |
| 173 | + oneof payload { |
| 174 | + // A single committed token id, in the order the verifier |
| 175 | + // accepted it. |
| 176 | + uint32 token_id = 1; |
| 177 | + |
| 178 | + // Terminal event. Sent exactly once at the end of the stream. |
| 179 | + GenerateDone done = 2; |
| 180 | + |
| 181 | + // History was truncated to fit within the verifier's |
| 182 | + // sink+window capacity (ADR 0008 §8 OQ-2 default). Emitted at |
| 183 | + // most once per Generate call, before any token_id event in |
| 184 | + // that call. The truncation discards the oldest non-sink tokens; |
| 185 | + // no information beyond what was discarded is recoverable. |
| 186 | + HistoryTruncated truncated = 3; |
| 187 | + } |
| 188 | +} |
| 189 | + |
| 190 | +message GenerateDone { |
| 191 | + enum StopReason { |
| 192 | + STOP_REASON_UNSPECIFIED = 0; |
| 193 | + STOP_REASON_MAX_TOKENS = 1; |
| 194 | + STOP_REASON_EOS = 2; |
| 195 | + STOP_REASON_CANCELLED = 3; |
| 196 | + STOP_REASON_TRUNCATED = 4; |
| 197 | + } |
| 198 | + StopReason stop_reason = 1; |
| 199 | + |
| 200 | + // Number of tokens this Generate call appended to the session |
| 201 | + // history (excludes the prefill phase). |
| 202 | + uint32 generated_token_count = 2; |
| 203 | + |
| 204 | + // Wall-clock duration of the prefill phase for this call, in |
| 205 | + // seconds. Exposes the §2.9 metric `generate_prefill_duration_seconds` |
| 206 | + // observation that this call contributed. |
| 207 | + double prefill_duration_seconds = 3; |
| 208 | + |
| 209 | + // Wall-clock duration from prefill start to last token committed. |
| 210 | + double total_duration_seconds = 4; |
| 211 | +} |
| 212 | + |
| 213 | +message HistoryTruncated { |
| 214 | + // Number of tokens dropped from the history. The verifier slab |
| 215 | + // now holds (sink + window) tokens at most; this event reports |
| 216 | + // history_length - (sink + window) at the moment of truncation. |
| 217 | + uint64 dropped_token_count = 1; |
| 218 | +} |
| 219 | + |
| 220 | +// ----------------------------------------------------------------------------- |
| 221 | +// Diagnostic messages |
| 222 | +// ----------------------------------------------------------------------------- |
| 223 | + |
| 224 | +message GetSessionInfoRequest { |
| 225 | + string session_id = 1; |
| 226 | +} |
| 227 | + |
| 228 | +message GetSessionInfoResponse { |
| 229 | + // Number of tokens currently in the session's history (after any |
| 230 | + // truncation; the runtime never reports a history longer than the |
| 231 | + // slab can hold). |
| 232 | + uint64 history_length = 1; |
| 233 | + |
| 234 | + // Live KV bytes held by the session's slab. Sum of all sessions' |
| 235 | + // values equals the §2.9 `session_kv_live_bytes` gauge. |
| 236 | + uint64 kv_live_bytes = 2; |
| 237 | + |
| 238 | + // Anomaly-invariant violation counters per ADR 0008 §2.8. MUST be |
| 239 | + // 0 under healthy operation. Non-zero is a paging-grade signal |
| 240 | + // that the session has been failed and the slab freed; the |
| 241 | + // session_id is no longer usable for AppendTokens / Generate |
| 242 | + // (those return NOT_FOUND or FAILED_PRECONDITION). |
| 243 | + uint64 cache_invariant_inv1_violations = 3; |
| 244 | + uint64 cache_invariant_inv2_violations = 4; |
| 245 | + |
| 246 | + // Wall-clock seconds since this session's last RPC interaction. |
| 247 | + // The runtime evicts sessions idle longer than its configured |
| 248 | + // session_idle_ttl_s (ADR 0008 §2.6 default 1800s). |
| 249 | + double idle_seconds = 5; |
| 250 | +} |
0 commit comments