Skip to content

Harden the reactive pipeline path: argument recovery, once on react bindings, wiring advisories, scoped scrapling renders - #442

Merged
andersonleal merged 6 commits into
mainfrom
worktree-pipeline-hardening
Jul 7, 2026
Merged

Harden the reactive pipeline path: argument recovery, once on react bindings, wiring advisories, scoped scrapling renders#442
andersonleal merged 6 commits into
mainfrom
worktree-pipeline-hardening

Conversation

@andersonleal

@andersonleal andersonleal commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

Fixes MOT-3698

Why

Running the same self-driving research-pipeline prompt repeatedly surfaced a set of failures across the stack (session console-02fc75a2 and successors). This PR fixes each at its root; the final live run of the same prompt completes correctly, self-corrects on advisories, and leaves zero bindings behind.

Harness

  • plan_calls no longer drops arguments. A call with args flattened beside function (or the target id used as the tool name) previously forwarded an empty payload, producing misleading missing field <x> errors — 11 failed calls in one session. Args are now hoisted into the payload; stringified payloads are recovered with a leading-value parse (models append stray closing braces that strict parsing rejects).
  • once: true honored on react bindings. Explicit true on a simple edge stamps __once, echoes the effective flag, and the binding retires itself after its first successful spawn. Join predecessors keep join-owned lifecycle. Previously once was silently ignored and echoed false, leaving standing kickoffs that respawned the whole pipeline on every matching state write. (Extends MOT-3698's one-shot-trigger intent to the react path.)
  • Registration advisories (observed to be obeyed 6/6 times live, unlike prose): a turn-event join predecessor filtered on parent_session_id (starves multi-key joins — one live join stuck at 1/2 forever), and a standing non-once, non-join reaction.
  • Engine registration failures carry the engine's reason instead of an opaque `harness::turn-completed` failed (cost one agent ~10 messages of guessing).

provider-anthropic identity prompt

  • Rule 1 names argument flattening as the top failure with its exact symptom and a WRONG/RIGHT example; prove an unproven call shape once before batching.
  • once semantics documented; one-run pipeline kickoffs default to once: true, including in the canonical example.
  • Join delivery: leaving metadata.session_id out of predecessor specs lands the fan-in result in the registering chat; pinning any id re-aims it away (one live run "lost" its report this way).

scrapling

  • css_selector on all fetch renders (scope markdown/text to one subtree, e.g. #mw-content-text); crawl forwards main_content_only/css_selector per page.
  • Guidance: main_content_only is best-effort, css_selector is the fix for leaked chrome, and never re-fetch with include_html just to clean a render (one run burned a ~460KB response doing exactly that).

Testing

  • cargo test (harness): 182 passed — includes new tests for every fix, each replicating the exact live failure shape (flattened args, trailing-brace payload, starving join filter, once semantics).
  • pytest (scrapling): 82 passed — includes scoped-render and crawl-knob tests.
  • Live validation: the originally-failing prompt now runs end-to-end — kickoffs echo once: true and self-retire, the starvation advisory fired once and was obeyed, the brief landed in the chat, and the engine finished with 0 leftover bindings and 0 join records.

…indings, add wiring advisories

Root-causes a session where the same pipeline prompt failed repeatedly:

- plan_calls hoists arguments flattened beside `function` into the payload
  instead of silently dropping them (was: empty payload + misleading
  "missing field" errors), and recovers stringified payloads via a
  leading-value parse (models append stray closing braces that a strict
  parse rejects as trailing data).
- `once: true` is now honored on simple harness::react bindings: the
  registration stamps `__once`, echoes the effective flag, and the binding
  retires itself after its first successful spawn. Join predecessors keep
  join-owned lifecycle; only explicit `true` opts in.
- New registration advisories: a turn-event join predecessor filtered on
  parent_session_id (starves multi-key joins) and a standing non-once,
  non-join reaction (refires on every matching event).
- Engine registration failures now carry the engine's rejection reason
  instead of an opaque "failed".
…t failures

- Rule 1 now names argument flattening (args beside `function` instead of
  inside `payload`) as the top failure, with its exact symptom and a
  WRONG/RIGHT agent_trigger example; error handling maps the symptom back.
- Prove an unproven call shape once before batching parallel copies.
- Document `once` semantics on react bindings (explicit true only, ignored
  on join predecessors, trust the echo) and make one-run pipeline kickoffs
  `once: true` by default, including in the canonical example.
- Join delivery: leaving metadata.session_id out of predecessor specs is
  what lands the fan-in result back in the registering chat; pinning any
  id re-aims delivery away from it.
…knobs, sharpen guidance

- All four fetch functions accept `css_selector` to scope markdown/text
  renders to one subtree (render_content already supported it); crawl now
  forwards `main_content_only`/`css_selector` to per-page fetches.
- Injected guidance: `main_content_only` is best-effort on heavy-chrome
  sites, `css_selector` is the fix when chrome leaks into a render, and
  never re-fetch with `include_html` (full page + headers/cookies) just to
  clean up a render you already have.
@vercel

vercel Bot commented Jul 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
workers Ready Ready Preview, Comment Jul 7, 2026 2:40pm
workers-tech-spec Ready Ready Preview, Comment Jul 7, 2026 2:40pm

Request Review

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

skill-check — worker

0 verified, 37 skipped (no docs/).

Layer Result
structure
vale
ai
render

Four for four. Nicely done.

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@andersonleal, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 27 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1db7738f-00a3-42d2-a12e-d51864810b88

📥 Commits

Reviewing files that changed from the base of the PR and between 932cf68 and f184dc3.

📒 Files selected for processing (4)
  • harness/src/functions/react.rs
  • harness/src/functions/subscribe.rs
  • harness/src/subscriptions/registry.rs
  • scrapling/tests/test_fetch.py
📝 Walkthrough

Walkthrough

This PR adds once: true semantics for harness::react bindings with auto-unregistration after successful spawn, adds advisory checks for misconfigured join watchers in subscribe.rs, recovers malformed/stringified agent_trigger payloads in policy.rs, updates identity.txt guidance accordingly, and adds css_selector/main_content_only forwarding through scrapling's rendering and crawl paths with corresponding tests.

Changes

Harness once-semantics and payload recovery

Layer / File(s) Summary
ReactSpec once field and unregister logic
harness/src/functions/react.rs
Adds ReactSpec.once (serialized __once), auto-unregisters after successful spawn for non-join edges, refactors join cleanup via resolve_engine_binding, adds once_unregister helper and tests.
Subscribe advisories and once computation
harness/src/functions/subscribe.rs
Adds parent_filter_join_advisory, standing_binding_advisory, react_once helpers; wires computed once into dedup responses and metadata; refactors registration response handling with explicit match; adds tests.
agent_trigger payload recovery
harness/src/policy.rs
Recovers stringified/malformed payload JSON and hoists flattened arguments into payload for agent_trigger calls in plan_calls; adds tests.
Identity prompt guidance updates
provider-anthropic/prompts/identity.txt
Rewrites payload-construction rules and expands harness::react once/join wiring guidance.

Estimated code review effort: 4 (Complex) | ~60 minutes

Scrapling css_selector and main_content_only forwarding

Layer / File(s) Summary
Schema and rendering support for css_selector
scrapling/src/schemas.py, scrapling/src/core.py, scrapling/src/crawl.py, scrapling/src/guidance.py
Adds css_selector to _CONTENT_OUT schema, merges into _CRAWL_REQUEST, forwards css_selector into render_content, forwards css_selector/main_content_only into crawl fetch payload, updates guidance text.
Tests for css_selector forwarding
scrapling/tests/test_crawl.py, scrapling/tests/test_fetch.py
Adds tests verifying crawl and fetch scope rendered content via css_selector.

Sequence Diagram(s)

sequenceDiagram
  participant Subscriber
  participant SubscribeHandler
  participant ReactHandler
  participant Engine

  Subscriber->>SubscribeHandler: register react binding (once=true)
  SubscribeHandler->>SubscribeHandler: react_once() / standing_binding_advisory()
  SubscribeHandler->>Engine: register trigger (__once stamped)
  Engine-->>SubscribeHandler: engine trigger id
  SubscribeHandler-->>Subscriber: SubscribeResponse(once=true)
  Note over ReactHandler: on triggered event
  ReactHandler->>ReactHandler: spawn_reaction()
  ReactHandler->>ReactHandler: resolve_engine_binding()
  ReactHandler->>Engine: once_unregister()
Loading

Estimated code review effort: 3 (Moderate) | ~25 minutes

Poem

A rabbit hops through once-fired trees, 🐇
Unregistering bindings with graceful ease,
Payloads recovered from strings gone wrong,
Selectors scope the page along,
Hop, review, and merge with glee! 🥕

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: reactive pipeline hardening, react once semantics, advisories, and scoped scrapling renders.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree-pipeline-hardening

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 (2)
harness/src/functions/subscribe.rs (1)

540-552: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Strip caller-supplied __once before stamping the effective value.

metadata starts from caller input. If it already contains "__once": true while top-level once is omitted/false, line 550 leaves it intact; ReactSpec.once then auto-unregisters even though the response echoes once: false.

Proposed fix
         m.insert(
             "__subscription_id".to_string(),
             Value::String(sub_id.clone()),
         );
+        m.remove("__once");
         if once {
             m.insert("__once".to_string(), Value::Bool(true));
         }
🤖 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 `@harness/src/functions/subscribe.rs` around lines 540 - 552, The subscription
metadata merge in subscribe should not preserve a caller-supplied "__once" flag
when the effective top-level once value is false. Update the metadata stamping
logic in the block that inserts OWNER_SESSION_KEY and "__subscription_id" so it
explicitly removes any existing "__once" from req.metadata before conditionally
re-inserting it only when once is true, ensuring the value used by
ReactSpec.once always matches the response.
harness/src/functions/react.rs (1)

449-453: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Evict the local subscription only after engine unregister succeeds.

resolve_engine_binding calls take/take_by_trigger_id before unregister_subscription. If the engine unregister fails, the durable trigger remains but the local mapping is gone, so later once/join cleanup cannot resolve sub_... and a one-shot binding can become orphaned/standing. Split lookup from eviction, or restore the mapping on failure.

Also applies to: 605-611, 623-626

🤖 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 `@harness/src/functions/react.rs` around lines 449 - 453, The subscription
cleanup flow in harness::react currently removes the local binding too early via
resolve_engine_binding, so if unregister_subscription fails the mapping is lost
and later once/join cleanup cannot resolve the sub_... entry. Update the
unregister path to separate lookup from eviction: first read the engine binding
without taking it, call unregister_subscription using that engine_id, and only
evict the local subscription after the engine unregister succeeds. Apply the
same ordering in the other cleanup sites referenced by the react helpers so
failure paths can leave the mapping intact or restore it before returning.
🧹 Nitpick comments (1)
harness/src/policy.rs (1)

191-217: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Optional: extract payload-recovery match into a named helper.

The nested match with inline map manipulation (flagged as high complexity) is correct but dense. A small fn recover_agent_trigger_payload(arguments: &Value) -> Value would isolate the three recovery paths (string-parse, direct-value, hoist) for readability and easier future testing in isolation.

♻️ Suggested extraction
+fn recover_agent_trigger_payload(arguments: &Value) -> Value {
+    match arguments.get("payload") {
+        Some(Value::String(s)) => serde_json::Deserializer::from_str(s)
+            .into_iter::<Value>()
+            .next()
+            .and_then(Result::ok)
+            .filter(Value::is_object)
+            .unwrap_or_else(|| Value::String(s.clone())),
+        Some(v) => v.clone(),
+        None => {
+            let mut map = match arguments {
+                Value::Object(m) => m.clone(),
+                _ => Default::default(),
+            };
+            map.remove("function");
+            Value::Object(map)
+        }
+    }
+}
+
 pub fn plan_calls(message: &AssistantMessage, expose: ExposeMode) -> Vec<PlannedCall> {
     ...
-                    let payload = match arguments.get("payload") {
-                        ...
-                    };
+                    let payload = recover_agent_trigger_payload(arguments);
🤖 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 `@harness/src/policy.rs` around lines 191 - 217, The payload recovery logic in
the `match arguments.get("payload")` block is correct but too dense; extract it
into a small helper such as `recover_agent_trigger_payload(arguments: &Value) ->
Value` near the existing policy code. Move the three cases into that helper:
stringified payload parsing, direct value cloning, and hoisting flattened
arguments from `arguments` when `payload` is missing, so the main flow stays
readable and the recovery behavior is easier to test and maintain.
🤖 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 `@harness/src/functions/subscribe.rs`:
- Around line 324-329: The subscribe flow in `subscribe` (and the matching path
around the other trigger wiring site) currently unregisters the engine trigger
when `deps.subscriptions.set_trigger_id` returns false but still proceeds as
success; update this branch to return an error instead of `Ok(SubscribeResponse
{ subscription_id: sub_id, ... })` so callers never receive an inactive
subscription id. Use the existing `set_trigger_id` and
`unregister_engine_trigger` logic in `subscribe` to clean up, then propagate a
failure result from the surrounding match/async flow when trigger-id wiring
cannot be completed.

---

Outside diff comments:
In `@harness/src/functions/react.rs`:
- Around line 449-453: The subscription cleanup flow in harness::react currently
removes the local binding too early via resolve_engine_binding, so if
unregister_subscription fails the mapping is lost and later once/join cleanup
cannot resolve the sub_... entry. Update the unregister path to separate lookup
from eviction: first read the engine binding without taking it, call
unregister_subscription using that engine_id, and only evict the local
subscription after the engine unregister succeeds. Apply the same ordering in
the other cleanup sites referenced by the react helpers so failure paths can
leave the mapping intact or restore it before returning.

In `@harness/src/functions/subscribe.rs`:
- Around line 540-552: The subscription metadata merge in subscribe should not
preserve a caller-supplied "__once" flag when the effective top-level once value
is false. Update the metadata stamping logic in the block that inserts
OWNER_SESSION_KEY and "__subscription_id" so it explicitly removes any existing
"__once" from req.metadata before conditionally re-inserting it only when once
is true, ensuring the value used by ReactSpec.once always matches the response.

---

Nitpick comments:
In `@harness/src/policy.rs`:
- Around line 191-217: The payload recovery logic in the `match
arguments.get("payload")` block is correct but too dense; extract it into a
small helper such as `recover_agent_trigger_payload(arguments: &Value) -> Value`
near the existing policy code. Move the three cases into that helper:
stringified payload parsing, direct value cloning, and hoisting flattened
arguments from `arguments` when `payload` is missing, so the main flow stays
readable and the recovery behavior is easier to test and maintain.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f841f26d-a577-4e96-8ec2-3288caf03143

📥 Commits

Reviewing files that changed from the base of the PR and between fef9d6b and 932cf68.

📒 Files selected for processing (10)
  • harness/src/functions/react.rs
  • harness/src/functions/subscribe.rs
  • harness/src/policy.rs
  • provider-anthropic/prompts/identity.txt
  • scrapling/src/core.py
  • scrapling/src/crawl.py
  • scrapling/src/guidance.py
  • scrapling/src/schemas.py
  • scrapling/tests/test_crawl.py
  • scrapling/tests/test_fetch.py

Comment thread harness/src/functions/subscribe.rs
…cal binding only after engine unregister succeeds

Review findings from PR #442:

- A caller-supplied `__once` in react metadata could make a binding retire
  while the response echoed `once: false`; the stamp is now stripped before
  the effective value is conditionally re-inserted.
- retire_binding unregisters the engine trigger BEFORE evicting the local
  slot: a failed engine call now keeps the `sub_` mapping resolvable so the
  next fire (or a manual unregister) can retry, instead of orphaning the
  durable binding as a standing refire. The bind-window race keeps its old
  contract: eviction hands cleanup to the registration path's
  set_trigger_id=false branch.
- Documented why set_trigger_id=false deliberately returns Ok: the once
  fire already delivered; erroring would invite duplicate re-registration.
@andersonleal
andersonleal merged commit e0089aa into main Jul 7, 2026
36 checks passed
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