Skip to content

register_env_triggers: optional await of start-of-run triggers (FD-3998) - #227

Merged
earakely-scale merged 6 commits into
mainfrom
edgararakelyan/fd-3998-register_env_triggers-optional-await-of-start-of-run-time
Oct 16, 2026
Merged

earakely-scale merged 6 commits into
mainfrom
edgararakelyan/fd-3998-register_env_triggers-optional-await-of-start-of-run-time

Conversation

@earakely-scale

@earakely-scale earakely-scale commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

What

register_env_triggers gains two optional fields: await_fired (trigger ids) and await_timeout_seconds (default 120). After the register call the step polls the triggers extension's state method until every named trigger has fired, stops early on failed or cancelled, and raises with the per-trigger statuses and the gateway's failed / eval_error / action_failed events for those ids when the deadline passes. The registration audit entry gains awaited: {id: status}, so judges and the explorer can see it. The await has one deadline: each state read is cut to the time left and never starts inside the last second, a read that fails is retried on the next poll, and if the deadline passes the last read failure is reported with the statuses last seen. Nothing changes for steps that do not set the field; stored tasks load unchanged.

Guards: preflight names an awaited id the step does not register and execute raises preflight's own problems before touching the context, and a gateway whose card does not offer state is refused before any request, in the same style as the feature check.

Why

A task whose world is seeded by absolute-time triggers (an owner's invitation at 17:00:02, a supervisor DM at 17:00:04) must not start its prompt step before those records exist. Until now the only way to guarantee that was a per-task run_code warm-up script that polled /triggers/state; every Cooperation Bench task carries a copy, and an author can forget it. The gateway already fires time triggers on its own; this moves the wait into the step that registers them.

Out of scope: staged clock advancement for worlds whose events are spread over virtual time. The step does not touch the clock.

Tests

  • Unit (tst/unit/task_step/test_register_env_triggers.py, 14 new): round trip with and without the fields, construction validation (including a boolean timeout), preflight naming an unregistered id, refusal before any request for an unregistered id and for a card without state, return once each id reads fired, no state read when not awaiting, timeout message with statuses and failure events, early stop on failed, a missing id waits to the deadline, a budget shorter than one read fails without reading, slow reads cannot outlive the budget, a failed state read is retried on the next poll, a persistently failing read is reported with its cause at the deadline.
  • Integration (tst/integration/task_step/test_register_env_triggers_local.py, local gateway on Docker): two time triggers at +2 and +3 virtual minutes at sixty-to-one fire and the step returns with awaited after about three seconds; a trigger eight hours out times out with the expected message.

Chaos

Harness kept out of the tree (~/sp-scratch/chaos3998/ on Edgar's machine). Each run deploys the in-repo items env on local compose with its gateway, arms the virtual clock at sixty-to-one, and registers one-shot time triggers against the real trigger engine.

Scenarios: S1 one to three triggers 1–4 virtual minutes out, the step must return with every id fired within about the latest mark; S2 a trigger eight hours out with a 2–4 s budget, must time out naming the armed status; S3 a trigger whose action is a bad tool call, must stop early with the gateway's action_failed event; S4 clock cleared, must time out; S5 a proxy injecting 503s, 2–4 s delays and connection resets on ~45% of state reads, must still return or fail only with the read error; S6 four steps awaiting their own ids concurrently on one env; S7 an awaited id the step does not register, refused before any request; S8 a recurring trigger awaited as fired, times out by design. Soak: S1, S6 and S5 in rotation. Disruptions: SIGKILL of the process eight seconds into a 40 s await, and systemctl restart docker five seconds into a 60 s await, each followed by a clean rerun and a leftover check.

Platform Suite Soak SIGKILL mid-await Docker restart mid-await Leftovers
macOS arm64 (Docker Desktop), ebb27bf 10/10 — — gateway container killed instead: failed at the 90 s deadline with the read error, rerun OK 0
Ubuntu 24.04 amd64, m6i.xlarge, a2b9420 10/10 12/12 rerun OK failed in 16 s with the read error, rerun OK 0
Ubuntu 24.04 arm64, c7g.xlarge, a2b9420 10/10 12/12 rerun OK failed in 16 s with the read error, rerun OK 0
Ubuntu 22.04 amd64, m6i.xlarge, a2b9420 10/10 12/12 rerun OK failed in 15 s with the read error, rerun OK 0

What the disruptions showed: after SIGKILL the only things left behind are the deploy's own compose containers, which the step cannot be responsible for; after a Docker restart, or a killed gateway container, the gateway does not come back on local compose. On a2b9420 the await gave up after its three bounded reads (could not read /triggers/state … All connection attempts failed) about 15 s in; on ebb27bf a failed read is retried on each poll instead, so the step ran to its deadline and reported the last failure with the statuses last seen (could not read /triggers/state within the 90s await budget: All connection attempts failed; statuses {'s1-0': 'armed'}, 86 s after the kill), never hanging past the budget. S5 across all runs: every trial returned with the trigger fired; no trial needed the read-error path. The new integration test also passed on all three VMs (139–149 s). Three unit-tier failures on the VMs are environmental (unzip absent; the CLI-builder tests run generated CLIs with the system Python) and unrelated; the tier is green locally.

🤖 Generated with Claude Code

RetriggerView in GreptileConfidence Score: 5/5

The latest changes introduce no new blocking issues.

Summary

RegisterEnvTriggersStep can wait for named triggers before allowing later steps to start.

  • Trigger registration can wait until selected triggers fire.

No new actionable issues were found in the changes since the previous review. Tests were inspected, not run.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Check settings and gateway support] --> B[Register triggers]
    B --> C[Record registration]
    C --> D{Await requested?}
    D -- No --> E[Return]
    D -- Yes --> F{Enough time remains?}
    F -- No --> G[Raise with last statuses and failure details]
    F -- Yes --> H[Read trigger state]
    H -- Read fails --> I[Keep last state and read error]
    I --> J[Wait before next poll]
    H -- Read succeeds --> K[Update recorded statuses]
    K --> L{All fired?}
    L -- Yes --> E
    L -- No --> M{Failed or cancelled?}
    M -- Yes --> G
    M -- No --> J
    J --> F
Loading

Reviews (5) · Last reviewed commit: "register_env_triggers: one deadline for ..." · Reviewed by Greptile

The step returns when its triggers are armed, so a task whose world is seeded by
absolute-time triggers had to carry a run_code warm-up script that polled the
gateway until they fired. `await_fired` moves that guard into the step: after
registering, it polls the triggers extension's `state` method until every named
trigger reads `await_status` (default `fired`), stops early on `failed` or
`cancelled`, and raises with the per-trigger statuses and the gateway's failure
events for them when the deadline passes. The audit entry gains `awaited`.
`preflight` and `execute` refuse an awaited id the step does not register, and a
gateway whose card lacks `state` is refused before any request.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@earakely-scale
earakely-scale requested a review from a team as a code owner October 15, 2026 21:56
Comment thread src/agent_env/task_step/task_steps/register_env_triggers.py Outdated
Comment thread src/agent_env/task_step/task_steps/register_env_triggers.py Outdated
Comment thread src/agent_env/task_step/task_steps/register_env_triggers.py Outdated
earakely-scale and others added 3 commits October 15, 2026 15:05
…stration before waiting, name the wait settings

Each trigger-state read and each retry backoff is now cut to what is left of
`await_timeout_seconds`, so a stalled gateway cannot hold the step past its
deadline. The registration audit entry is appended before the wait, with
`awaited` carrying the last statuses seen, so a failed wait no longer hides a
registration that later steps look up. The poll interval, read timeout, attempt
count, backoff and the number of failure events quoted are named module
constants.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…stration entry

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A state read now starts only with a full minimum read window left before
the deadline and is cut to that remainder, so neither a slow read nor a late
reply can carry the step past `await_timeout_seconds`; a budget shorter than
one read fails at once with the statuses last seen. The failure message is
built in one place.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@earakely-scale

Copy link
Copy Markdown
Collaborator Author

@greptileai review

`await_status` is gone: the warm-up only ever waits for triggers to fire, and a
configurable target brought a validation branch, a computed terminal set and odd
cases such as awaiting `failed`. The target and the terminal statuses are
constants; the messages say "fire" instead of quoting a status.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@earakely-scale

Copy link
Copy Markdown
Collaborator Author

@greptileai review

… in execute

The state read no longer has its own three-attempt retry with backoff; a
failed read is retried on the next poll under the step's single deadline,
and the last failure is reported if the deadline passes. execute raises
preflight's own problems instead of a second copy of the unknown-id
message, and await_timeout_seconds rejects booleans.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@earakely-scale
earakely-scale merged commit e4a81ea into main Oct 16, 2026
15 checks passed
@earakely-scale
earakely-scale deleted the edgararakelyan/fd-3998-register_env_triggers-optional-await-of-start-of-run-time branch October 16, 2026 05:27
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