Skip to main content

Driving Dapper from an autonomous agent

The single page an agent should read before invoking dapper. Covers the operational rules, the end-to-end debugging loop, common pitfalls, and the deeper-dive topics to consult when the situation calls for them.

Rules of engagement​

Discovery first, action second​

Run dapper debug sessions before any other debug command. Dapper's session targeting is the most common source of agent confusion — knowing what's there avoids guessing.

Say why you're running each command​

Pass --reason on every dapper invocation except dapper help, --help and -h, and a reason argument on every MCP tool call. Keep it to one short line. It is recorded in Dapper's telemetry.

dapper --reason "inspect the deadlocked worker" debug threads

Put the flag before the subcommand, or set DAPPER_REASON instead.

Pin a session when there are several​

When more than one session is active, pass --control-port=PORT (deterministic) or --scope-id=SCOPE (narrows the auto-discovery candidate set). Both are options of debug and mcp, not of dapper itself, so place them right after debug: dapper debug --control-port=PORT threads. --scope-id alone is not sufficient when multiple sessions share the same scope (e.g. dual-attach C++/Java) — the CLI errors out and tells you to use --control-port. Don't retry blindly. The full identifier model lives in dapper help sessions.

Don't infer flags from training data​

dapper's surface evolves. Run dapper help <topic> against the installed binary instead of assuming a flag exists. The help output reflects the version that's running, not what some older revision documented.

Wait for events in headless mode​

When a debug session is started in headless mode (dapper proxy from-config), the proxy emits SESSION_READY and PROGRAM_STOPPED events on stdout as JSON lines prefixed with [DAPPER_SESSION] (or as bare JSON lines on the numeric file descriptor passed via --events-fd FD — see dapper help proxy). Wait for them rather than polling — polling races against the adapter's startup and produces flaky behavior.

Variable references invalidate on every stop​

The variablesReference returned by scopes and variables is bound to the current stop. After any continue/step/pause, re-fetch via scopes then variables — re-using a stale reference will return wrong data or fail.

Don't dapper proxy directly unless you mean to​

The proxy is normally started by an IDE or by fdb. Running dapper proxy by hand is a manual-setup tool, not the default workflow.

Read once per session​

Topic content is stable for a given binary version. There's no value in re-running dapper help <topic> mid-session — the bytes don't change.

When to reach for a debugger​

Reach for the debugger when:

  • You need to inspect runtime state (variable values, call stack, thread interactions) that logging didn't capture or would be expensive to add.
  • You're investigating a behavior — wrong output, deadlock, race — rather than a known crash whose stack trace already names the culprit.
  • A test reproduces consistently and you want to step through the failing path.

Skip the debugger when a trace, log, or single targeted print would answer the question faster.

The loop​

  1. Examine. Discover what's running with dapper debug sessions. Look at dapper debug threads once stopped.
  2. Stop where it matters. Set a breakpoint at the suspected site. Prefer one or two well-placed breakpoints to many speculative ones.
  3. Inspect at the breakpoint. Walk the stack: stack-trace <thread> → scopes <frame> → variables <ref>. Re-fetch references after every stop.
  4. Form a hypothesis. "If X were Y, the variable would be Z." Use eval to test it without rerunning.
  5. Iterate. step over, step in, step out, continue. After each, re-examine state. Variable references invalidate on every stop.
  6. Fix and verify. Change source, restart the session, confirm the breakpoint no longer fires the bad path.

Multi-client driving​

The whole point of Dapper is that the IDE, the CLI, and an MCP-aware agent can share one debug session. Typical pattern:

  1. IDE launches the program, hits a breakpoint.
  2. The agent connects via dapper debug … from the shell, or via the MCP server.
  3. Agent and human take turns inspecting and stepping. The proxy keeps both sides consistent.

Common pitfalls​

  • Stepping blind. Examining the call stack before stepping costs nothing and often reveals the answer immediately.
  • Too many breakpoints. Each one slows execution and makes stops noisy. Add one, learn, then move it.
  • Stale variable references. A variablesReference from before the last stop will silently return wrong data; refresh after every navigation.
  • Wrong session. When multiple sessions are active and you didn't pin one, the next command may target the wrong debuggee.

Follow-up topics​

Read these when the situation calls for them — not preemptively:

  • dapper help sessions — deep dive on the scope-id / control-port / session-id model and the auto-discovery rules. Read when dapper debug sessions returns more than one entry or when a dual-attach error message points you here.
  • dapper help debug — full CLI reference for dapper debug subcommands with example outputs. Read once before constructing your first command, then return only when you need a flag you haven't used.
  • dapper help breakpoints — JSON spec for line / conditional / log-message breakpoints; function and exception breakpoint workarounds. Read before constructing anything beyond a bare line number.

For the full list of topics run dapper help.