fix(historian): defer child session cleanup
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_subagentsis 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.
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_subagentsis 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_msis600000; exportsMAX_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_msto300000. 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
| Filename | Overview |
|---|---|
| packages/plugin/src/hooks/magic-context/compartment-runner-historian.ts | Removes immediate successful-child deletion and retains historian sessions for deferred cleanup. |
| packages/plugin/src/features/magic-context/dreamer/retrospective-orphan-sweep.ts | Adds historian title matching and a stale threshold covering retries, suggestion attempts, and fallback models. |
| packages/plugin/src/plugin/dream-timer.ts | Runs separate Dreamer and historian orphan sweeps, including historian-only maintenance configurations. |
| packages/plugin/src/hooks/magic-context/empty-task-output.ts | Annotates completed native Task wrappers when their task-result body contains no final text. |
| packages/plugin/src/config/schema/magic-context.ts | Raises the historian timeout default and centralizes the maximum prompt-attempt count. |
| packages/plugin/src/index.ts | Supplies 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:
- Knowledge Base — Magic Context plugin runtime
- Knowledge Base — Configuration and public contracts