yardsort

Triggers

A trigger starts a workflow or a smoke test when something happens in a workspace, instead of when you press Run or a schedule's time comes: an agent says its work is done, an agent finishes a turn or exits, a smoke test or a workflow run ends, or something happens on the workspace's pull request — it opens, a review or a comment comes in, its checks fail. What it starts is an ordinary run, in that workspace, with the inputs you gave the trigger.

For example: when someone reviews the pull request of fix-login, tell its agent to address the review. Or: when the agent says it is done, have a second agent review the branch.

This release has triggers from the command line. ys trigger adds, lists and changes them, and Yardsort listens and starts their runs while it is open. The runs show in the workflow's runs and in Tests like any other. The window does not list triggers yet. See What it does not do yet.

Three things to know before you make one:

  • Triggers fire while Yardsort is open. What happened while it was closed — an agent that ran ys workspace done, a run that ended, a turn its agent reported, a review posted — is found when it next opens, and acted on once.
  • Only what happens after you make it counts. A trigger never fires for something that happened before it was saved, or while it was switched off.
  • A trigger approves nothing. A smoke test file nobody has approved waits for you, as it does when an agent runs it. See Smoke tests and approval.

Ready-made triggers

Five triggers come ready made, each an event and a built-in workflow, with at most one question:

PresetWhenRunsAsks for
address-reviewsSomeone other than you reviews or comments on the pull request, 2 min quietaddress-review
fix-checksThe checks on the pull request's newest commit failfix-checks
review-when-openedThe pull request opens, or a draft is marked readycode-reviewreviewer: who reviews
review-when-doneThe agent says its work is donereview-branchreviewer: who reviews
smoke-when-doneThe agent says its work is donesmoke-then-tellsuite: which smoke test
ys trigger presets
ys trigger add --preset address-reviews --workspace fix-login
ys trigger add --preset review-when-done --input reviewer=codex --workspace fix-login

A trigger made from one is an ordinary trigger: --quiet, --cap, --name and --notify change it as they would any other.

Making a trigger

ys trigger add --on pr.review --on pr.comment --workflow address-review --workspace fix-login
ys trigger add --on workspace.done --workflow smoke-then-tell --input suite=login --workspace fix-login
ys trigger add --on agent.exited --failed-exit --workflow tell-me --workspace fix-login
ys trigger add --on test.finished --of-test login --verdict failed --workflow fix-it

--on says what to listen for; give it more than once for more than one event. --workflow or --test says what to run, and --input id=value gives each of its inputs. --workspace takes a workspace's name or id, and can be left out inside a workspace's folder or in an agent's terminal there. Everything a run would be refused for is checked before anything is written: the workspace is there and not archived, the workflow or the test is there and ready, its inputs fit, and the filters are ones the events have.

OptionWhat it is
--presetA ready-made trigger, in place of --on and what to run
--onAn event, below. More than once for more than one
--workflowRun this workflow, or
--testrun this smoke test, from the workspace's .yardsort/tests
--workspaceWhere. Default: the workspace this is run in
--sessionOnly this agent session's events, by its id or the start of it. See One agent's trigger
--inputid=value, for each of the target's inputs
--nameWhat lists call it. Default: the workflow's or the test's own name
--quietWait this long after the last event before starting the run: 30s, 2m. Default: 2m for reviews and comments, else none
--capRuns it may start for one agent session, one pull request, or the workspace, before it stops. Default: 3
--notifyfailure (the default), always or never: when to be told how a run ended

What it listens for

EventHappens whenFilters
workspace.doneAn agent ran ys workspace done
agent.settledAn agent finished a turn: it went quiet after real work, or its harness said so--new-commits, --clean
agent.exitedAn agent's program ended--failed-exit
test.finishedA smoke test run in the workspace reached a verdict, or could not--of-test <id>, --verdict passed or failed
workflow.finishedA workflow run in the workspace ended--of-workflow <id>, --outcome succeeded or failed
pr.openedThe workspace's branch has an open pull request it did not have before--drafts
pr.reviewA review was submitted on the workspace's pull request--from, --state, --no-bots
pr.commentA comment on its conversation, or a reply on a review thread--from, --no-bots
pr.checks_failedIts checks finished on its newest commit, and one failed
pr.checks_passedIts checks finished on its newest commit, and none failed
pr.conflictedGitHub says it conflicts with its base
pr.mergedIt was merged
pr.closedIt was closed without being merged
  • An agent's events are about the agents you started in the workspace. A shell is not an agent, and neither is an agent a workflow run started: the reviewer of a code review finishing its turn sets nothing off. An agent's events, the pull request's and the workspace's cannot share one trigger, since each counts its runs for something different; make two.
  • agent.settled fires at every turn, including a turn where the agent stops to ask you something. That is what "tell me each time it stops" wants, and not what "it is finished" means. --new-commits waits for the branch to have commits it did not have the last time this trigger started a run for that agent, or, the first time, commits its base branch does not have. --clean waits for nothing to be uncommitted. Both are looked at when the run would start, after the quiet time. For "the work is done", workspace.done is the surer one.
  • --failed-exit is a code that is not 0, a program that was stopped, or one that ended without Yardsort hearing how.
  • --verdict failed includes a run that could not finish; --outcome failed includes a cancelled run.
  • The pull request's events are about the open pull request for the branch the workspace has checked out, on GitHub, as gh sees it. See Watching a pull request.

Watching a pull request

While an enabled trigger in a workspace listens for a pr.* event, Yardsort looks at that workspace's pull request once a minute, with gh, and starts what the trigger starts when something changed since the last look. Nothing is asked of GitHub for a workspace no trigger watches.

  • What is there when you add the trigger is not news. Adding it reads the pull request as it is: a pull request already open is not pr.opened, checks already red are not pr.checks_failed, and a review already posted is not pr.review. ys trigger add says which pull request it is watching, or that there is none yet.
  • Reviews and comments count when they are someone else's. You and every agent post to GitHub as your gh account, and the agent answering a review answers each comment as you. So by default only posts from other accounts count — a teammate, a review bot. --from anyone counts yours too, except what is posted while a run this trigger started is going, which is the agent's own answers. --no-bots leaves out accounts whose name ends in [bot]. --state (approved, changes_requested, commented) narrows pr.review to reviews that concluded that.
  • A reply on a review thread is a comment. GitHub also records each reply as a review of its own; Yardsort counts it once, as pr.comment. A review is the review's verdict and words with the comments on lines made in it.
  • Reviews and comments come in bursts, so a trigger on them waits two minutes after the last one by default and starts one run for all of them.
  • Checks are told once they have all finished: pr.checks_failed when nothing is running and something failed, pr.checks_passed when nothing is running and nothing failed. Every check on the commit is counted, however many there are, and one GitHub has marked stale counts as finished. Each is told once per commit, so a push that fails again is news again.
  • A draft is pr.opened when it is marked ready for review. --drafts counts it as soon as it opens instead.
  • pr.merged and pr.closed are told once. A different pull request opened on the branch afterwards is pr.opened, and counts its own runs; the same one reopened is not new, and what was already on it is not news again. A workflow that needs the open pull request — {{ pr.* }}, wait_pr_activity — cannot run on these, and saving says to use {{ event.url }}, {{ event.number }} and {{ event.title }} instead.
  • GitHub not answering — no network, gh logged out — is no news; the trigger looks again a minute later.

The done signal

ys workspace done
ys workspace done --note "Login validates on blur; tests added."

ys workspace done, run in an agent's terminal, says the work in that workspace is committed and ready to be looked at. A trigger on workspace.done starts what it starts. The note, if there is one, is there for the workflow as {{ event.note }}, and the commit the worktree was on as {{ event.head }}. Outside an agent's terminal, run it in the workspace's folder or give --workspace. It prints whether a trigger there listens for it.

An agent is told about it in its first message — a short section after the project's memory and the smoke tests — when a trigger in its workspace listens for workspace.done at the moment it starts. It is asked to run it once its work is committed, and not to pause or to ask a question. An agent that was already running when you added the trigger was not told; ys trigger add says so. Agents started from then on are.

Yardsort reads nothing an agent prints to know it is done: the agent runs a command, as it runs git or gh.

When a run starts

A burst is one run. With --quiet 2m, the trigger waits until nothing new has happened for two minutes, then starts one run for everything that came — but never later than ten minutes after the first. For agent.settled, the agent going back to work before the quiet time is up means that turn did not end, and nothing starts. Without --quiet, a run starts within a second.

A run in the way makes it wait. Only one run of a workflow, or of a test, goes at a time in a workspace, whoever started it. When the trigger's run would be the second, the trigger waits, and starts it when the first ends, with everything that came meanwhile. A schedule skips such a time, because another time will come; something that happened will not happen again.

It stops before it loops. Three limits, each told to you once:

  • The cap. At most --cap runs — three by default — for one agent session, for an agent's events, for one pull request, for the pull request's, or for the workspace, for the rest. The next fire is capped, you are told, and the trigger starts nothing more for it until you run ys trigger reset.
  • The rate. At most ten runs an hour for one trigger, whatever the cap.
  • Chains. A run a trigger started can end in an event another trigger listens for. A run a person or a schedule started is depth 0; a run started by the ending of a run of depth n is n + 1. A fire past depth 3 is refused.

One agent's trigger

--session <id> makes a trigger that belongs to one agent session: it hears that agent's own turns and exits, and its runs' session: origin is that agent, whichever agent in the workspace is newest. A fire while that agent's program is not running is skipped: there is no one to tell. The trigger goes when the session's record does. ys session list shows the ids.

No agent to tell

A workflow that tells the workspace's own agent something — address-review, fix-checks, the last step of review-branch — has nothing to do when no agent is running there. Such a fire is skipped, and you are told once, rather than starting a run that skips every step and calls that a success. Start an agent in the workspace and the next event reaches it.

Smoke tests and approval

A smoke test file comes from a repository and says what to run on your computer, so it never runs until you have seen it. A trigger keeps to that: a run of a new or changed file waits on the card at the top of its workspace, the trigger reads waiting for approval, and you are told once (see Being told). Approve it and that run goes. Meanwhile the trigger's next events wait behind it.

Nothing else a person does is waited for:

  • A test with a manual step cannot be triggered. Nobody is there to press Pass. This is checked when the trigger is saved and again each time it fires, for a test it runs itself and for one a workflow's run_test step names.
  • A workflow's run_test steps each need a timeout. A test named by an input is not known until the run, and an approval may not come; the timeout ends the wait.

What the workflow is told

A workflow a trigger starts can read what set it off as {{ event.* }}:

VariableWhat it is
event.kindThe event: workspace.done, agent.settled, …
event.countHow many events the run is for, when a burst was gathered into one
event.noteworkspace.done: what the agent passed to --note
event.headworkspace.done, agent.settled: the commit the worktree was on. pr.checks_failed, pr.checks_passed, pr.conflicted: the commit it was about
event.summaryagent.exited, test.finished, workflow.finished: how it ended, in a line. pr.review, pr.comment: 2 reviews and 3 comments from alice and coderabbitai[bot]. pr.checks_failed: 2 checks failed: lint, test (ubuntu)
event.verdicttest.finished: passed, failed, errored, …
event.urlThe pr.* events: the pull request, or for a review or comment, the newest one
event.number, event.titleThe pr.* events: the pull request's number and title
event.authorspr.review, pr.comment: who posted, comma-separated

A trigger whose workflow uses a variable one of its events does not leave is refused when it is saved, naming it. Run by hand, every event.* is empty, and Run… and ys workflow run say so.

Being told

A triggered run has nobody watching it, so the trigger can tell you how it ended, with a desktop notification: when a run fails (the default), how every run ends, or never. Unless it is set to never, a run waiting for your approval is told once, and so is a trigger that is capped, could not start its run, or had no agent to tell, once while it lasts.

Its history

ys trigger fires <trigger> lists every fire, newest first:

OutcomeMeaning
running, succeeded, passed, failedA run was started; this is how it stands, or how it ended
waiting for approvalIts run waits for you to approve the test file
waitingA run of the same thing is going there; this one starts when it ends
skippedIts agent session, or any agent in the workspace, was not running to be told
cappedIt had started as many runs as --cap allows. Later events go on this line
refusedA run could not be started, with the reason

From the command line

ys trigger presets              # the ready-made triggers
ys trigger list                 # every trigger: what it listens for, what it runs, where, how it stands
ys trigger show Tell            # its events, filters, inputs, limits and last fire
ys trigger pause Tell           # off: keeps its history, fires nothing
ys trigger resume Tell          # on again; only what happens from now counts
ys trigger reset Tell           # lift its cap
ys trigger fires Tell           # its history
ys trigger remove Tell          # the trigger and its history; the runs it started stay

A trigger is named by its name, its id, or the start of its id. --json works as everywhere. See ys trigger.

What it does not do yet

  • Forges other than GitHub. The pull request events need gh.
  • The window. Triggers are listed, made and changed with ys for now; the composer's ready-made triggers and a view of them come later.
  • Run while Yardsort is closed. What happened is found when it next opens.