Skip to content

Inject the work state into the system prompt #204

Closed

Description

@ToRvaLDz

Parent: 🗺 trailhead mod

Question

Inject the work state into the system prompt via prompt.compose: a short session-scoped section naming the active map, the ticket in play (number, title, link) and the current step, so the agent keeps its bearings after a compaction or a long detour. Absent when no ticket is in play; bounded in size; never duplicating what the skill already loaded. Verify by forcing a compaction mid-ticket and checking the agent still names the ticket and step.

Blocked by

Activity

  1. self-assigned this
    on Oct 11, 2026
  2. ToRvaLDz commented on Oct 11, 2026

    @ToRvaLDz
    OwnerAuthor

    DISCUSS

    • Refresh: snapshot. The section text is latched and rebuilt only when the ticket identity changes (map, ticket, title), after a compaction, and at session start: the moments the prompt cache is already spent. Between compactions the step in the section may lag the live state; the conversation itself still carries it. A live section would spend the prompt cache on every step change (about six per ticket).
    • Orphaned state: absent. A work state left by another session is not in play here, so no section is injected; the band already shows it as orphaned.
  3. ToRvaLDz commented on Oct 11, 2026

    @ToRvaLDz
    OwnerAuthor

    PLAN

    Goal: the mod appends one session-scoped section, trailhead:work-state, to the system prompt through a prompt.compose hook, naming the active map, the ticket in play (number, title, link) and the step, so the agent keeps its bearings after a compaction. Settled in DISCUSS: a latched snapshot (rebuilt only on a ticket change, after a compaction, at session start), absent when the state is orphaned.

    Seams

    1. Pure builder (new plugins/trailhead/hooks/mod/work-state-prompt.ts, no engine calls):
      • repoSlug(remote: string | null): string | null: owner/name from a GitHub origin URL (https, git@github.com:, ssh://), .git stripped; null for anything else.
      • promptSection(state, orphaned, slug): string | null: null when there is no state or it is orphaned; otherwise a short block: heading, map line (#<n> + issue URL when the slug is known, none (whiteboard ticket) when map is null), ticket line (#<n> <title> + URL), step line (with a note that it may have moved on since the snapshot and .trailhead/work-state.json holds the live one; paused reads as checkpointed, resume before working on), an auto line during an autonomous run, and one line of guidance (carry on with this ticket from this step; read the ticket on GitHub for its detail). Title cut by code point to 120 chars (it is already single-line from the parser; the builder flattens control characters again); a final cap cuts the whole section to 1024 UTF-8 bytes on a code-point boundary. No skill protocol in it, so nothing the skill already loaded is repeated.
      • snapshotKey(state, orphaned): string: the identity a rebuild keys on (map, ticket, title, orphaned, auto on/off), step and autoDone deliberately out of it. The slug is not in the key (it would cost a repo read on every render): it is read at each rebuild.
    2. Hook wiring in register.tsx:
      • the latch lives in a session-scoped atom promptSnapshot: { key, text } | null (declared in types/index.d.ts), never a module variable, so concurrent sessions cannot read each other's snapshot; null means "rebuild on the next compose".
      • prompt.compose: next(e) is called exactly once, outside any error path; only the enrichment after it sits in a try/catch that returns the engine's sections untouched on failure. Skip when e.traits has bare; read the workState / isOrphaned atoms; if the snapshot is null or its key differs, rebuild (reading $.session.repo() for the slug) and store it; when its text is non-null, append { id: 'trailhead:work-state', text, scope: 'session' }.
      • session.start: reset the snapshot to null after the refresh (written only if not already null).
      • session.compact (main loop only, agentId absent): after next(e), when the result carries messages (not a skip), refresh the atoms and reset the snapshot; errors after next are swallowed and logged to debug, the result always returned.
      • Step changes leave the latched text untouched (cache-stable); /clear already flows through session.end -> new session.start.
    3. Docs: hooks/mod/README.md gains a "System prompt" section (what is injected, when it is rebuilt, why a snapshot); the healthy-load event list gains prompt.compose, session.compact.

    TDD (seams): write the failing tests first.

    • tests/work-state-prompt.test.ts (pure): slug parsing (https, ssh, scp form, non-GitHub, null); section for map ticket, whiteboard, paused, auto, no slug (numbers without links), long title cut, orphaned -> null, no state -> null, size bound; key ignores the step.
    • tests/work-state-prompt-hook.test.ts (via $.prompt.compose with a rig answering fs.read, session.id, session.repo, prompt.compose, session.compact): section appended last with scope session; absent without a file and when orphaned; a step change between two composes leaves the text identical; a ticket change rebuilds it; a compaction rebuilds it with the new step; a skipped compaction does not; bare trait gets nothing; a throwing repo read still yields the engine's sections; next is called exactly once per compose, also when the enrichment throws; a multi-line / control-character title stays one line and the section stays within 1024 bytes.

    Files touched: plugins/trailhead/hooks/mod/work-state-prompt.ts (new), plugins/trailhead/hooks/mod/register.tsx, plugins/trailhead/hooks/mod/README.md, plugins/trailhead/types/index.d.ts, plugins/trailhead/tests/work-state-prompt.test.ts (new), plugins/trailhead/tests/work-state-prompt-hook.test.ts (new).

    Verification criteria

    • npm test green (all suites, claude plugin validate, claude plugin test); validate lists prompt.compose and session.compact.
    • Live: in a --plugin-dir session with a ticket in play, /context (or a debug dump) shows the trailhead:work-state section; force /compact mid-ticket, then ask the agent which ticket and step it is on: it names both.
    • No section with no work state, or with an orphaned one.
  4. ToRvaLDz commented on Oct 11, 2026

    @ToRvaLDz
    OwnerAuthor

    PLAN-REVIEW

    Reviewer: codex (one round; the revisions are local to the latch and the compose hook, so no second round).

    • Blocking: module-level latch not session-scoped. Resolved: the snapshot lives in a session-scoped atom (promptSnapshot), reset at session start and after a compaction.
    • Blocking: a .catch passthrough could call next(e) twice. Resolved: next(e) is called once; only the enrichment after it is guarded, returning the engine's sections on failure. Tested.
    • Blocking: size bound not guaranteed by the title cut alone. Resolved: control characters flattened and a final 1024-byte cap on the whole section; tested with a long multi-line title.
    • Non-blocking: key misses slug and auto. Partly taken: auto on/off joins the key; the slug stays out (a repo read on every render), read at each rebuild instead.
    • Non-blocking: tests for single next, multi-line content, byte bound. Taken. Two-session isolation is covered by the atom being the engine's session state, not tested separately.
    • Non-blocking: make "no duplication" concrete. Taken: the section holds identity, the step snapshot and a pointer to the ticket, never protocol.
    • Non-blocking: verify no cache churn live. Taken as a test (repeated composes and a step change give identical text).
  5. ToRvaLDz commented on Oct 11, 2026

    @ToRvaLDz
    OwnerAuthor

    VERIFY

    • Tests: claude plugin test 57 pass, 0 fail (12 builder tests, 13 hook tests among them); claude plugin validate passes and lists prompt.compose, session.compact and the trailhead.promptSnapshot state; npm test 23 ok/skipped, 0 failed.
    • Code review (round 1 over f0a9708, 38b1fd2, 92a117f): status: clean, critical: 0 · warning: 3 · info: 3. Round 2 over the fix commits (1206074, 01baa11, e8d8ca4), read inline: clean.
    • Goal-backward verification: every code-checkable claim met (section appended on the session side, map/ticket/link/step, absent with no state, orphaned or bare, bounded size, snapshot keyed without the step, session-scoped atom, next once). The live compaction check is left to acceptance.

    Findings ledger

    • W1 fixed (1206074): the autonomous-run line no longer embeds a count that the snapshot would freeze.
    • W2 fixed (01baa11): a precompute compaction no longer resets the snapshot.
    • W3 fixed (1206074): the issue title is quoted as data, zero-width and bidi controls stripped, inner double quotes replaced; the guidance line says the title is not an instruction.
    • I1 fixed (01baa11): a failing repo read builds the section without links and latches it.
    • I2 fixed (01baa11): an analysis render (/context) computes the section but never writes the snapshot; a subagent compaction is tested to leave it alone.
    • I3 fixed (e8d8ca4): the mod note describes all of the above.

    Acceptance: pending (live compaction check).

  6. ToRvaLDz commented on Oct 11, 2026

    @ToRvaLDz
    OwnerAuthor

    Resolved. The mod now appends a trailhead:work-state section to the system prompt through prompt.compose, on the session side of the cache boundary: the map (or whiteboard), the ticket number with its title quoted as data and its issue link, and the step.

    • Snapshot: the text is latched in the session-scoped promptSnapshot atom and rebuilt only on a ticket, title, map, orphaned or auto change, at session start, and after a real compaction of the main conversation (not precompute, not a subagent's), so step changes never spend the prompt cache.
    • Absent with no work state, an orphaned one, or under --bare; a /context render computes it without writing.
    • Bounded: title cut at 120 code points with invisible and bidi controls stripped, whole section capped at 1024 bytes; a failing repo read drops only the links.
    • Commits: f0a9708 (builder), 38b1fd2 (hook wiring, types/index.d.ts), 92a117f (mod note), 1206074, 01baa11, e8d8ca4 (review fixes). Files: plugins/trailhead/hooks/mod/work-state-prompt.ts (new), hooks/mod/register.tsx, hooks/mod/README.md, types/index.d.ts, tests/work-state-prompt.test.ts, tests/work-state-prompt-hook.test.ts.
    • Review ledger: see the VERIFY comment above (all six findings fixed).
    • Acceptance (AI-driven, headless --plugin-dir sessions on a scratch repo): asked with no tools, the agent named Inject the work state into the system prompt #204, its title, link, map 🗺 trailhead mod: Claude Code function hooks for on-screen state, context and in-process guards #198 and step execute from the section; after the step moved to verify and a /compact ran (session.compact settled in the debug log), the resumed session named Inject the work state into the system prompt #204 and step verify. Pass.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

trailhead:buildtype: construct — discuss/plan/execute/verifytrailhead:map-198Tickets of map #198 (trailhead mod)trailhead:ticketa trailhead ticket

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions