Skip to content

feat(explorer): show a run's Triggers tab, with the trigger-state route it reads - #27

Merged
polakamtejas merged 1 commit into
mainfrom
tejas/explorer-triggers-tab
Oct 1, 2026
Merged

polakamtejas merged 1 commit into
mainfrom
tejas/explorer-triggers-tab

Conversation

@polakamtejas

@polakamtejas polakamtejas commented Sep 30, 2026 •

Copy link
Copy Markdown
Collaborator

Builds on #25 (merged): the new route takes task and instance ids in the path through #25's EntityId and encoded-id routing. Rebased onto main after #24.

The run viewer shipped the Triggers components (TriggersPanel, the event timeline, the useLiveTriggers polling hook) but never mounted the tab. The route the timeline polls, GET /api/v1/tasks/{task_id}/instances/{instance_id}/triggers, didn't exist in the explorer. Both came over from Scale's hosted AgentEnvHub frontend, where the run viewer has a Triggers tab. This PR adds the route and mounts the tab.

UI (task-instance-viewer.tsx, +16): a Triggers tab after Server Config, the same position as the hosted viewer. It shows only when the run recorded trigger metadata (triggerRuntime, already computed from context.metadata), and renders TriggersPanel:

  • the registered env and agent triggers
  • the final env-trigger state
  • the event timeline on the gateway's virtual clock (the part that polls the route)
  • the per-turn record

Route (explorer/routers/triggers.py, new; included in app.py): the run's trigger metadata ledger, plus each env's verbatim /triggers/state under state:

  • while the run is running: read live from each env's gateway (/triggers/state and /clock/state, concurrently, 2s timeout). An unreachable gateway reports unavailable rather than looking like "no triggers".
  • otherwise: read from the envelope prompt_agent saved at the end of the prompt step (context.metadata.env_trigger_state[env].object_url).
  • state_meta carries the gateway clock the events' virtual_time is measured on, and state_sources / source say where each env's state came from.

The response payload is the hosted route's, key for key, which is what useLiveTriggers and parse-trigger-* already parse.

What differs from the hosted route

  • Records: the hosted route queries Mongo directly. Here the instance and env-instance records are read through the configured document store (Filter / In).
  • Saved envelope: the hosted route reads it with a raw S3 helper. Here it is read through the configured object store (get_object_store_at(url)). A url that store doesn't own is refused and the env falls back to its metadata, and an envelope captured by another instance is skipped. prompt_agent already writes the envelope through the configured store, so this works on the local filesystem store.
  • Gateway urls: taken from the env-instance records the deploy writes, not the run context's own copy, which a run request can supply. Same as the hosted route.
  • No id regex: the hosted _SAFE_TASK_ID_RE rejects /, so it would refuse bundle ids.
  • No stale-running → cancelled normalization: the hosted route shares that rule with its list and detail endpoints. The explorer's other routes report an instance's status as stored, so this one does too. A run left running by a killed process keeps polling its gone gateway, which reads unavailable.

Testing

  • 6 new tests in explorer_app_test.py:
    • the metadata ledger
    • a finished run's saved envelope (state from the object store, state_sources: artifact, and its clock in state_meta)
    • a url outside the store, an envelope from another instance, and a capture error, all falling back to metadata
    • a running run read live (httpx MockTransport; the gateway comes from the env-instance record, not the context's url; the env with no gateway reports unavailable)
    • an unknown instance is 404
    • a namespaced task and instance id
  • Without the route, 5 of the 6 fail (the 404 test passes either way). With it, all 87 explorer tests pass.
  • The full unit tier passes except 2 test_mcp_cli_builder tests that fail identically on main here: the generated CLI's interpreter has no click, which is environmental.
  • check_plugin_api.py: no break.
  • UI typecheck, test:smoke and build:static pass.
  • Browser check used a seeded run, not a live trigger run. A real trigger-registering run needs a deployed gateway plus an agent. I seeded one completed instance of a flat task with trigger metadata in the shape of the UI's smoke fixtures, plus a saved envelope in the local object store (2 env triggers with 7 events, 1 agent trigger, 2 turns). Against agent-env up:
    • the Triggers tab appears next to Task Run Context
    • the registrations table, final env-trigger state and per-turn record render
    • the event timeline renders tagged artifact, with the virtual-clock line read from state_meta
    • the live path is covered by the unit test only

🤖 Generated with Claude Code

RetriggerConfidence Score: 3/5

The PR is not ready to merge: task-runner navigation can target the wrong task, and saved trigger state still stops after 20 environments.

Fix All in CursorFindings

  1. P1 Saved events vanish after 20 environments ▶
  2. P2 Live events disappear after failed registration ▶
Fix with agent prompt
### Issue 1
src/agent_env/explorer/routers/triggers.py:undefined-78
For a run with more than 20 trigger-bearing environments, this route reads saved state for only the first 20. The writer saves the rest, but the Triggers tab never gets their events or state sources. Read the remaining saved state in bounded batches, or tell the viewer that the response is incomplete.

### Issue 2
src/agent_env/explorer/routers/triggers.py:96-100
If the environment-instance record write fails, deployment can still succeed with a working gateway. This lookup skips that environment because it has no `instance_id`. The Triggers tab then shows none of its live events and does not mark it unavailable. Account for working gateways without a saved instance record, without trusting a gateway URL supplied by a run request.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

The run viewer now has a Triggers tab for runs with trigger data, backed by a new endpoint for the tab’s timeline. The endpoint combines the run’s trigger records with live gateway state or a saved capture, so the viewer can show trigger activity during and after a run.

  • Adds the endpoint to the explorer and mounts the tab in the run viewer.
  • Includes trigger state, its source, and clock details in the endpoint response.
  • Adds tests for live and saved state, missing instances, and namespaced IDs.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart LR
  URL[Task-runner URL] --> ID[Task ID prop changes]
  ID --> Init[Initialization effect returns]
  Init --> Old[Runner keeps the old task]
Loading

Reviews (3) · Last reviewed commit: "feat(explorer): show a run's Triggers ta..."

@polakamtejas
polakamtejas requested review from a team as code owners September 30, 2026 20:58

pending = {
env_id: entry
for env_id, entry in list(captures.items())[:MAX_TRIGGER_ENVS]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Saved events vanish after 20 environments

For a run with more than 20 trigger-bearing environments, this route reads saved state for only the first 20. The writer saves the rest, but the Triggers tab never gets their events or state sources. Read the remaining saved state in bounded batches, or tell the viewer that the response is incomplete.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agent_env/explorer/routers/triggers.py
Line: 78

Comment:
**Saved events vanish after 20 environments**

For a run with more than 20 trigger-bearing environments, this route reads saved state for only the first 20. The writer saves the rest, but the Triggers tab never gets their events or state sources. Read the remaining saved state in bounded batches, or tell the viewer that the response is incomplete.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Cursor Fix in Claude Code Fix in Codex

Comment on lines +96 to +100
pairs = [
(env["env_id"], env["instance_id"])
for env in deployed_envs
if isinstance(env, dict) and isinstance(env.get("env_id"), str) and isinstance(env.get("instance_id"), str)
][:MAX_TRIGGER_ENVS]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Live events disappear after failed registration

If the environment-instance record write fails, deployment can still succeed with a working gateway. This lookup skips that environment because it has no instance_id. The Triggers tab then shows none of its live events and does not mark it unavailable. Account for working gateways without a saved instance record, without trusting a gateway URL supplied by a run request.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agent_env/explorer/routers/triggers.py
Line: 96-100

Comment:
**Live events disappear after failed registration**

If the environment-instance record write fails, deployment can still succeed with a working gateway. This lookup skips that environment because it has no `instance_id`. The Triggers tab then shows none of its live events and does not mark it unavailable. Account for working gateways without a saved instance record, without trusting a gateway URL supplied by a run request.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Cursor Fix in Claude Code Fix in Codex

@polakamtejas
polakamtejas force-pushed the tejas/explorer-encoded-entity-ids branch from b11d910 to 2c9d29a Compare September 30, 2026 21:29
@polakamtejas
polakamtejas requested a review from a team as a code owner September 30, 2026 21:29
@polakamtejas
polakamtejas force-pushed the tejas/explorer-triggers-tab branch from 5b734b2 to 35ad55f Compare September 30, 2026 21:29
Base automatically changed from tejas/explorer-encoded-entity-ids to main October 1, 2026 05:38
…te it reads

The run viewer had the Triggers components (TriggersPanel, the event
timeline and its polling hook) but never mounted the tab, and the route the
timeline polls, GET /tasks/{task_id}/instances/{instance_id}/triggers,
did not exist in the explorer.

The tab now shows for any run that recorded trigger metadata, in the same
place as the hosted hub's: registrations, the final env-trigger state, the
event timeline on the gateway's virtual clock, and the per-turn record.

The route returns the run's trigger metadata plus each env's verbatim
/triggers/state: from the live gateway while the run is running, else from
the envelope prompt_agent saved through the object store. It keeps the
payload the UI parses. Instance and env-instance records are read through
the configured document store, the envelope through the configured object
store (a url that store does not own is refused), and gateway urls come from
the env-instance records rather than the run context's copy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@polakamtejas
polakamtejas force-pushed the tejas/explorer-triggers-tab branch from 35ad55f to 1148716 Compare October 1, 2026 06:08
@polakamtejas
polakamtejas merged commit 1c5c54b into main Oct 1, 2026
13 of 14 checks passed
@polakamtejas
polakamtejas deleted the tejas/explorer-triggers-tab branch October 1, 2026 06:24
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