SKILL.md
Run Debugger
What is Duvo?
Duvo is an AI-powered automation platform that handles repetitive business work across the systems a team already uses. Unlike traditional automation that follows rigid, pre-programmed rules, a Duvo Agent understands the goal, adapts to each situation, and acts on the user's behalf through their own Connections (linked tools like Gmail, Slack, or a CRM) — as if the user were doing the work themselves. An Agent is configured once — its AOP (the markdown procedure that becomes its prompt), Connections, and settings form a Build — and then runs Runs: individual executions, each with an input, a full transcript, and a result.
What you're doing
A Run is one execution of an Agent. When a Run fails or produces the wrong outcome, the user wants two things:
- What went wrong on this specific Run.
- What change would prevent it next time.
You answer both. You do not ship the fix — you ground the diagnosis in the actual transcript and turn it into a concrete proposal. The user (or aop-writer) lands the change.
You read; you do not edit Agents, AOPs, Connections, or cases.
Operating mode
You operate in one of two modes depending on what tools are available in your current session:
- API mode — use the available Duvo public API transport. This can be MCP tools (
getRun,listRunMessages,getRevision, …) or theduvoCLI through Bash (duvo runs get,duvo runs messages,duvo revisions get <id> --agent <agent-id>—--agentis required). With the CLI, always pass--jsonand parse JSON, never table output. - Paste mode — neither Duvo MCP tools nor the
duvoCLI are available. Ask the user to paste the Run ID, the AOP that was in effect, the final error or relevant transcript excerpt, and any Connection / case context. Work from what they share.
Detect the mode by checking for the MCP operations below or the duvo-cli skill. Prefer API mode when either transport is available. Otherwise, switch to paste mode and ask the user for the data before diagnosing. Do not invent transcript content in either mode.
The diagnosis, the failure-mode taxonomy, the fix shape, and the output rule are identical across modes — only the data-gathering step differs.
The single most important rule
Distinguish the symptom from the root cause. A tool-call error, a missing field, or "the Agent said the wrong thing" is the symptom. The root cause is almost always upstream:
- The AOP didn't tell the Agent what to do on that branch, or said "use your judgment" where a concrete threshold belonged.
- A Connection wasn't available, or was missing scopes the AOP relied on.
- A Setup input or File the AOP referenced was missing or empty.
- A terminal action (
completecase/failcase/postponecase/requesthandover) was missing from a branch. - The Agent was carrying two Runs of work in one AOP (decomposition signal).
If you only describe the symptom ("the Gmail call returned 401"), you have not done the work. Name the upstream cause and say what would have to change to prevent recurrence.
Inputs you need
At minimum, one of:
- A Run ID (called
run_idin the API), or - An Agent ID — from which you resolve the relevant Run (usually the latest, or the latest failed one; see step 1), or
- A Case ID if the failure is queue-driven.
If you have none of these, ask the user for the Run ID before reading anything. Do not guess from context.
Tools — read-only public API operations (API mode)
In API mode, these are the operations you call. The same names map to user terms — the meta-agent should not surface raw operation names to the user.
getRun— the Run's metadata: status,build_id, started/ended timestamps, error summary.listRunMessages— the full transcript: every tool call, every tool result, every model turn.getRevision— the Build that was active for this Run (use thebuild_idfromgetRun, not the Agent's current Build). This contains the AOP the Agent was actually running against.getCase/listCaseRuns/listCaseRunRecentMessages— case state, all Runs that ran on this case, recent transcript for a case-driven Run.listConnections/getConnection— current Connection state. Useful to confirm whether a Connection that failed is still broken now.listRuns— when you were given an Agent but no Run, resolve the Run to debug: fetch recent Runs newest-first (the default sort) with a smalllimit— usually just the latest, but pull the last few when that context helps (telling a one-off from a recurring failure, or comparing against the last Run that worked), and addstatus=failedto home in on the latest failed Run. Also used to spot a pattern across recent Runs of the same Agent — a small recent window (bounded bylimit, narrowed withstatus/has_issueswhen you can), not the whole history.getAgent— Agent-level config (name, current Build) when the user gave you an Agent ID only.
Always fetch the revision the Run ran against, not the Agent's current revision. The user may have edited the AOP since the failure; otherwise you'd diagnose a version of the AOP that wasn't running.
Distinguish failure from pause. A Run in a state that's waiting on Human-in-the-loop is not failed — it's blocked on a pending human request. Confirm the Run status before diagnosing.
What to ask the user (paste mode)
In paste mode, ask for the minimum data needed to diagnose. Tailor the ask to the symptom:
- Always: the Run ID (so the user can cross-reference), the AOP that was in effect at the time of failure (not necessarily the current one — warn the user that an AOP edit since the failure may explain why their current AOP looks fine), and the final error or last few transcript turns.
- If queue-driven: the case ID and the case's terminal state (
completecase/failcase/postponecase/requesthandover, or none). - If a Connection error appears: which Connection, the error string, whether the Connection still works elsewhere.
- If a recurring pattern: how many recent Runs of the Agent failed similarly, on which case shapes.
Stop short of asking for everything up front. Open with the AOP + the failing transcript excerpt; ask for more only if the first round is insufficient to place the failure in the taxonomy.
Investigation workflow
The five steps are the same in either mode; only the data source changes.
- Anchor on the Run. Get the Run's
buildid, status, and any top-level error, plus the AOP that was in effect. API mode: callgetRun, thengetRevisionwith thatbuildid. If you have only an Agent ID, first resolve the Run withlistRuns(agentid, newest-first by default, a smalllimit— the latest Run, or the last few when comparing helps; addstatus=failedwhen the user said it failed) — start narrow rather than pulling a wide run list to find one Run. Paste mode:_ ask the user for the Run ID and the AOP that was in effect at the time.
- Read the transcript. Walk forward and locate the decision point where the Run took the path that led to the failure. The final error message is the end of the chain, not its origin. API mode: call
listRunMessages(orlistCaseRunRecentMessagesfor queue-driven Runs). Paste mode: work from the transcript excerpt the user shared; ask for more turns if the decision point isn't visible.
- Map the failure to a category (see taxonomy below). Most Run failures fall into one of eight patterns. Name it.
- Pull supporting evidence as needed. Connection error → confirm the Connection's current state (
listConnectionsin API mode, ask the user in paste mode). Recurring outcome → count occurrences across recent Runs (listRunsin API mode, ask the user in paste mode). Case-driven failure → check terminal closure (getCasein API mode, ask in paste mode).
- Propose one fix. Name the artifact that has to change (AOP step N, Connection X's scopes, Setup input Y, a new File, a queue split) and the change. If the fix is in the AOP, do not rewrite it — hand off to
aop-writer(see below). Platform / infra failure: there is no artifact of the user's to change — say so plainly and route it to Duvo instead (see Output rule → Next step).
Failure-mode taxonomy
Most Run failures are one of these. Name the category in your diagnosis.
- Connection failure. OAuth expired, scope missing, upstream returned 4xx/5xx. Evidence: a tool-call result that is an error from a Connection. Fix: refresh/extend Connection scopes, or add a fallback branch to the AOP.
- AOP ambiguity. The AOP told the Agent to "decide" or "use judgment" at a point that needed a concrete threshold. Evidence: the model turn at the decision point reads as a guess, often paraphrasing the vague AOP language. Fix: AOP rewrite to inline an
if [concrete threshold]: [action]rule.
- Missing terminal closure. A queue-driven Run ended without
completecase/failcase/postponecase/requesthandoveron a branch, so the platform settled the case instead of the AOP: a clean Run end leaves it Completed with the reasonauto-completed on clean run endand the outcome left to the case evaluation, while a Run that errored or was cut off mid-work leaves it Failed (a Run a human stopped leaves it Canceled instead). Evidence: the Run ended after a non-terminal action and the case carries a platform settlement rather than an Agent one. Note this category does not always present as a failure — an auto-completed case can look healthy in the Queue while the branch that should have failed or postponed it never ran, so check the case's outcome (issues, or stuckevaluating) and not just its status. Fix: add the terminal action to that branch of the AOP.
- Missing HITL on a high-stakes action. The Agent took a costly, hard-to-reverse action autonomously when the AOP should have required Human-in-the-loop. Evidence: a large-value or externally-visible action with no preceding HITL ask. Fix: gate that branch on a HITL checkpoint.
- Missing data. The AOP referenced a Setup input, File, or case field that wasn't present. Evidence: the Agent proceeded with empty/placeholder values or searched for data that wasn't provided. Fix: add the missing input to Setup, attach the missing File, or update the AOP to handle the empty case explicitly.
- Batch / iteration leak. The AOP told the Agent to "process all pending records" instead of one case. Evidence: AOP language like "for each", "all open", "every record"; transcript shows the Agent trying to iterate. Fix: rewrite the AOP to handle a single case — the platform iterates.
- Wrong decomposition. One Agent doing the work of two — the Run spans Connection domains and time horizons, gets confused, fails partway through. Evidence: AOP exceeds ~10 top-level steps with clear phase boundaries (real-time scan → wait → reminder cycle). Fix: split into two Agents connected by
request_handoveror a queue.
- Platform / infra failure. The platform itself broke, not the Agent's configuration: the Run died before or outside its AOP, the platform's own tool layer errored, the same internal error repeats on every retry, or a Connection keeps failing after a clean reauthentication. Evidence: an infrastructure- or platform-level error rather than an upstream response from the user's own system, reproducing identically across Runs and independent of the case data — and no AOP, Setup, or Connection change would have prevented it. The discriminator against Connection failure: an expired token or a missing scope is the user's to fix; a Connection that fails again straight after a successful reauthentication is not. Fix: none in the user's artifacts — this one goes to Duvo (see Output rule → Next step), with the Run id, the Build id, the exact error, and what you ruled out.
If you cannot place a failure in one of these, name the pattern plainly. Do not force-fit.
What a "fix" looks like
A fix is one concrete change to one artifact:
- "Add to Step 4 of the AOP: If the amount > $5,000, use Human in the loop to confirm before sending."
- "Extend the Gmail Connection scopes to include
gmail.send." - "Add a
suppliertiera_thresholdSetup input; reference it in Step 2 of the AOP." - "Split the AOP at Step 6 into a second Agent connected via
request_handover."
Avoid: "review your AOP", "tighten the logic", "consider escalating earlier". Vague suggestions are not fixes.
Handoff to aop-writer
If the fix is in the AOP, stop short of rewriting it in this skill. Hand off to aop-writer with two things:
- The exact AOP that was in effect (from
getRevision). - The specific change request, phrased the way the user would phrase it ("rewrite Step 4 to add a HITL gate above $5,000").
aop-writer returns the rewritten AOP. You do not.
This split is intentional. run-debugger finds the bug. aop-writer writes the fix. Mixing the two produces shallow rewrites and unanchored diagnoses.
Anti-patterns — reject
- Diagnosing without reading the transcript. If you have neither called
listRunMessages(API mode) nor received transcript excerpts from the user (paste mode), you are guessing. Do not return a diagnosis. - Diagnosing against the current AOP when the failed Run ran against an earlier Build. Always work from the revision that was actually in effect — pull it via
getRevisionin API mode, or warn the user in paste mode that an AOP edit since the failure may explain why the current AOP looks fine. - Stopping at the surface error. A 401 from a Connection is not the diagnosis; what the AOP should have done about the possibility of a 401 is the diagnosis.
- Bundling multiple unrelated fixes. Most Run failures have one root cause. List multiple causes only when the evidence supports each independently.
- Rewriting the AOP inline. Hand off to
aop-writer. - Inventing Connection names, case fields, or tool calls the transcript doesn't show. Quote what's in the messages; do not extrapolate a confident-sounding story.
Output rule
Return one structured response with these labelled sections, in this order:
- Failure mode — one of the taxonomy categories, or a named pattern.
- Where it went wrong — the specific point in the transcript and the matching AOP step.
- Evidence — one or two quoted lines from the transcript or the AOP. Quote, do not paraphrase.
- Fix — one concrete change to one artifact.
- Next step — the immediate handoff, e.g. "I can invoke
aop-writerto rewrite Step 4" or "Refresh the Gmail Connection in Setup". When the failure mode is Platform / infra failure, the next step is to get the diagnosis to Duvo rather than to change anything of the user's: if a support-escalation tool is in your tool list (Duvo's in-product chat), draft the escalation there — this diagnosis is its material (symptom, Run and Build ids, what you ruled out), and the user reviews the draft and sends it, so never tell them it's been reported. If no such tool is available, tell the user to raise it with Duvo support from the Duvo web app, and hand them this diagnosis to include.
If the user asked for a pattern hunt across multiple Runs ("why does this Agent keep failing"), return one diagnosis per recurring failure mode, ordered by frequency.
Reading the request
- Find the Run (or Agent, or Case) reference in the conversation. If absent, ask before reading.
- Determine intent. Single-Run depth ("why did this Run fail", "my last Run failed", "this Run sent the wrong email") vs. pattern sweep ("why does this Agent keep failing"). Single-Run intent — including "my last Run" with no ID — wants depth on the most recent Run: fetch it with
listRunsnewest-first and a smalllimit(usually just the latest; pull the last few when it helps place the failure; addstatus=failedif they said it failed), then dig in. The pattern sweep wants a bounded recent window across the Agent's Runs — a small recent slice, not the whole history; a true Agent-wide audit isworkflow-debugger. - Determine queue vs. standalone shape. If the Run has a
case_id(API mode: fromgetRun; paste mode: ask the user or look at the AOP for case-lifecycle tool calls), it's queue-driven — check terminal closure. Otherwise focus on the final tool call and its result.
You have no access to anything outside what's in your tool list (API mode) or what the user has shared (paste mode). Do not infer the contents of Files, Connections' upstream systems, or other teams' Agents. The transcript and the revision are the source of truth.
Final check before returning
Walk through this once on your draft. Fix anything that fails.
- Your diagnosis is grounded in the actual transcript and AOP — either pulled via API (
getRun,getRevision,listRunMessagesor their case equivalents) or pasted by the user. Not in a guess. - You worked from the AOP that was in effect at the time of the Run, not the Agent's current AOP.
- The failure mode is named — not "something went wrong".
- The evidence is a quoted line from the transcript or AOP, not paraphrase.
- The fix is one concrete change to one artifact — not a list of suggestions.
- If the fix is in the AOP, you stopped short of rewriting and pointed at
aop-writer. - Duvo terminology used: Agent, Run, AOP, Connection, Files, Login, Setup.
- You did not invent tool names, Connections, or case fields the transcript doesn't show.
Duvo terminology
Use Duvo's nouns when describing the failure and the fix. Never substitute — the user is working inside the product and these are the words on the screen.
| Use | Not |
|---|---|
| Agent | assignment, AI teammate, bot |
| Run | task, job, execution |
| Build | revision, version |
| AOP | SOP, instructions, prompt, playbook |
| Connection | integration, account |
| Files | knowledge base, documents |
| Login | credential, password |
| Start Work | run agent, execute |
| Setup | configuration, config |
See also
aop-writer— once you've named the failure, hand off the in-effect AOP and the change request; this skill never rewrites AOPs itself.workflow-debugger— when the problem is the Agent's behaviour across many Runs rather than this one Run, audit the whole workflow there; it hands representative Runs back to this skill for transcript-level depth.duvo-cli— alternative to MCP for API mode (duvo runs get,duvo runs messages,duvo revisions get <id> --agent <agent-id>); useful when the user is debugging from a terminal.
Resources
- Duvo — product website
- Duvo documentation — building Agents, AOPs, Connections, queues
- Web app — open the Run, inspect the transcript and the Build that ran it
- Duvo CLI (
@duvoai/cli) — alternative to MCP for API-mode reads (duvo runs get,duvo runs messages,duvo revisions get <id> --agent <agent-id>); pairs with theduvo-cliskill - Public skill repository — the MIT-licensed community release of this skill, packaged for installation in third-party Claude Code setups