Skip to main content

ADR-178: Orphan Task Detection and Context Vacuum

Status

Accepted - 2026-02-12

Context

The Problem

CODITECT has 1,216 unchecked tasks across 14 TRACK files (pilot tracks alone), plus hundreds more in 30+ extension tracks. Sessions die, contexts compact, projects get parked — and the work those sessions were doing becomes invisible.

Current orphan sources:

SourceScaleHow tasks get orphaned
Dead session claims2 active claims / 36 historical sessionsSession PID dies, task_claims row persists until stale timeout (10 min) — but the work context is lost forever
Compacted contexts1000+ sessions, ~512K messagesSession hits token limit, compacts, loses awareness of in-progress task. Next /orient starts fresh with no memory of partial work
Parked projectsUnknownHuman switches focus. Tasks left [ ] in TRACK files with no indicator they were ever started
Cross-session conflicts37 stale alerts in messaging.dbMultiple sessions touched same task. One finished, one didn't. Which subtasks are actually done?

Why existing mechanisms don't catch this:

MechanismWhat it doesWhat it misses
_cleanup_stale_sessions()Marks sessions stale after 10 min, deletes task_claimsDoesn't check if the task work was completed — just releases the lock
TRACK file [ ] / [x]Manual checkbox trackingNo timestamp, no session attribution. Can't distinguish "never started" from "started and abandoned"
Session logsChronological record of workBuried in daily logs. No structured query for "what was in-progress when this session died?"
/session-rescueRecovers hung sessionsReactive (user must notice). Doesn't scan for orphaned tasks, only hung processes

The Scale

Pilot tracks (TRACK-A through TRACK-N):

TrackUncheckedDoneOrphan Risk
H (Framework)256320High — most active, many session rollovers
J (Memory)145653High — complex multi-session work
N (GTM)47105Medium — recent financial model work
C (DevOps)4543Medium — infrastructure tasks
K (Workflow)7527Low — mostly planned, not started
F (Docs)865Low — stable

Extension tracks (track-aa through track-ak): ~800+ unchecked tasks, mostly 0% complete (planned, not orphaned).

What "Orphan" Means

A task is orphaned when any of these are true:

  1. Claimed but owner is deadtask_claims row exists, PID no longer running
  2. Partially completed — session log shows work started, but [ ] still unchecked in TRACK
  3. Context-lost — session that worked on it compacted/died, no handoff note exists
  4. Stale in-progress — last session log mention is >7 days old, still [ ]
  5. Conflict-abandoned — operator alert fired (task_conflict), neither session completed it

Decision

Implement a Context Vacuum — a periodic sweep that cross-references TRACK files, messaging.db, session logs, and session history to detect orphaned tasks and surface them for reactivation.

Architecture

┌─────────────────────────────────────────────────────┐
│ Context Vacuum │
│ │
│ ┌──────────┐ ┌──────────┐ ┌───────────────────┐ │
│ │ TRACK │ │messaging │ │ Session Logs │ │
│ │ Files │ │ .db │ │ (JSONL + .md) │ │
│ │ [ ] / [x]│ │claims, │ │ task mentions, │ │
│ │ │ │lifecycle │ │ timestamps │ │
│ └────┬─────┘ └────┬─────┘ └────────┬──────────┘ │
│ │ │ │ │
│ └──────────────┼─────────────────┘ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Orphan Detector │ │
│ │ (correlator) │ │
│ └────────┬────────┘ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Orphan Report │ │
│ │ (actionable) │ │
│ └────────┬────────┘ │
│ ▼ │
│ ┌────────────┴────────────┐ │
│ ▼ ▼ │
│ /orient surfaces /next-task │
│ "3 orphaned tasks" prioritizes orphans │
│ │
└───────────────────────────────────────────────────────┘

Three Sweep Modes

1. Quick Sweep (on every /orient)

Cost: <2 seconds. Queries only messaging.db.

CheckQueryOrphan Signal
Dead claimstask_claims WHERE PID not in ps outputClaimed task, dead owner
Stale claimstask_claims WHERE claimed_at > 24h agoLong-running claim, possibly abandoned
Unread alertssession_messages WHERE message_type = 'task_conflict' AND status = 'pending'Conflict never resolved

Output in /orient:

Orphaned Tasks: 2
H.13.9.7 — claimed by claude-55631 (dead PID), last activity 2h ago
N.6.10.5 — task_conflict alert unresolved (claude-9958 vs claude-46833)
Run /vacuum for full analysis

2. Standard Sweep (/vacuum command)

Cost: 10-30 seconds. Cross-references TRACK files + messaging.db + recent session logs.

Data SourceWhat it provides
TRACK files [ ]All unchecked tasks with IDs
messaging.db task_claimsWhich tasks were claimed, by whom, when
messaging.db task_broadcastLast activity timestamp per task
Session logs (last 30 days)Task ID mentions with timestamps
sessions.db messagesTask IDs in conversation history

Correlation logic:

for task_id in unchecked_track_tasks:
last_claim = query_last_claim(task_id) # messaging.db
last_broadcast = query_last_broadcast(task_id) # messaging.db
last_log_mention = grep_session_logs(task_id) # session logs
last_message = query_sessions_db(task_id) # sessions.db

last_activity = max(last_claim, last_broadcast, last_log_mention, last_message)

if last_activity is None:
status = "never_started"
elif last_activity > 7_days_ago:
status = "stale_in_progress" # ORPHAN
elif claim_exists and pid_dead:
status = "dead_claim" # ORPHAN
elif conflict_unresolved:
status = "conflict_abandoned" # ORPHAN
else:
status = "active" # Not orphaned

Output:

Context Vacuum Report — 2026-02-12
═══════════════════════════════════

ORPHANED TASKS (action required):
[DEAD_CLAIM] H.13.9.7 Documentation updates Last: 2026-02-11 (claude-55631, dead)
[STALE 14d] J.29.3.2 Dashboard data refresh Last: 2026-01-29
[CONFLICT] N.6.10.5 Test validation Conflict: claude-9958 vs claude-46833

PARKED PROJECTS (no activity >30 days):
Track K (Workflow) 75 tasks, 0% done, last activity: never
Track G (DMS) 64 tasks, 0% done, last activity: never

RECENTLY ACTIVE (no action needed):
H.13.9 95% done Last: 2026-02-12 (this session)
N.6.10 100% done Last: 2026-02-12 (completed)
J.31 In progress Last: 2026-02-11

Summary: 3 orphans, 2 parked tracks, 1216 total unchecked

3. Deep Sweep (/vacuum --deep)

Cost: 2-5 minutes. Full historical analysis.

Adds to Standard Sweep:

  • Scans ALL session logs (not just 30 days)
  • Checks sessions.db for task ID mentions in conversation messages
  • Cross-references git commit messages (feat(H.13.9): pattern)
  • Builds a task lifecycle timeline per task ID
  • Identifies tasks that were worked on but never marked [x]

Storage

Vacuum results are stored in messaging.db (new table):

CREATE TABLE IF NOT EXISTS vacuum_reports (
id INTEGER PRIMARY KEY AUTOINCREMENT,
sweep_type TEXT NOT NULL, -- 'quick', 'standard', 'deep'
created_at TEXT NOT NULL,
task_id TEXT NOT NULL,
orphan_type TEXT NOT NULL, -- 'dead_claim', 'stale_in_progress',
-- 'conflict_abandoned', 'never_started',
-- 'parked_project'
last_activity TEXT, -- ISO 8601 timestamp
last_session_id TEXT, -- Session that last touched it
track TEXT, -- Track letter
evidence TEXT, -- JSON: {claim_at, broadcast_at, log_mention, commit}
resolution TEXT, -- NULL, 'adopted', 'completed', 'deferred', 'cancelled'
resolved_at TEXT,
resolved_by TEXT
);

CREATE INDEX idx_vacuum_task ON vacuum_reports(task_id);
CREATE INDEX idx_vacuum_orphan ON vacuum_reports(orphan_type);
CREATE INDEX idx_vacuum_unresolved ON vacuum_reports(resolution) WHERE resolution IS NULL;

Commands

CommandPurposeFrequency
/vacuumStandard sweep — orphan reportWeekly or on-demand
/vacuum --deepDeep sweep — full history correlationMonthly or when resuming parked project
/vacuum --quickQuick sweep — dead claims onlyBuilt into /orient
/vacuum --adopt <task_id>Claim an orphaned task for this sessionOn-demand
/vacuum --defer <task_id>Mark orphan as intentionally deferredOn-demand
/vacuum --cancel <task_id>Mark orphan as no longer neededOn-demand (requires confirmation)

Integration Points

SystemIntegration
/orientQuick sweep runs automatically. Shows orphan count.
/next-taskPrioritizes orphaned tasks over new work (dead_claim > stale > never_started)
/session-endChecks if current session has claimed tasks, warns if uncompleted
/session-gcCleans up dead claims, triggers quick sweep
Session log/vacuum results logged as standalone analysis document

Parked Project Detection

A project/track is "parked" when:

  • All tasks are [ ] (0% complete), OR
  • Last activity across all tasks is >30 days ago, AND
  • No active session claims for any task in the track

Parked projects are surfaced separately from individual orphans — they represent strategic decisions (intentional deferral) not accidental abandonment.

Consequences

Positive

  • No more invisible abandoned work — orphans are surfaced automatically
  • Faster session startup/orient shows what needs attention without manual searching
  • Parked project awareness — tracks that haven't been touched in months become visible
  • Task lifecycle visibility — first time we can answer "when was this task last worked on?"
  • Builds on existing infrastructure — uses messaging.db, session logs, TRACK files. No new databases.

Negative

  • Standard sweep reads TRACK files on disk — I/O cost, but TRACK files are small (<100KB each)
  • Deep sweep is slow — 2-5 min for full history. Acceptable for monthly use.
  • False positives on "parked" — Track K and Track G are intentionally deferred, not orphaned. Need --defer to suppress.

Risks

RiskMitigation
Vacuum report noise (too many orphans)Filter by track, priority, age. Default to high-activity tracks only.
sessions.db query performanceUse FTS5 index on messages table. Already indexed.
Stale TRACK file stateVacuum detects this — if git shows task [x] but no commit evidence, flag it.

Alternatives Considered

AlternativeRejected Because
Manual /grep for task IDsDoesn't correlate across sources. Can't distinguish "never started" from "started and abandoned."
Automatic task reaping (auto-cancel after N days)Too aggressive. Parked projects are intentional. Human must decide.
Real-time task lifecycle tracking (event sourcing)Over-engineered for current scale. The vacuum approach is batch-oriented and simpler. Reconsider if task volume reaches 10K+.
Extend /session-status with orphan detection/session-status is already overloaded (v4.1). Separate command keeps concerns clean.

Success Criteria

  • Quick sweep runs in <2s on /orient
  • Standard sweep identifies orphans across TRACK + messaging.db + session logs
  • Deep sweep correlates with git commits and full session history
  • /vacuum --adopt claims orphaned task and broadcasts to bus
  • Parked projects are distinguished from individual orphans
  • Vacuum results stored in messaging.db for historical tracking
  • /next-task prioritizes orphaned tasks over fresh work
  • False positive rate <10% (validated on current TRACK files)

Implementation Plan

PhaseScopeEffort
1Quick sweep + /orient integration1 session
2Standard sweep (/vacuum command)1-2 sessions
3Storage (vacuum_reports table) + adopt/defer/cancel1 session
4Deep sweep + /next-task integration1-2 sessions
5/session-end warning + /session-gc integration1 session

Version: 1.0.0 Created: 2026-02-12 Author: Claude (Opus 4.6) Track: H (Framework Autonomy) Task: H.13.10

Changelog

  • v1.1.0 (2026-02-12) — Accepted. Full implementation complete (context_vacuum.py, 1277 lines). All 5 phases implemented. Integration with /orient, /next-task, /session-end, /session-gc command specs.
  • v1.0.0 (2026-02-12) — Initial proposal. Three sweep modes, vacuum_reports table, 5-phase implementation plan.