Sigil Debugging
Sigil debugging is built around machine-readable compiler and runtime surfaces. The compiler, runner, and test harness expose the state an LLM or a human actually needs instead of assuming a traditional IDE debugger.
Examples below use the installed sigil CLI. When working from a source build, the equivalent form is:
cargo run -q -p sigil-cli --manifest-path language/compiler/Cargo.toml --
The Model
Sigil debugging is split into four surfaces:
inspect: what the compiler or runtime setup believesrun: what one program execution didtest: what one test suite execution diddebug: replay-backed stepping over one recorded run or one recorded test
The important design choices are:
- JSON is the primary debugging surface
- replay is the determinism backbone
- runtime failures prefer exact Sigil expression blame when available
- stepping is replay-backed rather than attached to a live debugger process
- watches, traces, and breakpoints all use compact machine-oriented summaries
Choose The Right Command
| Question | Command | | --- | --- | | Did the compiler reject the source shape? | sigil inspect validate | | What top-level types did the checker solve? | sigil inspect types | | What runtime world will this env use? | sigil inspect world | | What TypeScript did this compile to? | sigil inspect codegen | | Where did one run fail? | sigil run --json | | How did execution flow? | sigil run --json --trace | | Which exact expression failed? | sigil run --json and inspect error.details.exception.sigilExpression | | Can I reproduce the same run exactly? | sigil run --json --record then --replay | | What did one test suite do? | sigil test ... | | Can I replay one failing test? | sigil test --record then sigil debug test start --replay | | Can I step through a recorded execution? | sigil debug run ... or sigil debug test ... |
Inspect Surfaces
sigil inspect validate
Use this when you suspect canonical-shape or source-form issues.
sigil inspect validate language/examples/genericFunctions.sigil
This returns:
- whether validation succeeded
- the canonical source printer output
- validation diagnostics when the source is parseable but non-canonical
Use it first for:
- declaration ordering problems
- canonical helper-surface violations
- rejected top-level shapes
- other “the compiler says this source is wrong” issues
sigil inspect types
Use this when you need solved top-level types without compiling or running.
sigil inspect types language/examples/genericFunctions.sigil
Current scope:
- top-level declaration-focused
- useful for exported/library surfaces and entry declarations
- not a nested-expression type explorer
sigil inspect world
Use this for topology/config/runtime-world questions.
sigil inspect world projects/topology-http --env test
This returns:
- the selected environment
- topology presence and declared dependencies
- a compact summary of singleton world entries
- the normalized runtime world template Sigil will actually use
Use it when debugging:
- missing or wrong env bindings
- HTTP/TCP dependency setup
- random/timer/log/process/fs backend differences between environments
inspect world is project-env scoped:
--envis required- success output does not restate derivable canonical config/topology paths
- test-local
world { ... }overlays are not part of this surface
sigil inspect codegen
Use this when you need the emitted TypeScript or the derived output/span-map paths without writing artifacts.
sigil inspect codegen language/examples/genericFunctions.sigil
This returns:
- inline generated TypeScript for the requested file
- derived
.tsand.span.jsonpaths - span-map summary counts
- full module inventory for the resolved compile graph
It is useful when a runtime trace or exception already points into generated code and you need to understand the emitted shape.
Runtime Debugging With sigil run
Baseline JSON
Use:
sigil run --json
This gives one structured success or failure envelope. On runtime failures, inspect:
- top-level
phase - top-level
error.code error.locationerror.details.runtimeerror.details.exception.sigilFrameerror.details.exception.sigilExpression
sigilExpression is the precise runtime-blame surface:
- exact expression span
- exact source location
- compact error/value summary
- current-frame locals and stack when available
Trace
Use:
sigil run --json --trace
sigil run --json --trace --trace-expr
Rules:
--tracerequires--json--trace-exprrequires both--traceand--json
--trace adds bounded inline events such as:
callreturnbranch_ifbranch_matcheffect_calleffect_result
--trace-expr adds:
expr_enterexpr_returnexpr_throw
Use ordinary trace first. Add expression trace only when you need finer control flow or value flow.
Breakpoints
Use:
sigil run --json --break
sigil run --json --break-fn
sigil run --json --break-span
sigil run --json --break-fn helper --break-mode collect --break-max-hits 8
Rules:
- breakpoint selectors require
--json stopmode pauses the run early and returnsok: truecollectmode keeps running and returns bounded hit snapshots
Each hit includes:
- resolved span id and span kind
- source location
- declaration context
- current-frame locals
- stack summaries
- recent trace window
Record And Replay
Use:
sigil run --json --record .local/run.replay.json
sigil run --json --replay .local/run.replay.json
Replay is strict and artifact-owned:
--recordand--replayare mutually exclusive--replaycannot be combined with--env- replay is bound to the recorded entry path, argv, and source fingerprint
Current replay coverage:
randomtimerandtime.nowprocesshttptcpfile
Replay artifacts preserve enough information to reproduce:
- successful runs
- child exits
- exact file-operation failures
- recorded effect ordering
Use replay whenever:
- a run is flaky
- an effectful run is expensive to recreate
- you want stepping or repeated breakpoint sessions over one exact execution
Test Debugging With sigil test
sigil test is already JSON-first. Use it directly for suite-level debugging:
sigil test --trace projects/algorithms/tests/basicTesting.sigil
sigil test --break-fn helper projects/algorithms/tests/basicTesting.sigil
sigil test --record .local/tests.replay.json projects/algorithms/tests/basicTesting.sigil
Current debug-capable test flags include:
--trace--trace-expr--break--break-fn--break-span--break-mode stop|collect--break-max-hits--record--replay
Test-specific behavior:
- stop-mode breakpoints stop only the current test
- a stopped test result uses
status: "stopped" - the suite continues with later selected tests
sigil test --replaycannot be combined with--env- replay artifacts store the resolved per-test world after local
world { ... }overlays
Per-test results may include:
tracebreakpointsreplayexception
When replaying a failing test, use the exact stable results[].id later with sigil debug test start.
Replay-Backed Debug Sessions With sigil debug
Stepping is replay-backed. There is no long-lived debugger process.
Debug One Run
sigil run --json --record .local/run.replay.json app.sigil
sigil debug run start --replay .local/run.replay.json --watch user.score --watch result.value app.sigil
sigil debug run snapshot .local/debug/.json
sigil debug run step-into .local/debug/.json
sigil debug run step-over .local/debug/.json
sigil debug run step-out .local/debug/.json
sigil debug run continue .local/debug/.json
sigil debug run close .local/debug/.json
Debug One Exact Test
sigil test --record .local/tests.replay.json projects/algorithms/tests/basicTesting.sigil
sigil debug test start --replay .local/tests.replay.json --test "projects/algorithms/tests/basicTesting.sigil::cache hit returns cached value" --watch result.value projects/algorithms/tests/basicTesting.sigil
sigil debug test continue .local/debug/.json
Rules:
sigil debugis JSON-onlystartcurrently requires--replaystartmay also preload repeatable--break,--break-fn, and--break-spanselectorssigil debug test --testrequires one exactresults[].id- watch selectors are
local(.field)* snapshotreads the current stored session state without advancing executionstep-intoadvances to the next source-level pause pointstep-overstays at the current frame when possiblestep-outruns until the current frame returns or throwscontinueruns until the next breakpoint, failure, or normal exitcloseends the session and removes the session file
Every debug snapshot includes:
- current state and pause reason
- source file, span id, span kind, and location
- declaration or test context
- current-frame locals
- stack summaries
- recent trace
- replay progress
- stdout/stderr so far
- ordered
watches
Watches are session-scoped and recomputed on every pause:
ok: selector resolved successfullynot_in_scope: root local is not visible in the current framepath_missing: a later field is missing or traversal hit a non-record value
Worked Workflow
1. Compiler Or Canonical Failure
Use:
sigil inspect validate src/main.sigil
sigil inspect types src/main.sigil
Do not start from emitted TypeScript or runtime behavior if the source does not pass canonical validation and typechecking first.
2. Runtime Crash
Use:
sigil run --json --trace src/main.sigil
Read in this order:
phaseerror.codeerror.locationerror.details.exception.sigilExpressionerror.details.trace.events
If the failure looks effectful or flaky, record it next:
sigil run --json --trace --record .local/crash.replay.json src/main.sigil
sigil run --json --trace --replay .local/crash.replay.json src/main.sigil
3. Topology Or Config Problem
Use:
sigil inspect world . --env test
Check:
- declared dependency handles
- singleton backend kinds
- normalized runtime world entries
Only move to run once the selected env world looks correct.
4. Step Through A Recorded Run
Use:
sigil run --json --record .local/run.replay.json src/main.sigil
sigil debug run start --replay .local/run.replay.json --watch state.count src/main.sigil
sigil debug run step-into .local/debug/.json
sigil debug run step-over .local/debug/.json
This is the right path when:
- a trace is too dense
- you need to compare state across pauses
- you want one stable watched value across several steps
5. Debug One Failing Test
Use:
sigil test --record .local/tests.replay.json tests/
Then take the exact results[].id for the failing test and start:
sigil debug test start --replay .local/tests.replay.json --test "" --watch result.value tests/
This lets you step only that test's recorded execution without rerunning the whole suite in a live mode.
Human + LLM Workflow
The intended workflow is the same for a person and an LLM, but the machine surfaces mean the LLM can stay precise instead of guessing from prose.
A good loop is:
- run one machine-readable command first
- branch on
phase - inspect the smallest relevant surface next
- replay if the behavior may vary
- step only after the run is deterministic
For an LLM, that usually means:
sigil run --json --trace ...orsigil test ...- inspect the returned code, phase, location, and exact expression
- use
inspect worldorinspect codegenonly if the failure suggests it - record/replay before suggesting fixes for effectful behavior
- use watches to pin the few values that matter across steps
For a human, the same rule applies:
- prefer one structured run over ad hoc logging first
- use replay instead of hoping the same bad run happens again
- use debug sessions when you need state transitions, not just one failure site
Related References
language/spec/cli-json.mdlanguage/spec/cli-json.schema.jsonlanguage/spec/testing.mdlanguage/spec/run-replay.schema.jsonlanguage/spec/test-replay.schema.jsonlanguage/spec/debug-session.schema.jsonlanguage/docs/TESTING.mdlanguage/docs/TESTING_JSON_SCHEMA.md