Skip to content

Debugging — aowlidbg


aowlidbg is aowli's trace/debug layer: the aowli-dbg binary, plus the trace/debug tools the aowlcode Claude Code plugin exposes over it. Both inspect a live typed-AIF execution — call tree or frame locals — without adding a single echo/write to the source.

Flags

FlagBinaryEffect
--break:LINEaowli-dbgBreakpoint on a source line number. Fires in every routine that line number appears in, across every module — file-agnostic.
--break-func:NAMEaowli-dbgBreakpoint scoped to a routine name. Fires on every statement inside that routine only.
--expand:PATH[,…]aowli-dbgDrill into a deep field of a captured local without dumping the whole value — dotted/indexed paths like c.currentModule.name or xs.3.field (fields by name, seq/array by index, ref/ptr auto-followed), each rendered with a generous budget as an extra path = value line.
--sessionaowli-dbgInteractive / progressive mode — run once and pause, stepping and inspecting the live frame on demand over a stdin/stdout control channel (see below).
--traceaowli-interpRenders the whole call tree: → callee(args) :LINE on enter, ← <ret> on exit, depth-indented.
--trace-depth:Naowli-interpCaps the call tree at depth N — use on a deep call chain where the full tree is too long to read.
--trace-profileaowli-interpAggregates call counts/time instead of printing every frame — use on a wide program (many calls, shallow tree) where a full trace would be mostly repetition.

All of --break/--break-func can repeat and combine in one invocation. In the default batch mode every hit is captured (not just the first) and dumped at exit; for pause/step/resume, use --session (below).

Bounded value rendering

Every captured value is rendered under a whole-value character budget, so a large frame local — a compiler's SemContext, a memo table, a grammar blob — can no longer explode a frame dump into a wall of interning-table internals. When the budget runs out, expansion stops with a …{budget} marker: the value's shape stays legible and you know detail was deferred, not missing. To see a deferred field, name it directly with --expand:PATH (or the expand command in a session) rather than re-dumping the whole value — token-thrift without losing the thread.

--break vs --break-func

A line number is not a unique coordinate across a whole program — the same line number exists in every module. --break:LINE breaks on that line in whichever routine reaches it, so a common line number (a helper's return statement, a loop increment) can fire hundreds of times across unrelated routines. --break-func:NAME scopes capture to one routine's own statements, which is almost always what you actually want when chasing a bug you've already localized to a function. See Debugging a real bug for a real instance of --break firing 271 times as noise before --break-func isolated the actual frame.

Reading a capture

Each hit is one block: source line + enclosing routine + name = value per local, taken at statement entry — a line shows the value before that statement runs. Break on the following line to see a post-assignment value.

Choosing a mode

SituationUse
Know roughly where the bug is, want every local at that point--break-func:NAME
Want to see the shape of execution (what called what)--trace
Call tree is very deep and unreadable--trace-depth:N
Program is wide (many shallow calls) rather than deep--trace-profile
Only have a line number, no routine name yet--break:LINE, expect noise, then narrow to --break-func

Interactive / progressive mode (--session)

Batch mode re-runs the whole program every time you want to look somewhere new, so you must decide up front what to capture — and a slow program (an aowlsem compile) pays that full re-run on every inspection. --session turns aowli-dbg into a co-process instead: it runs once and stays paused between commands, so you inspect and step the live frame on demand — no re-execution, no predicting captures in advance.

The interpreter emits JSON events on stdout (paused, output, expanded, locals, stack, exited) and reads plain line commands on stdin:

CommandEffect
stepStep into the next statement (descends into calls).
nextStep over — stay in this frame; skip into called routines.
finishStep out — run until the current routine returns.
continueRun to the next breakpoint (or exit).
break func:NAME · break file.nim:LINE · break LINEAdd a breakpoint live, mid-session.
clearRemove all breakpoints.
expand PATH[,…]Drill dotted/indexed paths in the paused frame (stays paused).
locals · stackRe-inspect the paused frame's locals / routine-name call stack.
quitEnd the session.

The session stops on entry, then you set the pace. It needs no coroutine machinery: the interpreter is already parked on the stack inside its per-statement hook, so a blocking read on the control channel is the pause. Batch mode and the zero-overhead default path are byte-for-byte unchanged. Via aowlcode, this is the debug_session tool.

Via aowlcode

The trace/debug/debug_session MCP tools wrap this exact binary pipeline (compile → locate .s.aif → run aowli-interp/aowli-dbg) with structured JSON returns and binary resolution through $AOWLI_BIN_DIR~/.aowl/bin~/aowli/binPATH. Full args/returns/failure-mode reference: aowlcode → Execution.

aoughwl — self-hosted platform for things n stuff. Contact / Support on Discord for access to the private backends.