Repository navigation
feat(triggers): fire env triggers on requests to the env's websites - #237
Open
sarah-chitty-1 wants to merge 2 commits into
Open
sarah-chitty-1 wants to merge 2 commits into
sarah-chitty-1 wants to merge 2 commits into
Conversation
A `when.type: "website"` trigger matches the requests the gateway proxies
to one of its websites (`/website/<app>/...`): `app`, an optional `method`,
`path` and `status` predicates, plus `where` over the request's args (its
query and form/JSON-object body fields), `roles`, `repeat` and a
`provoking_call` barrier, which holds the website's response until the
actions have landed. Without a `status` predicate only a response below
400 matches, as only a tool call that did not fail provokes an action
trigger. An unsafe request that did not fail also prompts the state
triggers, as a non-readonly tool call does.
This lets an agent that browses with its own browser or HTTP client
provoke triggers, not only one calling the env's tools.
The firing log attributes the request (`provoking: {source: "website",
app, method, path, status, role}`), the trajectory's `trigger_fired`
carries `provoking_request`, the card declares the `website` feature, and
the explorer names website requests in the trigger timeline.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…keep old error text; one-line comments - The /website proxy feeds the trigger engine only while a website trigger is registered, so an env without one pays one check per request and its state triggers are never re-checked on website traffic. The request body is read inside the fail-open path. - Error messages keep their previous wording as a substring (e.g. "only valid on action triggers"), so the existing test fragments are unchanged. - Pre-existing docstrings are back as they were; every added comment and docstring is one line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sarah-chitty-1
marked this pull request as ready for review
October 19, 2026 19:42
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
A new trigger type,
when.type: "website", that fires on requests the gateway proxies to an env's websites (/website/<app>/...).Action triggers only see tool calls today. An agent that browses with its own browser or HTTP client (an external A2A agent, a computer-use agent with its own browser, a script) never calls the env's
browser_*tools, so "when the agent opens the order page" never fires for it. Its page traffic does go through the gateway's website proxy, so the gateway can match on that.{"id": "carrier-emails", "when": {"type": "website", "app": "porchbite", "method": "GET", "path": {"regex": "^/api/orders/"}}, "barrier": {"at": "provoking_call"}, "actions": [...]}app: required, and must be one of the env's websites (400 otherwise)method: optional, upper-casedpathandstatus: optional, using the existingregex/equals/existscheckswhereon the request's args (query parameters plus form or JSON-object body fields; the body wins a clash),roles,repeat,${args.*}in templates, and theprovoking_callbarrier, which holds the website's response until the trigger's actions have landed.statuscheck, only responses below 400 match, the same way a failed tool call never fires a trigger. Astatuscheck can match 4xx/5xx on purpose.GET /triggers/state): same contract. Adetectedevent names the request asprovoking: {source: "website", app, method, path, status, role}. The query string and body are never logged. The trajectory'strigger_firedalso carriesprovoking_request.websiteis added to the triggers/v1featureslist.Worth a look
/websiteproxy hands requests to the trigger engine only while a website trigger is registered. An env without one pays a single check per request: no body parsing, no time-trigger tick, no state re-check./website/<app>/traffic counts. Direct backend calls through/svc/<app>/don't. A broaderhttptype could be a follow-up.${result.*}andwhereon the result are refused at registration. Capturing small JSON responses could be a follow-up.AgentEnv-Roleheader and counts asdefault. A client sets the header to act as another role.?for a website detection.unlessstill matches only tool calls, and website requests themselves aren't written to the trajectory, onlyprovoking_requestontrigger_fired.Tests
tst/unit/env/gateway/triggers_website_test.py, 26 tests:/website/slack/.ruffcleanuv lock --checkOKregister_env_triggersall passTested end to end with an external agent's own browser
Three agents opened a stayncook link through our front door on Modal, each with its own browser: a plain Playwright script,
claude-opus-5-5driving headless Chrome, and Meta Muse's own cloud browser. Their requests went through the gateway's/website/stayncook/proxy; none of them used the env'sbrowser_*tools.With Muse, the barrier held the first page about 57 ms, and the gateway sent the 200 0.3 ms after
fired. Muse then found the stay under Canceled reservations, and the state grader counted the cancellation as the world's change, not the agent's (verdict: pass).GET /:provoking: {source: website, app: stayncook, method: GET, path: /, status: 200, role: psi_agent}, once (fire_count: 1).provoking_callbarrier held the page until the host's cancellation and its verify step had landed, about 45 ms. The 200 went out 0.2 ms afterfiredin the gateway's own log.Docs note: the provoking response is held, not re-rendered. A trigger meant to change what the agent sees should key on the page load (the HTML document), not on the data call it wants to change. Otherwise the barrier holds a response that already has the old data.
🤖 Generated with Claude Code


Confidence Score: 5/5
The PR appears safe to merge; no actionable issues were found.Summary
Adds
websitetriggers for requests through the gateway’s website proxy.No actionable issues were found. Tests were inspected but not run during this review.
Acknowledged scope from
sarah-chitty-1: the Hub parser update is deferred; deadlineunlesschecks remain tool-only; website requests are not separately recorded in the trajectory; barriers hold responses without rebuilding their contents.Diagram
sequenceDiagram participant Browser participant Gateway participant Website participant Triggers Browser->>Gateway: /website/app/path Gateway->>Website: Forward request Website-->>Gateway: Response headers alt Website triggers registered Gateway->>Triggers: Match request and schedule actions opt Successful write Triggers->>Triggers: Re-check state triggers end opt Provoking-call barrier Gateway->>Triggers: Wait for actions or timeout end end Gateway-->>Browser: Stream website responseReviews (1) · Last reviewed commit: "review: only a registered website trigge..." · Reviewed by Greptile