Appearance
Debugging — aowlidbg
Archived
aowli is archived. Its interpreter and VM continue inside engine, the aoughwl runtime, where they share one heap and one garbage collector with our JavaScript engine. This page is kept as the record of aowli as it was; its figures are from September 2026 and are not updated.
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
| Flag | Binary | Effect |
|---|---|---|
--break:LINE | aowli-dbg | Breakpoint on a source line number. Fires in every routine that line number appears in, across every module — file-agnostic. |
--break-func:NAME | aowli-dbg | Breakpoint scoped to a routine name. Fires on every statement inside that routine only. |
--expand:PATH[,…] | aowli-dbg | Drill 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. |
--session | aowli-dbg | Interactive / progressive mode — run once and pause, stepping and inspecting the live frame on demand over a stdin/stdout control channel (see below). |
--trace | aowli-interp | Renders the whole call tree: → callee(args) :LINE on enter, ← <ret> on exit, depth-indented. |
--trace-depth:N | aowli-interp | Caps the call tree at depth N — use on a deep call chain where the full tree is too long to read. |
--trace-profile | aowli-interp | Aggregates 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
| Situation | Use |
|---|---|
| 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:
| Command | Effect |
|---|---|
step | Step into the next statement (descends into calls). |
next | Step over — stay in this frame; skip into called routines. |
finish | Step out — run until the current routine returns. |
continue | Run to the next breakpoint (or exit). |
break func:NAME · break file.nim:LINE · break LINE | Add a breakpoint live, mid-session. |
clear | Remove all breakpoints. |
expand PATH[,…] | Drill dotted/indexed paths in the paused frame (stays paused). |
locals · stack | Re-inspect the paused frame's locals / routine-name call stack. |
quit | End 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.nif → run aowli-interp/aowli-dbg) with structured JSON returns and binary resolution through $AOWLI_BIN_DIR → ~/.aowl/bin → ~/aowli/bin → PATH. Full args/returns/failure-mode reference: aowlcode → Execution.

