Repository navigation
Add trace-backed boundary value artifacts and history - #3844
Conversation
Replace the public RunStore RunId surface with host-created BoundaryId while preserving the existing baml_id_1_ runtime-id encoding. Thread BoundaryContext through native and WASM execution entry points so the root VM frame, RunStore boundary key, and boundary.id.current() agree while structural CallRef attachment remains available for profiling and call reconstruction. Rename playground protocol, store, projection, and adapter fields from runId/RunId to boundaryId/BoundaryId. Add the boundary.id stdlib package resolver, keep baml.id aliases over the same runtime-id surface, update identity/profile tests, and record phase-1 validation plus handoff notes in TASK.
Implement the phase 2 live root value path for playground execution. Add TraceHeap snapshots and bounded capture queues in bex_engine for root input, output, and thrown BAML values. Add value artifact framing and ValueRef metadata in bex_events, then project refs through RunResult, RunError, and root capturedValue payloads without storing bodies in RunStore. Wire native and WASM playground hosts to drain captured values at terminal safe points, retain live value bodies in host-owned stores, and answer readValue requests with codec-tagged BamlOutboundValue bodies. Update the TypeScript protocol, RunStore client, value-body cache, projections, and execution UI so root input/result/error bodies hydrate lazily before rendering. Add focused Rust and TypeScript coverage for value framing, root value refs, WebSocket value frames, readValue correlation, cache hydration, and projection rendering.
Add boundary-scoped native history storage under project .baml/history with exact-root profile routing and per-thread stack/value segments. Extend .bamlvalue capture metadata and replay helpers so opened history can hydrate RunStore snapshots, lazy value refs, root outputs, errors, and diagnostics through the same paths as live runs. Wire native LSP, WASM bridge, playground protocol, and execution-store clients for explicit listHistory/openHistory/readValue behavior, including warm-session retained-byte replay for WASM.
Route reserved $baml_log events through the TraceCaptureProducer with boundary and call identity so log bodies are captured through TraceHeap rather than direct playground transport. Persist log bodies and aggregate capture-loss records in .bamlvalue artifacts, replay them into RunStore payloads and diagnostics, and hydrate log bodies through readValue for live and history views. Update native and WASM drain paths plus playground protocol/projections/UI so identified logs attach under reconstructed call nodes with value refs, lazy hydration states, and focused attribution coverage for repeated nested calls.
Add function capture metadata and VM capture masks so output/error capture can resolve Auto at the host boundary while ordinary helpers remain disabled by default. Queue bytecode, native, and sys-op call outputs/errors through the TraceHeap value producer, preserving root outcomes as canonical RunResult/RunError refs and recording thrown values once at their origin. Persist and replay call output/error value refs through RunStore, .bamlvalue history, native LSP, and WASM drains, then surface hydrated call values in playground trace rows. Cover repeated-call attribution, native call output capture, origin-only rethrow semantics, RunStore/history replay, and playground projection/rendering.
Implement the architecture-safe explicit capture surface for Phase 6. This adds boundary.id(), boundary.LocalId.capture(...), hidden $id call lowering, ID-aware bytecode call forms, and VM dispatch support for explicit local ids without changing ordinary call arity. Thread call-entry input capture through the narrow VM hook owned by bex_engine, preserving TraceHeap and TraceCaptureProducer ownership. Bytecode and sys-op boundaries can now capture named input snapshots with single-use LocalId policy, while unsupported native builtins and opaque host closures reject explicit $id for this v1 boundary. Persist call inputs through the existing value artifact path as CallInput records, RunStore/history payloads, readValue bodies, and playground UI trace call values. LLM functions now resolve Auto for inputs, output, and error while ordinary functions remain capture-disabled by default. Add validation coverage for mutation-isolated input snapshots, LocalId single-use behavior, native builtin rejection, sys-op input capture, call-value projection, and native playground hydration ordering. Native playground now flushes profile events before root trace attachment and completion so captured inputs attach to their call rows deterministically.
Add boundary-local SHA-256 blob storage for large value bodies while keeping readValue as the single hydration path. Blob-backed .bamlvalue records now retain small metadata inline and resolve bodies through HistoryStore without changing public availability semantics. Add artifact-safe value encoding guardrails so persisted artifacts preserve durable primitives, containers, media, prompt AST, and type values while converting host-owned or process-local values into safe omission descriptors instead of serializing live handles. Teach trace-owned snapshots to recognize MediaValue stored as RustData, copy media content into TraceHeap-owned data, and encode generated image results as BamlValueMedia. This keeps model-generated images replayable and blob-backed in the native playground. Cover the phase with focused blob, value, history, artifact-safe, handle-table, trace-owned, media trace, bridge host-value, proto, and playground tests.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
⏭️ Performance benchmarks were skippedPerf benchmarks (CodSpeed) are opt-in on pull requests — they no longer run on every push. They always run automatically after merge to To run them on this PR, do any of the following, then push a commit (or re-run CI):
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughBoundaryId replaces runId across identity, run records, VM call execution, capture routing, history storage, and playground APIs. The compiler and bytecode layers also add runtime_id and rethrow handling, while toolchain output and release verification are updated. ChangesBoundaryId, runtime_id, capture, history, and playground v2
Workspace wiring and release support
Sequence Diagram(s)sequenceDiagram
participant Client
participant PlaygroundServer
participant BexEngine
participant TraceCaptureProducer
participant HistoryStore
participant LiveValueStore
Client->>PlaygroundServer: FunctionRun / TestRun
PlaygroundServer->>BexEngine: call with BoundaryId
BexEngine->>TraceCaptureProducer: capture inputs, logs, outputs
BexEngine-->>PlaygroundServer: outcome and queued captures
PlaygroundServer->>TraceCaptureProducer: drain captured values
TraceCaptureProducer->>HistoryStore: append value or log bodies
TraceCaptureProducer->>LiveValueStore: retain live value body
Client->>PlaygroundServer: ReadValue(boundaryId, valueRef)
PlaygroundServer->>LiveValueStore: get value body
PlaygroundServer->>HistoryStore: read_value_result fallback
PlaygroundServer-->>Client: ValueBody
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Binary size checks passed✅ 7 passed
Generated by |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
baml_language/crates/bex_events/src/value/read.rs (1)
90-98: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick winAlso reject stray
body/blobon capture-loss records.The capture-loss guard rejects
metadata,log_event, andcapture, but notbodyorblob. A malformed/corrupted.bamlvaluecapture-loss record carrying a body or blob is accepted and those fields are silently dropped, unlike the strict lifecycle path above (Lines 71-83). Tighten for consistency.🛡️ Proposed fix
if let Some(loss) = record.capture_loss { - if record.metadata.is_some() || has_log_event || record.capture.is_some() { + if record.metadata.is_some() + || has_log_event + || record.capture.is_some() + || !record.body.is_empty() + || record.blob.is_some() + { return Err(io::Error::new( io::ErrorKind::InvalidData, "value record mixed capture loss metadata with body metadata", )); } return Ok(ValueFileRecord::CaptureLoss(loss.try_into()?)); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@baml_language/crates/bex_events/src/value/read.rs` around lines 90 - 98, The capture-loss handling in read_value_record is too permissive because it rejects metadata, log_event, and capture but still accepts stray body/blob fields, which are then silently dropped. Tighten the guard in the if let Some(loss) = record.capture branch to also reject any record where body or blob is present, matching the stricter validation used in the normal lifecycle path and keeping ValueFileRecord::CaptureLoss conversion only for clean capture-loss records.
🧹 Nitpick comments (1)
baml_language/crates/bex_events/src/history/router.rs (1)
77-83: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win
record(id)is now an O(n) linear scan (regression from O(1)).
recordsis always sorted ascending byid(ids are assigned monotonically iningestand only the oldest are drained from the front), so a binary search restores logarithmic lookup. This matters because routing claims records by id and the buffer can hold up tomax_records(default100_000), turning per-claim lookups into a potential O(n²) over a full buffer.♻️ Use the sorted-by-id invariant
#[must_use] pub fn record(&self, id: HistoryProfileRecordId) -> Option<&HistoryProfileRecord> { - self.records - .iter() - .find(|record| record.id == id) - .map(|record| &record.record) + self.records + .binary_search_by_key(&id, |record| record.id) + .ok() + .map(|index| &self.records[index].record) }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@baml_language/crates/bex_events/src/history/router.rs` around lines 77 - 83, `HistoryRouter::record` currently does a linear scan over `records`, which regresses lookup performance; update this method to use the existing sorted-by-`id` invariant and a binary search instead of `iter().find(...)`. Locate the fix in `record(id)` and keep the return type/behavior unchanged by returning the matched `HistoryProfileRecord` when the search succeeds, preserving the current `Option<&HistoryProfileRecord>` API.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@baml_language/crates/bex_events/src/value/live_cache.rs`:
- Around line 158-170: The read path in LiveCache::get leaves stale tombstones
in lru on every cache hit, so the deque can grow without bound during repeated
reads. Update LiveCache to periodically compact/rebuild lru from the live
entries in sequence order when it grows too large relative to entries, and apply
the same cleanup to evicted_order since LiveCache::insert removes from evicted
without trimming that deque. Use the existing LiveValueKey, next_sequence, and
evict_until_within_budget flow to place the fix without changing lookup
behavior.
---
Outside diff comments:
In `@baml_language/crates/bex_events/src/value/read.rs`:
- Around line 90-98: The capture-loss handling in read_value_record is too
permissive because it rejects metadata, log_event, and capture but still accepts
stray body/blob fields, which are then silently dropped. Tighten the guard in
the if let Some(loss) = record.capture branch to also reject any record where
body or blob is present, matching the stricter validation used in the normal
lifecycle path and keeping ValueFileRecord::CaptureLoss conversion only for
clean capture-loss records.
---
Nitpick comments:
In `@baml_language/crates/bex_events/src/history/router.rs`:
- Around line 77-83: `HistoryRouter::record` currently does a linear scan over
`records`, which regresses lookup performance; update this method to use the
existing sorted-by-`id` invariant and a binary search instead of
`iter().find(...)`. Locate the fix in `record(id)` and keep the return
type/behavior unchanged by returning the matched `HistoryProfileRecord` when the
search succeeds, preserving the current `Option<&HistoryProfileRecord>` API.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 7e8711dd-c427-4040-8ec9-3ecf9668c3aa
⛔ Files ignored due to path filters (1)
baml_language/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (30)
baml_language/crates/baml/src/main.rsbaml_language/crates/baml_compiler2_hir/Cargo.tomlbaml_language/crates/baml_compiler2_hir/src/package.rsbaml_language/crates/baml_compiler2_mir/src/pretty.rsbaml_language/crates/baml_lsp_server/src/playground_server.rsbaml_language/crates/baml_pack_host/Cargo.tomlbaml_language/crates/baml_release/src/lib.rsbaml_language/crates/bex_engine/src/trace_heap.rsbaml_language/crates/bex_engine/src/trace_value_encode.rsbaml_language/crates/bex_engine/src/value_capture.rsbaml_language/crates/bex_engine/tests/prof_gate.rsbaml_language/crates/bex_events/src/history/boundary_writer.rsbaml_language/crates/bex_events/src/history/mod.rsbaml_language/crates/bex_events/src/history/router.rsbaml_language/crates/bex_events/src/value/artifact.rsbaml_language/crates/bex_events/src/value/encode.rsbaml_language/crates/bex_events/src/value/live_cache.rsbaml_language/crates/bex_events/src/value/mod.rsbaml_language/crates/bex_events/src/value/proto/bamlvalue.protobaml_language/crates/bex_events/src/value/read.rsbaml_language/crates/bex_events/src/value/record.rsbaml_language/crates/bex_events/src/value/writer.rsbaml_language/crates/bex_project/src/lib.rsbaml_language/crates/bex_vm/src/vm.rsbaml_language/crates/bex_vm/tests/events.rsbaml_language/crates/bridge_ctypes/Cargo.tomlbaml_language/crates/bridge_ctypes/src/handle_table.rsbaml_language/crates/bridge_ctypes/src/lib.rsbaml_language/crates/bridge_ctypes/src/value_encode.rsbaml_language/crates/bridge_wasm/src/lib.rs
💤 Files with no reviewable changes (1)
- baml_language/crates/baml_pack_host/Cargo.toml
✅ Files skipped from review due to trivial changes (2)
- baml_language/crates/baml_compiler2_hir/Cargo.toml
- baml_language/crates/bex_project/src/lib.rs
🚧 Files skipped from review as they are similar to previous changes (13)
- baml_language/crates/bex_events/src/value/mod.rs
- baml_language/crates/baml/src/main.rs
- baml_language/crates/bex_events/src/value/proto/bamlvalue.proto
- baml_language/crates/baml_release/src/lib.rs
- baml_language/crates/baml_compiler2_mir/src/pretty.rs
- baml_language/crates/bex_engine/src/trace_heap.rs
- baml_language/crates/bex_events/src/history/boundary_writer.rs
- baml_language/crates/bex_events/src/value/artifact.rs
- baml_language/crates/bex_events/src/history/mod.rs
- baml_language/crates/bex_engine/tests/prof_gate.rs
- baml_language/crates/bex_events/src/value/record.rs
- baml_language/crates/baml_lsp_server/src/playground_server.rs
- baml_language/crates/bex_vm/src/vm.rs
# Conflicts: # baml_language/crates/baml_compiler2_mir/src/lower.rs # baml_language/crates/baml_tests/snapshots/compiles/type_builder_errors/baml_tests__compiles__type_builder_errors__06_codegen.snap # baml_language/crates/baml_tests/snapshots/diagnostic_errors/runtime_id_misuse/baml_tests__diagnostic_errors__runtime_id_misuse__04_tir.snap # baml_language/crates/baml_tests/snapshots/diagnostic_errors/runtime_id_misuse/baml_tests__diagnostic_errors__runtime_id_misuse__05_diagnostics.snap # baml_language/crates/bex_engine/Cargo.toml # baml_language/crates/bex_vm/src/vm.rs # baml_language/crates/bex_vm_types/src/lib.rs # baml_language/crates/bex_vm_types/src/types.rs
The bridge_wasm runtime + pkg-playground protocol renamed runId -> boundaryId (#3844/#3864), but the website worker still read msg.runId and forwarded notification.runId — both undefined at runtime, so run snapshots/subscriptions never matched and the run UI never updated. - rename runId -> boundaryId across run handlers (cancelRun, respondToInput, respondToEnv, snapshot, subscribe) and the runSnapshot/runCursorExpired notification forwarders - add the missing listHistory/openHistory/readValue handlers and the historyList/valueBody notification forwarders (matches app-promptfiddle) Co-Authored-By: Claude Opus 4.8 <[email protected]>
…tx)) (BoundaryML#3886) ## What `catch (e, ctx)` now binds a second value — an **`ErrorContext`** — giving BAML implicit error chaining in the style of Python's `__context__`. When code throws a *new* error while handling another, the new error's `ctx.cause` automatically links back to the error being handled, and rendering the context prints the whole chain oldest-first: ``` <original error + stack trace> During handling of the above error, another error occurred: <new error + stack trace> ``` This completes BEP-042 (alongside `defer` BoundaryML#3785 and `cleanup` BoundaryML#3835). ## The type ```baml class ErrorContext { error unknown stack_trace StackTrace cause ErrorContext? } ``` - `baml.errors.ErrorContext`, registered in `baml_builtins2::ALL`. - pure-BAML `root_cause()` walks `cause` to the original failure. - `implements baml.ToString` — the native renderer prints the full chain. **Breaking change:** the optional second binding of `catch (e, ctx)` changes type from `StackTrace` to `ErrorContext` (the trace is now `ctx.stack_trace`). ## How it works `cause` is computed **lazily at the cold throw funnel** — no in-flight register, no per-edge teardown opcode. When a throw unwinds, the VM walks the live frames read-only; if the throw's PC lies in an active handler body, the error that handler is handling becomes the new error's `cause`. Handler-body extents live in a side table (`handler_context_table`), one **per-block** range so layout-fragmented handler bodies are covered exactly — no over-covering the gaps between fragments. The teardown design was chosen after studying how CPython/Go/JVM/.NET/V8 represent "currently-handling" state from source. Conclusion: BAML keeps no teardown opcode — the same move it already made going from `PushHandler`/`PopHandler` to exception tables. CPython is the lone VM that keeps a per-edge teardown opcode, and only for two constraints BAML provably lacks. ### Coverage - **Nested `catch { throw }`** — `throw B` while handling `A` ⇒ `B.cause = A`. - **Sibling defers** — `defer{throw A}; defer{throw B}; defer{throw C}; throw X` runs LIFO and chains `A → B → C → X`; `root_cause()` reaches `X`. ### Rethrow A bare re-raise (`throw e`, the no-match fall-through, `ThrowIfPanic`) is flagged as a *rethrow* and does **not** add a chain link — it re-propagates the same error rather than recording a new one (pairs with the `Rethrow` mechanism from BoundaryML#3844, so the no-match fall-through doesn't self-link onto its own context). > **Known limitation:** a prior chain carried by a rethrown value is not preserved across the re-raise (its next catch sees `cause = null`). This is a deliberate conservative choice — recovering it would need a value→context association we don't track, and reusing the cause-walk result instead would mis-attribute a nested outer-binding rethrow (binding the inner handler's error). ## Validation - New acceptance tests, **all proven non-vacuous** (each fails when its fix is reverted): root-cause-is-self, nested-catch chaining, two slot-liveness hazards, no-rethrow-self-link, layout-gap non-coverage, sibling-defer chain-to-root-cause. - `errorcontext` 7/7 · `exceptions` · `defer` · `cleanup` · `bytecode_format` · `bex_vm_types` green · full `baml_tests --lib` **1714/0**, no codegen snapshot drift · pre-commit clippy/fmt green. ## Follow-up (separate PR) Surfacing the cause chain in the BEX tracing system (so `RunError` carries the `cause`) builds on this and lands separately. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added error context tracking with chained causes and readable, stack-trace-aware error output. * Improved exception handling so nested errors preserve the original failure context during unwinding. * **Bug Fixes** * Fixed cause-chain resolution in nested handlers and edge cases involving deferred or fragmented handler bodies. * Preserved stack-trace/context data even when it appears unused, improving reliability of error reporting. * **Tests** * Added coverage for error-context chaining and updated stack-trace snapshots to reflect richer output. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
Summary
This PR implements the staged trace-backed boundary value ingestion work through Phase 7:
BoundaryIdas the execution/history identity and threads boundary context through native, WASM, and playground pathsTraceHeapsnapshots instead of retaining live BEX heap state.bamlprofstructure plus.bamlvaluevalue records, with replay/open/history support in native and warm-session WASM pathsreadValue(boundaryId, valueRef)as the single hydration path for live and replayed valuesMediaValueRustData into trace-owned media snapshots and encoding them asBamlValueMediaWhy
Boundary traces need durable, replayable value artifacts without holding onto mutable VM heap pointers, CFFI handle-table keys, JS/Python registry entries, or other process-local state. The new pipeline separates hot structural tracing from cold value ingestion: capture copies BAML-visible values into
TraceHeap, value consumers encode artifact-safe bytes,.bamlvalue/blob storage owns the body data, and the UI hydrates lazily viareadValue.Validation
Automated checks run across the phase stack include:
cargo test --manifest-path baml_language/Cargo.toml -p bex_events idscargo test --manifest-path baml_language/Cargo.toml -p bex_events run_storecargo test --manifest-path baml_language/Cargo.toml -p bex_events historycargo test --manifest-path baml_language/Cargo.toml -p bex_events logcargo test --manifest-path baml_language/Cargo.toml -p bex_events blob -- --nocapturecargo test --manifest-path baml_language/Cargo.toml -p bex_events value -- --nocapturecargo test --manifest-path baml_language/Cargo.toml -p bex_engine trace_heap -- --nocapturecargo test --manifest-path baml_language/Cargo.toml -p bex_engine value_capturecargo test --manifest-path baml_language/Cargo.toml -p bex_engine trace_value -- --nocapturecargo test --manifest-path baml_language/Cargo.toml -p bex_engine --test identitycargo test --manifest-path baml_language/Cargo.toml -p bex_engine --test prof_gatecargo test --manifest-path baml_language/Cargo.toml -p bridge_ctypes artifact_safe -- --nocapturecargo test --manifest-path baml_language/Cargo.toml -p bridge_ctypes handle_table -- --nocapturecargo test --manifest-path baml_language/Cargo.toml -p bex_heap trace_owned -- --nocapturecargo test --manifest-path baml_language/Cargo.toml -p bridge_cffi host_value -- --nocapturecargo check --manifest-path baml_language/Cargo.toml -p baml_lsp_servercargo check --manifest-path baml_language/Cargo.toml -p bridge_wasm --target wasm32-unknown-unknownpnpm --filter @b/pkg-proto testpnpm --filter @b/pkg-playground testgit diff --checkManual verification included native playground runs for boundary identity, history replay, root values, logs, explicit input snapshots, and Phase 7 image/blob behavior. The final Phase 7 browser check generated an image through
ai-gateway-images/google/imagen-4.0-fast-generate-001; the playground renderedimage (image/png)and persisted the large generated body as a SHA-256 blob while keeping the.bamlvaluerecord small.Summary by CodeRabbit
ReadValuesupport.current/new/capture) and expanded trace value/log capture surfaces.$id/identity tracking and error tracing with correct rethrow behavior.