Skip to content

feat(session): deterministic session continuity via logical keys#376

Merged
marcusrbrown merged 1 commit intomainfrom
feat/agent-cohesion-session-continuity
Mar 23, 2026
Merged

feat(session): deterministic session continuity via logical keys#376
marcusrbrown merged 1 commit intomainfrom
feat/agent-cohesion-session-continuity

Conversation

@marcusrbrown
Copy link
Copy Markdown
Collaborator

Summary

  • Repeated agent runs on the same PR, issue, discussion, or scheduled task now continue the same OpenCode session instead of creating a new one each time
  • Session titles encode entity-scoped logical keys (e.g., fro-bot: pr-347) for deterministic lookup with content-based search as fallback
  • Prompt restructured with Instruction Sandwich pattern: non-negotiable rules at top + constraint reminder at end, thread identity before task context
  • fro-bot.yaml now uploads opencode-logs artifact for all trigger types

What Changed

Phase 1: Logical Keys + Session Resolution

File Change
src/services/session/logical-key.ts New — buildLogicalKey(), buildSessionTitle(), findSessionByTitle(), resolveSessionForLogicalKey() with tri-state return
src/services/session/logical-key.test.ts New — 21 tests covering all 7 event types, edge cases, exact-match collision safety, compacting guard

Phase 2: Session Continuity in Execution

File Change
src/harness/phases/session-prep.ts Resolve logical key → set continueSessionId/isContinuation
src/features/agent/execution.ts Conditional create/continue, title on create, best-effort title re-assertion via session.update()
src/harness/phases/execute.ts Pass new params through to executeOpenCode()
src/services/session/writeback.ts Logical key in summary (Logical Thread: pr-347)
src/shared/types.ts logicalKey on RunSummary
src/features/agent/types.ts continueSessionId/sessionTitle on ExecutionConfig

Phase 3: Prompt Restructuring

File Change
src/features/agent/prompt-thread.ts New — Non-Negotiable Rules, Thread Identity, Current Thread Context, Constraint Reminder section builders
src/features/agent/prompt.ts Reordered sections: rules at top, thread identity + task before env/history, agent context demoted, constraint reminder at end
src/features/agent/types.ts logicalKey/isContinuation/currentThreadSessionId on PromptOptions
src/features/agent/prompt.test.ts 3 new tests for section ordering

Phase 4: Observability

File Change
.github/workflows/fro-bot.yaml upload-artifact step with retention-days: 7, compression-level: 9, include-hidden-files: true

Design Decisions

  • Title as identity: Session titles (fro-bot: pr-347) are the primary lookup mechanism — no external state files to corrupt or lose
  • Title re-assertion: OpenCode auto-overwrites titles after first message; we re-set via session.update() after each prompt
  • Exact match only: fro-bot: pr-34 must NOT match fro-bot: pr-347 — server-side search + client-side exact filter
  • Tri-state resolver: found/not-found/error — SDK failures are never collapsed to "not found"
  • Compacting guard: Sessions mid-compaction are skipped (same as archived)
  • Instruction Sandwich: Non-negotiable rules at position 1 AND constraint reminder at end (U-shaped LLM attention model)

Testing

  • 1064 tests passing (70 files, 4 new tests added)
  • 0 lint errors (31 pre-existing warnings)
  • Types clean, build passes, dist/ in sync
  • Key test coverage: all 7 event types, prefix collision safety, resolver tri-state, compacting guard, prompt section ordering

Known Limitations (Deferred)

  • Schedule key uses cron-hash only — multi-workflow same-cron collision possible (low risk for v1)
  • No explicit busy-state preflight — relies on compacting guard + SDK's own assertNotBusy() rejection
  • XML tag migration for prompt sections deferred to future iteration

Post-Deploy Monitoring & Validation

  • What to monitor: Agent run logs for Session continuity: entries showing found existing session vs no existing session found
  • Expected healthy behavior: Repeated runs on the same PR show found existing session with consistent session IDs
  • Failure signals: Every run shows no existing session found despite prior runs existing → title lookup broken
  • Validation window: First 48h after merge, spot-check 3-5 PR workflows
  • Rollback trigger: If session continuity causes agent failures (prompt rejection, session corruption), revert this PR

Origin

  • Brainstorm: docs/brainstorms/2026-03-22-agent-cohesion-session-context-brainstorm.md
  • Plan: docs/plans/2026-03-22-feat-agent-cohesion-session-continuity-plan.md

Repeated agent runs on the same PR, issue, discussion, or scheduled task
now preferentially continue the same OpenCode session instead of creating
a new one each time. Session titles encode entity-scoped logical keys
(e.g., `fro-bot: pr-347`) for deterministic lookup with content-based
search as fallback.

- Add logical key derivation for all 7 trigger families
- Title-based session resolution with exact-match lookup
- Tri-state resolver (found/not-found/error) with compacting guard
- Best-effort title re-assertion after prompt to counter auto-overwrite
- Prompt restructured: non-negotiable rules at top, thread identity
  before task, current thread context before generic history,
  constraint reminder at end (Instruction Sandwich pattern)
- Upload opencode-logs artifact in fro-bot.yaml for all trigger types
- Logical key included in writeback summaries and execution logs
Copy link
Copy Markdown
Owner

@fro-bot fro-bot left a comment

Choose a reason for hiding this comment

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

Review Summary

This PR implements deterministic session continuity via logical keys, enabling the agent to resumecontext across multiple invocations on the same PR/issue/discussion. The implementation is well-designed, follows existing patterns, and has comprehensive test coverage.


Code Quality Assessment

✅ Architecture & Design

Logical Key System (logical-key.ts):

  • Clean entity-to-key mapping across all 7 event types
  • Proper handling of schedule (SHA256 hash of cron) and workflow_dispatch (run ID)
  • Tri-state resolution (found/not-found/error) correctly distinguishes SDK failures from"no session found"

Session Continuity Flow:

  • Clear separation: session-prep.ts resolves → execution.ts conditionally creates/continues
  • Title re-assertion after prompt completion is a smart defensive measure against OpenCode's auto-title-overwrite behavior

Prompt Restructuring:

  • Instruction Sandwich pattern (rules at top, reminder at bottom) is well-motivated by LLM attention research
  • Thread identity section correctly positioned before task context to establish continuation awareness

✅Test Coverage

  • 21 tests in logical-key.test.ts covering all event types, edge cases, collision safety, compacting guard
  • 3 tests in prompt.test.ts validating new section ordering
  • All 1064 tests pass with clean types and build

✅ Implementation Quality

Strengths:

  1. Exact match safety: findSessionByTitle() uses strict equality (session.title === title), preventing pr-34 from matching pr-347
  2. Most-recent selection: When duplicate titles exist, correctly selects the most recently updated session
  3. Compacting/archived guard: Lines 120-122 in logical-key.ts properly skip mid-compaction sessions
  4. Error isolation: SDK failures don't halt execution (lines 68-72 in session-prep.ts)
  5. Function-only pattern: No ES6 classes, follows project conventions
  6. Logger injection: All functions take logger parameter as required

✅ Documentation

The PR description is excellent:

  • Clear design decisions documented
  • Known limitations acknowledged
  • Post-deploy monitoring plan included
  • Rollback triggers defined

Detailed Code Review

logical-key.ts (New File)

Lines 19-25 — Entity key builder:

function buildEntityKey(entityType: LogicalSessionKey['entityType'], entityId: string): LogicalSessionKey

Clean, deterministic entity key construction. ✅

Lines 27-29 — Schedule hashing:

function buildScheduleHash(value: string): string {
  return createHash('sha256').update(value).digest('hex').slice(0, 8)
}

First 8 chars of SHA256 provides sufficient uniqueness for cron expressions. Collision probability is acceptable for v1 as noted in PR description.

Lines 31-90 — Event type dispatch:
Complete coverage of all 7 event types with proper null handling for unsupported events andmissing targets. Well-structured.

Lines 96-103 — findSessionByTitle:
Correct exact match with most-recent selection for duplicates. Reduces to single pass with reduce()—efficient.

Lines 105-129 — resolveSessionForLogicalKey:
Proper tri-state return. SDK errors are logged but don't throw, allowing continuation with new session creation.

session-prep.ts (Modified)

Lines 48-78 — Session resolution flow:
Clear conditional logic with appropriate logging at each resolution state. Error path correctly falls through to new session creation.

execution.ts (Modified)

Lines 54-68 — Conditional create/continue:
Clean branching. Title is set on create via optional payload.

Lines 123-134 — Title re-assertion:
Defensive best-effort update wrapped in try/catch. Failures are logged at debug level (non-breaking). Smart pattern to counteract OpenCode's auto-title behavior.

prompt-thread.ts (New File)

All four builder functions are clean and focused:

  • buildNonNegotiableRulesSection() — Constant rules header
  • buildThreadIdentitySection() — Conditional continuation context
  • buildCurrentThreadContextSection() — Prior work excerpt
  • buildConstraintReminderSection() — Footer reminder

prompt.ts (Modified)

Lines 133-138 — Section ordering:
Correctly places thread identity immediately after critical rules for continuation awareness.

Lines 140-143 — Prior work filtering:
Correctly filters prior work for current thread session to avoid duplication in "Related Historical Context".


Minor Observations (Non-blocking)

1. Schedule Key Collision Risk

The PR description acknowledges this: multiple workflows with identical cron expressions will collide. The SHA256 approach is reasonable for v1, but consider future enhancement to include workflow name or ID in the hash seed.

2. No Explicit Busy-State Check

The implementation relies on the compacting guard and SDK's assertNotBusy() rejection. This is consistent with existing patterns but could be documented more explicitly in the code comments.

3. session.update Type Cast

Line 127 in execution.ts:

await (client.session as unknown as {update: (args: Record<string, unknown>) => Promise<unknown>}).update({

The type cast is necessary because session.update isn't in the typed SDK yet, but a TODO comment referencing the SDK API would be helpful for future maintainers.


Test Validation

Ran locally:

✓ 1064 tests passing (70 files)
✓ Type check clean
✓ 0 lint errors (31 pre-existing warnings)
✓ Build passes

Key test patterns verified:

  • Collision safety: findSessionByTitle tests confirm pr-34pr-347
  • Tri-state resolution: Error/not-found/found branches tested
  • Compacting guard: Archived and compacting sessions return not-found
  • Section ordering: Prompt tests verify instructional sandwich pattern

Verdict

Approved

This is a well-architected feature with:

  • Clear design decisions documented
  • Comprehensive test coverage
  • Clean integration with existing patterns
  • Proper error handling and fault tolerance
  • Good observability through logging

The known limitations are reasonable v1 trade-offs and are explicitly documented. The prompt restructuring with the Instruction Sandwich pattern is a thoughtful improvement based on LLM attention research.


Run Summary
Field Value
Event pull_request
Repository fro-bot/agent
Run ID 23455842050
Cache hit
Session ses_2e3d9a017ffeEgOWOx2XvDbXY9

Copy link
Copy Markdown
Owner

@fro-bot fro-bot left a comment

Choose a reason for hiding this comment

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

Review Summary

This PR implements deterministic session continuity via logical keys, enabling the agent to resume context across multiple invocations on the same PR/issue/discussion. The implementation is well-designed, follows existing patterns, and has comprehensive test coverage.


Code Quality Assessment

✅ Architecture & Design

Logical Key System (logical-key.ts):

  • Clean entity-to-key mapping across all 7 event types
  • Proper handling of schedule (SHA256 hash of cron) and workflow_dispatch (run ID)
  • Tri-state resolution (found/not-found/error) correctly distinguishes SDK failures from "no session found"

Session Continuity Flow:

  • Clear separation: session-prep.ts resolves → execution.ts conditionally creates/continues
  • Title re-assertion after prompt completion is a smart defensive measure against OpenCode's auto-title-overwrite behavior

Prompt Restructuring:

  • Instruction Sandwich pattern (rules at top, reminder at bottom) is well-motivated by LLM attention research
  • Thread identity section correctly positioned before task context to establish continuation awareness

✅ Test Coverage

  • 21 tests in logical-key.test.ts covering all event types, edge cases, collision safety, compacting guard
  • 3 tests in prompt.test.ts validating new section ordering
  • All 1064 tests pass with clean types and build

✅ Implementation Quality

Strengths:

  1. Exact match safety: findSessionByTitle() uses strict equality (session.title === title), preventing pr-34 from matching pr-347
  2. Most-recent selection: When duplicate titles exist, correctly selects the most recently updated session
  3. Compacting/archived guard: Lines 120-122 in logical-key.ts properly skip mid-compaction sessions
  4. Error isolation: SDK failures don't halt execution (lines 68-72 in session-prep.ts)
  5. Function-only pattern: No ES6 classes, follows project conventions
  6. Logger injection: All functions take logger parameter as required

✅ Documentation

The PR description is excellent:

  • Clear design decisions documented
  • Known limitations acknowledged
  • Post-deploy monitoring plan included
  • Rollback triggers defined

Detailed Code Review

logical-key.ts (New File)

Lines 19-25 — Entity key builder:

function buildEntityKey(entityType: LogicalSessionKey["entityType"], entityId: string): LogicalSessionKey

Clean, deterministic entity key construction. ✅

Lines 27-29 — Schedule hashing:

function buildScheduleHash(value: string): string {
  return createHash("sha256").update(value).digest("hex").slice(0, 8)
}

First 8 chars of SHA256 provides sufficient uniqueness for cron expressions. Collision probability is acceptable for v1 as noted in PR description.

Lines 31-90 — Event type dispatch: Complete coverage of all 7 event types with proper null handling for unsupported events and missing targets. Well-structured.

Lines 96-103 — findSessionByTitle: Correct exact match with most-recent selection for duplicates. Reduces to single pass with reduce()—efficient.

Lines 105-129 — resolveSessionForLogicalKey: Proper tri-state return. SDK errors are logged but don't throw, allowing continuation with new session creation.

session-prep.ts (Modified)

Lines 48-78 — Session resolution flow: Clear conditional logic with appropriate logging at each resolution state. Error path correctly falls through to new session creation.

execution.ts (Modified)

Lines 54-68 — Conditional create/continue: Clean branching. Title is set on create via optional payload.

Lines 123-134 — Title re-assertion: Defensive best-effort update wrapped in try/catch. Failures are logged at debug level (non-breaking). Smart pattern to counteract OpenCode's auto-title behavior.

prompt-thread.ts (New File)

All four builder functions are clean and focused:

  • buildNonNegotiableRulesSection() — Constant rules header
  • buildThreadIdentitySection() — Conditional continuation context
  • buildCurrentThreadContextSection() — Prior work excerpt
  • buildConstraintReminderSection() — Footer reminder

prompt.ts (Modified)

Lines 133-138 — Section ordering: Correctly places thread identity immediately after critical rules for continuation awareness.

Lines 140-143 — Prior work filtering: Correctly filters prior work for current thread session to avoid duplication in "Related Historical Context".


Minor Observations (Non-blocking)

1. Schedule Key Collision Risk

The PR description acknowledges this: multiple workflows with identical cron expressions will collide. The SHA256 approach is reasonable for v1, but consider future enhancement to include workflow name or ID in the hash seed.

2. No Explicit Busy-State Check

The implementation relies on the compacting guard and SDK's assertNotBusy() rejection. This is consistent with existing patterns but could be documented more explicitly in the code comments.

3. session.update Type Cast

Line 127 in execution.ts:

await (client.session as unknown as {update: (args: Record<string, unknown>) => Promise<unknown>}).update({

The type cast is necessary because session.update isn't in the typed SDK yet, but a TODO comment referencing the SDK API would be helpful for future maintainers.


Test Validation

Ran locally:

✓ 1064 tests passing (70 files)
✓ Type check clean
✓ 0 lint errors (31 pre-existing warnings)
✓ Build passes

Key test patterns verified:

  • Collision safety: findSessionByTitle tests confirm pr-34pr-347
  • Tri-state resolution: Error/not-found/found branches tested
  • Compacting guard: Archived and compacting sessions return not-found
  • Section ordering: Prompt tests verify instructional sandwich pattern

Verdict

Approved

This is a well-architected feature with:

  • Clear design decisions documented
  • Comprehensive test coverage
  • Clean integration with existing patterns
  • Proper error handling and fault tolerance
  • Good observability through logging

The known limitations are reasonable v1 trade-offs and are explicitly documented. The prompt restructuring with the Instruction Sandwich pattern is a thoughtful improvement based on LLM attention research.


Run Summary
Field Value
Event pull_request
Repository fro-bot/agent
Run ID 23455842050
Cache hit
Session ses_2e3d9a017ffeEgOWOx2XvDbXY9

@marcusrbrown marcusrbrown merged commit f157cfe into main Mar 23, 2026
10 of 17 checks passed
@fro-bot fro-bot mentioned this pull request Mar 24, 2026
10 tasks
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