Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
f426445
feat(transport): 默认禁用 SDK HTTP keep-alive 以缓解流中 ECONNRESET
sunerpy Jul 31, 2026
a813442
feat(streaming): 暴露流转换器只读观测状态(sawToolIntent/reasoning 阶段/dialect 状态)
sunerpy Jul 31, 2026
9a834bc
feat(request): 失败日志补三通道观测字段并新增无条件发送记录
sunerpy Jul 31, 2026
18ef21b
docs(config): 记录流观测字段与 stream_max_attempts 语义
sunerpy Jul 31, 2026
dbc85f2
feat(request): 新增流恢复协调器(跨 attempt 单一 SSE 生命周期与 Tier 判定)
sunerpy Jul 31, 2026
59730c4
feat(config): 新增 stream_recovery_mode 并为流终端 503 补 Retry-After
sunerpy Jul 31, 2026
6185fbb
feat(request): live 流输出后失败接入 Tier A reasoning-only 恢复与签名安全闸门
sunerpy Jul 31, 2026
ddd4a62
test(request): 补齐 Tier A 恢复故障注入矩阵
sunerpy Jul 31, 2026
232d7fb
feat(request): Tier B 精确影子重放(三通道前缀追平 + exact_replay 档)
sunerpy Jul 31, 2026
04ca20f
test(request): 补齐 Tier B 精确重放验收矩阵
sunerpy Jul 31, 2026
1722897
fix(request): 语义截断改为未闭合工具意图判定,修复恢复档全量误判
sunerpy Jul 31, 2026
b3ed493
fix(request): 恢复入口初始 attempt 失败补齐单次 onTerminal
sunerpy Jul 31, 2026
5a544a4
fix(streaming): dialect 工具意图闭合改为三态并与解析器代码区规则对齐
sunerpy Jul 31, 2026
e96a9c7
fix(request): 恢复入口初始失败不再过早执行请求级清理
sunerpy Jul 31, 2026
1fff159
fix(streaming): 恢复档下未闭合 dialect 不再泄漏文本与部分工具调用
sunerpy Jul 31, 2026
88d4e16
fix(history): 折叠轮次 content 置空以消除历史污染
sunerpy Jul 31, 2026
85a7eaa
fix(history): 清洗客户端回放的历史污染标记
sunerpy Jul 31, 2026
be056d3
fix(history): <thinking> 文本回放收敛为活跃工具环内最近一轮
sunerpy Jul 31, 2026
e19851b
fix(history): 合并相邻同角色轮次以消除合成 assistant 标记
sunerpy Jul 31, 2026
d9fcc3b
docs(probes): 更正工具结果填充修复的实现状态
sunerpy Jul 31, 2026
218dd48
docs(agents): 同步流恢复、观测与历史污染修复到知识库
sunerpy Jul 31, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
207 changes: 171 additions & 36 deletions AGENTS.md

Large diffs are not rendered by default.

16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,15 @@ multi-account or long-idle setups, enable
(`token_keepalive_enabled: true`) to keep idle accounts' tokens fresh while
OpenCode is running.

For long-running agent tasks that are frequently interrupted by upstream
`ECONNRESET` event-stream failures, enable
`"stream_buffer_until_complete": true`. The plugin then withholds a failed
attempt from OpenCode and safely retries it instead of exposing a partial
assistant response or partial tool call. See
[stream recovery configuration](docs/CONFIGURATION.md#options) for the latency
and quota tradeoffs.
The SDK transport uses fresh HTTP sockets by default
(`"sdk_http_keep_alive": false`) to reduce Bun stale-connection
`ECONNRESET` failures without serializing requests or delaying live tokens.
Each request pays one additional TCP/TLS handshake, while active streams and
multiple OpenCode processes remain concurrent. For workloads that prefer task
continuity over live output even after a mid-stream failure,
`"stream_buffer_until_complete": true` remains available. See
[stream recovery configuration](docs/CONFIGURATION.md#options) for the
different latency and quota tradeoffs.

Paid-overage protection is on by default; see
[Overage protection](docs/CONFIGURATION.md#overage-protection) before disabling
Expand Down
77 changes: 73 additions & 4 deletions docs/CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ root [README](../README.md#configuration) for the short version.
"max_request_iterations": 20,
"sdk_response_timeout_enabled": false,
"sdk_response_timeout_ms": 300000,
"sdk_http_keep_alive": false,
"stream_event_timeout_enabled": false,
"request_timeout_ms": 120000,
"stream_buffer_until_complete": false,
Expand Down Expand Up @@ -107,6 +108,14 @@ because moving a live database during an upgrade is unsafe.
- `sdk_response_timeout_ms`: Fixed SDK response deadline when
`sdk_response_timeout_enabled` is `true` (30000-600000ms, default: `300000`).
Override with `KIRO_SDK_RESPONSE_TIMEOUT_MS`.
- `sdk_http_keep_alive`: Reuse a completed SDK HTTP connection for a later
request (default: `false`). The default gives every request a fresh socket to
avoid Bun reusing a stale pooled connection. This does not serialize or cap
active streams: the SDK still permits up to 50 concurrent sockets per client,
and multiple OpenCode processes remain independent. The tradeoff is one
additional TCP/TLS handshake per request. Set this to `true` only when
connection reuse has proven stable in your runtime. Override with
`KIRO_SDK_HTTP_KEEP_ALIVE`.
- `stream_event_timeout_enabled`: Opt into a fixed inactivity deadline between
upstream stream events (default: `false`). It is disabled because high-effort
models can legitimately compute for several minutes between events, so event
Expand All @@ -130,10 +139,18 @@ because moving a live database during an upgrade is unsafe.
process-local Kiro request queue until the upstream response completes.
Override with `KIRO_STREAM_BUFFER_UNTIL_COMPLETE`.
- `stream_max_attempts`: Maximum complete event-stream attempts (`1`-`10`,
default: `3`). In normal live-stream mode, retries remain limited to failures
before semantic output. With `stream_buffer_until_complete` enabled, this
limit also covers failures after upstream output because none of that attempt
has reached OpenCode yet. Override with `KIRO_STREAM_MAX_ATTEMPTS`.
default: `3`). This caps the total SDK sends for **one** inbound provider
request — the initial send plus any pre-output stream retries — so `3` means
at most three `generateAssistantResponse` calls for that request. It is
distinct from `max_request_iterations`, which bounds the overall per-request
loop that also covers HTTP-error retries and account switches. Both budgets
apply at the same time: a stream retry is refused once `stream_max_attempts`
is reached even if loop iterations remain, and the loop still stops at
`max_request_iterations` regardless of remaining stream attempts. In normal
live-stream mode, retries remain limited to failures before semantic output.
With `stream_buffer_until_complete` enabled, this limit also covers failures
after upstream output because none of that attempt has reached OpenCode yet.
Override with `KIRO_STREAM_MAX_ATTEMPTS`.
- `token_expiry_buffer_ms`: Token refresh buffer time (30000-300000ms, default:
`300000`). An access token within this window of expiry is treated as expired
and refreshed on next use.
Expand Down Expand Up @@ -201,6 +218,58 @@ settings can also be overridden with `KIRO_LOG_RETENTION_DAYS`,
`KIRO_LOG_MAX_TOTAL_SIZE_MB`, `KIRO_LOG_COMPRESS_AFTER_DAYS`, and
`KIRO_LOG_SEGMENT_SIZE_MB`.

## Stream observability logging

Stream health is tracked in `plugin.log` independently from
`enable_log_api_request`, so you can measure upstream stream failures without
recording prompt or tool payloads. Two records anchor that measurement, and
every stream log line carries a fixed set of volume-only fields.

**`Kiro stream request started`** (INFO) is written exactly once per inbound
streaming request, unconditionally — it does not depend on
`enable_log_api_request`, and non-streaming requests are not recorded. Fields:
`conversationId`, `model`, `effectiveModel`, `processId`. This is the
denominator every stream-failure rate is measured against, so an account switch
or HTTP-error retry inside the same inbound request still produces only one
record. The string is a grep target for log-analysis scripts; it is exported as
`STREAM_REQUEST_STARTED_LOG` from `src/core/request/request-handler.ts` and is
treated as a stable contract.

**`Kiro stream ended without completion metadata`** (WARN, exported as
`STREAM_MISSING_COMPLETION_LOG`, `outcome:
'clean_eof_without_completion_metadata'`) fires when the upstream event stream
ends cleanly but never sent completion metadata. The response still completes
normally, exactly as before — this record adds no behavior change, it only makes
a case visible that previously left no trace at all. A rising count here means
upstream is closing streams early without erroring, which is worth watching even
though nothing fails today.

Every stream log record — the clean-EOF warning above and each stream failure
outcome (`retrying`, `exhausted`, `terminated_after_output`,
`ignored_after_completion_metadata`, `recovered`) — carries these shared fields:

| Field | Meaning |
| ------------------------------------------- | -------------------------------------------------------------------------------- |
| `conversationId`, `model`, `effectiveModel` | Request identity and the resolved wire model |
| `region`, `account`, `accountId` | Which account and region served the attempt |
| `streamAttempt`, `maxStreamAttempts` | Attempt number and the `stream_max_attempts` cap |
| `streamDeliveryMode` | `buffered` or `live`, from `stream_buffer_until_complete` |
| `sdkHttpKeepAlive` | The effective `sdk_http_keep_alive` value |
| `processId`, `bunVersion` | OS process id and the Bun runtime version |
| `upstreamEventCount` | Raw upstream events seen in this attempt |
| `streamElapsedMs` | Wall time from the start of this stream attempt |
| `emittedReasoningChars` | Character count of reasoning text already emitted |
| `emittedVisibleChars` | Character count of visible reply text already emitted |
| `emittedToolCount` | Number of tool calls already emitted |
| `sawToolIntent` | Whether upstream showed tool intent, including a partial or discarded tool event |

The last four are lengths, counts, and a boolean only. No reasoning text, reply
text, or tool arguments are ever written to these records — a character count
cannot reconstruct content. They exist so a failed attempt can be classified
after the fact: an attempt with zero emitted characters, zero tool calls, and no
tool intent is safe to reason about differently from one that already put output
in front of you.

## Account distribution across processes

If you run several OpenCode processes at once (multiple terminals, editor
Expand Down
Loading
Loading