Skip to content

fix(v2): isolate project capture and persist acknowledged batches - #112

Open
Dhravya wants to merge 2 commits into
mainfrom
capy/isolate-project-capture-and
Open

Dhravya wants to merge 2 commits into
mainfrom
capy/isolate-project-capture-and

Conversation

@Dhravya

@Dhravya Dhravya commented Oct 12, 2026 •

Copy link
Copy Markdown
Member

Keep each OpenCode 2 directory's tools, context hooks and capture alive, and route global session events only to their owning location. Same-directory replacements await registration disposal and capture flush, then transfer unfinished capture state through a plain snapshot that works across separately evaluated bundle generations.

Persist received capture ACKs and fully submitted turn fingerprints in atomic host plugin storage, scoped to the API destination, namespace, directory and session. Reloads/restarts skip confirmed uploads; partial remainder batches exclude already submitted turns. Unreadable or malformed ledgers defer capture without overwriting the original. Failed requests remain retryable without a permanent attempt cap; received ACKs whose storage commit fails are retained across same-process replacement and retried for persistence. Compaction summaries use the same scoped ledger and teardown barrier. Deletion awaits any existing summary retry outside the conversation queue; failed sends or dirty ACKs retain deleted-session state until recovery commits the ACK and removes the terminal ledger. All owner-cache inserts are bounded, and optional storage removal is guarded.

This maintainer change builds on BobbyNooby's per-directory ownership proposal in #99, a-monteiro's owner registry in #107, and ishaanxgupta's event routing, flush ordering and durable-capture proposal in #110. Their branches and tests are untouched. It intentionally does not adopt #110's three-attempt delivery cutoff. Related issues: #104 and the confirmed-ACK replay portion of #106.

Compatibility boundaries

  • The .17 → .18 first transition is not universally replay-free. Released .17 has no persistent ACK ledger: its old cleanup replays accepted history, and the first ledger-less .18 resume cannot distinguish prior acceptance from pending content. The native upgrade observed one replay at each boundary; subsequent ledger-backed reload/restart suppressed the old batches. Configuration, synthetic credential bytes, namespace and original host transcripts were preserved.
  • A timed-out POST may have been accepted after the client stopped waiting. Stable IDs are not an exactly-once guarantee; SDK5 documents append behavior. No speculative document-GET reconciliation, processing/billing guarantee or retry abandonment is added.
  • A process exit before a failed ACK-ledger write recovers still leaves acceptance/persistence uncertainty. Same-process handoff is not a durable commit.
  • The existing 100,000-character cap remains lossy for oversized turns. Only fully submitted turns get overlap-suppression fingerprints; an accepted exact truncated request retains its existing batch-ACK behavior.
  • SDK5/legacy routing, configuration precedence, namespace derivation and the merged V1 server entry are unchanged. Reserve patch version 2.0.18; no publisher/workflow changes.

Verification

  • Existing suite unchanged: 33 pass, 0 fail; typecheck, build, git diff --check, and cold npm pack pass. No test-file or test-workflow changes.
  • Unmodified official Linux OpenCode V2 2.0.26, installed packed artifacts, synthetic local model/memory APIs: native A → B → A tools/context/capture, eight baseline ACK IDs each posted once through activation/reload/full restart, four failed attempts then recovery, malformed/failed HTTP 200 rejection, valid differing canonical document ID, late acceptance after timeout, atomic-storage failure/recovery and cross-bundle handoff, compaction, session deletion and concurrent sessions.
  • At corrected head 54611b9, native deletion during a 2.5-second summary ACK and a storage fault installed after native DELETE retain/commit ACK knowledge before terminal cleanup, with one summary acceptance and no late ledger resurrection or repost. A cross-evaluated-class source probe retains the deleted/dirty snapshot.
  • Native SIGTERM flushes a cadence-three remainder; resumed partial-overlap capture sends only the new turn. The final packed artifact also preserves oversized original host text and suppresses its exact accepted request replay without marking unsent turn coverage complete.
  • V1 native 1.18.35 remains blocked by the unchanged installer's V2 permissions config before loading; no unrelated compatibility fix is included. Production APIs, real credentials, paid inference, user devices, publication and merges were not used.

The confirmed inherited 100k partial-delivery finding remains open for a separately reviewed, stacked V2 fragment-delivery follow-up. This mechanical PR preserves the disclosed cap rather than silently dropping an oversized turn or changing V1 delivery policy.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

@capy-ai capy-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capy found 1 potential issue (1 high).

View 4 other findings in Capy.

Open in Capy

Comment thread src/v2/runtime.ts
Comment on lines +1195 to +1205
const submittedMessageCount = result.submittedMessageCount ?? messages.length;
state.acknowledgedCaptureIds.add(acknowledgedId);
let messageCount = 0;
for (const turn of batch.turns) {
messageCount += turn.messages.length;
if (messageCount <= submittedMessageCount) {
state.acknowledgedTurnFingerprints.add(sha256(JSON.stringify(turn)));
}
}
state.captureLedgerDirty = true;
await this.#persistCaptureLedger(sessionID, state);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partial 100k truncation ACKs batch and skips remainder turns

High Severity

When ingestConversation truncates at MAX_CONVERSATION_CHARS, #saveBatch still adds the full-batch acknowledgedId, fingerprints only turns fully covered by submittedMessageCount, and returns success. #captureSessionEnd and #captureCadence invoke saveBatch once per batch and treat that success as complete, so they never re-enter with the unfingerprinted suffix. Default/session-end batches (captureEveryNTurns 0, or any multi-turn batch over 100k) therefore drop every turn after the truncation point on flush, session.deleted, or shutdown; the ledger can record success and clear state while those turns were never uploaded. A later retry of the same remaining set also returns success at the acknowledgedId hit when no turns were fingerprinted (first turn alone exceeds the cap), so sibling turns stay blocked. Fix by only batch-ACKing full submits, returning false (or looping saveBatch) while unfingerprinted turns remain, and marking an oversized in-progress turn exhausted so the suffix can upload as its own batch.

Open in Capy

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed: the inherited 100k cap can omit an oversized batch's suffix. This finding stays open for the approved, separately reviewed V2 fragment-delivery follow-up after the mechanical isolation/deletion head is reviewed. That follow-up will preserve ordinary IDs and the per-request budget, retain original ledgers/transcripts, checkpoint deterministic pieces independently, and retry unACKed content; it will not mark an oversized turn permanently exhausted or dropped.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
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