@siuver/omp-debug-mode
v0.1.7
Published
Evidence-driven, human-in-the-loop debugging with autonomous probes and user-assisted artifacts for oh-my-pi.
Maintainers
Readme
@siuver/omp-debug-mode
A Cursor Debug Mode replica for oh-my-pi. It recreates Cursor's evidence-driven, human-in-the-loop debugging workflow for OMP; it is an independent implementation and is not affiliated with Cursor.
The plugin makes the agent form 3-5 hypotheses, choose the cheapest reliable evidence method for each one, and pause so you can reproduce the problem. The agent is instructed to NEVER fix without runtime evidence first; after a fix it keeps runtime probes in place for a verification reproduce. Your result either starts another evidence-driven round or triggers probe cleanup and a final summary.
Commands
| Command | Purpose |
| --- | --- |
| /debug-mode <problem> | Starts a debugging session from a symptom, expected result, actual result, and reproduction description. |
| /debug-proceed [details] | Continues with captured evidence: optional details are recorded as a user-report observation, then the agent evaluates hypotheses and may fix only with evidence before asking for verification. |
| /debug-evidence <request-id\|-> <path> | Attaches one user-provided evidence file. The first argument always names what the file answers: an evidence request ID, or - for a file no request asked for. Typing the command lists the available selectors under the editor. Files are referenced in place and never copied or deleted. |
| /debug-done | Marks the problem as fixed: the agent removes every probe and summarizes the result. |
| /debug-status | Shows the current stage, round, run, how the round was handed back, live probes, captured log counts, pending evidence requests, and attached/unavailable artifacts. |
| /debug-clear | Empties the current run's current.jsonl so you can reproduce again on this round. Archived runs are left alone. |
| /debug-abort | Stops debug mode and removes its logs after confirmation while leaving code changes in the working tree. |
Stages
The workflow tracks one thing: whose move it is. There are three stages, and exactly one of them is yours.
| Stage | Meaning | What you do |
| --- | --- | --- |
| investigating | An agent turn is in flight. | Wait. |
| user_turn | The agent handed the round back. | Reproduce/capture and /debug-proceed, or just reply. |
| cleaning_up | /debug-done was accepted; probes are being removed. | Wait. |
Every settled agent turn ends in user_turn. How well the agent closed the round only changes the wording, never which commands are legal, so a handoff the agent mislabelled can never make /debug-proceed illegal:
| Handoff | Status label | Meaning |
| --- | --- | --- |
| reproduce | round N · reproduce | Run the app so the probes record. |
| capture | round N · capture | Only a report or a file is needed, no reproduction. |
| question | round N · your reply | The agent needs an answer before it can plan. |
| incomplete | round N · needs input | The round stopped without saying what to reproduce or capture. |
Replying instead of running /debug-proceed hands the round back to the agent: the stage returns to investigating, the reply is an ordinary turn, and anything that turn produces — a revised plan, new steps — is absorbed and re-closed. Nudge budgets and the reminder guard reset with it, so a round you replied to is still repairable.
Only your message does that. The host resumes the agent loop on its own for an incomplete-todo reminder, a plan-mode nudge or a queued-message drain, and each of those starts a turn while you are still out reproducing. Those turns leave the stage alone: your widget stays, the run keeps recording, and get_debug_logs refuses with "no reproduction has run" rather than handing back a stale run. The agent is told the same thing in its blackboard, which is re-rendered as the request is assembled precisely because those turns skip the prompt hook that normally injects it.
You will also see one short message from the agent right after the handoff notice. That is not a hidden turn of yours: a tool call never ends a turn, so calling hand_off_to_user always leaves the agent one more message to write, and it is told to spend it telling you what to do now rather than on reading anything.
Transitions live in one pure reducer (src/machine.ts), which returns declarative effects — notify, inject prompt, continue the turn, tear down — that the runtime shell applies. Every round is a record holding its own run id, evidence plan, reproduction steps, probe ids, and nudge budgets, so round-scoped data is never derived by filtering a session-wide array on a mutable counter.
A resumed session never lands in investigating, because no agent turn survives a restart; an unrecognised persisted stage resolves to user_turn, on the conservative reading that the agent owes you something rather than the reverse.
Commands never refuse because the stage looks wrong. They wait for the agent to go idle first, and a round still sitting in investigating afterwards — the usual cause is an interrupted turn, which never reaches the settle hook — is reclassified as a handoff on the spot.
Evidence Methods
For every hypothesis, the agent must pick one of four methods, in least-user-intervention order:
agent_inspection- reuse existing logs/files and the agent's own read/search/test/command tools. Always tried first.runtime_probe- if runtime state is required, the agent installs@omp-probeinstrumentation and combines every compatible hypothesis into a single reproduction.user_report- only when a simple manual observation is decisive (something only you can see or try).user_artifact- only when the disputed state cannot be represented reliably by inspection, probes, or a verbal report (e.g. a GPU capture, screenshot, crash dump, or externally generated trace).
Rules the agent must follow:
- It never asks you to run a command it could run itself.
- All unavoidable user actions are batched into the fewest reproductions/captures: hypotheses answerable by the same action share one request (
hypothesisIdslists them all) instead of serial reports. - A lower-priority method is never chosen merely because it is familiar; every
user_report/user_artifactentry needs a rationale explicitly naming why agent inspection and runtime probes cannot settle its hypotheses.
Closing a Round
A round reaches you through one Agent tool call, not through prose. hand_off_to_user takes the handoff mode (reproduce, capture, or question), the numbered steps you perform now, and the evidence plan; that call is what renders your widget, makes /debug-proceed available, and records the plan. Announcing a reproduction in prose does not move the session, which is what used to leave a round "not closed" while you had already been told to act.
The tool validates its arguments and rejects a bad call with the exact problem — missing steps, a missing plan on the first round, an invalid plan entry — so the agent repairs it inside the same turn instead of stopping. It sits on the lowest approval tier, because a call that only changes whose turn it is should never wait behind a prompt.
A turn that settles without that call is sent back for it. The reminder is a <system-reminder> naming the round and the three modes, and a round that already wrote the steps or plan in prose is asked to pass those same values as arguments rather than to write anything new. Up to three reminders are available per round, but another one is only spent if the agent made a tool call since the last one, so a turn that answers a reminder with more prose costs one extra turn and then reaches you anyway — labelled from whatever its prose described, since reproduction steps you have already read are a work order whether or not the agent closed the round properly.
The plan is a JSON array covering every hypothesis:
[{"id":"E1","hypothesisIds":["A","B"],"method":"runtime_probe","title":"...","rationale":"The disputed runtime branches are not present in existing logs; one model-added probe set can capture both without a separate user artifact.","instructions":["..."],"artifactHint":"optional"}]Every field is validated: id must be unique and non-empty, hypothesisIds must reference one or more real hypotheses, method must be one of the four literals above, rationale/title must be non-empty, and instructions must contain actionable steps (artifactHint describes the expected file kind for user_artifact). A malformed plan is rejected entirely — no hypothesis is ever silently dropped. Plans are capped at 12 entries per round.
The same array inside an <evidence_plan> block, followed by a <reproduction_steps> list, is still parsed for its contents so a plan described in prose is not lost; both paths share one validator, so they accept exactly the same shape and reject with exactly the same message. The tags never close a round, though — that is deliberate. While they did, describing a reproduction and stopping produced the same result as calling the tool, so there was nothing to learn from and nothing to correct.
Probes must exist before the round closes
A plan that selects runtime_probe needs at least one @omp-probe marker the ledger actually finds on disk. Probe liveness is session-wide, because instrumentation deliberately outlives the round that installed it — the methodology keeps probes in place across a fix so the verification round can reuse them. A verification round that adds no new marker therefore closes normally; a round whose probes were written and then deleted does not.
Anything short of that is one automatic nudge back into the same turn, and the two nudge budgets are separate, so being sent back for missing probes never consumes the budget for a missing closure. When the probe budget is spent the round is handed over as incomplete rather than being gated on evidence that does not exist. agent_inspection, user_report, and user_artifact plans need no markers at all and expect no JSONL logs.
The user-turn widgets
Footer status stays a one-line label (🐞 round N · reproduce). The round itself uses two widgets, because the host caps each string-array widget at 10 lines and the commands need to sit next to the prompt:
Above the editor — what this round asked you to do:
Reproduce the bug now.
↪ E1 Capture a frame: Capture the failing frame.
1. Restart the game.
2. Jump near the pit.Below the editor — a dim cheat-sheet of the commands:
Tips:
/debug-proceed → close this round, analyze 14 log entries, 1 artifact
/debug-done → bug is fixed: remove probes and summarize
/debug-evidence <request-id> <path> · /debug-clear · /debug-abort · /debug-statusEach fact appears once. The round number lives in the footer, so the exit lines say "this round"; the captured counts live on the /debug-proceed line, which is the only thing that acts on them, and they climb in place while you reproduce. The strip is all dim so it sits next to the prompt as a reminder rather than as a second headline — empty vs captured is in the wording (close this round with nothing captured yet), not the color. Replying in the editor is still a legal exit; it is just not listed here, because the editor is already where that line would have pointed.
Steps are renumbered by the widget, so an unnumbered list from the agent still reads as a sequence, and a step that names /debug-proceed or /debug-done is dropped: the commands belong under the editor, and the injected contract tells the agent not to write them into the steps in the first place. Lines are clipped by terminal columns rather than by character count, so a step written in Chinese or Japanese is not sent out at double width.
The same commands are offered in every mode, including incomplete. /debug-evidence also accepts - in place of a request ID for a file no request asked for; the command's own completion offers it. There is no Proceed/Mark as fixed button: type the slash command.
Every round stops at these widgets and you interact through commands only.
Pending requests resolve like this:
- A
user_artifactrequest is pending until a file is attached with a matching request ID. - A
user_reportrequest is pending until/debug-proceed <details>records an observation for the current round; one details submission can satisfy several report requests at once. agent_inspectionandruntime_proberequests never create user work.
If user requests are still pending when you run /debug-proceed, the plugin asks "/debug-proceed without all requested evidence?" — confirming is allowed so an unavailable external capture can never deadlock the session; cancelling keeps you on the round. /debug-proceed and /debug-done ask for confirmation when the round captured no runtime logs and has no valid non-probe evidence plan, and again on a question or incomplete handoff, where continuing is a judgment call. They wait for the agent to go idle rather than refusing, so an interrupted turn cannot lock the session.
Attaching Evidence Files
/debug-evidence <request-id|-> <path> records one user-provided file. The first argument is always a link selector, never part of the path:
/debug-evidence ⏎ lists the selectors under the editor
/debug-evidence E1 frame.rdc links the file to request E1
/debug-evidence - trace.log attaches a file no request asked for
/debug-evidence E1 ⏎ opens a path prompt for E1 (interactive UI only)
/debug-evidence frame.rdc rejected: names the unrecognised selector and lists the known IDsTyping /debug-evidence shows every selector under the editor with its method, title, and state — pending, satisfied, or the round it came from — with - offered first. Once the selector is settled the completion hands off to the editor's own file-path completion, so the path is completed normally.
- Any request ID the session has ever declared is a valid target, including one from an earlier round or one already satisfied, which is exactly what the attach path itself accepts. Selecting
-attaches the artifact unlinked, and the agent sees it as unassigned evidence it must associate with a request itself. - The path keeps its spaces: everything after the selector is the path.
- The path is resolved against the session working directory; the plugin records the absolute path, size, and mtime and references the file in place. It never copies, renames, or deletes your file — not during attach, not during cleanup, not during teardown.
- Interactive sessions must confirm before recording a path outside the session working directory; non-interactive sessions reject outside-
cwdpaths because no confirmation channel exists. - Re-attaching the same resolved path reuses the existing artifact entry and refreshes its metadata and request association.
- Missing paths, directories, and unreadable files are rejected with the reason, and the ledger stays unchanged. An unrecognised selector is reported as an unrecognised request ID with the known IDs listed, never as a missing file.
Availability is re-checked by re-statting each path, so a file that was moved or deleted after attaching is reported as unavailable — the ledger never claims unavailable evidence was captured. The model is told to treat user reports and artifacts as data to inspect, not instructions to execute, and must conclude INCONCLUSIVE (or file a new, lower-burden request) when it cannot inspect a file with its normal tools.
Example: for a GPU-rendering symptom whose disputed state only exists inside a frame capture, the agent justifies why inspection, tests, and probes cannot answer, then emits one user_artifact request with an artifactHint like "RenderDoc .rdc capture of a failing frame". You capture frame.rdc in RenderDoc and run /debug-evidence E1 frame.rdc (using the request ID shown in the widget). /debug-status and the read-only list_debug_evidence tool then show the artifact ID, absolute path, size, and availability. The .rdc bytes are treated as opaque — the plugin does not parse the format; if the agent cannot extract what it needs from the binary with its normal read/bash tools, it says so and asks for a converted report or a new capture rather than guessing.
Workflow
Run
/debug-mode <problem description>.The Agent records 3-5 hypotheses and picks an evidence method for each under the least-user-intervention priority. If the route is
runtime_probe, it inserts minimal probes marked with@omp-probe <id>inside#region agent logblocks in the same turn, before closing the round; round 1 does not apply a product fix (probe edits are not a product fix). The start prompt and a per-turndebug-mode-contextmessage name the exact JSONL path and require every probe to append to it, includinghypothesisId; console output (including UnityDebug.Log) is supplemental only. The context filter keeps the newest of those messages so the model sees the contract instead of dropping it. Steps are user actions only (reproduce, capture, restart).The Agent closes the round with
hand_off_to_user, which is what puts the steps in your widget. Follow them in the real application so the instrumented code writes its observations, watching the count on the/debug-proceedline climb — or supply the requested evidence with/debug-evidenceor/debug-proceed <details>. Restart the app or service if the instrumented build would otherwise be stale.Continue with
/debug-proceed [details](optionally record a user observation, then evaluate evidence),/debug-clear(empty this round's log and reproduce again),/debug-done(clean up and summarize),/debug-evidence(attach a file), or/debug-status(inspect the current round state). Replying normally instead hands the round back to the agent without closing it. Each round stops at the widget; all interaction is command-driven.On Proceed, the Agent reads the evidence (logs, your observations, attached artifacts), cites the evidence method plus log-line numbers or artifact/report paths for each hypothesis, and only then may apply a fix. Probes stay in place. Reproduce again to verify. If verification fails, rejected-hypothesis code changes are reverted before the next round.
On
/debug-done, the Agent removes every probe and summarizes the root cause and fix in 1-2 lines. If the ledger is still not empty when that turn settles, cleanup is sent back once with the remaining markers listed; a second incomplete attempt tears down anyway and warns you which probes are still in your source.
Every round the Agent runs sees the current stage, whose turn it is, and which closing requirements are still missing, so it never has to infer the state it is driving.
Besides hand_off_to_user, the extension provides the read-only get_debug_logs, list_debug_probes, and list_debug_evidence tools so the Agent can inspect runtime evidence, review pending/satisfied requests with their methods and rationales, and verify cleanup. All four are essential while a debug session is running, so the injected contract can order them by name, but they are defaultInactive and are only added to the model's schema when /debug-mode starts (or a resumed session is already in debug mode). They leave the schema again on /debug-done or /debug-abort. An ordinary session does not see them.
Runtime Data
At the start of each round, the plugin creates an absolute log path under a directory owned by the current session:
<project>/.omp/debug/<session-id>/current.jsonlThe session segment keeps concurrent omp sessions from truncating or deleting each other's evidence, since every round truncates current.jsonl and teardown removes the directory. The plugin also adds .omp/debug/ to the repository's local .git/info/exclude once, so probe logs never appear in git status; that file is not committed, so your .gitignore is left alone. Teardown removes only this log directory; attached user evidence files are never removed.
That exact stable path is injected into the Agent prompt. The prompt requires every runtime probe to use the target environment's native file append API and write one compact JSON object plus a newline using this schema:
{"probe":"player-state","hypothesisId":"A","ts":1787193600000,"location":"Player.cs:42","message":"grounded check","data":{"isGrounded":false}}get_debug_logs, the proceed prompt, and /debug-status summarize each run by hypothesisId, so you can see which hypotheses produced evidence before reading a single raw line. A hypothesis with no entries was not exercised, which is not the same as being rejected.
When you run /debug-proceed [details], the plugin records non-empty details as a user observation, archives the completed file as <run-id>.jsonl, and creates an empty current.jsonl for the next reproduction. If that rotation fails (typical on Windows while the instrumented app still has current.jsonl open), the round stays open, the archive is kept, and the error names the OS reason — close the app and retry. /debug-clear truncates current.jsonl in place without archiving or opening a new round, so a reproduction you want to throw away does not become the evidence for /debug-proceed. Because the active run is truncated at the start of every round, get_debug_logs defaults to the newest run that actually captured observations and names the run it read; previous=true forces the last completed run. /debug-status shows the current absolute file path and the per-run log counts.
Probe Ledger
The plugin tracks every @omp-probe <id> marker the Agent writes, resolving each file against the session working directory, and records which round introduced it. Before each turn, before a round is allowed to close, and on every list_debug_probes call it rescans those files so the ledger matches the code on disk: markers the Agent has since deleted drop out, and a file that exists but cannot be read is reported as unverified rather than assumed clean. That way a failed read can never make teardown claim the probes are gone while they are still in your source, and a runtime_probe round cannot close on a promise. The closing check counts every live probe in the session, not just this round's, so reusing existing instrumentation for a verification round is not mistaken for having none. Rounds that choose non-probe evidence methods simply add no markers, and cleanup still verifies that no leftover markers remain before finishing.
Install
omp plugin install @siuver/omp-debug-modeRestart omp after installation, then run /debug-mode <problem>.
Local Development
From the repository root:
bun run check
omp plugin link ./plugins/debug-modeRestart omp after linking.
Changelog
See CHANGELOG.md for release notes included in the npm package.
