Skip to content

feat(gateway): trigger events name the provoking call's arguments - #486

Merged
earakely-scale merged 5 commits into
mainfrom
edgararakelyan/trigger-detected-args
Nov 4, 2026
Merged

earakely-scale merged 5 commits into
mainfrom
edgararakelyan/trigger-detected-args

Conversation

@earakely-scale

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

Copy link
Copy Markdown
Collaborator

Summary

A trigger's detected and cancelled events record the call that provoked them as provoking: {tool, role}. That says which tool set a trigger off, but not what the call said. So to find out why a trigger did or didn't match, you had to replay the agent's trajectory against the trigger's where rules. A common case: a DM addressed by conversation id when the trigger expects a user id.

provoking now also carries the call's args, echoed the same way a mirror action's args already are in action_ok:

  • id and metadata values pass through (keys ending in id/Id/_id, plus the existing allowlist);
  • every other value is replaced by a shape such as {"redacted": {"type": "str", "len": 120}}, so message bodies and addresses stay out of the log.
{"kind": "detected", "trigger_id": "dm", "provoking": {"tool": "slack_send_message", "role": "default",
  "args": {"channel_id": "D0ACC8C6C66", "message": {"redacted": {"type": "str", "len": 84}}}}}

Three kinds of event share the dict and so gain args:

  • detected for action triggers;
  • cancelled, naming the call that cancelled a deadline;
  • detected for state triggers re-checked after a call.

Website (source: website) and clock (source: clock) provoking are unchanged.

Size bound. The agent controls these arguments. The log keeps up to 10,000 events, and every event is also logged at info. So args holding more than 256 values, or whose echo passes 4 KB of JSON, are recorded as their shape alone ({"redacted": {"type": "dict", "len": N}}). The value count runs first and stops at 256, so a large argument is shaped without building its echo: 0.7 µs for a 10k-row argument, and 3.4 µs to echo a Slack-sized {channel_id, message}.

Compatibility

The change is additive. The explorer UI and the verifiers' run_context read only tool/role, or pass provoking through. A step talking to an older gateway simply sees no args, so no card feature is needed.

Testing

  • Unit: tst/unit/env/gateway plus run_context, 780 passed. Two tests are new:
    • each detection, queued ones included, names its own call's ids but not its content;
    • args too large to echo are recorded as their shape, and the trigger still fires. This covers both too many values and a few long id values.
  • End-to-end on a Linux VM: the in-repo items server behind a real gateway on local docker, with action, deadline and state triggers registered through RegisterEnvTriggersStep.
    • Calls went over MCP and over /step.
    • GET /triggers/state showed args on the action detected, cancelled and state detected events.
    • An extra item_id was echoed, while bodies were redacted and absent from the log.
  • Integration: gateway_test.py (integration-local-gateway) and test_register_env_triggers_local.py on the same VM: 32/32 passed.
  • Chaos on the same gateway:
    • Huge argument: a 10k-row (~700 KB) list argument is recorded as a 204-byte shape and still fires. The call takes 58 ms, against ~47–73 ms on main. Calls with ordinary args show no difference (7–8 ms p50 before and after).
    • Secrets and PII: a card number, email, password, SSN, and a 500-char value under an _id key all come out as shapes, and none of them appears anywhere in /triggers/state. A nested file_id is echoed.
    • Concurrency: 50 concurrent calls into one repeat trigger give 50 detections, each carrying its own call's item_id, with none missing or duplicated. p50 latency under that load matches main (about 178 ms on both).
    • Deep nesting: args nested about 300 levels or more are refused by the gateway before detection, with "Circular reference detected (depth exceeded)", exactly as on main. So the echo never sees anything deep enough to approach the recursion limit.

🤖 Generated with Claude Code

RetriggerView in GreptileConfidence Score: 5/5

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

Summary

Trigger events now include a redacted copy of the call's arguments.

  • Tool-trigger events show a safe summary of the call that caused them.

Reviews (5) · Last reviewed commit: "refactor(gateway): keep a provoking call..." · Reviewed by Greptile

A detected or cancelled event recorded only the tool and role of the call that provoked it, so finding which
call missed or matched a trigger meant replaying the solver's trajectory against the trigger's rules. The
event now carries the call's args, echoed as mirrors' args already are: id and metadata values pass, every
other value is a shape. An echo past 4 KB, or too deep to build, is recorded as the args' shape alone, since
detection is fail-open and a raise would skip every trigger the call provokes.

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 17:39
…s echo

The gateway refuses a tool call whose args nest past a few hundred levels ("Circular reference detected (depth
exceeded)") before the trigger engine sees it, so the echo can't recurse anywhere near Python's limit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment thread src/agent_env/env/gateway/triggers.py Resolved
…n they hold too many values

Building the echo of a 10k-row argument only to measure it and throw it away doubled that call's time. A count
that stops at 256 values now decides first, so large args are shaped without the copy.

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

Copy link
Copy Markdown
Collaborator Author

@greptileai review

earakely-scale and others added 2 commits November 4, 2026 11:24
…s args reach its event

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

The limits and their two helpers now sit beside `_ECHO_VALUE_MAX` and `_echo_args`, named as a pair
(`_ECHO_ARGS_MAX_CHARS`, `_ECHO_ARGS_MAX_VALUES`). `_echo_args`'s docstring is back to describing mirrors, and a
comment by the event vocabulary names the three forms `provoking` takes. No behaviour change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@earakely-scale
earakely-scale merged commit fd9e2f1 into main Nov 4, 2026
31 checks passed
@earakely-scale
earakely-scale deleted the edgararakelyan/trigger-detected-args branch November 4, 2026 20:38
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