feat(cli): nerve workflow thread — agent-friendly context retrieval #77

Closed
opened 2026-04-23 14:02:36 +00:00 by xiaoju · 0 comments
Owner

Summary

Add nerve workflow thread <runId> CLI command so that agents (Cursor, Hermes, OpenCode) can pull workflow thread context on demand instead of receiving it via CLI arguments (which breaks on escaping).

Motivation

When a workflow role spawns an agent, the agent needs conversation history. Passing full context as CLI args is fragile (shell escaping, length limits). Instead, agents should self-serve:

nerve workflow thread <runId> --budget 8000
nerve workflow thread <runId> --before 7 --budget 8000

Design

Two modes

Default mode (no --before):

  • Returns first message + last N messages until total length >= --budget (default 8000 chars)
  • Omitted messages in the middle show a hint: ... N messages omitted (use --before M to load) ...

Pagination mode (--before <round-id>):

  • Returns messages before the specified round, accumulating backwards until >= budget
  • Does NOT include the first message (that is only for the default overview)

Output format

Each message:

[#1 analyzer] 2026-04-23T13:00:00Z
---
exit_code: 0
files_changed: 3
---
Analysis complete. Found 3 files that need updating...

... 3 messages omitted (use --before 5 to load) ...

[#5 tester] 2026-04-23T13:00:12Z
---
exit_code: 0
assertions_passed: 3
---
All tests passed, sense validates correctly.
  • round-id: 1-based sequential integer, derived at query time via ROW_NUMBER() OVER (ORDER BY id ASC)no schema change needed
  • Header: [#<round> <role>] <ISO timestamp>
  • Frontmatter (between ---): YAML of the CommandEvent metadata (the structured fields from the role output)
  • Body: the content/message field of the CommandEvent
  • Moderator is not a role — only role execute() results appear as rounds

Implementation

Package: packages/daemon (log-store.ts)

  • Add getThreadRounds(runId: string): Array<{ round: number; role: string; ts: number; metadata: Record<string, unknown>; content: string }> method
  • Uses window function: ROW_NUMBER() OVER (ORDER BY id ASC) on thread_command_event rows
  • Parse each event payload JSON to extract role, metadata, and content

Package: packages/cli (commands/workflow.ts)

  • Add thread subcommand to the existing workflow command
  • Args: runId (positional), --before <round-id> (optional), --budget <chars> (default 8000)
  • Logic:
    1. Fetch all rounds via getThreadRounds(runId)
    2. If no --before: take first round + accumulate from end until >= budget. Insert omission hint in gap.
    3. If --before N: accumulate backwards from round N-1 until >= budget
    4. Format and output

CommandEvent convention

For this to work, role execute() must return CommandEvents with a consistent shape. The recommended convention:

// Role execute() returns:
{
  type: "role_result",   // or any string
  role: "tester",        // which role produced this
  content: "Human-readable output...",
  // ... any other fields become frontmatter metadata
}

The thread command treats type, role, and content as reserved fields; everything else goes into the YAML frontmatter.

Acceptance Criteria

  • nerve workflow thread <runId> shows first + last messages within budget
  • --before <round> paginates backwards
  • --budget controls output size (default 8000)
  • Omission hints include the exact command to fetch omitted section
  • Round IDs are 1-based sequential integers
  • Output format matches spec (header + frontmatter + content)
  • Unit tests for budget logic, pagination, and formatting
  • No schema migration needed (round derived at query time)

Notes

  • This is CLI package only + a small addition to log-store
  • Moderator decisions are internal, not shown as rounds
  • The role field in CommandEvent is a new convention — existing workflows that dont set it can show unknown as fallback
## Summary Add `nerve workflow thread <runId>` CLI command so that agents (Cursor, Hermes, OpenCode) can **pull workflow thread context on demand** instead of receiving it via CLI arguments (which breaks on escaping). ## Motivation When a workflow role spawns an agent, the agent needs conversation history. Passing full context as CLI args is fragile (shell escaping, length limits). Instead, agents should self-serve: ```bash nerve workflow thread <runId> --budget 8000 nerve workflow thread <runId> --before 7 --budget 8000 ``` ## Design ### Two modes **Default mode** (no `--before`): - Returns **first message** + **last N messages** until total length >= `--budget` (default 8000 chars) - Omitted messages in the middle show a hint: `... N messages omitted (use --before M to load) ...` **Pagination mode** (`--before <round-id>`): - Returns messages **before** the specified round, accumulating backwards until >= budget - Does NOT include the first message (that is only for the default overview) ### Output format Each message: ``` [#1 analyzer] 2026-04-23T13:00:00Z --- exit_code: 0 files_changed: 3 --- Analysis complete. Found 3 files that need updating... ... 3 messages omitted (use --before 5 to load) ... [#5 tester] 2026-04-23T13:00:12Z --- exit_code: 0 assertions_passed: 3 --- All tests passed, sense validates correctly. ``` - `round-id`: 1-based sequential integer, derived at query time via `ROW_NUMBER() OVER (ORDER BY id ASC)` — **no schema change needed** - Header: `[#<round> <role>] <ISO timestamp>` - Frontmatter (between `---`): YAML of the CommandEvent metadata (the structured fields from the role output) - Body: the content/message field of the CommandEvent - **Moderator is not a role** — only role `execute()` results appear as rounds ### Implementation **Package: `packages/daemon` (log-store.ts)** - Add `getThreadRounds(runId: string): Array<{ round: number; role: string; ts: number; metadata: Record<string, unknown>; content: string }>` method - Uses window function: `ROW_NUMBER() OVER (ORDER BY id ASC)` on `thread_command_event` rows - Parse each event payload JSON to extract role, metadata, and content **Package: `packages/cli` (commands/workflow.ts)** - Add `thread` subcommand to the existing `workflow` command - Args: `runId` (positional), `--before <round-id>` (optional), `--budget <chars>` (default 8000) - Logic: 1. Fetch all rounds via `getThreadRounds(runId)` 2. If no `--before`: take first round + accumulate from end until >= budget. Insert omission hint in gap. 3. If `--before N`: accumulate backwards from round N-1 until >= budget 4. Format and output ### CommandEvent convention For this to work, role `execute()` must return CommandEvents with a consistent shape. The recommended convention: ```ts // Role execute() returns: { type: "role_result", // or any string role: "tester", // which role produced this content: "Human-readable output...", // ... any other fields become frontmatter metadata } ``` The `thread` command treats `type`, `role`, and `content` as reserved fields; everything else goes into the YAML frontmatter. ## Acceptance Criteria - [ ] `nerve workflow thread <runId>` shows first + last messages within budget - [ ] `--before <round>` paginates backwards - [ ] `--budget` controls output size (default 8000) - [ ] Omission hints include the exact command to fetch omitted section - [ ] Round IDs are 1-based sequential integers - [ ] Output format matches spec (header + frontmatter + content) - [ ] Unit tests for budget logic, pagination, and formatting - [ ] No schema migration needed (round derived at query time) ## Notes - This is CLI package only + a small addition to log-store - Moderator decisions are internal, not shown as rounds - The `role` field in CommandEvent is a new convention — existing workflows that dont set it can show `unknown` as fallback
This repo is archived. You cannot comment on issues.
No Label
1 Participants
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: uncaged/nerve#77