Skip to content

feat(gateway): an nl action's action_ok counts the calls its executor made for the fire - #496

Merged
earakely-scale merged 5 commits into
mainfrom
edgararakelyan/nl-action-ok-calls
Nov 4, 2026
Merged

earakely-scale merged 5 commits into
mainfrom
edgararakelyan/nl-action-ok-calls

Conversation

@earakely-scale

@earakely-scale earakely-scale commented Nov 4, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

For an nl action, action_ok meant only that the executor's A2A task completed. It said nothing about whether the colleague did anything. "Replied" and "decided to stay quiet" looked the same, so telling them apart meant reading the env, or the changelog_id_before/after bracket, which concurrent writes from the agent and other triggers also move.

Each fire in flight now counts the tool calls its executor makes for it, by tool, and the nl action_ok detail carries the counts:

{"nl": "reply to the agent in the DM...", "executor": "brooke", "calls": {"slack_conversations_history": 2, "slack_send_message": 1}}
{"nl": "reply to the agent in the DM...", "executor": "brooke", "calls": {}}
{"nl": "...", "executor": "brooke", "calls": {"slack_conversations_history": 1}, "failed_calls": {"slack_send_message": 1}}
{"nl": "...", "executor": "tony", "calls": {"slack_send_message": 2}, "calls_shared_with": ["t-dm"]}
  • calls counts the executor's calls that succeeded.
  • failed_calls appears only when some calls came back isError.
  • calls_shared_with appears only when another fire of the same executor was in flight at the same time; it names those triggers (see below).
  • They are counts rather than a list, so the event's size is bounded by the env's tools, however many calls an executor makes.

How

  • on_tool_call already runs for every external call, executors' included. At its top, a call by a role that has fires in flight adds one to each of those fires' counts.
  • _in_flight[role] now holds a small record per fire, built by _in_flight_fire: its cause, the two counts, and the triggers it overlapped.
  • caused_by still returns just the causes, so trajectory attribution is unchanged.
  • _run_nl returns the fire record, or None when the action failed.

Semantics worth knowing:

  • When one executor runs two fires at once, each fire's counts include the calls made during both. This is the rule caused_by already applies, since an executor's calls carry only its role. Both fires name each other in calls_shared_with, so the two identical tallies don't read as twice the work.
  • A call whose tool raised (no result) isn't counted.
  • Website requests aren't counted.
  • Calls the executor makes after its task completed aren't counted.

Compatibility

The detail is additive. The explorer and the verifiers' run_context read executor/nl or pass detail through. Three gateway tests that seed _in_flight by hand now build the record with _in_flight_fire.

Testing

  • Unit: tst/unit/env/gateway plus tst/unit/task_step, 3013 passed. The new test covers:
    • per-tool counts, with failed calls kept apart;
    • a silent executor reading {};
    • another role's calls during the fire not counting;
    • calls after the fire not counting, and caused_by emptying afterwards;
    • two fires of one executor held open together, each counting both calls and naming the other in calls_shared_with.
  • Integration: test_each_nl_action_reaches_the_executor_it_names now asserts each executor's counts, {"items_whoami": 1, "items_add_item": 1}. On a Linux VM, the local trigger suite plus gateway_test.py's trigger, change-attribution, barrier and mirror tests gave 15/15.
  • End-to-end on the same VM: the in-repo items server behind a real gateway, with A2A executors over HTTP.
    • A colleague that acts reads {"items_whoami": 1, "items_add_item": 1}.
    • A silent one reads {}.
    • One whose write is refused reads calls: {"items_whoami": 1}, failed_calls: {"items_add_item": 1}.
  • Chaos:
    • 1,000 calls in one fire: {"items_whoami": 1000} in a 251-byte action_ok, with the gateway answering in about 7 ms afterwards.
    • Overlapping fires: two fires of one executor running at once both read {"items_whoami": 2, "items_add_item": 2}, which is the overlap rule above, and name each other (twice-1 reads calls_shared_with: ["twice-2"], and the reverse). Fires that didn't overlap carry no calls_shared_with.
    • Stuck executor: one that never completes ends in action_failed. A call it makes after the fire timed out carries no caused_by, so the fire left nothing behind in _in_flight.

🤖 Generated with Claude Code

RetriggerView in GreptileConfidence Score: 5/5

The PR appears safe to merge; no blocking issues were found.

Summary

Adds per-tool call counts to an nl action’s action_ok detail.

  • Successful nl actions now show what their executors did.

No actionable issues were found in the changes since the previous review or the full-PR rules check.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[NL action starts] --> B[Register fire under executor role]
    B --> C[Record overlapping trigger IDs]
    C --> D[Executor makes tool calls]
    D --> E[Count each result for active fires]
    E --> F[Executor finishes]
    F --> G[Remove fire from active list]
    G --> H[Emit action_ok with counts and overlap IDs]
Loading

Reviews (2) · Last reviewed commit: "test(gateway): the overlap test opens bo..." · Reviewed by Greptile

earakely-scale and others added 2 commits November 4, 2026 12:51
… made for the fire

`action_ok` for an nl action meant only that the executor's A2A task completed; whether the colleague posted, or
said nothing, took reading the env and a global changelog bracket that concurrent writes also move. Each fire in
flight now counts its executor's tool calls by tool, so the detail reads `calls: {"slack_send_message": 1}` for a
reply, `calls: {}` for a silence, and `failed_calls` for calls that came back as errors. Counts, not a list, so
the event's size is bounded by the env's tools, not by how many calls an executor makes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n_ok now carries

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@earakely-scale
earakely-scale requested a review from a team as a code owner November 4, 2026 21:17
earakely-scale and others added 3 commits November 4, 2026 13:57
…lls with

An executor's calls carry only its role, so two fires it realizes at once both count every call made during
either. `calls_shared_with` now names the overlapping triggers beside the counts, instead of leaving two
identical tallies to read as twice the work. The in-flight record is built by `_in_flight_fire`, which the
gateway tests that seed `_in_flight` use too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lap test

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…or calls

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@earakely-scale

Copy link
Copy Markdown
Collaborator Author

@greptileai review

@earakely-scale
earakely-scale merged commit 1ef185a into main Nov 4, 2026
34 of 35 checks passed
@earakely-scale
earakely-scale deleted the edgararakelyan/nl-action-ok-calls branch November 4, 2026 22:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant