Skip to content

feat(cloud-agent-next): attribute incomplete Kilo session failures - #5068

Open
eshurakov wants to merge 2 commits into
mainfrom
eshurakov/diligent-birch
Open

feat(cloud-agent-next): attribute incomplete Kilo session failures#5068
eshurakov wants to merge 2 commits into
mainfrom
eshurakov/diligent-birch

Conversation

@eshurakov

@eshurakov eshurakov commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Summary

Why

Twelve code-review sessions on 2026-08-04/05 ended with incomplete Kilo results (output-limit terminations, unresponsive wrappers), but the Worker terminalized all of them identically — wrapper_error_after_activity / wrapper_liveness, up to ~90s after the real cause. The terminal record erased which watchdog fired and why, making the failure class invisible to dashboards and support. The wrapper is the only component that observes Kilo's event stream directly, so it is the right place to attribute the mechanically detectable endings; the DO stays a durable coordinator.

What was done

  • Preserve the specific watchdog failure code (wrapper_no_output, wrapper_ping_timeout, wrapper_disconnected) when agent activity was observed, instead of collapsing it into wrapper_error_after_activity; the {agent_activity, code} pairs were added to the queue-report allowlist so those reports are not silently dropped by telemetry validation.
  • Detect output-limit termination proactively in the wrapper: a root-session assistant message.updated with finish === 'length' (or a MessageOutputLengthError) raises terminal failure kilo_output_limit in ~1s, and the completion signal is intentionally not armed for that message. Child sub-agent messages hitting output limits are explicitly excluded and never terminalize the run. Detection is skipped while state.isFinalizing so post-completion turns (e.g. condense-on-complete) cannot false-fail a sealed run.
  • Thread the code through the fixed taxonomy: ingest protocol enum, worker-utils classification (buckets as platform / wrapper_liveness, so reporting taxonomy is unchanged), safe-failure projection, telemetry diagnostics, the web terminal-reason map (assistant_failed), and the DB text().$type<>() union — no migration.
  • Resolve the cloud.status broadcast and persisted error-event message from the structured failure code for kilo_output_limit, so live status, cloud.message.failed, callbacks, and push all show the same per-code message instead of a generic "Assistant request failed".

Not in this PR: empty-terminal-response / visible-text failure. Idle without an explicit error is success; a tools-only empty final assistant message is complete.

High-level architecture

sequenceDiagram
  participant Kilo as Kilo server (sandbox)
  participant Wrapper as Wrapper (sandbox)
  participant Ingest as Ingest WS (Worker)
  participant DO as Session DO (supervisor)
  participant Web as Web / callback

  Kilo->>Wrapper: SSE message.updated / session.idle
  alt finish = "length" on root assistant message
    Wrapper->>Ingest: fatal error event (failureCode=kilo_output_limit)
  else normal completion
    Wrapper->>Ingest: completion signal (unchanged path)
  end
  Ingest->>DO: forwardIngestTerminalEvent(status=failed, failureCode)
  DO->>DO: terminalizeSessionMessageOnce — stage from agent activity, per-code safe message
  DO-->>Web: cloud.message.failed + callback
Loading

Worker-side liveness paths (runMaintenance no-output/ping watchdogs, disconnect grace) follow the same settlement flow and now keep their specific code when agent activity was observed.

Architecture decision

Decision: Attribute incomplete-session endings in the wrapper from Kilo SDK event shapes (finish === 'length') and preserve the specific post-activity watchdog code, rather than inferring causes Worker-side after watchdog expiry.

Context: Once a session goes silent, the Worker has no bounded authoritative Kilo terminal query — disconnect or liveness expiry terminalizes accepted work as a generic wrapper failure. Only the wrapper sees the event stream where output-limit endings are mechanically distinguishable.

Rationale: Wrapper-side detection fails the run in ~1s with a precise code instead of ~90s later with a collapsed one, and it matches the service's ownership boundary: Kilo/job behavior lives in the wrapper, the DO remains a durable coordinator.

Alternatives considered:

  • Detect output limits via error.name === 'MessageOutputLengthError'. The SDK types that error, but the current Kilo server never constructs it — it appends an ignored warning text part and persists finish: "length" — so the predicate would ship as a well-tested no-op. (The error-name check is still OR-ed in for forward-compatibility.)
  • Keep collapsing post-activity watchdog failures into wrapper_error_after_activity. That erases which watchdog fired at the terminal record; downstream consumers already map the specific codes, so preservation is behavior-neutral for classification.
  • Fail empty stop-finished assistant responses. Rejected: idle without an explicit error is the normal success path; requiring visible final text would false-fail tools-only runs.

Consequences: kilo_output_limit shares the existing wrapper_liveness reason bucket, so dashboards keep their current taxonomy while gaining the specific code. Detection is deliberately narrow (root session only; skipped while finalizing). Rollback skew is a known gap: an old worker receiving an unknown failureCode drops the terminal frame, and the watchdog terminalizes ~90s later with a generic code; ingest schema hardening for that case is a noted follow-up, not part of this PR.

Verification

No manual verification performed. The wrapper detection path is covered by a unit suite (test/unit/wrapper/terminal-failure-attribution.test.ts) and the Worker paths by extended supervisor/ingest unit tests; the local fake-LLM e2e harness (services/cloud-agent-next/test/e2e) is manual and was not run.

Visual Changes

N/A

Reviewer Notes

  • No DB migration: failure_code is text().$type<CloudAgentSessionRunFailureCode>(), so the union extension is TS-only.
  • Automated checks: cloud-agent-next unit tests (ingest, wrapper-supervisor, wrapper attribution suite), worker-utils classification tests, typecheck, oxlint.
  • Out of scope / follow-ups: Tier 2a (forwarding cgroup OOM events wrapper→Worker), and forward-compatible failureCode parsing in the ingest schema for rollback skew.

…fic terminal failure codes

- Preserve watchdog failure codes (wrapper_no_output, wrapper_ping_timeout,
  wrapper_disconnected) after agent activity instead of collapsing them into
  wrapper_error_after_activity
- Detect finish=length output-limit terminations on the root session in the
  wrapper and fail fast with kilo_output_limit
- Fail code-review runs whose final root assistant message has no visible
  text with kilo_empty_terminal_response instead of sealing them as complete
- Thread both codes through the DB type union, worker-utils classification
  (platform/wrapper_liveness), queue-report allowlist, safe-failure
  projection, telemetry diagnostics, and web terminal-reason mapping
- Resolve cloud.status and persisted error event messages from the structured
  failure code for the new codes so all user-facing surfaces agree
Comment thread services/cloud-agent-next/wrapper/src/connection.ts Outdated
Comment thread services/cloud-agent-next/wrapper/src/connection.ts Outdated
@kilo-code-bot

kilo-code-bot Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

Incremental review of the 14 files changed since commit cdc751e: the kilo_empty_terminal_response detection was removed end-to-end (taxonomy, wrapper tracking, tests) and kilo_output_limit detection is now gated on !state.isFinalizing, which resolves both prior findings (stale root-assistant tracker, post-completion false-fail) with high confidence and no new issues introduced.

Files Reviewed (14 files)
  • apps/web/src/lib/code-reviews/terminal-reason-from-failure.ts
  • packages/db/src/schema.ts
  • packages/worker-utils/src/cloud-agent-failure.test.ts
  • packages/worker-utils/src/cloud-agent-failure.ts
  • packages/worker-utils/src/cloud-agent-queue-report.ts
  • services/cloud-agent-next/src/session/safe-failure-projection.ts
  • services/cloud-agent-next/src/session/wrapper-supervisor.test.ts
  • services/cloud-agent-next/src/session/wrapper-supervisor.ts
  • services/cloud-agent-next/src/shared/protocol.ts
  • services/cloud-agent-next/src/telemetry/queue-reports.ts
  • services/cloud-agent-next/src/websocket/ingest.test.ts
  • services/cloud-agent-next/src/websocket/ingest.ts
  • services/cloud-agent-next/test/unit/wrapper/terminal-failure-attribution.test.ts
  • services/cloud-agent-next/wrapper/src/connection.ts
Previous Review Summary (commit cdc751e)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit cdc751e)

Status: 2 Issues Found | Recommendation: Address before merge

Executive Summary

The new wrapper-side empty-response/output-limit detection in connection.ts is not lifecycle-scoped: the lastRootAssistant tracker is never reset across session rebinds, and the root session.idle check fires on any later root turn (including post-completion condense summaries), so a healthy code-review run can be falsely terminalized with kilo_empty_terminal_response/kilo_output_limit.

Overview

Severity Count
CRITICAL 0
WARNING 2
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
services/cloud-agent-next/wrapper/src/connection.ts 514 lastRootAssistant is never reset on close/session rebind; stale {finish:'stop', hasVisibleText:false} plus a replayed root idle can false-fail the next code-review run
services/cloud-agent-next/wrapper/src/connection.ts 1324 Empty-response check (and the kilo_output_limit path) is not scoped to the review turn's terminal idle; post-completion root turns (e.g. condense-on-complete summarize) can false-fail a successful review
Files Reviewed (14 files)
  • apps/web/src/lib/code-reviews/terminal-reason-from-failure.ts
  • packages/db/src/schema.ts
  • packages/worker-utils/src/cloud-agent-failure.test.ts
  • packages/worker-utils/src/cloud-agent-failure.ts
  • packages/worker-utils/src/cloud-agent-queue-report.ts
  • services/cloud-agent-next/src/session/safe-failure-projection.ts
  • services/cloud-agent-next/src/session/wrapper-supervisor.test.ts
  • services/cloud-agent-next/src/session/wrapper-supervisor.ts
  • services/cloud-agent-next/src/shared/protocol.ts
  • services/cloud-agent-next/src/telemetry/queue-reports.ts
  • services/cloud-agent-next/src/websocket/ingest.test.ts
  • services/cloud-agent-next/src/websocket/ingest.ts
  • services/cloud-agent-next/test/unit/wrapper/terminal-failure-attribution.test.ts
  • services/cloud-agent-next/wrapper/src/connection.ts - 2 issues

Verified non-issues: taxonomy threading is consistent across all seven enumeration sites (DB union, worker-utils codes/classifier, queue-report allowlist, protocol enum, telemetry, safe-failure projection, web reason map) with no typos; the model-not-found diagnostics gating is preserved; omitting assistantFailureReason/providerOwnership for the new codes is safe for all consumers; {stage, code} pairs emitted by the supervisor are all covered by the queue-report allowlist; no unbounded-memory growth introduced (lastRootAssistant is a single replaced object). The platform/wrapper_liveness bucketing of the new codes matches the PR's documented intent.

Fix these issues in Kilo Cloud


Reviewed by kimi-k3 · Input: 60.8K · Output: 12.4K · Cached: 601.1K

Review guidance: REVIEW.md from base branch main

Idle without an explicit error is success. Remove kilo_empty_terminal_response
and the visible-text tracker; keep output-limit fail-fast and watchdog code
preservation. Skip kilo_output_limit while finalizing so post-completion turns
cannot false-fail a sealed run.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant