fix(historian): defer child session cleanup

Closed
CO
2mo ago
+265-62

Summary

  • stop deleting successful historian children on the prompt-return path
  • retire stale historian children through the existing age-gated orphan sweep, including dreamer-disabled installs
  • budget the stale threshold for outer retries, model-suggestion retry, and configured fallback models
  • preserve historian children when keep_subagents is enabled
  • raise the default historian prompt timeout from 5 to 10 minutes
  • surface current OpenCode native Task wrappers whose completed <task_result> is empty

Closes #336.
Defensively addresses the silent empty-result symptom in #270.

Root cause

OpenCode prompt completion and session.idle are not global session-writer barriers. Detached step/summary writes can persist after either event. Immediate session.delete therefore races foreign-key-backed part inserts.

Verification

  • red: successful historian lifecycle observed immediate deletion without a quiescence barrier
  • green: 105 affected tests, 0 failures
  • plugin build and declaration emit pass
  • plugin typecheck passes
  • Biome format check passes
  • Oracle race review: SHIP

Notes

The sweep retains a minimum safety factor of 3 after accounting for all configured prompt candidates and retry layers. Recent/live children are not eligible.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


Summary by cubic

Defers historian child-session cleanup to an age-gated orphan sweep to avoid racing OpenCode’s detached writes and FK insert failures. Separates historian vs. privacy sweeps so privacy windows stay short while historian cleanup uses its own retry/fallback budget. Also raises the historian timeout and surfaces empty native Task completions for debugging.

  • Keeps historian children after prompt completion; the orphan sweep removes them after a window that covers outer retries, model-suggestion attempts, and the normalized fallback chain. Excludes historian children from the sweep when keep_subagents is true.
  • Preserves privacy windows: sweeps dreamer/privacy titles and historian titles separately. Privacy stale = task timeout × 3; historian stale = historian_timeout_ms × MAX_HISTORIAN_PROMPT_ATTEMPTS × (primary+suggested) × fallback_count.
  • Runs the orphan sweep even when Dreamer is disabled. Registers historian timeout and normalized fallback count (via resolveFallbackChain) and derives privacy timeouts before scheduling.
  • Updates configuration, docs, and schema: default historian_timeout_ms is 600000; exports MAX_HISTORIAN_PROMPT_ATTEMPTS = 3.
  • Diagnostics: annotates completed native Task outputs with an empty <task_result> using a sentinel via the tool-execute-after hook.
  • Migration: if you relied on the old 5-minute default, set historian_timeout_ms to 300000. Expect historian child sessions to persist until their sweep; privacy-only children retain their shorter sweep window.

Closes #336. Defensively addresses the empty-result symptom in #270.

Greptile Summary

This PR defers historian child-session deletion to an age-gated maintenance sweep, preventing deletion from racing detached OpenCode writes.

  • Separates historian and Dreamer privacy sweeps so each uses its own runtime-derived stale window.
  • Runs historian cleanup when Dreamer is disabled while honoring keep_subagents.
  • Raises the default historian timeout to ten minutes and synchronizes the public configuration surfaces.
  • Annotates completed native Task wrappers whose <task_result> is empty.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

FilenameOverview
packages/plugin/src/hooks/magic-context/compartment-runner-historian.tsRemoves immediate successful-child deletion and retains historian sessions for deferred cleanup.
packages/plugin/src/features/magic-context/dreamer/retrospective-orphan-sweep.tsAdds historian title matching and a stale threshold covering retries, suggestion attempts, and fallback models.
packages/plugin/src/plugin/dream-timer.tsRuns separate Dreamer and historian orphan sweeps, including historian-only maintenance configurations.
packages/plugin/src/hooks/magic-context/empty-task-output.tsAnnotates completed native Task wrappers when their task-result body contains no final text.
packages/plugin/src/config/schema/magic-context.tsRaises the historian timeout default and centralizes the maximum prompt-attempt count.
packages/plugin/src/index.tsSupplies normalized historian timeout, fallback count, and retention settings to timer registration.

Sequence Diagram

sequenceDiagram
    participant H as Historian runner
    participant O as OpenCode session
    participant W as Detached writers
    participant T as Maintenance timer
    participant D as OpenCode DB
    H->>O: Create and prompt child session
    O-->>H: Prompt completes
    H-->>O: Preserve child session
    W->>D: Persist remaining step/summary parts
    T->>D: Find historian children older than stale threshold
    T->>O: Delete stale child unless keep_subagents

Reviews (3): Last reviewed commit: "fix(historian): preserve child privacy w..." | Re-trigger Greptile

Context used: