Skip to content

fix(cloud-agent): stop terminal input from extending sandbox lifetime - #7946

Merged
eshurakov merged 2 commits into
mainfrom
fix/cloud-agent-terminal-idle-lifetime
Oct 19, 2026
Merged

eshurakov merged 2 commits into
mainfrom
fix/cloud-agent-terminal-idle-lifetime

Conversation

@eshurakov

@eshurakov eshurakov commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Remove terminal input from the control-plane heartbeat activity computation and remove its unused boolean helper/dependency. Recent input counts remain diagnostic-only.
  • Preserve preparation, native compute, bounded pending delivery, and finalization as activity sources. Keep degraded reporting unchanged.
  • Preserve accepted hello/heartbeat liveness updates to lastFrameAt. Reconnect and inactive heartbeat processing still do not refresh lastActivityAt or renew the provider lease.
  • Update docs/control-plane.md section 6 and stale terminal activity contracts. No new timer, idleMs/unattendedIdleMs change, or production logic moved outside wrapper/src/control-plane/.

Intended Trade-Off

An interactive terminal without an agent turn now lets the sandbox stop after the applicable idle window, even while terminal input continues. This is the intended trade-off, not a regression: terminal-only workloads must not hold a container and its provider lease indefinitely. Preparation, native compute, pending delivery, and finalization keep their existing protection. The existing 10-minute interactive and 1-minute unattended policies remain unchanged.

Regression Coverage

  • The real session-host composition and terminal adapter/shared WebSocket bridge receive terminal input while native compute and pending work are false; the heartbeat remains inactive. This test failed before the fix with active: true and passes after it.
  • Preparation remains active with no native compute or pending work. Pending delivery and blocked finalization also remain active independently of native compute.
  • Same-wrapper and replacement-wrapper reconnects update liveness without changing activity, producing a lease effect, or moving the original idle alarm. An inactive heartbeat preserves that deadline, and the reducer stops at the original idle boundary.

Verified

  • pnpm --filter cloud-agent-next run test: 224 files passed; 6,819 tests passed, 3 skipped.
  • pnpm --filter cloud-agent-next run typecheck: passed, including wrapper typecheck.
  • pnpm --filter cloud-agent-next run lint: passed, 0 warnings/errors.
  • pnpm --filter cloud-agent-next run build:wrapper: passed; all five bundles rebuilt, including control-plane-wrapper.js.
  • From services/cloud-agent-next/wrapper, bun test src/control-plane/main.test.ts src/control-plane/session-host.test.ts src/control-plane/terminals.test.ts src/control-plane/connection.test.ts: 79 passed, 0 failed.
  • pnpm --filter cloud-agent-next exec vitest run src/control-plane/sandbox/allocation.test.ts src/control-plane/session/terminals.test.ts: 17 passed.
  • Changed files formatted with pnpm format; git diff --check origin/main...HEAD passed.
  • GitHub CI passed the wrapper process tests, all remaining wrapper tests, isolated production-dependency bundle build, and Postgres aggregates, and Workers integration tests. CI unit, typecheck, lint, and format checks also passed.

Live Local Acceptance: Passed

Follow-up verification on 2026-10-18 used bash .kilo/cloud-agent-setup.sh, then pnpm dev:start --no-attach agents fake-llm. The supported script installed Docker/tmux, configured the environment/database, and built the sandbox images. The local stack was real Worker + Durable Objects + Docker sandbox + wrapper + Kilo CLI 7.8.1, with deterministic fake inference. No idle values or timer divisor were changed: the running wrapper reported divisor 1, and public status reported 600000ms.

A one-off ignored driver at services/cloud-agent-next/.wrangler/terminal-idle-acceptance.ts created a fresh browser worktree and a gated bootstrap turn. It connected two real terminal PTYs in the SAME exclusively owned sandbox: one silent/open and one sending harmless printf input every five seconds. Output confirmation required actual RS/US-delimited command results, not terminal command echo. Input began before releasing the bootstrap gate; after bootstrap completed, no further turns were sent and native idle/message/inference state was checked throughout.

Measured run: terminal-idle-fa89e112-ee60-4b3c-a5af-3ad02ca7ae53.

  • Host bundle, built sandbox image, and running wrapper all matched SHA-256 2fad31d778a1775ad417cf7b1a03dcf4d1810f7ee63ef4d5399c1014726950f7. The running wrapper process allocation ID matched the owned SQLite allocation 45a77299-c79c-4820-b187-4adfb1f01ca4.
  • lastActivityAt stayed fixed at 2026-10-18T20:17:02.393Z; the advertised sleep deadline stayed fixed at 20:27:02.393Z. There were 117 observed advances of accepted-heartbeat lastFrameAt while activity and deadline did not move.
  • 123 terminal input/output round trips were confirmed over 610035ms; maximum confirmed round-trip cadence gap was 5061ms. Both PTYs stayed connected through the deadline. No later native turn-open was observed; scoped fake inference remained exactly one request, including an independent final check after physical stop.
  • The exact allocation transitioned connected -> stopping from tick with stopReason=sandbox_stopped at 20:27:02.401Z. Provider native_stop was correlated by the SAME allocation intentId, started at 20:27:02.404Z, and returned terminal at 20:27:02.705Z.
  • Independent Docker daemon events for the exact main container 0e25d4095718 recorded physical die at 20:27:02.674000659Z, 281ms AFTER the unchanged deadline, and destroy at 20:27:02.688324911Z. Public status became sleeping. Probe acceptance was recorded at 20:27:04.030Z; cleanup began only afterward at 20:27:04.031Z. Thus cleanup did not produce the observed stop.
  • Safe local evidence is in services/cloud-agent-next/.wrangler/terminal-idle-evidence/terminal-idle-fa89e112-ee60-4b3c-a5af-3ad02ca7ae53/{evidence.jsonl,result.json}; result is passed: true. The probe/evidence are ignored local verification artifacts, not committed tests. An owned local egress proxy retained after the main runtime stop was cleaned up separately afterward; this result does not claim a proxy-lifecycle fix.

Remaining Verification Limits

  • The initial aggregate local pnpm --filter cloud-agent-next run test:wrapper did not complete within the 210-second environment limit. It reported unchanged restore-timing, connection-snapshot, and certificate-refusal failures; restore/connection passed in isolation, while local certificate refusal assumes a CA file absent that is present here. That exact local aggregate invocation remains incomplete. Subsequent CI passed BOTH normal wrapper partitions: bun test ./src/control-plane/process.test.ts and bun test src --path-ignore-patterns=**/control-plane/process.test.ts.
  • The live acceptance is one local interactive Cloudflare Containers case with two terminals in one sandbox and fake inference, not independent silent/input cases or an automated CI e2e scenario. Unattended-policy timing, multi-user/multi-session/restart cases, and hosted-provider behavior or actual hosted lease-renewal API calls were not validated end-to-end. No deployment was performed.
  • Two initial one-off-driver attempts failed before the measured idle observation because of driver-only UUID/request-signal mistakes. Those were corrected only in the ignored probe and cleaned up; they are not counted as passes. No tracked production, timer, environment-policy, or test-source changes were needed for this follow-up.

Wrapper Build / Version

No wrapper version bump is required by the current source-based build, and the heartbeat wire protocol is unchanged. WRAPPER_VERSION remains 2.4.0. The wrapper bundles were rebuilt successfully; wrapper/dist/ is repository-ignored and is not committed. Shipping still requires the normal rebuilt sandbox image/runtime artifact path.

@kilo-code-bot

kilo-code-bot Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

Reviewed the 8-file change removing terminal input from control-plane heartbeat activity; the change is a consistent, well-covered contract update with no correctness issues found in the changed lines.

Files Reviewed (8 files)
  • services/cloud-agent-next/docs/control-plane.md
  • services/cloud-agent-next/src/control-plane/sandbox/allocation.test.ts
  • services/cloud-agent-next/src/control-plane/session/terminals.ts
  • services/cloud-agent-next/wrapper/src/control-plane/main.ts
  • services/cloud-agent-next/wrapper/src/control-plane/session-host.test.ts
  • services/cloud-agent-next/wrapper/src/control-plane/terminals.test.ts
  • services/cloud-agent-next/wrapper/src/control-plane/terminals.ts
  • services/cloud-agent-next/wrapper/src/control/terminal-runtime.ts

Verification Notes

  • Heartbeat activity is now isPreparing() || runtimes.needsCompute() || manager.hasPendingWork(), matching the updated spec §6 text and the removed hasRecentTerminalInput dependency.
  • The new allocation test expectations align with reduceAllocation: hello-accepted on an existing connection and inactive heartbeat update only lastFrameAt, active heartbeats refresh lastActivityAt and emit the lease effect, and the idle tick still stops at lastActivityAt + idleMs.
  • No dangling references to hasRecentInput/hasRecentTerminalInput remain; recentInputCount and the onInput/inputAt diagnostics path are still consistent, and no new memory leak was introduced (the input map is still cleared on forgetSession/detachDirectory).
  • WRAPPER_VERSION is not a behavioral gate for heartbeat semantics, only a health/identity check, so leaving it at 2.4.0 is consistent with the unchanged wire protocol.

Reviewed the documented behavior trade-off (a terminal-only session no longer pins the sandbox/lease) and consider it intentional per the PR description rather than a defect.


Reviewed by deepseek-v4.1-flash · Input: 89.3K · Output: 13.1K · Cached: 1.3M

Review guidance: REVIEW.md from base branch main

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.

2 participants