Skip to content

orban/relay

Repository files navigation

relay

A Claude Code plugin that persists structured agent state across compaction events.

When Claude Code's context window fills up and compaction wipes the conversation, relay keeps track of what the agent was doing — its objective, working files, hypothesis, next actions, and open questions. On resume, that context is injected back automatically so work can continue without the agent losing its place.

How it works

Relay uses Claude Code's hook system to run Python scripts at key lifecycle points. No agent cooperation is required for basic tracking — hooks fire automatically on file operations and turn boundaries.

Write/Edit/Read (PostToolUse)     Stop/SubagentStop (pack.py)
       │                                    │
       ▼                                    ▼
  Register artifact              Rebuild INDEX.md, EXEC_PACKET.md
  Track reads                    Append to TASKLOG.md
  Update working set             Parse markers, infer objective
                                 Spawn async extraction
                                 Check staleness, warn on compaction

PreCompact (pre_compact.py)      SessionStart (reload.py, nudge.py)
       │                                    │
       ▼                                    ▼
  Snapshot state                 Post-compaction: inject RELOAD.md
  Write RELOAD.md                Non-compact: nudge if stale or
  Record compaction history        missing objective, surface
                                   deferred notifications

What gets tracked

Automatically (no agent action needed):

  • File artifacts — every Write, Edit, and Read is registered with provenance
  • Working set — the 10 most recent artifacts, used for RELOAD.md
  • Turn count, compaction count, compaction history
  • Git context — branch, status, recent commits
  • Read tracking — last 5 unique file reads

Semi-automatically (via markers in assistant output):

  • Objective, hypothesis, next actions, open questions — parsed from <!-- relay:field value --> HTML comments at the end of responses
  • Done signal — <!-- relay:done --> clears next_actions and sets status to idle

Background (async LLM extraction):

  • Every 5 marker-free turns, a background claude --print --model haiku call infers state changes from the assistant's message
  • Results auto-apply on the next Stop hook (prefixed with [auto] for traceability)

Workspace structure

All state lives in .agent-workspace/ at the project root:

.agent-workspace/
├── STATE.json           # Source of truth — all structured state
├── EXEC_PACKET.md       # Narrative summary for the agent
├── INDEX.md             # Full artifact registry table
├── TASKLOG.md           # Append-only turn history
├── artifacts/           # Reserved for artifact metadata
├── summaries/           # Pre-compaction snapshots
├── derived/
│   ├── RELOAD.md                  # Post-compaction injection payload
│   ├── file_snapshots.md          # First lines of working set files
│   ├── extraction_context.json    # Input for background LLM extraction
│   ├── pending_suggestions.json   # Output from background extraction
│   └── notifications.txt          # Deferred notifications for next SessionStart
└── errors.log           # Hook error log (capped at 50 entries)

Installation

Deploy to the Claude Code plugin cache:

bash scripts/deploy.sh

This rsyncs the source to ~/.claude/plugins/cache/ryo-marketplace/relay/1.0.0/, excluding .git, .agent-workspace, __pycache__, and docs.

Commands

All commands are accessed via /relay:

Command Description
/relay or /relay status Show current workspace state
/relay sync Update all semantic fields (objective, status, hypothesis, next_actions, open_questions)
/relay pack Manually trigger a pack cycle
/relay objective <text> Set or show the workspace objective
/relay forget <path-or-id> Remove an artifact from the registry and working set
/relay reset Delete .agent-workspace/ and start fresh

Hooks

Defined in hooks/hooks.json:

Event Script What it does
PostToolUse (Write/Edit/Read) post_tool_use.py Registers artifacts, tracks reads, manages working set
Stop pack.py Rebuilds derived files, parses markers, spawns extraction, checks staleness
SubagentStop pack.py Same as Stop but skips marker parsing, extraction, and blocking
PreCompact pre_compact.py Snapshots state, writes RELOAD.md, records compaction history
SessionStart (compact) reload.py Injects RELOAD.md as additional context after compaction
SessionStart (*) nudge.py Nudges on missing objective or stale state, surfaces deferred notifications

Seamless state tracking

Two layers keep workspace state fresh without the agent running /relay sync every few turns.

Layer 1: structured markers

The agent drops HTML comments anywhere in its response:

<!-- relay:objective fix the OAuth redirect loop -->
<!-- relay:next add PKCE support -->
<!-- relay:next write integration tests -->
<!-- relay:question should we support refresh token rotation? -->
<!-- relay:hypothesis the 401s are caused by clock skew -->
<!-- relay:done -->

The Stop hook parses these from last_assistant_message and applies them to workspace state. Values are tagged [auto] to distinguish from manual updates.

Limitation: markers must be at the end of the response, after all tool calls. last_assistant_message only contains the final text block — anything before a tool call gets cut off.

Layer 2: async LLM extraction

When 5+ turns pass without markers, the Stop hook spawns extract_state.py in the background. It calls claude --print --model haiku with isolation flags (--setting-sources local, --strict-mcp-config, --dangerously-skip-permissions) to infer state changes from the assistant's message.

Results are written to derived/pending_suggestions.json and auto-applied on the next Stop hook. All auto-applied values are prefixed with [auto] for traceability. Suggestions expire after 3 turns if not applied. When markers are present in the same turn, they take priority and suggestions are skipped.

Priority: markers > manual /relay sync > LLM suggestions.

Compaction survival

The core value of relay. When compaction hits:

  1. PreCompact fires — snapshots current state, writes derived/RELOAD.md with objective, hypothesis, working set, recent history, git context, file snapshots, and a recovery checklist
  2. Context is wiped — the conversation loses everything
  3. SessionStart (compact) fires — reload.py reads RELOAD.md and injects it as additionalContext, so the agent immediately has its bearings

The recovery checklist in RELOAD.md tells the agent to:

  1. Read TASKLOG.md for recent history
  2. Read file_snapshots.md for code context
  3. Run /relay sync to verify and update state
  4. Read STATE.json only if full artifact details are needed

Staleness detection

Relay tracks how many turns have passed since the last semantic update:

  • Fresh (0-5 turns): no warnings
  • Mild (6-15 turns): gentle nudge
  • Loud (16+ turns): strong warning
  • Periodic reminder: every 8 turns of staleness

The SessionStart hook also nudges if the workspace has artifacts but no objective, or if state is stale from a previous session.

Tests

146 pytest tests covering all hook scripts:

python3 -m pytest hooks/scripts/test_hooks.py -q

Covers: artifact registration, working set management, index/exec_packet rebuilding, TASKLOG trimming, staleness detection, compaction prediction, marker parsing/application, suggestion TTL, extraction spawning, nudge logic, reload injection, git context capture, error logging, and more.

Design docs

Detailed design rationale for each feature set:

Known limitations

  • Markers must be at end of response. last_assistant_message only contains the final text block. Markers before tool calls are lost.
  • SubagentStop carries subagent's message. All marker/extraction logic is gated to main Stop events only.
  • Stop hooks can't inject additionalContext. Only decision/reason/systemMessage are supported. Non-critical notifications are deferred to file and surfaced on next SessionStart.
  • Async extraction requires claude CLI. If claude isn't installed or can't run, extraction silently skips. The 30s timeout handles hangs.

About

Seamless workspace state tracking for Claude Code — survives context compactions

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors