Skip to content

Commit 463cf0a

Browse files
authored
Merge pull request #40 from FluffyAIcode/AgentMemory/v030-pr-a1-runtime-proto-8e7f
PR-A1 (ADR 0008 Phase A): runtime.proto + buf lint CI gate
2 parents a9f86b1 + d5c4ef2 commit 463cf0a

3 files changed

Lines changed: 312 additions & 0 deletions

File tree

.github/workflows/ci.yaml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,3 +175,38 @@ jobs:
175175
docker run --rm \
176176
kakeya:ci-${{ github.sha }} \
177177
--help
178+
179+
proto-lint:
180+
name: proto lint (buf)
181+
runs-on: ubuntu-latest
182+
# ADR 0008 PR-A1: the proto/ schema is the wire-contract source of
183+
# truth for the gRPC runtime + Python / TypeScript SDKs. `buf lint`
184+
# under the STANDARD ruleset enforces public-style conventions
185+
# (service / message naming, enum zero-value naming, file-package
186+
# alignment). `buf format --diff` enforces canonical formatting so
187+
# the source of truth is also character-stable across editors.
188+
steps:
189+
- name: Check out
190+
uses: actions/checkout@v4
191+
192+
- name: Install buf
193+
uses: bufbuild/buf-setup-action@v1
194+
with:
195+
version: 1.50.0
196+
github_token: ${{ secrets.GITHUB_TOKEN }}
197+
198+
- name: Lint .proto files
199+
run: buf lint
200+
201+
- name: Verify .proto formatting is canonical
202+
run: |
203+
# `buf format` is an in-place rewriter; --diff makes it print
204+
# what it would change instead of editing files (and always
205+
# exits 0). A non-empty diff is a CI failure.
206+
diff_output="$(buf format --diff)"
207+
if [ -n "$diff_output" ]; then
208+
echo "::error::buf format would have rewritten one or more .proto files."
209+
echo "Run 'buf format -w' locally and commit the result."
210+
printf '%s\n' "$diff_output"
211+
exit 1
212+
fi

buf.yaml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# buf v2 workspace config — ADR 0008 PR-A1.
2+
#
3+
# v0.3 ships only a single module (the runtime schema). When PR-A2
4+
# and beyond add dependent schemas (e.g., a separate metrics or
5+
# control-plane module), additional `modules` entries land here.
6+
#
7+
# The lint policy uses buf's STANDARD set, which enforces the public-
8+
# style guide (file-level package matching, message naming, enum
9+
# zero-value naming, etc.). FILE-level breaking-change detection is
10+
# enabled so that any incompatible edit to a published RPC or
11+
# message field is caught at PR time. Until v0.3 GA the schema is
12+
# still flagged Unstable in §1 of ADR 0008 — breaking changes are
13+
# allowed, but they require an explicit ADR-update commit referencing
14+
# this file's diff so the audit trail is preserved.
15+
16+
version: v2
17+
18+
modules:
19+
- path: proto
20+
21+
lint:
22+
use:
23+
- STANDARD
24+
25+
breaking:
26+
use:
27+
- FILE

proto/kakeya/v1/runtime.proto

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
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

Comments
 (0)