kcode-agent-runtime 0.3.1

Provider-neutral agent loops over kcode-intelligence-router
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
# kcode-agent-runtime 0.3.1

`kcode-agent-runtime` owns provider-neutral agent execution over
`kcode-intelligence-router`. It exposes a host-independent handle for one
primary provider inference, retains `run_session` as the behavior-compatible
primary-session loop over that handle, and retains `run` for one fresh-context
native subagent turn with replaceable projected tool state.

All paths use one native `call_ktool` bridge and preserve router accounting.
The inference handle owns no application Session, Chatend, or host reference.

## Complete public API

```rust
pub type HostFuture<'a, T> =
    Pin<Box<dyn Future<Output = anyhow::Result<T>> + Send + 'a>>;

pub const DEFAULT_ROUND_LIMIT: u64 = 250;

pub struct ToolCall {
    pub name: String,
    pub arguments: serde_json::Value,
}

pub struct ToolCallError { /* private bounded classification */ }

impl std::fmt::Display for ToolCallError;
impl std::error::Error for ToolCallError;

pub enum AuditEvent {
    Started {
        parent_operation_id: Uuid,
        model: String,
        provider_model: String,
        provider: kcode_intelligence_router::AgentProvider,
        context_window_tokens: u64,
        max_input_tokens: u64,
        context: Vec<String>,
        task: String,
        host: serde_json::Value,
    },
    InferenceSubmitted {
        parent_operation_id: Uuid,
        round: u64,
        manifest_hash: String,
        estimated_input_tokens: u64,
    },
    ToolCall {
        parent_operation_id: Uuid,
        name: String,
        arguments: serde_json::Value,
    },
    ToolResult {
        parent_operation_id: Uuid,
        name: String,
        ok: bool,
        projection_accepted: bool,
        result: String,
    },
    ProviderReceipt {
        parent_operation_id: Uuid,
        round: u64,
        manifest_hash: String,
        usage: Option<kcode_codex_runtime_v2::TokenUsage>,
        receipt: Box<kcode_intelligence_router::UsageReceipt>,
    },
    Completed {
        parent_operation_id: Uuid,
        model: String,
        response: String,
    },
}

pub struct StateUpdate {
    pub key: String,
    pub text: Option<String>,
}

pub struct ToolOutcome {
    pub text: String,
    pub ok: bool,
    pub state_updates: Vec<StateUpdate>,
    pub displayed_state_keys: Vec<String>,
    pub capture: Option<serde_json::Value>,
}

impl ToolOutcome {
    pub fn success(text: impl Into<String>) -> Self;
    pub fn failure(text: impl Into<String>) -> Self;
}

pub struct ContextBudget { /* private projection */ }

impl ContextBudget {
    pub fn estimated_tokens(&self) -> u64;
    pub fn max_input_tokens(&self) -> u64;
    pub fn fits_state(
        &self,
        key: impl Into<String>,
        text: impl Into<String>,
    ) -> bool;
}

pub trait Host: Send {
    fn render_tool_call(&mut self, call: &ToolCall) -> anyhow::Result<String>;
    fn execute_tool<'a>(
        &'a mut self,
        call: ToolCall,
        operation_id: Uuid,
        budget: ContextBudget,
    ) -> HostFuture<'a, ToolOutcome>;
    fn complete_capture<'a>(
        &'a mut self,
        capture: serde_json::Value,
        contents: String,
        budget: ContextBudget,
    ) -> HostFuture<'a, ToolOutcome>;
    fn record(&mut self, event: AuditEvent) -> anyhow::Result<()>;
}

pub struct SessionRunRequest {
    pub user_id: String,
    pub operation_id: Uuid,
    pub rounds_used: u64,
    pub round_limit: u64,
}

pub struct PreparedRound {
    pub input: String,
    pub provider_input: String,
    pub continuation: Option<kcode_intelligence_router::AgentContinuation>,
    pub model: String,
    pub reasoning_effort: String,
    pub tool_description: String,
    pub timeout: Option<Duration>,
}

pub struct SessionInferenceRequest {
    pub user_id: String,
    pub operation_id: Uuid,
    pub round: u64,
    pub prepared: PreparedRound,
}

pub struct SessionInference { /* private native provider turn and state */ }

pub enum SessionInferenceEvent {
    ProviderInput {
        context: kcode_codex_runtime_v2::ModelContext,
    },
    UsageUpdated {
        usage: kcode_codex_runtime_v2::TokenUsage,
    },
    ToolCall {
        call_id: String,
        call: Result<ToolCall, ToolCallError>,
    },
    Completed {
        answer: String,
        usage: Option<kcode_codex_runtime_v2::TokenUsage>,
        receipt: Box<kcode_intelligence_router::UsageReceipt>,
        continuation: Option<kcode_intelligence_router::AgentContinuation>,
    },
}

impl SessionInference {
    pub async fn next_event(
        &mut self,
    ) -> anyhow::Result<Option<SessionInferenceEvent>>;
    pub async fn respond(
        &mut self,
        call_id: &str,
        result: kcode_codex_runtime_v2::ToolResult,
    ) -> anyhow::Result<()>;
    pub fn finish_unavailable(
        &mut self,
    ) -> anyhow::Result<Box<kcode_intelligence_router::UsageReceipt>>;
}

pub enum RoundPreparation {
    Run(PreparedRound),
    Complete(Option<String>),
}

pub enum SessionEvent {
    InferenceSubmitted {
        round: u64,
        manifest_hash: String,
        model: String,
    },
    ProviderInput {
        round: u64,
        context: kcode_codex_runtime_v2::ModelContext,
    },
    UsageUpdated {
        round: u64,
        usage: kcode_codex_runtime_v2::TokenUsage,
    },
    ProviderReceipt {
        round: u64,
        usage: Option<kcode_codex_runtime_v2::TokenUsage>,
        receipt: Box<kcode_intelligence_router::UsageReceipt>,
        continuation: Option<kcode_intelligence_router::AgentContinuation>,
    },
}

pub struct SessionToolOutcome {
    pub text: String,
    pub ok: bool,
    pub capture: Option<serde_json::Value>,
    pub stop: bool,
    pub finish_after_round: bool,
    pub emitted_response: bool,
}

pub enum ProviderResume {
    Continue(SessionToolOutcome),
    Complete(Option<String>),
    RestartFresh,
}

impl SessionToolOutcome {
    pub fn success(text: impl Into<String>) -> Self;
    pub fn failure(text: impl Into<String>) -> Self;
}

pub struct RoundCompletion {
    pub answer: String,
    pub used_tool: bool,
    pub finish_requested: bool,
    pub emitted_response: bool,
}

pub enum SessionControl {
    Continue,
    Complete(Option<String>),
}

pub trait SessionHost: Send {
    fn prepare_round<'a>(
        &'a mut self,
        round: u64,
    ) -> HostFuture<'a, RoundPreparation>;
    fn record<'a>(&'a mut self, event: SessionEvent) -> HostFuture<'a, ()>;
    fn execute_tool<'a>(
        &'a mut self,
        call: anyhow::Result<ToolCall>,
        provider_operation_id: Uuid,
    ) -> HostFuture<'a, SessionToolOutcome>;
    fn prepare_provider_resume<'a>(
        &'a mut self,
        outcome: SessionToolOutcome,
    ) -> HostFuture<'a, ProviderResume>;
    fn complete_capture<'a>(
        &'a mut self,
        capture: serde_json::Value,
        contents: String,
    ) -> HostFuture<'a, SessionControl>;
    fn complete_round<'a>(
        &'a mut self,
        completion: RoundCompletion,
    ) -> HostFuture<'a, SessionControl>;
}

pub struct SessionRoundLimitError { /* private limit */ }
pub fn is_session_round_limit(error: &anyhow::Error) -> bool;

pub struct RunRequest {
    pub user_id: String,
    pub parent_operation_id: Uuid,
    pub model: String,
    pub reasoning_effort: String,
    pub context: Vec<String>,
    pub task: String,
    pub timeout: Option<Duration>,
    pub start_metadata: serde_json::Value,
}

pub struct RunResult {
    pub answer: String,
    pub model: kcode_intelligence_router::ResolvedAgentModel,
}

#[derive(Clone)]
pub struct AgentRuntime { /* private router */ }

impl AgentRuntime {
    pub fn new(intelligence: kcode_intelligence_router::Intelligence) -> Self;
    pub async fn resolve_model(
        &self,
        requested: &str,
    ) -> anyhow::Result<kcode_intelligence_router::ResolvedAgentModel>;
    pub async fn start_session_inference(
        &self,
        request: SessionInferenceRequest,
    ) -> anyhow::Result<SessionInference>;
    pub async fn run_session<H: SessionHost>(
        &self,
        request: SessionRunRequest,
        host: &mut H,
    ) -> anyhow::Result<Option<String>>;
    pub async fn run<H: Host>(
        &self,
        request: RunRequest,
        host: &mut H,
    ) -> anyhow::Result<RunResult>;
}
```

## Host-independent primary inference

`start_session_inference` starts exactly one native primary provider turn from
the caller-prepared `PreparedRound`. It applies the supplied user attribution,
parent operation, continuation, model, reasoning effort, single
`call_ktool` definition, and optional timeout. It does not render the logical
context again, execute application tools, spawn work, poll, write application
state, or retain a reference to the application Session, Chatend, or host.

`SessionInference::next_event` hides raw transport-input events and returns
normalized submitted model context, live usage, strictly parsed tool calls, and
one terminal completion. Invalid tool calls are returned as bounded
`ToolCallError` values with the exact native call ID; they do not abort the
inference handle. Terminal completion returns the assistant answer, final usage,
canonical router receipt, and native continuation exactly once.

`respond` sends one native success or failure result for a surfaced call ID. If
the native response fails, the handle remains available for interrupted-call
accounting. `finish_unavailable` accounts for an interrupted active turn and
terminalizes the handle. Successful terminal completion and successful
unavailable accounting are single-use; later handle operations fail clearly.

Provider start and stream errors preserve their original router error as a
source and retain any canonical receipt attached by the router. Callers must
record a returned or attached receipt exactly once. Dropping a still-active
handle is not evidence that the provider had no effect.

## Primary-session compatibility loop

`run_session` remains public and behavior-compatible. It is implemented as the
application-host adapter over `SessionInference`.

The adapter resumes at `rounds_used`, rejects a zero limit or a restored count
above the limit, and calls `SessionHost::prepare_round` before every provider
call. `PreparedRound::input` is the complete logical projection used for audit
identity, while `provider_input` is the new context delta transported to a typed
native continuation, or the same full projection for a fresh thread. Before
starting the handle, the adapter records `InferenceSubmitted` using the
SHA-256 identity of `input`.

The normalized submitted model context and every usage update are delivered as
`SessionEvent` values. Parsed and invalid tool calls both pass through
`execute_tool`; host errors abort the run, while `SessionToolOutcome::ok`
controls the native success or failure result sent to the provider.

Immediately before every native tool response resumes the provider,
`prepare_provider_resume` selects one of three boundary actions. `Continue`
keeps the append-only behavior: its possibly amended result is sent through
`SessionInference::respond`, and the current provider round continues.
`Complete` accounts for the interrupted current call and returns without another
inference.

`RestartFresh` is for a successful tool whose effect and result were already
durably applied by the host. The adapter does not send a native tool result,
does not invoke `complete_round` for the interrupted round, and does not retain
or replay the tool outcome. It accounts for the interrupted provider call and
selects the next cumulative outer round. The host can return no continuation
from the next `prepare_round` to start a fresh native thread. If interrupted
accounting fails, the adapter returns that failure instead of selecting another
round, so it cannot replay an ambiguous effect.

Provider-start, stream, provider-end, host-tool, resume-preparation, and native
response failures retain the established receipt paths. `capture` directs the
terminal answer to `complete_capture`. Otherwise `complete_round` receives the
answer plus accumulated tool and delivery facts. `stop` returns `Ok(None)` after
a resumed response unless the host completes at the pre-resume boundary.
`SessionControl` selects another affine round or the final optional answer.

Exhausting the cumulative limit returns `SessionRoundLimitError`. Use
`is_session_round_limit` instead of matching its text.

## Single-turn subagent loop

`run` resolves one exact model for the entire run and renders, in order, the
immutable context sections, task, exact call/result history, and latest
replaceable state. The provider input must fit the resolved model's maximum
input capacity including the runtime's protocol reserve.

`StateUpdate.key` is a stable identity: a later update replaces the prior
projected value, and `None` removes it. `ContextBudget::fits_state` predicts the
complete projection after one replacement. A host may identify complete state
values displayed by a tool result through `ToolOutcome::displayed_state_keys`.
When a later successful update uses the same key, the old projected result is
replaced in place with `[Tool output was displayed here, but has since been
updated and now appears elsewhere in the context]`, and only the latest complete
value is rendered after history. Removing that state uses a truthful removal
marker. Exact audited results are never rewritten.

A successful tool result whose exact result and updates would exceed the budget
is audited with `projection_accepted = false` and is returned to the provider as
a failure without changing projected state. Successful results are retained
exactly in provider context; the resolved model's input capacity is the size
boundary.

A delegated run starts exactly one fresh ephemeral native provider turn.
Multiple dynamic tools, including successive state updates, execute
sequentially inside that turn. A non-`None` `ToolOutcome::capture` designates
the same turn's nonempty terminal answer as the complete captured contents and
passes it to `Host::complete_capture`; an empty terminal capture is an explicit
failure. `run` returns only a nonempty terminal result and the resolved model.

## Ownership boundary

The crate owns provider-round sequencing, host-independent native inference
state, native bridge parsing and responses, fresh subagent rendering, capacity
estimation, state replacement, capture sequencing, cancellation lineage,
receipt surfacing, and round limits. Hosts own prompts, application tool
authorization and effects, durable audit/checkpoint storage, Kmap and object
access, session lifecycle, and interpretation of opaque JSON metadata or
capture tokens. The intelligence router remains the sole provider, credential,
model-catalog, cancellation, and canonical receipt owner.