Agent execution trace observability and debugging toolkit for MoonBit
Agent execution trace observability and debugging toolkit for MoonBit.
moon add guo-dong-123/moon-trace@0.1.3git clone https://github.com/guo-dong-123/moon-trace.git
cd moon-trace
curl -fsSL https://cli.moonbitlang.com/install/unix.sh | bash -s -- "$(cat .moonbit-version)"
export PATH="$HOME/.moon/bin:$PATH"
python3 scripts/check_toolchain.py
moon update
moon check --target native --deny-warn
moon build --target native --deny-warn
moon test --target native --deny-warn// moon.pkg
import {
"guo-dong-123/moon-trace/src/trace" @trace,
"guo-dong-123/moon-trace/src/storage" @storage,
"guo-dong-123/moon-trace/src/replay" @replay,
}fn run_agent(query : String) -> String raise Failure {
ignore(@trace.start_trace())
let result = @trace.span("agent.run", Some(query), () => {
// Nested spans automatically form a tree
let _plan = @trace.span("agent.plan", Some(query), () => {
"search -> calculate -> answer"
})
let search_result = @trace.span("tool.web_search", Some(query), () => {
search_web(query)
})
@trace.span("agent.synthesize", None, () => {
"Final answer based on " + search_result
})
})
// Save trace
match @trace.end_trace() {
None => ()
Some(trace) => {
let store = @storage.JsonFileStore::default()
ignore(store.save(trace))
}
}
result
}export MOONTRACE_DIR="$(mktemp -d /tmp/moontrace-demo.XXXXXX)"
moon run examples/research_agent
TRACE_ID=$(moon run src/cli ids --errors | head -n 1)
# List and inspect saved traces
moon run src/cli list
moon run src/cli show "$TRACE_ID"
# List only failed traces or filter by root span name
moon run src/cli errors
moon run src/cli list --errors
moon run src/cli list --name research_agent
# Compare two saved traces by span name
moon run src/cli compare <baseline_id> <candidate_id>
# Display recorded outputs without calling external tools
moon run src/cli replay "$TRACE_ID"
# Record and rerun an Agent using captured tool responses in a separate directory
MOONTRACE_DIR="$(mktemp -d /tmp/moontrace-replay.XXXXXX)" moon run examples/replay_agent
# Check a new execution against a known-good baseline
moon run src/cli regress <baseline_id> <candidate_id>
moon run src/cli regress <baseline_id> <candidate_id> --check-output
# Export to interactive HTML
moon run src/cli export "$TRACE_ID" "$MOONTRACE_DIR/trace.html"
# Export all traces + index page
moon run src/cli export-all "$MOONTRACE_DIR/exports/"src/
├── trace/ # Core data model + instrumentation SDK
│ ├── span.mbt # Span, SpanEvent, SpanStatus, Trace data structures
│ ├── tracer.mbt # Global Tracer, start_trace, span, add_event, set_metadata
│ ├── session.mbt # Isolated sessions and explicit concurrent contexts
│ └── context.mbt # Legacy single-flow scope snapshots
├── storage/ # Persistence layer
│ ├── store.mbt # MemoryStore, TraceSummary, TraceFilter, query
│ └── json_file.mbt # Immutable JSON snapshots + rebuildable index cache
├── tui/ # Terminal UI viewer
│ └── viewer.mbt # ANSI-colored tree rendering, list/detail views
├── exporter/ # Export tools
│ └── html.mbt # Self-contained HTML export with timeline
├── replay/ # Recorded tool outputs and regression checks
└── cli/ # Command-line interface
└── main.mbt # trace browsing, replay, regression, and export
examples/
├── demo_agent/ # Minimal instrumentation demo
├── storage_test/ # Storage layer test
├── tui_test/ # TUI viewer test
├── research_agent/ # Offline multi-step research agent
├── failure_diagnosis/ # Failed tool plus recovery path demo
├── replay_agent/ # Offline Agent replay with repeated tool calls
├── concurrent_agent/ # Independent requests and parallel sibling tools
└── bailian_agent/ # Real Qwen tool-calling agent with async traces| Function | Description |
|---|---|
| @trace.start_trace() | Start a new trace, returns trace ID |
| @trace.end_trace() | End current trace, returns Option[Trace] |
| @trace.span(name, input, body) | Execute body as a span, captures input/output/duration/errors |
| @trace.span_async(name, input, body) | Trace an async LLM or network-backed operation |
| @trace.add_event(name, data) | Add a custom event to the current span |
| @trace.set_metadata(key, value) | Attach metadata to the current span |
| @trace.print_trace(trace) | Print the supplied trace tree to stdout |
| TraceSession::new() / session.context() | Create an isolated recording session and root context |
| span_in(ctx, name, input, body) / span_async_in(...) | Record with explicit child context, including concurrent tasks |
| session.finish() | Complete a session after tasks join; rejects active spans |
| trace.duration_ms() | Elapsed duration, without double counting nested work |
| trace.cumulative_span_duration_ms() | Sum of all recorded span durations |
| Function | Description |
|---|---|
| MemoryStore::new() | In-memory storage |
| JsonFileStore::default() | File storage at ~/.moontrace/traces/ |
| JsonFileStore::new(dir) | File storage at custom directory |
| store.save(trace) | Atomically save a snapshot; refuse conflicting overwrite |
| store.load(id) | Load a trace by ID |
| store.list() | Scan valid snapshots; does not depend on index.json |
| store.delete(id) | Delete a trace |
| store.repair() | Rebuild index cache and report unreadable records |
| store.last_error() | Diagnostic for the last storage operation |
| query_summaries(summaries, filter) | Filter traces by name/status/time |
let session = @replay.ReplaySession::from_trace(saved_trace)
let result = @trace.span("tool.lookup", Some(query), () => {
session.run("tool.lookup", Some(query))
})let session = @trace.TraceSession::new()
let answer = @trace.span_async_in(session.context(), "agent.run", None, root => {
let results = @async.all([
() => @trace.span_async_in(root, "tool.lookup", None, ctx => {
ctx.set_metadata("source", "local")
"lookup result"
}),
() => @trace.span_async_in(root, "tool.calculate", None, _ => "42"),
])
results.join(" | ")
})
let recorded = session.finish()=== Trace trace_8 ===
Spans: 6 Errors: 1 Elapsed: 15ms Cumulative span time: 23ms
└── [ok] research_agent.run (15ms)
input: {"query": "unavailable topic..."}
├── [ok] agent.plan (3ms)
├── [ok] tool.web_search (1ms)
├── [ok] tool.calculator (1ms)
├── [error] tool.knowledge_base (1ms) error=Knowledge base timeout after 5000ms
└── [ok] agent.synthesize (2ms)# Update dependencies and build
moon update
moon build
# Run example agent
moon run examples/research_agent
# Run the real Bailian tool-calling Agent
BAILIAN_API_KEY='your-key' TEXT_MODEL='qwen3.7-plus' \
MOONTRACE_DIR=/tmp/moontrace-bailian \
moon run --target native examples/bailian_agent
# Run CLI from this checkout
moon run src/cli list
moon run src/cli show <trace_id>
moon run src/cli export <trace_id> output.html
# Run executable demonstrations
moon run examples/storage_test
moon run examples/tui_test
# Run package tests
moon test| Variable | Required | Default |
|---|---|---|
| BAILIAN_API_KEY | Yes | None |
| TEXT_MODEL | No | qwen3.7-plus |
| BAILIAN_BASE_URL | No | https://dashscope.aliyuncs.com/compatible-mode/v1 |
| MOONTRACE_DIR | No | ~/.moontrace/traces/ |
# Build and run all tests
moon build --target native
moon test --target native
# Run the Agent demo: nested tools, events, and handled tool failure
moon run examples/demo_agent
# Run the full Research Agent and persist traces in an isolated directory
export MOONTRACE_DIR="$(mktemp -d /tmp/moontrace-mvp.XXXXXX)"
moon run examples/research_agent
TRACE_ID=$(moon run src/cli ids --errors | head -n 1)
moon run src/cli list
moon run src/cli show "$TRACE_ID"
moon run src/cli export "$TRACE_ID" "$MOONTRACE_DIR/trace.html"
# Run the failure diagnosis demo
FAILURE_DIR="$(mktemp -d /tmp/moontrace-failure.XXXXXX)"
MOONTRACE_DIR="$FAILURE_DIR" moon run examples/failure_diagnosis
MOONTRACE_DIR="$FAILURE_DIR" moon run src/cli errorsmoon build --target native
python3 -m unittest discover -s scripts -p 'test_*.py'
python3 scripts/verify_native.py
moon package
python3 scripts/verify_package.py
# Optional Chromium checks using the agent-browser CLI:
python3 scripts/verify_html.pyInstall
Download zipAgent execution trace observability and debugging toolkit for MoonBit