Kiro Provider native Responses integration
Status: the Zuno session-affinity slice was implemented on 2026-08-27. The 2026-08-28 compiled-provider validation is reflected here. Native Kiro instruction projection remains owned upstream; native document transport is a separate unimplemented Zuno capability.
Decision
Zuno should continue to use kiro-provider as an independently deployed loopback service through the native Rust OpenAI Responses transport. It must not embed the provider, copy authentication identities, add private headers, or encode provider state in model-visible prompts.
The current integration is functionally usable, with one upstream limitation:
- Claude Opus 5 and GPT 5.6 Sol complete real Zuno turns;
- tools, streaming, effort, usage, cancellation, and durable Zuno history flow through the normal provider interface;
- the gateway owns OpenCode-shared authentication, account selection, reasoning envelope sealing and validation, and upstream transport pools;
- encrypted reasoning replay is a shared contract rather than a gateway-only concern: the gateway mints and validates the envelope, while Zuno must ask for
include: ["reasoning.encrypted_content"]and echo every sealed item back verbatim in the order it was produced. Zuno declares that per provider withreasoningReplay: "encrypted"; - Zuno sends each foreground root or child durable session id through standard Responses
metadata.zuno_session_id, and keeps lifecycle model calls isolated; - the provider owns live account-aware model discovery, history-lineage fallback, account/conversation pools, its single-instance database lock, strict stream completion, and exact reasoning-placeholder filtering;
- current Zuno instructions require the gateway's explicit
legacy-user-prefixmigration mode. Stable completion remains blocked until Kiro has a lossless native instruction projection.
No Kiro-specific Zuno plugin is required for those provider-owned behaviors. In particular, Zuno must not install a request-rewriting plugin that strips unsupported fields or changes prompt content. Provider compatibility remains a validated custom-provider configuration plus the standard Responses adapter.
Pre-affinity validation baseline
The live validation used:
- Zuno release binary SHA-256
cafc5200e0ac40cd36643b86b57d25ae0c28eb1a2f9a05594aeaa85dc82a4e4d; - kiro-provider binary SHA-256
80c9eb07885f1fc20aefb94f80872af8e0559cea1a6e1b3a653510fe9fe19ee5; surface: "responses",maxTokens: null, and a private loopback Bearer key;- provider
auth_source: "opencode-shared",session_affinity_mode: "explicit-only",sdk_http_keep_alive: false, andprotocol_projection_mode: "legacy-user-prefix".
Observed results:
| Route | Result | Provider evidence |
|---|---|---|
deep → claude-opus-5 | shell wrote 23 exact bytes, read returned the file, final text was ZUNO_KIRO_OPUS5_TOOL_OK | main calls used effort=high |
build → gpt-5.6-sol | final text was ZUNO_KIRO_GPT56_SOL_OK | main call used effort=max |
/health and authenticated /ready | both healthy after service restart | 59 catalog entries, including all six Opus 5 variants |
The same baseline logs showed affinity_bound=false for every Zuno request. That request-shaping gap is the Zuno-side issue addressed by this design.
Post-implementation E2E
The installed release binary used for the affinity validation had SHA-256 d89ea044875e32143630d243b1070647e3944ffe381c54d93a714e695e2a4824. The kiro-provider binary remained 80c9eb07885f1fc20aefb94f80872af8e0559cea1a6e1b3a653510fe9fe19ee5.
The real validation covered two model families:
| Route | Durable session | Zuno result | Provider evidence |
|---|---|---|---|
deep → claude-opus-5 | ses_600cc6ef5e47472cab0a2087b26cd8a2 | native read of Cargo.toml, exact turn-1 marker, provider service restart, native read of AGENTS.md, exact turn-2 marker | all four requests used responses.metadata.zuno_session_id, affinity_bound=true, one stable account hash, and one stable conversation hash across the provider-process restart |
build → gpt-5.6-sol | ses_13ee353337a24a08b8743659ac1e7e4b | native read of Cargo.toml, then exact marker ZUNO_AFFINITY_GPT56_SOL_OK | both requests used responses.metadata.zuno_session_id, affinity_bound=true, one stable account hash, one stable conversation hash, and effort=max |
For Opus 5, zuno session list still exposed the durable session after the gateway restart, and resuming that id produced the same upstream affinity binding. For GPT 5.6 Sol, the second tool-loop request hit both the gateway's transport and SDK client pools. No private header or model-visible session prefix was used for either route.
Implemented call path
The main path is:
durable SessionIdentity
-> zuno-cli turn host
-> zuno-engine run_turn_in_span
-> completion_request
-> zuno_llm::CompletionRequest + private ProviderRequestContext
-> official OpenAI endpoint: zuno-provider-openai
custom OpenAI baseURL: zuno-provider-compatible
-> Responses metadata.zuno_session_id
-> POST /v1/responses2
3
4
5
6
7
8
9
Both Responses adapters emit the same standard field:
{
"metadata": {
"zuno_session_id": "ses_..."
}
}2
3
4
5
It hashes this value together with the authenticated tenant and protocol, then persists the resulting account/conversation binding. The raw id does not enter Kiro input or any model-visible field.
Implemented design
1. Provider-neutral request context
CompletionRequest carries a private typed context rather than a Kiro-specific field or arbitrary metadata bag:
pub enum ProviderRequestContext {
MainTurn(ProviderSessionIdentity),
ChildTurn(ProviderSessionIdentity),
Title,
Summary,
Compaction,
Reflection,
Council,
}2
3
4
5
6
7
8
9
ProviderSessionIdentity validates and wraps the durable ses_... identifier. It is routing state, not prompt content and not an arbitrary JSON metadata bag. RequestPurpose is derived from the context for durable observability.
2. Consumer ownership
The turn host supplies the identity:
- every main-turn request and tool continuation uses the root durable session id;
- every delegated child uses its own child session id;
- resume and process restart reuse the same durable id;
- title, summary, compaction, learning extraction, and Council calls have an explicit purpose and no main-session affinity unless their design later declares an independent durable identity.
The engine passes this context when constructing the request. Providers do not rediscover it from messages, prompt hashes, directories, or headers.
3. OpenAI Responses projection
Only an OpenAI Responses surface maps the typed identity:
"metadata": { "zuno_session_id": "<durable id>" }Chat Completions and Anthropic Messages do not receive a fabricated equivalent. The official OpenAI provider and the compatible provider used by custom OpenAI base URLs implement the same rule. Existing user/provider metadata may coexist, but zuno_session_id is reserved: an attempted override returns a typed local request error rather than replacing the durable identity.
The field is added after ordinary body construction and before sending. It must remain absent from input, instructions, tool definitions, logs that could cross trust boundaries, and provider headers.
4. Durable observability
The model-visible request remains reconstructable from durable events. Each foreground provider-request event also records non-model-visible routing intent:
requestPurpose;affinityAttached;affinitySource: "durable-session"when attached.
The event deliberately omits the raw durable id. Zuno also does not log raw gateway keys, OpenCode credentials, replay tokens, reasoning envelopes, or upstream account/conversation identifiers.
5. Effort and resume semantics
Effective effort must be part of the resolved session/agent execution state. Resuming a session or supplying an explicit --model must not silently turn high or max into null. The implementation should resolve one effective model plus effort before constructing every request, persist the selection needed for restart, and use the same rule for tool continuations.
The CLI should also reject unsupported --variant combinations before starting a session with a message that names the supported alternative.
6. Provider lifecycle and catalog
The provider remains one long-lived OS-user service:
- Zuno never spawns one process per session;
- startup/readiness failures are reported as provider unavailability;
- cancellation remains request-scoped and does not stop the shared service;
- gateway upgrades are independent from Zuno upgrades.
Zuno's static custom-provider catalog remains authoritative for capabilities. models --refresh may later import a signed/validated compatible catalog, but must not infer attachment, reasoning, context, or output capabilities from a model id alone.
The checked Kiro profile intentionally advertises only text and inline image input. The 2026-08-28 provider accepts inline Responses input_file documents in its verified native formats, but Zuno currently has no native document request block: ACP resource_link remains typed durable metadata and lowers to text, while inline bytes exist only for images. Advertising PDF now would therefore overstate the Zuno-to-provider path. Native documents should land only when the typed block, durable storage/replay, model capability check, compatible Responses projection, and real Kiro E2E test arrive together.
Current provider configuration is:
{
"provider": {
"kiro-local": {
"transport": "openai",
"surface": "responses",
"retry": {
"max_attempts": 3,
"recovery_window_ms": 660000
},
"options": {
"baseURL": "http://127.0.0.1:8787/v1",
"maxTokens": null,
"timeout": false,
"headerTimeout": 330000,
"chunkTimeout": 330000
}
}
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
The gateway owns a 900-second request deadline and a 300-second stream-idle deadline. Zuno's chunk limit remains 30 seconds larger, while the whole-request timeout is disabled, so the gateway can return a typed timeout. Only then does the 660-second retry recovery window start.
The provider now retains consecutive all-text blocks in its canonical request and joins them byte-for-byte with no inserted separator only at Kiro's scalar text boundary. The old responsesTextBlocks: "single" Zuno compatibility option must be removed because it deliberately inserts one blank line. Mixed text and non-text blocks whose ordering cannot be preserved still fail closed.
Do not configure reasoningSummary for Kiro routes. Zuno correctly lowers an explicit summary request to standard reasoning.summary, while the provider correctly rejects that field because no lossless Kiro equivalent is proven. reasoning.effort remains supported. This boundary is configuration, not a reason for provider-id special casing or a field-stripping plugin.
Encrypted reasoning replay is the one provider capability that does require Zuno adaptation, because half of the contract is the client's. A gateway that seals reasoning still returns unsealed items to a client that never sends include, and it rejects an echo whose items are summary-only or out of order. Zuno therefore asks for the envelope, persists each sealed item as its own positioned durable part, and replays the items in the position the model produced them, scoped to the model that sealed them and to the endpoint's validity window. See Harness runtime.
Two details of the gateway's validation decide whether a replay is accepted, and both are client obligations:
- the envelope is bound to a SHA-256 fingerprint of the turn it sealed, computed over the assistant text plus each tool call's
id,name, and itsargumentsstring as the gateway sent it. Re-serializing the parsed arguments changes key order and spacing, which is why Zuno persists the provider's own bytes on the tool part and replays those, and answers a mismatch with400 reasoning_replay_context_mismatch; - a reasoning item with no following output in the same turn is
400 invalid_reasoning_replay, because there is nothing to fingerprint. Durable history reaches that shape honestly, so Zuno withholds such an item instead of replaying it and failing every later request to that model.
An automatic Goal continuation creates another assistant turn without admitting a user message. The standard Responses representation is the real developer context that started that continuation, placed between the two assistant output runs. Zuno persists only the prompt-receipt reference on the new turn's first assistant row and reconstructs the actual post-hook suffix from that receipt; older rows recover the reference from their provider-request event. The engine keeps generic Message content free of this Responses-only concern and attaches a structured ResponsesInputBoundary sidecar to the later request message. One shared cursor projects that boundary for OpenAI, compatible, and Bedrock Responses paths. Kiro V3 already routes instructions/system/developer input through its native or ordered stateless lane, so no private Kiro-only boundary item or token-based turn guessing is required. When an old export or compaction summary has no receipt, Zuno withholds only the ambiguous capsules and keeps the transcript usable; malformed history that survives that repair still fails locally without exposing token bytes.
Zuno's own routing adds a third obligation. reasoningReplay: "encrypted" is accepted only beside transport: "openai" and surface: "responses": a custom baseURL under the openai-compatible transport resolves its surface from provider-id rules and lands on Chat Completions, where the option can never take effect. Config validation refuses the silent combinations rather than running a whole session that asks for sealing and never gets it.
The remaining 2026-08-28 provider changes do not require Zuno adaptation:
- live model catalogs and per-account eligibility are routing facts owned by the long-lived provider; Zuno's catalog remains the validated client capability declaration;
- history-lineage affinity is a provider fallback for clients without metadata; Zuno continues to send the stronger durable
metadata.zuno_session_id; - singleton SQLite ownership, transport/client pools, and completion witnesses are internal provider lifecycle concerns;
- GPT 5.6 Sol placeholder suppression is response semantics owned by the provider and must not be duplicated in Zuno rendering.
TDD evidence
zuno-llmtests cover typed purpose, identity validation, and inaccessible routing state.- Engine tests prove one identity across a main tool loop, a distinct child identity, isolated title/summary/compaction/learning/Council requests, and durable routing provenance.
- Both OpenAI Responses adapters test exact metadata projection, collision rejection, unrelated metadata preservation, and absence on Chat where applicable.
- Encrypted replay is proven from user-shaped config through the request body, and in the engine from a streamed sealed item through persistence to an ordered multi-item replay, model-switch withholding, envelope expiry, and the three durable evidence fields.
- Autonomous Goal replay tests cover persisted and prompt-receipt-recovered developer boundaries, provider ordering, local fail-closed behavior, and token-redacted diagnostics.
- A real CLI loopback integration captures title plus a two-request tool loop, proves the title is isolated, proves identical foreground metadata, and proves the raw identity never enters
instructionsorinput. - Real kiro-provider validation proves
affinity_bound=trueand stable account/conversation hashes for Opus 5 across a provider-process restart and for a GPT 5.6 Sol native tool loop. - Documentation tests reject a stale Kiro
responsesTextBlocks: "single"option and rejectreasoningSummaryin the checked Kiro model definitions.
Changing CompletionRequest affects all native providers and compaction callers. Constructors should therefore be updated in one change; providers other than OpenAI Responses explicitly ignore the new routing context.
Completion gates
The Zuno-owned session-affinity slice is complete only when:
- Opus 5 and GPT 5.6 Sol text and tool loops pass;
- same Zuno session, including restart, logs
affinity_bound=trueand stable account/conversation hashes; - different root and child sessions remain isolated;
- title, summary, compaction, learning extraction, and Council do not join the main provider conversation;
- cancellation, usage, retry classification, proxy settings, and provider errors continue through existing typed interfaces;
- no private header, prompt prefix, copied OAuth identity, hard-coded project id, or authorization bypass is introduced.
Those Zuno-owned gates passed on 2026-08-27. Stable completion of the whole native Kiro integration has one separate upstream gate: the gateway must be able to return to protocol_projection_mode: "safe" after a proven lossless Kiro instruction projection exists.
Until that upstream item is satisfied, the supported deployment is an explicit pre-release migration configuration, not a stable native Kiro integration.