Skip to content

feat(cli): run the ingest gateway's AI stamping on local traces - #1178

Open
JeremyFunk wants to merge 2 commits into
mainfrom
feat/cli-ai-session-stamps
Open

JeremyFunk wants to merge 2 commits into
mainfrom
feat/cli-ai-session-stamps

Conversation

@JeremyFunk

@JeremyFunk JeremyFunk commented Sep 30, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

The local CLI server (apps/cli, chDB mode) ingests OTLP traces without the ingest gateway's AI stamping. The local ai_trace_index MV projects only spans with maple_ai.vendor.id != '' and reads only maple_ai.* stamps, so Agent Sessions is always empty in local mode.

Plan: options considered

Option Parity Cost
(a1) Compile the Rust stamping to WebAssembly and embed it in the CLI (chosen) Exact: one implementation Crate split in apps/ingest; Rust + wasm32 target needed wherever the CLI is built or tested
(a2) Route local ingest through the maple-ingest binary Exact Not viable: the CLI does not ship the ingest binary (single Bun-compiled executable + libchdb), and the gateway is a Tinybird/ClickHouse-HTTP writer, not a chDB one
(b) TypeScript port of ai_session*.rs Drifts ~2.5k lines of rule code to mirror, changed about 10 times in the last 30 days, and a parity suite to keep in sync. The owner rule is that integration rules live on the ingest path, and a second copy splits that path

Also considered: checking the .wasm into git. That avoids Rust in CI lanes, but every stamping PR would then commit a ~370 KB binary and hit merge conflicts on it (several stamping PRs are in flight at once), and CI would need a drift check to catch a forgotten rebuild. Building from source means the embedded module can't drift from the source.

What changed

  • apps/ingest/crates/ai-session: src/ai_session*.rs moved as-is, plus the AnyValue helpers it used from telemetry.rs (value.rs). maple-ingest re-exports it as maple_ingest::ai_session, so main.rs and the bench are unchanged. apps/ingest becomes a Cargo workspace with shared lints.
  • apps/ingest/crates/ai-session-wasm: a ~50-line cdylib with no wasm-bindgen. Protocol: input(len) → host writes the protobuf ExportTraceServiceRequest → stamp() → output()/output_len() (on failure the output holds prost's decode error). [profile.wasm] optimizes for size: 377 KB, about 12 s cold build.
  • apps/cli/src/server/otlp/ai-stamp.ts: instantiates the embedded module (import … with { type: "file" }, verified to embed under bun build --compile). decodeOtlp now stamps every trace request before anything reads it. The gateway itself runs the stamping at decode time, so encoders and local eventing both see stamped spans.
    • OTLP/JSON: hex ids become bytes, the request is encoded to protobuf, then stamped.
    • Invalid protobuf: now a 400 with prost's error. This matches the gateway, which rejects what prost rejects, e.g. invalid UTF-8.
    • A trap (a panic, i.e. a stamping bug) ingests that batch unstamped and re-instantiates the module. A trap is not bad input, so it shouldn't 400 and drop a batch that ingested before this change, and one panic can't poison later requests.
    • Wasm memory never shrinks, so the module is re-instantiated once it passes 64 MiB. Otherwise one huge batch would pin its peak memory for the life of the server.
  • Build wiring:
    • bun run --cwd apps/cli build:ai-stamp; the package's test and start scripts run it first. It is a no-op rebuild when nothing changed.
    • scripts/build-local-binary.sh builds it before bun build --compile.
    • mise.toml adds the wasm32-unknown-unknown target.
    • .cargo/config.toml sets getrandom_backend="unsupported" for wasm32, because opentelemetry-proto's trace feature pulls rand; the stamping never draws randomness.
  • CI:
    • The tests lane installs Rust only when it runs @maple/cli.
    • The cli paths filter and the @maple/cli#test turbo inputs include apps/ingest/crates/**.
    • The ingest build cache key includes the crates.
    • native-checkpoint-smoke.sh POSTs an AI SDK span to the real binary and asserts the ai_trace_index row: an end-to-end check against chDB.
  • Path references to the moved Rust files are updated: comments, gen-ai.test.ts, turbo inputs. Migration 0035's comment is left as history.

Decisions made without the owner

  • Pre-PR review (Effect v4 + tests) had no criticals. Taken: trap and memory handling, more test coverage. Skipped: a tagged error for the stamping failure (it is a string mapped straight to OtlpDecodeFailed), and lazy instantiation (the embedded file import fails at load anyway when missing).

  • opentelemetry (a hard dependency of opentelemetry-proto) links js-sys on wasm32. Its four wasm-bindgen imports are stubbed as no-ops; the stamping never reaches them.

  • JSON decoding stays in TypeScript (protobufjs + hex-id fixup) rather than moving the gateway's otlp_json leniency pass into the crate. That keeps the wasm module trace-stamping-only.

Verification

  • cargo test -p maple-ai-session: 82 pass. cargo clippy is clean on both new crates. cargo check -p maple-ingest --all-targets passes.
  • apps/cli: new test/ai-stamp.test.ts covers protobuf, JSON and invalid bodies against the gateway's own vercel_v7 fixture. It checks that a forged maple_ai.tool_call is stripped, that trace/span/parent/link ids round-trip as hex, that an 8 MiB attribute grows wasm memory and survives, and that the rejection carries prost's error. Existing trace tests pass (local-eventing-ingest, server-network, serve-describe-thrown, encode), and tsc for apps/cli passes.
  • packages/domain gen-ai.test.ts (which pins stamp keys against the moved sources): 15 pass.
  • Not run locally: the full native bundle and the chDB smoke (no libchdb on this machine). CI's local-checkpoint-native job runs them.

Dev impact

Running the CLI from source now needs the mise Rust toolchain with the wasm32 target (docs/local-mode.md updated). In-flight PRs that edit apps/ingest/src/ai_session*.rs follow the rename on merge; git rename detection handles the pure move.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Local ingest skipped the gateway's maple_ai.* stamping, so the local
ai_trace_index (which projects stamped spans only) stayed empty and Agent
Sessions showed nothing in local mode.

The stamping moves into its own crate (apps/ingest/crates/ai-session),
which the gateway links as before and crates/ai-session-wasm compiles to
WebAssembly. The CLI embeds that module and stamps every trace request
before encoding it, so local and cloud run one implementation.
A panic in the stamping is a bug, not bad input: ingest the batch
unstamped on a fresh instance instead of rejecting it. Re-instantiate once
wasm memory passes 64 MiB, since it never shrinks. Tests cover parent and
link ids, a multi-MiB attribute and prost's rejection message.
@coderabbitai

coderabbitai Bot commented Sep 30, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 11 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 804dfef3-854d-4444-bb67-7ad8bf149096

📥 Commits

Reviewing files that changed from the base of the PR and between 2e91012 and 76179a4.

⛔ Files ignored due to path filters (1)
  • apps/ingest/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (34)
  • .github/workflows/build-ingest-binary.yml
  • .github/workflows/ci.yml
  • .gitignore
  • apps/cli/package.json
  • apps/cli/src/server/assets.d.ts
  • apps/cli/src/server/otlp/ai-stamp.ts
  • apps/cli/src/server/otlp/proto.ts
  • apps/cli/src/server/serve.ts
  • apps/cli/test/ai-stamp.test.ts
  • apps/cli/test/native-checkpoint-smoke.sh
  • apps/ingest/.cargo/config.toml
  • apps/ingest/Cargo.toml
  • apps/ingest/crates/ai-session-wasm/Cargo.toml
  • apps/ingest/crates/ai-session-wasm/src/lib.rs
  • apps/ingest/crates/ai-session/Cargo.toml
  • apps/ingest/crates/ai-session/src/claude_code.rs
  • apps/ingest/crates/ai-session/src/facts.rs
  • apps/ingest/crates/ai-session/src/lib.rs
  • apps/ingest/crates/ai-session/src/usage.rs
  • apps/ingest/crates/ai-session/src/value.rs
  • apps/ingest/src/lib.rs
  • apps/ingest/src/telemetry.rs
  • apps/web/src/lib/agent-sessions/vendor-label.ts
  • docs/local-mode.md
  • docs/otel-spec/resource-and-config.md
  • mise.toml
  • packages/backend/src/services/warehouse/clickhouse-e2e-support.ts
  • packages/domain/src/gen-ai.test.ts
  • packages/domain/src/gen-ai.ts
  • packages/domain/src/tinybird/gen-ai-columns.ts
  • packages/query-engine-integrations/src/ai/ai-integrations.ts
  • packages/query-engine-integrations/src/ai/ai-sessions.ts
  • scripts/build-local-binary.sh
  • turbo.json

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@maple-review-bot

maple-review-bot Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Maple review

🟡 Confidence 3/5 · needs attention
quality 100/100 · no findings · tests partial · risk low

Warning

This review ended early; what follows is what it established.

Repoints the AI-stamping doc comments from apps/ingest/src/ai_session to the new apps/ingest/crates/ai-session layout and wires that crate's wasm32 build into the CLI test lane, local binary build and cargo cache key. Build plumbing only; no runtime behavior changes.

  • Comment paths in packages/domain/packages/query-engine-integrations now cite apps/ingest/crates/ai-session/src/*.rs
  • turbo.json adds @maple/cli#test inputs covering apps/ingest/crates/** and Cargo.*
  • mise.toml rust tool installs the wasm32-unknown-unknown target
  • scripts/build-local-binary.sh runs build:ai-stamp before bun build --compile
What was checked
  • Referenced files exist: apps/ingest/crates/ai-session/src/lib.rs, usage.rs, facts.rs
  • The CLI test lane gets rust: install_args keyed on contains(matrix.filters, '@maple/cli') (ci.yml:371)
  • apps/ingest keeps its own exclusion from the TS test matrix (plan-tests.py:53), so no Rust suite is duplicated

76179a4 · Updated on every push. Reply "won't fix" to dismiss a finding, or mention @maple-review-bot to ask about one.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant