Skip to content

feat(harness): distinguish recorded context from Assistant tasks [SAP-3292] - #1020

Closed
ynadge wants to merge 1 commit into
assistant-context-consumerfrom
assistant-prepared-context
Closed

ynadge wants to merge 1 commit into
assistant-context-consumerfrom
assistant-prepared-context

Conversation

@ynadge

@ynadge ynadge commented Sep 14, 2026 •

Copy link
Copy Markdown
Contributor

Primary change type

  • Feature

Problem and motivation

A no-reply continuation seed must not look like a failed human request, trigger automatic recovery, or inflate retained task counts.

Summary and scope

Bind the recorded seed to exact server-projected native IDs, operation, text and hash. Verify its bounded brief in the browser, display it as provenance, and exclude only the exact unanswered seed from task status and archive counts. Preserve arbitrary synthetic messages, later human requests and unexpected answers. Add the typed missing-native-history review action.

Related work

Related issue: https://linear.app/sapiom/issue/SAP-3292

Depends on #1019. Native stack target: main.

Validation

55 focused tests passed across seed attestation, turn status, record projection and capture. Full Harness server/web TypeScript, scoped server and explicit browser ESLint, formatting and diff checks passed.

Tests and documentation

Fifteen seed cases include exact empty preparation, twelve independent identity/content mismatches, missing attestation, changed hash, later user task and unexpected native answer. Browser flow coverage follows with Continue controls. Updated assistant-history.md. Increment:430 changed lines including tests/docs/changeset; SDK pr:size is absent.

Compatibility and release impact

  • Normal task/recovery semantics stay authoritative for every unverified message. No runtime, provider or tool work is introduced by display.
  • Changeset: @sapiom/harness patch.
  • Fix-forward: retain exact attestation checks and keep recorded input separate from explicit human dispatch while correcting display/status behavior.

Security

  • No secrets, credentials, private data, or unsanitized logs included.
  • No suspected vulnerability disclosed publicly.

AI assistance

  • AI assistance used: Codex implemented and reviewed the change with the validation described above.

Checklist

  • CONTRIBUTING.md read; maintainer-authorized issue work.
  • One focused increment, checks, documentation and release impact addressed.
  • I can explain and maintain the submitted changes.

Devin Review

@coderabbitai

coderabbitai Bot commented Sep 14, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 8b5292c1-fccc-43ab-80d8-085ce080ef46

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@devin-ai-integration devin-ai-integration 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.

Devin Review found 4 potential issues.

Devin Review

Comment on lines +174 to +180
if (
!attached ||
(attached.continuation &&
!(await verifyAssistantContinuationView(
attached.continuation,
attached.conversationId,
)))

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.

🔴 Prepared child provenance never arrives

When a prepared child opens, attached.continuation is absent because publicLease omits it. The seed appears as a failed human request after Continue and every reload.

Learn more

The chat can distinguish a continuation seed only when it receives an AssistantContinuationView. Normal opening calls /opencode/:id/attach, whose response comes from publicLease and contains only conversationId, lease, and lifecycle. The separate Continue response projects provenance, but no browser code carries that sibling field into later Attach responses. Consequently parseAssistantAttachment accepts a valid attachment with no continuation, and all new filtering and display logic remains disabled.

Example: Continue prepares child child-1 with seed msg_seed. Opening child-1 receives a valid attachment without continuation; openCodeTurn sees msg_seed as the latest user request and reports failed instead of ready.

Recommended fix: Make the server resolve and project the verified prepared receipt for every child Attach response, including retries and reloads. Include it inside the attachment object before the browser parses it, and preserve the existing exact conversation and hash checks.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +72 to +74
private readonly continuation?: (
signal: AbortSignal,
) => Promise<AssistantContinuationView | null>,

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.

🔴 Captured seeds remain human tasks

For prepared children, continuation stays undefined because the production factory omits the resolver. Captures retain the seed and inflate task counts.

Learn more

The new archive filtering runs only when AssistantRecordCapture receives an AssistantContinuationView. The sole production construction site still passes three arguments, so the optional resolver is always absent outside tests. Every capture therefore calls projectAssistantRecord with null, preserving the old behavior for the exact scenario this change targets.

Example: A prepared child contains only its no-reply seed. Its first observer checkpoint projects one turn and one message instead of zero because no continuation attestation reaches the capture.

Recommended fix: Construct the continuation store before the observer factory and pass a resolver that reads the hosted child receipt, validates the prepared phase and exact binding, then projects an AssistantContinuationView. Keep the lookup under the capture signal and fail the checkpoint if a linked child cannot be verified.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +287 to +290
const native = assistantTaskMessages(
validated as unknown as OpenCodeTurnMessage[],
continuation,
) as unknown as typeof validated;

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.

🟡 Existing seed counts never disappear

When a retained record contains the seed, assistantTaskMessages removes it only from the new projection. reconcileAssistantRecord restores it, so counts remain inflated.

Learn more

Capture reconciliation treats every missing turn as native compaction and restores it from the previous record. The new seed filter also expresses removal as a missing turn, so reconciliation cannot distinguish deliberate exclusion from compaction. This affects records created before this behavior ships and records written by any checkpoint that lacked attestation.

Example: Revision 4 contains only msg_seed and reports one turn. Revision 5 projects zero turns after verifying the seed, but reconciliation unions revision 4 back into revision 5 and still reports one turn.

Recommended fix: Remove an exact attested seed from the previous record during reconciliation, or pass enough provenance into reconciliation to exclude that turn before unioning. Recompute counts so the deliberate removal is not preserved by the monotonic Math.max logic.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +851 to +854
{notice.code === "native_history_missing" && onReviewHistory && (
<button type="button" className="btn-ghost" onClick={onReviewHistory}>
Review saved Assistant history
</button>

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.

🔍 Saved-history control lacks wiring

The native_history_missing button requires onReviewHistory, but the chat caller omits it. Follow up on wiring the retained-history navigation.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@ynadge ynadge closed this Sep 15, 2026
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