Orchestrata Ultimate › Plans › backlog-plan

Session-Tooling Backlog — Detailed Implementation Plan

Source: plans/backlog-plan.md at 8a2cb42ad · last changed 2026-07-27 · first committed 2026-07-04 · 273 lines.

Prose pending.

Plan text

Session-Tooling Backlog — Detailed Implementation Plan

Plan for the 6 remaining backlog items (Phase 9 / VizInt is done). Written after a file-and-line investigation of the session-hook + CLI system. Several items turned out to be smaller or differently-shaped than their backlog wording — those corrections are called out per item and change the recommended priority.


0. Shared context (applies to items 1–6)

Dual copies + redeploy. The session system is version-controlled under claude/ and deployed to ~/.claude/ by claude/install.sh:

In-repo (edit here) Deployed to install.sh
claude/tools/session/*.py (package + models.py) ~/.claude/tools/session/ L35
claude/tools/session_cli.py ~/.claude/tools/session_cli.py L39–40
claude/tools/kg_sync_jsonl.py ~/.claude/tools/kg_sync_jsonl.py —
claude/hooks/*.py ~/.claude/hooks/ L63–92
claude/commands/*.md (slash commands) ~/.claude/commands/ L97–99

Repo and deployed copies are currently byte-identical. Workflow for every item: edit the in-repo claude/… copy → run bash claude/install.sh → confirm diff -q repo-vs-deployed is clean.

Guardrails:

Recommended sequencing (cheapest / highest-signal first):

Order Item Effort Why here
1 #4 orphaned hook delete XS one-file delete, zero risk
2 #2 session-number drift S already self-heals; mostly decide + document
3 #1 SessionStart/End warning S partly already works; small nuanced edit
4 #5 goal exposure M clear user value, well-scoped
5 #6 RTM port M–L most code; unblocks the cts RTM step
6 #3 incremental KG sync M optimization, explicitly "if histories grow large" — defer

Item 4 — Delete the orphaned legacy hook (P3 · effort XS)

Verified state. claude/hooks/session_auto_start.py (161 LOC, git-tracked) is a legacy SessionStart hook superseded by session_resume_context.py. It is not copied or registered anywhere: grep session_auto_start claude/install.sh → 0 hits; ~/.claude/hooks/session_auto_start.py does not exist; not in ~/.claude/settings.json. No Python/shell reference to it exists in the main tree.

Do not confuse with src/orchestrata_ultimate/session/hooks/session_auto_start_hook.py (538 LOC, _hook suffix, has live tests) — that one stays.

Steps.

  1. git rm claude/hooks/session_auto_start.py.
  2. Fix stale prose at AGENTS.md:92 ("The session_auto_start.py hook begins a new session…").
  3. Adjust the §11 note in claude/SESSION_SYSTEM.md:220–226 (drop or mark removed).
  4. Mark backlog item done via session_cli complete-work --contains "orphaned legacy hook".

Verify. bash claude/install.sh still succeeds; SessionStart still works (resume banner appears). No redeploy of the deleted file needed (never deployed). Risk. None.


Item 2 — Reconcile session-number drift (P2 · effort S)

Correction — it already self-heals. The next session number is derived by ContextManager.next_session_num() (claude/tools/session/context.py:470–490) as max(max_from_history, max_from_context, max_from_tags) + 1 — it already reconciles all three sources. Called from SessionController.begin() (session.py:174–175).

Verified drift (reversed from the original S163 case). Tracker state.active_session = 193 (context.json:3); git tags/commits/CHANGELOG max = 190; context.history.md max = 188. Tracker is now 3 ahead, because auto-closed and open sessions bump the tracker but emit no session-NNN git tag (only the cts close path tags — SESSION_SYSTEM.md:104–106). Sessions 191, 192 are "auto-closed", 193 is open — none tagged.

The single owner: field ProjectState.active_session (models.py:173); deriver ContextManager.next_session_num() (context.py:470).

Options (pick one; recommend A+C).

Steps (A+C). Add a _tag_session(seq) helper (extract from the cts tagger) and call it from _auto_close_unclosed(); backfill missing tags 191/192 once (git tag session-191-… <sha>); document in docs/SESSION_MANAGEMENT.md; complete-work. Verify. Run a begin/auto-close cycle in a scratch dir; confirm a tag is created and next_session_num() stays monotonic. Cross-check spec REQs specs/006-session-management/spec.md:330–331. Risk. Low. Tag creation must be idempotent (git tag -f or existence check) to avoid dup-tag errors.


Item 1 — Warn on sessions ENDED without a handoff (P3 · effort S)

Correction — the SessionStart warning already covers this. The SessionStart hook claude/hooks/session_resume_context.py:69 gates only on if sess and not sess.get("handoff") — it does not inspect length, so it already fires for ended-without-handoff (verified against context.json seq 191 = length="auto-closed", no handoff). The real gaps are:

  1. It inspects only one session (the active_session, else hist[-1]; L65–68) — an older ended-without-handoff session is missed.
  2. The message doesn't distinguish "open/thin" from "ended without handoff."
  3. The SessionEnd hook session_handoff_reminder.py:59 is the one that literally excludes ended sessions: if sess.get("length"): return — so it never nags at close time.

Schema truth (not what the item guessed): SessionRecord.length: str|None (models.py:86) is the ENDED marker (None=open, non-null=ended); handoff: HandoffData|None (models.py:94) is presence. There is no status/ended_at on a session record.

Steps.

  1. In session_resume_context.py (L63–78): scan all of hist for s.get("length") is not None and not s.get("handoff"); branch the warning text:
    • open + no handoff → "session N is open with no handoff yet"
    • ended + no handoff → "session N ended (auto-closed) with NO handoff recorded".
  2. (Decide with user) In session_handoff_reminder.py: relax L59 so an ended session with no handoff still nags at SessionEnd (currently only open sessions do).
  3. Edit in-repo copies → bash claude/install.sh → diff -q repo vs ~/.claude/hooks/ clean.

Verify. Craft a scratch context.json with (a) ended+no-handoff and (b) open+no-handoff records; run each hook via stdin JSON; confirm the right message. Add a unit test under tests/session/ if one exists for these hooks. Risk. Low. Pure text/branch logic. Remember both copies must stay identical.


Item 5 — Expose goal-setting to the user (P2 · effort M)

Verified state. SPEC-023 goal subsystem is fully built but has no user entry point.

Steps.

  1. CLI subcommands in claude/tools/session_cli.py (3-edit pattern per existing commands):
    • Handlers cmd_set_goal, cmd_get_goal, cmd_goal_status, cmd_abandon_goal, cmd_complete_criterion (model after cmd_add_work L290 / cmd_set_handoff).
    • Parsers in build_parser() (~L364): set-goal --statement … --criterion "id:desc" … (repeatable, nargs="*" like set-handoff L431), get-goal, goal-status, abandon-goal, meet-criterion --id ….
    • Register in the COMMANDS dict (L471–485).
    • Import Goal, GoalCriterion from orchestrata.models; build GoalCriterion(id, description) from the --criterion args; call ctrl.set_goal(statement, criteria).
  2. Slash command claude/commands/goal.md — mirror claude/commands/session.md (plain markdown, no frontmatter): /goal set, /goal show, /goal met <id>, /goal abandon, each mapping to a python3 ~/.claude/tools/session_cli.py … invocation. Picked up by install.sh L97–99.
  3. (Optional) fix the dangling /goal abandon reference in session_close_hook.py:601 once real.
  4. bash claude/install.sh; confirm ~/.claude/commands/goal.md and CLI subcommands exist.

Verify. session_cli set-goal --statement "X" --criterion "c1:do Y" → get-goal shows it → session_cli status reflects the goal → the cts gate blocks until meet-criterion c1 or abandon-goal. Add a CLI round-trip test if tests/ covers the CLI. Risk. Low–med. Keep the orchestrata-not-session import identity. Criterion-arg parsing ("id:description") needs a clear delimiter + validation. Redeploy both CLI + command.


Item 6 — Enable full RTM generation at session close (P2 · effort M–L)

Verified state. session_close_hook.py::generate_rtm() (L172–268) runs three scripts/… at cts and warns "not found" because they're absent:

Source scripts live in /home/mohamed/workspace/orchestrata/scripts/:

Key facts that shrink the work:

Steps.

  1. Copy the three scripts into scripts/ and fix:
    • generate_traced_rtm_simple.py: import orchestrata_ultimate.core.traceability; scan dir "src" (not "packages", L17) + keep "claude".
    • generate_rtm.py: change RTMGenerator("packages/", "claude/") (L1049) and packages_dir.rglob (L189) to src/; extend spec_registry (L480–513) with the extra target specs 050–071 (optional — unlisted are silently skipped).
    • generate_detailed_rtm.py: cosmetic path-shorten (L120–122) — retarget src/orchestrata_ultimate/ or leave (paths just won't shorten).
  2. code-index.json gate — choose:
    • (a) generate a target plans/code-index.json (small module→{classes,functions} index — write a ~40-line generator over src/, or derive from the existing plans/ast-ultimate.json); or
    • (b) relax session_close_hook.py:216 to not require it (the file is unused by scripts). Recommend (a) so generate_rtm.py's step actually runs and the coverage numbers are real.
  3. Smoke-run each script from repo root (uv run python scripts/generate_traced_rtm_simple.py), confirm it writes plans/rtm-traced-only.*, prints the scraped strings, and finds >0 traces (proves packages→src retarget worked — else every REQ shows MISSING).
  4. Confirm the hook's git-add list (L242–250) matches the produced files.

Verify. Run a full cts in a scratch session; confirm the RTM step reports trace/req/ verified/missing counts instead of "not found", and the plans/*.md/json outputs commit. Risks.


Item 3 — Incremental KG sync (P3 · effort M · recommend DEFER)

Correction — wrong subsystem in the item wording. The measured "0.5 s / 2826 nodes" full rebuild is a flat JSONL export, triggered by the remember hook, not a Neo4j sync and not SNS:

Source of truth: Claude Code transcripts ~/.claude/projects/<cwd>/*.jsonl (append-only); nodes are flat per-turn dicts {session_id, seq, role, text, ts, uuid} — no edges.

Incremental design (JSONL path).

Steps. Add sidecar read/write; convert sync() from overwrite to append-past-watermark with uuid dedupe; keep a --rebuild flag for a full pass. Redeploy the deployed copy. Verify. Run remember twice; confirm the 2nd call reads only new transcript bytes (instrument node count / timing) and kg_nodes.jsonl is unchanged for unchanged history. Risk. Med — transcript files can be rewritten/compacted by the harness; on file shrink/hash-mismatch, fall back to a full rebuild of that file. Because the current cost is 0.5 s, this is a scalability guard, not a current problem — the backlog says "if histories grow large." Recommend deferring until a transcript set makes the rebuild visibly slow.


Cross-item checklist (do for every code change)