feat(riven): Riven cursor-terminal loop scaffold [B-0498] (decomposed)#3603
Merged
Conversation
There was a problem hiding this comment.
Pull request overview
Adds an IDE-visible (Cursor Terminal) background loop scaffold for the Riven autonomy surface, alongside supporting design/backlog documentation for B-0498.
Changes:
- Introduces
tools/riven/riven-cursor-terminal-loop.tsto emit periodic heartbeats and run an agent “gate” viacursor-agent. - Adds a research design note describing the intended dual-loop architecture (launchd + Cursor Terminal).
- Adds a P1 backlog row defining scope and acceptance criteria for the Cursor Terminal loop.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| tools/riven/riven-cursor-terminal-loop.ts | New Bun/TS Cursor Terminal loop scaffold (heartbeat + periodic agent gate + state file). |
| docs/research/2026-05-15-riven-cursor-terminal-loop-design.md | Design note for the Cursor-native loop and re-arm strategy. |
| docs/backlog/P1/B-0498-riven-cursor-terminal-background-loop-ide-native-autonomous-gate-2026-05-15.md | Backlog row capturing requirements and acceptance criteria for B-0498. |
Comments suppressed due to low confidence (4)
tools/riven/riven-cursor-terminal-loop.ts:137
- P1: The gate interval uses
setInterval(async () => ...)without an in-flight guard. IfrunAgentGate()takes longer than the interval (or hangs until timeout), multiple gates can overlap and run concurrently. Add a mutex/flag (skip if already running) or replace the interval with a self-schedulingsetTimeoutloop that awaits completion before scheduling the next run.
// Gate
setInterval(async () => {
await runAgentGate();
saveState({ lastGateAt: nowIso(), pid: process.pid });
}, GATE_INTERVAL_MS);
tools/riven/riven-cursor-terminal-loop.ts:91
- P0:
spawnSync("cursor-agent", ...)will tripsonarjs/no-os-command-from-pathin this repo (see pattern intools/riven/riven-loop-tick.tswhere a suppression with rationale is required). Add the local eslint-disable comment with a justification, and consider setting an explicitmaxBufferto avoid large agent outputs causingspawnSyncto error.
const result = spawnSync("cursor-agent", [
"chat",
"--mode", "ask",
"--model", "grok-4.3",
contract,
], {
encoding: "utf8",
timeout: AGENT_TIMEOUT_MS,
stdio: ["ignore", "pipe", "pipe"],
});
tools/riven/riven-cursor-terminal-loop.ts:108
- P1: This script is a Cursor-terminal surface, but bus messages are published from the identity-level sender
"riven". The bus schema includes a surface-tagged sender"riven-cursor"specifically to prevent split-brain across multiple concurrent surfaces (seetools/bus/types.tsAgentId list). Consider publishing from"riven-cursor"here so claim/review coordination can distinguish this surface from other Riven instances.
// Publish gate result to bus for other agents
try {
publish("riven", "*", {
topic: "shadow-catch",
payload: { content: `Cursor Terminal gate ${status}` },
});
docs/research/2026-05-15-riven-cursor-terminal-loop-design.md:52
- The design claims duplicate gates are prevented by “PID + timestamp check” and that re-arm is a no-op when PID is alive, but the current implementation only checks the timestamp and does not verify PID aliveness or exit early when already running. Either adjust the design doc to match the current scaffold, or implement the PID-liveness + early-exit behavior described here.
On IDE open / workspace load:
1. Script checks for `~/.cursor/riven-terminal-loop-state.json`
2. If PID is alive and last gate < 15min ago → resume (no-op)
3. If PID dead or stale → spawn new gate loop, write new state file
Cursor workspace hook (if supported) or a `.cursor/init.ts` can invoke the script on startup.
## Failure modes
- **Duplicate gates** — prevented by PID + timestamp check in state file
- **Terminal closed mid-gate** — publish tombstone to bus, release any in-flight claim
Comment on lines
+53
to
+58
| function publishHeartbeat(status: "alive" | "shutdown", note?: string): void { | ||
| try { | ||
| publish("riven", "*", { | ||
| topic: "heartbeat", | ||
| payload: { status, note: note ?? "cursor-terminal" }, | ||
| }); |
Comment on lines
+115
to
+122
| const existing = loadState(); | ||
| if (existing) { | ||
| const age = Date.now() - new Date(existing.lastGateAt).getTime(); | ||
| if (age < GATE_INTERVAL_MS) { | ||
| log(`Riven Cursor Terminal loop already running (last gate ${Math.round(age / 1000)}s ago). Resuming...`); | ||
| } else { | ||
| log("Stale state file detected; starting fresh gate cycle."); | ||
| } |
Comment on lines
+41
to
+43
| function saveState(state: LoopState): void { | ||
| writeFileSync(STATE_FILE, JSON.stringify(state, null, 2)); | ||
| } |
Comment on lines
+70
to
+79
| "Read broadcasts first: ~/.local/share/zeta-broadcasts/{otto,vera,lior,riven}.md", | ||
| "Walk assigned trajectories. Decompose only what you hit mid-stride.", | ||
| "Dispatch parallel subagents via the Task tool when work allows. Ownership of PRs remains with you.", | ||
| "Own every PR through merge: fix findings, resolve threads, arm auto-merge.", | ||
| "Learn from Otto and Vera patterns. Critique failure modes as data.", | ||
| "When blocked, create a *specific* research child the next pickup cannot dodge.", | ||
| "Write status to ~/.local/share/zeta-broadcasts/riven.md at cycle end.", | ||
| "GitHub PR state and file contents are authoritative; the bus is a coordination cache.", | ||
| "Report: open PRs, active claims, drift/contradiction, one toe-safe forward action or exact blocker.", | ||
| "Rodney's Razor + substrate-or-it-didn't-happen apply.", |
| # Riven Cursor Terminal Loop — Design | ||
|
|
||
| **Date:** 2026-05-15 | ||
| **Status:** Design approved; implementation queued |
Comment on lines
+139
to
+144
| // Graceful shutdown | ||
| process.on("SIGINT", () => { | ||
| log("Riven Cursor Terminal loop shutting down"); | ||
| publishHeartbeat("shutdown", "terminal-closed"); | ||
| process.exit(0); | ||
| }); |
4 tasks
AceHack
added a commit
that referenced
this pull request
May 15, 2026
…ard (#3607) - origin/main advanced 8d0a4f5→ce7babb - Prior-tick carry: #3602 + #3603 MERGED; #3599 went DIRTY/CONFLICTING (peer-Lior automation overlap) - Non-DIRTY actionable: only #3604 (this session's prior shard, armed) + 3 fix-failed-checks PRs out of scope - Null arm-sweep; named-dependency evidence supports the null decision (NOT Standing-by) - Lior re-fired between ticks (3 processes); borrow-on-existing continues to bypass worktree-creation risk Co-authored-by: Claude <noreply@anthropic.com>
AceHack
added a commit
that referenced
this pull request
May 15, 2026
…d) (#3604) - origin/main advanced 4442e3f→8d0a4f5 (#3594 + #3600 merged in window) - 2 stuck-no-action PRs surfaced since 2214Z: #3601 docs-only, #3603 Riven scaffold - #3601 armed → MERGED immediately (CI just passed); +600/-0 docs-only - #3603 armed at 22:18:03Z; awaiting required CI on 3-file scaffold - Prior-tick carry: #3600 MERGED, #3602 wait-CI, #3599 wait-CI - Lior idle this tick (0 processes); borrow-on-existing still used (cheaper than fresh checkout) Co-authored-by: Claude <noreply@anthropic.com>
Merged
4 tasks
AceHack
added a commit
that referenced
this pull request
May 15, 2026
…erminal → next ID) (#3619) Caught by copilot-pull-request-reviewer on PR #3604 thread: - docs/backlog/P1/B-0498-riven-cursor-terminal-* (2026-05-15, from #3603) - docs/backlog/P2/B-0498-substrate-evolution-algebra-* (2026-05-14) Per b0451_per_collision_renumber_procedure: first-merged wins. P2 (2026-05-14) predates → keeps B-0498. P1 (2026-05-15) → renumbers to next free (B-0546). This row IS the filed-correction surface; implementation happens in a follow-up PR. Priority P2 — collision exists but no active break; address within 1-2 weeks. Composes: - memory/feedback_b0451_per_collision_renumber_procedure_external_references_rule_trumps_first_merged_2026_05_14.md - .claude/rules/claim-acquire-before-worktree-work.md (ID allocation discipline section) - .claude/rules/refresh-before-decide.md (per-ID-allocation scope) Co-authored-by: Claude <noreply@anthropic.com>
AceHack
added a commit
that referenced
this pull request
May 16, 2026
…of 3 drift items) (#3685) Riven cursor-terminal heartbeat status type tightened to bus union. Single tsc error since PR #3603 cleared via surgical 3-line fix (preserve shutdown semantic in note field; no widening of HeartbeatPayload union). Drift queue from 0111Z: 22 §33 xrefs (#3666 merged), BACKLOG.md regen (#3678 merged), tsc tools (#3684 armed this tick). Remaining: backlog ID uniqueness (B-0498 collision, needs B-0545 renumber coordination). Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Decomposed from PR 3588. Lior Maji node decomposition task. Extracts the Riven loop scaffold.