Skip to content

[Bug]: V2 ACP adapter persists every streamed tool_call_update, Devin file writes bloat statev2.sqlite #16428

Description

@darjss

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

apps/server

Steps to reproduce

  1. Add Devin through the ACP registry (acpRegistry, agent devin).
  2. Ask it to create a ~150 line file in one write.
  3. Count the events written for that tool call: SELECT count(*) FROM orchestration_events WHERE event_type = 'turn-item.updated' AND payload_json LIKE '%<toolCallId>%'.

Deterministic version without Devin: inject one tool_call and then 200 tool_call_updates into AcpAdapterV2. Each update carries the file so far as a diff content block plus rawInput, and the last one has status: "completed". Then count turn_item.updated events for the file_change item.

Expected behavior

A streamed tool call persists a bounded number of snapshots, as V1 did after #7279 fixed #6556. decideToolCallUpdateEmission emits on status/title changes, on 256+ chars of growth, every 10th skipped update, and always on completed/failed.

Actual behavior

V2 persists every streamed tool_call_update as a full turn-item.updated plus a node.updated. Each snapshot repeats the whole file so far, so the bytes grow quadratically with file size.

Devin streams file writes as it generates them when T3 sends cognition.ai/messageGrouping in clientCapabilities._meta, which AcpRegistryAdapterV2 does. I captured the raw ACP traffic with T3's exact initialize capabilities. Writing a 4.5 KB file produced 1,156 tool_call_updates in 17 s. Without that meta flag, the same prompt produced 3.

The 200-update repro above against main (76d3c96fd5) gives:

file_change updates persisted Serialized bytes
main 202 849 KB
with the gate below 22 89.5 KB

On my server (184 threads, three weeks of mostly Devin use), statev2.sqlite grew to 17.8 GB:

event_type Rows Payload
turn-item.updated 1,919,296 6.0 GB
node.updated 1,584,636 1.8 GB
message.updated 102,796 658 MB
all others 73,694 ~310 MB

One Devin write of a 79 line file produced 2,194 turn-item.updated events in 32 s. 15,486 of the last 23,026 turn-item.updated rows were pending snapshots. The DB compresses 27x with zstd, so nearly all of it is repeated content.

Cause: #7279's gate still runs in AcpSessionRuntime.handleSessionUpdate (AcpSessionRuntime.ts:2895), but it only filters the runtime's event queue. AcpAdapterV2 registers its own runtime.handleSessionUpdate(...) handler and calls emitTool for every ToolCallUpdated from parseSessionUpdateEvent, so V2 never goes through the gate. ThreadLiveEventCoalescer only throttles the live push, not what is written.

Possible fix: in emitTool, run decideToolCallUpdateEmission for updates without a projectedStatus. Keep merging every update into context.tools, and skip only the persisted write. Turn-end terminalization passes a projectedStatus, so tools it closes still get their latest merged state written. A background tool that keeps running after the turn can lag by up to 9 updates until its next write, which matches V1. This is about 25 lines in AcpAdapterV2.ts plus a regression test in AcpAdapterV2.test.ts, which fails on main with 202 updates. I can open a PR if this approach works for you.

Impact

Major degradation or frequent failure

Version or commit

Nightly 0.0.46-nightly.20261003.2632. Code reading and repro on main 76d3c96fd5.

Environment

Headless t3 service on Linux (Ubuntu, x86_64), Devin CLI 3000.11.3 via the ACP registry.

Logs or stack traces

No errors. The cost shows up as event volume. Related: #14701 (slow reads on large databases) and #15707 (event write stalls).

Workaround

None found in the app.

Investigated and written with Claude Opus 5.5 via Claude Code.

Activity

  1. juliusmarminge commented on Oct 6, 2026

    @juliusmarminge
    Member

    Note

    Grok responding on behalf of Julius.

    Triage

    Thanks @darjss for the excellent report, the repro numbers and database breakdown made this very easy to verify.

    What I found

    Likely fix area

    The approach you outlined lines up with what I found. Details that seem important for it in AcpAdapterV2.ts:

    • Keep merging every update into context.tools (and the background-task, hydration, and subagent bookkeeping), and skip only the node.updated / turn_item.updated offers. Still call rearmDeferredFinalize when a write is skipped.
    • Let updates with a projectedStatus bypass the gate. Turn-end terminalizeOpenForegroundTools / terminalizeOpenRunOwnedItems pass "completed" / "interrupted", and the gate only forces an emit for completed / failed, so an interrupted tool could otherwise lose its final write.
    • Keep lastEmittedDetailLength and skippedSinceEmit beside the merged tool, since context.tools changes even when the write is skipped.
    • Apply the same gate to the child-session branch.
    • A regression test in AcpAdapterV2.test.ts (1 tool_call + 200 growing updates) would be expected to land around ~22 persisted rows: the first update, every 10th change, the completed update, and the turn-end write.

    One limit worth noting: toolCallProgressLength counts detail, text content blocks, and rawOutput text, but not diff newText or rawInput. So streamed file writes coalesce on the every-10th heartbeat rather than on 256 characters of growth. That's the existing V1 rule and matches your 849 KB → 89.5 KB numbers; snapshots still contain the whole file, so very large writes can still grow the log.

    Since you offered to open a PR, linking it to this issue would help reviewers connect the two. A maintainer will decide on the fix direction.

  2. added
    bugSomething is broken or behaving incorrectly.
    via-triageFiled through npx t3 triage
    on Oct 6, 2026
  3. added a commit that references this issue on Oct 8, 2026
    10e29a2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions