Skip to content

Fix/audit 2026 07#10

Closed
senamakel wants to merge 93 commits into
mainfrom
fix/audit-2026-07
Closed

Fix/audit 2026 07#10
senamakel wants to merge 93 commits into
mainfrom
fix/audit-2026-07

Conversation

@senamakel

Copy link
Copy Markdown
Member

Summary

API Or Behavior Changes

Tests

  • cargo fmt --check
  • cargo clippy --all-targets -- -D warnings
  • cargo clippy --all-targets --all-features -- -D warnings
  • cargo build --all-targets
  • cargo build --all-targets --all-features
  • cargo test
  • cargo test --all-features

Documentation

senamakel added 30 commits July 3, 2026 12:20
Treat the RedactingSink as a security boundary: if the event cannot be
serialized, redacted, and rebuilt, drop it rather than forward a record
that may still contain unredacted secrets. Add a fast path that forwards
the original record unchanged when no secrets are configured.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
The `required` check was nested inside the `properties` branch, so a
schema declaring `required` without listing `properties` skipped
validation and accepted calls that omitted mandatory arguments. Lift the
`required` enforcement out so it fails closed independently of
`properties`.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
The lexical path gate dropped leading `..` components, so a relative
candidate like `ws/../../ws/secret` collapsed back onto the root name and
was admitted even though it resolves into a same-named sibling outside
the workspace. Anchor relative candidates and roots to the current
directory and preserve escaping `..` during normalization so the gate
fails closed.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
…shared policy

The strict blueprint gate (`CapabilityResolver::bind_blueprint`) routed
every non-subgraph/router node through a model check, so `subagent`
`agent` references were never validated and `repl_agent` `script`
references were validated by no path at all — both admitted unregistered
capabilities.

Extract one shared kind-to-reference policy
(`CapabilityResolver::classify_reference` + `reference_allowed`) and route
all three binding gates through it — the compiler blueprint gate and both
`Resolver` paths — so they cannot drift. Move the agent allowlist into
`CapabilityResolver`, add a `scripts` allowlist and a `ComponentKind::Script`
so scripts can be registered and bound.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
`parse(&[])` underflowed `tokens.len() - 1` in the token cursor and
panicked. Reject an empty token slice up front with a parse error, since
a well-formed stream always ends with an `Eof` sentinel.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
Diagnostic::render panicked when a label span pointed past the end of the
source: `caret_start` could exceed `line_end`, tripping `clamp`'s
`min <= max` precondition (and risking an out-of-bounds slice). Clamp the
caret range into the line's byte range, mirroring `SourceFile::snippet`.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
Buffer the raw SSE byte stream as `Vec<u8>` and only lossily decode
complete newline-terminated lines, so a multi-byte UTF-8 character split
across two network chunks is reassembled before decoding instead of being
corrupted into replacement characters. Drain any leftover buffered bytes at
end-of-stream so a final `data:` event sent without a trailing newline (and
without a `[DONE]` sentinel) is still processed.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
…them

A mid-stream `{"error": ...}` SSE payload deserializes cleanly as an
all-defaults `ChatCompletionChunk`, so it was folded in as an empty chunk and
silently dropped. Parse each `data:` payload as a generic value first and, when
it carries an `error` object, emit a terminal `ProviderFailed` (with the
provider message and code) and stop the stream.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
…variant round-trips

Both enums were internally tagged (`tag = "type"`). Internal tagging cannot
serialize newtype variants wrapping a non-map payload: `ModelStreamItem::Failed`
(a `String`) errored, and `StreamChunk::Values(json!(null))` and other scalar /
array `Value` payloads corrupted (`null` became `{}`). Switch both to adjacent
tagging (`{"type": …, "content": …}`), which keeps the ergonomic tuple variants
and all existing call sites unchanged while making every variant round-trip.
Add serde round-trip tests covering every variant of both enums.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
Some OpenAI-compatible backends omit the tool-call `index` field, which
collapsed every parallel call onto slot 0. Make `index` optional and, when
absent, correlate fragments by id (new id opens a slot, known id reuses it,
id-less continuations append to the last slot). Also enumerate slots before
filtering in `into_response` and key the synthetic `tool-{slot}` fallback id to
the slot position so streamed delta ids always match the final call ids.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
Zero-argument tool calls from OpenAI-compatible backends arrive with an empty
arguments string. Treat a blank arguments payload as `{}` in both the unary and
streamed reconstruction paths instead of failing it as malformed JSON.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
The provider never set any reqwest timeout and ignored
ModelRequest::timeout_ms, so a stalled endpoint could hang a call forever. Give
the shared client a sane connect timeout, honor an explicit per-request
timeout_ms on both unary and streaming calls, and apply a default overall
timeout to unary calls (streaming stays uncapped so long streams are not
truncated).

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
o-series reasoning models (o1/o3/o4) reject `max_tokens` and require
`max_completion_tokens`. Route the request's output-token cap to whichever
field the target model accepts, add the new wire field, and reserve it from
provider_options passthrough.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
translate_message flattened every message to its text, so image and JSON blocks
were silently dropped even though the profile advertises image_in. Render user
messages with non-text blocks as OpenAI content-parts (text + image_url),
serialize JSON blocks into text, and fail closed with a validation error on
provider-extension blocks that have no OpenAI representation. Text-only messages
keep their plain-string wire shape.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
When a completed response carried usage only on its message (top-level usage
None) and no UsageDelta streamed, finish overwrote message.usage with None.
Merge the response, message, and streamed usage instead — prefer a present
value, never overwrite a known usage with None — and keep both fields in sync.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
Checkpoint and run ids were built from a process-local AtomicU64 that
restarts at 0 in every new process, so a resumed thread restarted after a
crash re-minted ids it had already used. That corrupts the parent-lineage
map (prune could delete a live record's ancestor) and can make
ResumeTarget::Checkpoint load the wrong record.

Add a process nonce to harness/ids and expose new_run_id/new_checkpoint_id
(`<prefix>-<nonce>-<seq>`), collision-free across restarts while next_seq
keeps them ordered within a process. The graph executor now mints ids
through those helpers instead of its own bare counter.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
The three checkpointer backends disagreed on `get(thread, Some(id))` when
an id appeared more than once: the in-memory backend returned the first
match while file and sqlite returned the last. Pin one semantic —
last-write-wins, matching the append-only history and `get(None)` — fix the
in-memory backend to `rfind`, and assert it in the checkpointer conformance
suite so all backends stay interchangeable.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
The step's goto map was keyed by node id, so two Send activations of the
same node that each returned a Command::goto collided: the second clobbered
the first, and both branches then routed to the survivor's target — losing
one branch's routing and duplicating the other. Key the map by active-set
index instead, and pass each activation's own goto targets into `route`.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
…rriers

Interrupt and failure boundaries lost durable state three ways:

- B2: the checkpoint only stored next_nodes (Vec<NodeId>), so every pending
  Send worker re-ran on resume with ctx.send_arg == None — breaking the
  map-reduce fanout the resumable-failure feature exists for.
- B3: successors of branches that completed before the pause were never
  scheduled, so in parallel [a->x, b] where b interrupts, x silently never
  ran after resume.
- B4: barrier (waiting-edge) arrivals were run-local, so a join whose first
  predecessor arrived before an interrupt could never satisfy its
  precondition after resume.

Add pending_activations (node + send_arg) and barrier_arrivals to the
Checkpoint, both #[serde(default)] for back-compat (old checkpoints load and
fall back to next_nodes / an empty barrier set). At the interrupt/failure
boundary the executor now routes the completed lower-index branches into
their successors, appends the pending tail with args preserved, and persists
the accumulated barrier arrivals; resume rebuilds activations and seeds the
barrier map from the checkpoint.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
execute_run always started with parent_checkpoint = None, so the first
boundary checkpoint written after a resume had no parent. That orphaned the
pre-interrupt history: get_state_history stopped at the resume point and
prune(keep_last) deleted the ancestors a live record still depended on.
Thread the loaded checkpoint id into the run as the initial parent so the
lineage spine stays connected.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
An embedded child graph that paused on a human-approval interrupt had its
partial state treated as a completed output: the subgraph node returned
NodeResult::Update(child.state) and the parent kept executing, so the
interrupt was unreachable through the parent's resume API. Detect an
interrupted child execution and re-emit its interrupt as the parent node's
interrupt so the parent pauses too. On a resumed activation the child is now
resumed from its own checkpoint (rather than re-run from scratch), carrying
the parent's resume value down.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
A subgraph child runs under the parent's thread id but a distinct namespace,
yet no backend filtered lookups on the stored namespace: a parent resume (or
state inspection) could load the child's checkpoint, resuming a same-named
node with the wrong role or failing with MissingNode. Add a default
get_scoped(thread, id, namespace) to the Checkpointer trait (composed from
list+get, so all three backends inherit it), route get_tuple through it, and
scope the executor's resume/update/fork lookups to the graph's own namespace.
This also completes nested resume: resuming the parent now resumes the child
from its own namespaced checkpoint. Pinned in the checkpointer conformance
suite.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
Errors raised at the step boundary — a reducer merge, route resolution, or
checkpoint persist — used `?` and unwound out of the run loop without
calling fail_run, so observers saw the run stuck in Running forever. Route
each boundary error through a fail_and_return helper that records the Failed
status first. A failure-boundary persist error no longer replaces the
original node error either: it just drops the resumable checkpoint reference
and keeps reporting the node error.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
resume_from emitted GraphEvent::CheckpointSaved when it *loaded* a
checkpoint, so durability observers counting saves double-counted every
resume as a persist. Add a CheckpointRestored event for the read side and
emit it on load instead.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
A command node routes dynamically via the Command it returns at runtime and
has no static successors, so route() resolves it as a sink. Attributing a
manual update_state write to such a node persisted an empty next_nodes and
silently rendered the thread non-resumable. Reject it at write time with a
clear Graph error instead.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
Items complete out of order under buffer_unordered, so FailFast broke on the
first error to *complete* and could return a later item's error instead of the
first failure in input order (as documented). Track the lowest-index error and
only stop once every earlier item has resolved, then cancel remaining work.
Also corrects the stale module doc that claimed ordering came from `buffered`.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
…ent_loop, observability

These modules had no README.md despite being complex, multi-file public
surfaces (onion middleware composition, provider translation/SSE decoding,
the default agent loop, durable observability journals/sinks). Document
design, public surface, and operational constraints for each, consistent
with the repo's per-module README convention.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
Document the superstep executor's parallel/retry/resumable-failure
semantics, the Checkpointer trait and its file/sqlite/in-memory backends,
and the channel-per-field reducer model, matching the repo's per-module
README convention.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
…graph, testkit

Document durable graph observability (journals/sinks/Langfuse export),
the managed child-work orchestration tool surface, graph-in-graph subgraph
embedding, and the graph-level test doubles/assertions/conformance suites.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
Document the graph runtime's module map and how authoring, execution,
persistence, observability, recursion, and testing pieces fit together,
linking to each submodule's own README.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
senamakel added 28 commits July 4, 2026 01:39
Wrap the synchronous filesystem append (FileCheckpointer) and rusqlite insert
(SqliteCheckpointer) in tokio::task::spawn_blocking so the step-critical
checkpoint write no longer blocks a tokio worker thread.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
- Observation metadata now carries only run lineage, offset, and event kind
  instead of the full event payload, which duplicated input/output already in
  the body and grew batch bytes O(turns^2) toward the ~3.5MB cap.
- Emit tool observations as `span-create`; `tool-create` is not a valid
  Langfuse ingestion type and is rejected by older/self-hosted Langfuse,
  silently dropping every tool observation.
- Surface `207 Multi-Status` partial failures (non-empty `errors`) as an error
  instead of reporting success.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
eval_cell snapshotted the pre-cell namespace and then cloned the whole
name->debug-string map into the shared vars_snapshot, a third O(namespace-bytes)
pass per cell. Move the single baseline snapshot into vars_snapshot and read it
back for the change diff, so a cell pays one baseline snapshot plus the after
snapshot rather than snapshot + clone + after.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
Follow-up to the Langfuse observation-type fix: the harness e2e export test
now looks for the valid `span-create` tool observation type instead of the
rejected `tool-create`.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
…/PromptCacheGuardMiddleware recorders

These middlewares accumulated PhaseTrace/SummaryRecord/CacheLayoutEvent
entries in an unbounded Vec for the lifetime of a long-running agent
loop. Switch each recorder to a bounded VecDeque ring buffer (default
cap 1024, configurable via with_max_records/with_max_events) so memory
stays flat regardless of run length.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
…e retention

Both in-memory backends grew a run_id-keyed map forever with no
eviction, so a long-lived process journaling/tracking many runs leaks
memory without bound. Add a configurable max_runs cap (default 10_000):
InMemoryEventJournal evicts the oldest run's stream on a new run once
exceeded; InMemoryStatusStore evicts the oldest terminal run first,
never evicting an active (Pending/Running/Interrupted) run to make
room.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
InMemoryEventJournal::append evicted a run's entire stream by FIFO
insertion order with no regard for whether that run was still actively
appending, unlike the sibling InMemoryStatusStore fix which never
evicts in-flight runs. Because the evicted run's Vec was fully removed,
a later append for the same run_id restarted numbering at offset 0,
so a consumer resuming with a previously saved durable offset had
read_from silently skip past its new entries instead of erroring.

Track the next offset for evicted runs (bounded the same way `streams`
is) so a resumed stream continues numbering, and make read_from return
a clear error when a requested offset falls in an evicted range instead
of silently truncating.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
lib.rs claimed hosted providers live behind a nonexistent `openai` Cargo
feature; they are compiled in unconditionally (only `sqlite` and `repl`
are real optional features). README repeated the same claim, pointed at
version 0.1 and the old rustagents clone URL, and was missing the
goals_and_todos/subconscious_loop examples that exist on disk.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
The Project Structure section still described src/chat.rs, src/model.rs,
src/tool.rs, and src/graph.rs as flat modules, and the tests/ description
said "currently focused on serialization behavior" though it now covers
dozens of e2e/live suites. Rewrite to describe the five real module
directories, the sqlite/repl Cargo features, docs/modules/, and the wiki
submodule.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
ROADMAP.md still described chat/model/tool primitives and a state graph as
aspirational "Current Foundation" and called the crate pre-1.0, even though
v1.5.0 ships the full harness, graph, registry, .rag, and .ragsh surfaces.
Rewrite each section to describe shipped functionality and real near-term
work instead of the original bootstrap plan.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
docs/spec/README.md had grown to 1121 lines, well past the repo's own
500-line limit, and its Package Layout described a flat src/chat.rs-style
layout that hasn't existed for a long time; its Milestones and Open
Questions sections described planning-stage work that has since shipped
in full (v1.5.0).

Extract the inline Harness/Graph/Expressive-Language module specs into
docs/spec/harness-spec.md, docs/spec/graph-spec.md, and
docs/spec/expressive-language-spec.md (linked from the README), and
rewrite Package Layout to match the real src/ module-directory layout and
Cargo features, and Milestones/Open Questions to reflect what has shipped.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
goal.md lived at the repo root looking like a top-level planning document,
but it is an internal OpenHuman-to-TinyAgents migration backlog. Move it
under docs/ alongside the rest of the design notes and flag it clearly as
an internal working document, pointing readers at ROADMAP.md for the
public roadmap instead.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
Add a note clarifying this is an internal OpenHuman-to-TinyAgents
migration backlog, not the public roadmap, and point readers at
ROADMAP.md instead.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
design.md had grown to 970 lines. Split the store/checkpointer/listener
registration and event model/bus/filters into events.md, and the
lifecycle/discovery/error-model/testkit/milestones sections into
operations.md, linked from design.md and the module README.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
README.md had grown to 941 lines. Split the tool registry/agent
loop/middleware/memory sections into runtime.md, and structured
output/events/errors/testkit/milestones into observability-overview.md,
linked from the module README.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
README.md had grown to 814 lines. Split node kinds, binding to Rust, the
state model, routes, policies, safety, examples, formatting, testkit, and
milestones into reference.md, linked from README.md.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
design.md had grown to 807 lines. Split the RLM feature map, CodeAct loop,
examples, Rhai embedding plan, Python compatibility backend, safety,
events, diagnostics, testkit, and milestones into operations.md, linked
from design.md and the module README.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
.gitmodules pointed at an SSH URL (git@github.com:...), which requires SSH
key setup even for read-only clones/CI. Switch to https and document the
wiki/ submodule (what it is, how to fetch it, and that it should not be
touched incidentally) in CONTRIBUTING.md.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
…DOT ids

- registry::AliasBinding was defined and used in the public
  RegistrySnapshot::aliases field but never re-exported from
  registry::mod or the crate root, so downstream code could observe it
  but not name its type. Re-export it alongside the rest of the registry
  surface.
- Re-export the ModelCatalog family (ModelCatalog, ModelCatalogEntry,
  ModelCatalogSnapshot, ModelCatalogSource, ModelCapabilities,
  ModelPricing) at the crate root for parity with registry::mod, which
  already exposed them.
- register_descriptor's doc comment claimed it was "reserved" for Store
  and Agent kinds, but Agent has its own dedicated register_agent method
  that does not call register_descriptor; the doc now names the actual
  fallback kinds (Store, Script, Middleware, Checkpointer, TaskStore,
  Listener).
- RegistrySnapshot::to_dot interpolated component ids directly into
  Graphviz DOT quoted strings without escaping backslashes or double
  quotes, so a component id containing a `"` could break out of its
  quoted string and inject arbitrary DOT syntax. Escape both characters
  and add a regression test.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
…retryable

Fold the per-attempt retry decision into a single
`RetryPolicy::should_retry_error` (classification + attempt cap) used by both
the agent loop and RetryMiddleware, so the retry logic that previously carried
the same off-by-one in two places now lives in exactly one place and cannot
drift. ModelFallbackMiddleware now stops switching models on a non-retryable
error (auth/validation/schema) instead of burning every backend on a failure
that will recur identically.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
…faces

Extract the `depth + 1` / `max_depth` fail-closed check into a single
`RunConfig::checked_child_depth`. SubAgent, SubAgentTool, and the REPL sub-run
builtin previously hand-rolled the same guard three times; consolidating removes
the drift that let one path bypass the depth limit.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
Remove the graph-local `SEQ` AtomicU64 in `compiled` and draw every graph id
(graph-*, subagent-*, testkit run ids) from `harness::ids::next_seq`, the same
process-unique counter todos/goals/orchestration already use. Checkpoint ids
keep delegating to `new_checkpoint_id` (restart-collision-free), so the
durability fix is preserved.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
Collapse five identical `now_ms()` copies (goals store, graph + harness
observability, langfuse, store) into one `harness::ids::now_ms`, alongside the
existing process-time primitives. Every timestamp now flows through one
epoch/`unwrap_or(0)` implementation.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
Collapse the duplicated HTTP tails of invoke, stream, and list_models into
`authorized` (bearer header), `send_checked` (send + transport-error + non-2xx
status decoding, returning the raw response), and `post_json` (chat POST with
timeout selection). Auth, timeout, and error/status handling now live in one
place instead of being hand-repeated three times. Behavior is unchanged.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
… on error

Replace seven near-identical stack-runner bodies (before/after agent, model,
tool + on_tool_delta) with one `run_stack_hook!` macro that centralizes the
event bracketing and on_error fan-out. The macro — and the two wrap-onion
handlers — now emit `MiddlewareCompleted` on the error path too, so a hook that
returns `Err` can no longer leave a dangling `MiddlewareStarted` with no
matching `Completed` in the event stream. Adds a regression test and documents
the balance guarantee in the module README.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
compiled/mod.rs had grown to ~2k lines mixing the superstep executor,
state-inspection/time-travel API, and step-routing logic in one impl
block. Split mechanically into executor.rs (run/resume/execute loop),
state_api.rs (get_state/update_state/fork_state), and routing.rs
(route/route_completed/interrupt-durability), keeping mod.rs for
shared helpers and the builder/configuration impl. No behavior change.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
library/mod.rs mixed 14 built-in middleware across 4 unrelated
concerns in ~1450 lines. Split mechanically into resilience.rs
(retry/timeout/fallback/rate-limit), budget.rs (token/cost tracking
and enforcement), tool_policy.rs (allowlisting, policy, dynamic/
contextual selection, human approval), and observe.rs (structured-
output validation, dynamic prompt, redaction, tracing). No behavior
change.

Claude-Session: https://claude.ai/code/session_01Ca2v3JeHcZvpYYsEX4WpcN
@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: af3aa916-1bce-4c97-8300-0869827f3bc9

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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

@senamakel senamakel closed this Jul 4, 2026
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