Skip to content

fix(ai): per-run repeat guard and one session per PR review - #1382

Merged
Makisuo merged 2 commits into
mainfrom
claude/inspiring-feistel-c593f7
Oct 10, 2026
Merged

Makisuo merged 2 commits into
mainfrom
claude/inspiring-feistel-c593f7

Conversation

@Makisuo

@Makisuo Makisuo commented Oct 10, 2026 •

Copy link
Copy Markdown
Collaborator

What

Two fixes for the PR review agent's review_files workers.

1. Workers were refused on their first pr_changed_files / pr_context call

The identical-call guard in buildMapleToolkit keeps its counts in a map created once per toolkit build. buildReviewFanout hands the workers the parent's own handlers, so the parent and every worker shared that map. Once the parent made three identical calls, every later worker got "has already been called 3 times" on its first call. In production that was 94 failed calls across 30 sessions in one day.

The guard now keys on the engine run id. The engine provides AgentSpawner to every run, child runs included, bound to that run's identity. The handler reads it with Effect.serviceOption(AgentSpawner). (Toolkit merges the handler's captured context under the current fiber's, so the calling run's service wins.) Each worker gets its own budget, and the parent's guard is unchanged.

The turn id stays shared on purpose: a review is one turn for metering and for turn grouping in the session view. The bug was the shared map, not the shared turn id.

2. One review showed up as several sessions

A worker's model-call and tool spans already carried the parent's maple_ai.session.id. The engine's own invoke_agent span did not, so ingest fell back to gen_ai.conversation.id, which is the worker's own thread-id_*. Tool calls rejected before a handler ran had the same problem.

New withRunSessionAttributes tracer wrapper (platform/genai-spans.ts), applied in runChatTurn, stamps the run's session and turn attributes on every invoke_agent and execute_tool span in the run. HTTP and database spans are untouched.

Tests

  • chat/run-review-fanout.test.ts (new): a review through the real engine with a scripted model. The parent spends its 3 identical calls, then calls review_files. The worker's first call must reach the executor, and its invoke_agent span and all tool spans must carry the review's session and turn ids.
  • mcp/tools/llm-tools.test.ts: four sibling runs each get their first call after the parent spent its budget, and the parent is still refused.
  • platform/genai-spans.test.ts: agent and tool spans get the attributes, other spans don't.

The two guard tests fail on main with the production symptom and pass here. tsc is clean.

🤖 Generated with Claude Code


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

Summary by CodeRabbit

  • Bug Fixes
    • Repeated tool calls are tracked separately for each run, so child runs no longer consume the parent run’s allowance. Calls with the same arguments count as repeats even when their keys are ordered differently.
    • Child-agent and tool activity is associated with the correct review session and turn, while child conversations remain distinct.

review_files workers run the parent's own Maple tool handlers, so the
identical-call guard's map was shared across the parent and every worker.
Once the parent made three identical pr_changed_files calls, later workers
were refused on their first call. The guard now keys on the engine run id
from AgentSpawner, which each child run gets with its own identity.

A worker's engine invoke_agent span carried no maple_ai.session.id, so
ingest fell back to gen_ai.conversation.id (the worker's own thread) and
one review showed up as several sessions. runChatTurn now stamps the run's
session and turn attributes on every invoke_agent and execute_tool span.
@maple-review-bot

maple-review-bot Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Note

A newer push replaced 651e002 before its review finished. The latest commit is reviewed in a new comment.

@coderabbitai

coderabbitai Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 675557f2-57a0-4285-b957-355bc435052b

📥 Commits

Reviewing files that changed from the base of the PR and between 651e002 and 141703e.


📒 Files selected for processing (2)
  • apps/ai/src/mcp/tools/llm-tools.test.ts
  • apps/ai/src/mcp/tools/llm-tools.ts

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 2 remain after this review.



📝 Walkthrough

Walkthrough

The change scopes identical-tool-call limits to engine runs and adds session and turn attributes to child-agent and tool spans. Chat completion setup shares session attributes. Tests cover review-file fanout and shared-toolkit runs.

Changes

Review fanout

Layer / File(s) Summary
Run-scoped tool-call limits
apps/ai/src/mcp/tools/llm-tools.ts, apps/ai/src/mcp/tools/llm-tools.test.ts
Identical calls are counted by engine run, tool name, and arguments. The three-call limit applies separately to each run. Tests cover parent and child runs using a shared handler, and calls with arguments in different key orders.
Session attributes on child spans
apps/ai/src/chat/run.ts, apps/ai/src/platform/genai-spans.ts, apps/ai/src/platform/genai-spans.test.ts, apps/ai/src/chat/run-review-fanout.test.ts
Chat setup shares computed session attributes with completion builders and wraps the run tracer. The wrapper adds session attributes to invoke_agent and execute_tool spans. Tests cover review-file fanout and confirm unrelated HTTP spans do not receive the session ID.

Priority: ⬇️ Low

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

Change: Bug fix

Suggested reviewers: jeremyfunk


Merge Risk: ⚪ Minimal · up to 14170

The reviewed change has no identified merge-blocking issue and is ready for normal checks.

Pre-merge checks | Passed 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title accurately summarizes both primary changes: scoping the repeat guard per engine run and using one session for each PR review.
Docstring Coverage Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 6…
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.

✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR




🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

…ring

A MutableHashMap keyed by { runId, tool, params } uses Effect's structural
Equal/Hash, so there is no string concatenation or JSON.stringify, and the
same arguments in another key order now count as the same call.
@maple-review-bot

maple-review-bot Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Maple review

🟢 Confidence 9/10 · safe to merge
Both behaviours — per-run call budgets and the child's session-stamped spans — are exercised end to end by the new fanout test through the real engine.
quality 100/100 · no findings · tests covered · risk medium

The identical-call guard in buildMapleToolkit becomes per engine run, so review_files workers stop inheriting the parent's spent budget, and a new withRunSessionAttributes wrapper files a run's agent and tool spans under the review's session. Both are pinned by tests through the real engine; safe to merge.

  • buildMapleToolkit counts identical calls per engine run id, not per toolkit build
  • withRunSessionAttributes stamps session and turn attributes on the run's invoke_agent and execute_tool spans
  • runChatTurn applies that wrapper to the whole run and reuses one sessionAttributes value
  • The review_files worker's own pr_changed_files call now reaches the executor

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

@Makisuo
Makisuo merged commit a15ce47 into main Oct 10, 2026
38 checks passed
@Makisuo
Makisuo deleted the claude/inspiring-feistel-c593f7 branch October 10, 2026 23:20
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