Task runs now track trigger source and action

Each task run now records where it was triggered from — SDK, API, dashboard, CLI, MCP, or schedule — plus the action type (trigger, replay, or test). This enables filtering runs by origin without traversing the run tree and makes it possible to route scheduled runs differently based on their source.
Task runs now know where they came from. When a run is triggered, the system captures its trigger source (sdk, api, dashboard, cli, mcp, or schedule), the action type (trigger, replay, or test), and propagates the root trigger source and schedule ID through the entire run tree.
Previously, determining whether a run was part of a schedule-originated tree required querying up the run tree — an expensive operation for deep trees. Now that information is stored directly on each run.
This matters because it enables two things: filtering runs by trigger origin for analysis, and making scheduling decisions based on the trigger source. For example, scheduled runs could use different affinities than ad-hoc SDK triggers.
The implementation adds an annotations JSONB column to the TaskRun table, with four fields: triggerSource, triggerAction, rootTriggerSource, and rootScheduleId. The trigger source flows through the system via an HTTP header (x-trigger-source) that's sanitized and propagated from API endpoints through to the run engine.
The change touches the webapp's trigger and replay services, the run engine, database schema, and API clients across multiple packages.
View Original GitHub Description
Adds an annotations JSONB column to task runs that captures where and how each run was triggered.
This enables filtering and analyzing trigger origins without querying up the run tree. Also enables making scheduling decisions based on the trigger source, e.g., use separate affinities for scheduled runs.
Each run records:
- triggerSource: who initiated it (sdk, api, dashboard, cli, mcp, schedule)
- triggerAction: what kind of action (trigger, replay, test)
- rootTriggerSource: the trigger source of the root ancestor, propagated through the entire run tree
- rootScheduleId: schedule id, in case the run tree was triggered from a schedule
Currently the main motivation for annotations it to determine whether a run is part of a schedule-originated tree without traversing ancestors.
A couple of design considerations
- Decoupled source from method: triggerSource and triggerAction are separate fields to avoid combinatorial explosion (every new source × every new action)
- Server-side first: all annotation values are primarily determined on the server, only a minor SDK change needed
- Forward-compatible: annotation fields use
z.enum([...]).or(anyString)so new values can be added without breaking validation; we currently don't need an explicit version field for annotations.
Note: metadata would have been a more fitting name for the db column, as it is consistent with other tables where we store this type of information. It is already in use to store user metadata though, so we go with annotations instead.