blog /
A watch may only ever add work
Nine watch schedules looked 1,447 times in four days here. Four findings started an agent, 425 looks could not look at all, and none of them may push or merge.
The Tade project·6 min read·watchessafetysentrydependencies
A loop that starts agents is the easiest dangerous thing to build. Poll something, find something, run something — and by the morning it has merged a branch, reverted somebody’s commit, or fixed the same failure four times in four lanes.
A watch in Tade is a schedule that looks before it acts, and the rule above it is one sentence: it may only ever add work. It never merges, never force-pushes, never reverts, never resolves a conversation, and past a fixed number of attempts on one thing it stops fixing and says what is wrong instead.
Here is what that produced on this machine between 24 and 28 September 2026,
out of ~/.tade/events.jsonl.
The arithmetic of four days
| looks | 1,447 |
| of those, could not look at all | 425 |
| found something | 115 |
| findings written | 65 |
| findings that started an agent | 4 |
Four agents out of fourteen hundred looks — and three of the four came from one watch on one afternoon, which a person then removed. That ratio is the point of the design rather than a sign the watches are idle: a watch that acted on every look which found something would start an agent every ten minutes for as long as the finding stayed true.
A finding’s key is what stops it. For CI the key is the commit and nothing else — not the branch, which moves, and not the check, because one push with three failing jobs would otherwise be three agents editing one repository at once. A workflow re-run is the same key and starts nothing new. A fix pushed on top is a new commit, which is new information, and is looked at again.
Ticked and not ticked
Setting up offers the watches once, on a machine where nothing has been decided, and says what each one costs before the question is answered. Two sentences do all the work:
const STARTS_AGENTS = 'starts an agent on each thing it finds'
const ONLY_TELLS = 'tells you what it finds, and starts nothing'
A watch that only tells somebody is ticked when the question appears. One that starts agents is offered and never ticked, however useful it is. The difference is not how much either is worth. It is that somebody who presses enter without reading has agreed to being told something, and has not agreed to finding four agents in four lanes in the morning.
Three watches stand themselves up rather than waiting to be offered: Jev’s two, which do nothing at all without a key, and CI on the branch you are on, wherever there is a forge. A red base branch is everybody’s, it opens no review for anything else to watch, and a look that finds nothing costs one request.
The three that matter, and what each refuses
CI on the branch you are on — every ten minutes. It asks git before it
asks a forge, and the answer is ten cases rather than a boolean and a throw:
red, passed, running, nothing ran, not pushed, no forge, no credential, unreachable, unknown commit, cannot tell. Only red
starts an agent. Four of the others are a look that went wrong and need a
person. The rest are facts about a repository nothing is wrong with. The agent
it starts is told never to force-push, never to revert somebody else’s commit
and never to merge — and that if the fix is not its to make, to say so and stop.
New Sentry errors — every hour, and only because somebody turned it on. It asks for issues first seen since its last look, so nothing that was already there when it was turned on is new. Turned on at 08:23 on 26 September here, by hand.
Dependency updates — once a day, and this one is worth reading for its
refusals. It bumps patch and minor, never major: those two promise not to
break you and the checks say within the hour when they did, while a major
promises the opposite and is somebody’s decision. So majors are named and
never bumped by a clock, and the setting offers patch and minor and no
third option, which is where the refusal is written down rather than described.
The patches are one commit and each minor is its own, capped at twenty.
Nothing red is committed — the value is not the bump, it is the evidence
that the project still works on it, so where the agent cannot make the
project’s own checks green it puts every manifest and lockfile back, commits
nothing, and says what broke. And it refuses to run at all in a checkout other
agents share: twenty agents in one checkout is twenty installs racing one
lockfile.
Its sibling, vulnerable dependencies, asks OSV once a day and starts an agent per vulnerable package.
A look that could not look says so
Four hundred and twenty-five of the 1,447 looks failed, and each kind is a different sentence rather than a silence:
| asked about a commit nobody had pushed | 185 |
| could not reach the host it had to ask | 142 |
the judge refused the ask: max_tokens_exceeded |
76 |
| gave up after its minute | 17 |
| could not reach the judge | 5 |
| all of them | 425 |
None of those is counted as found nothing. found: 0 would mean both
“everything is fine” and “there was nothing to look at”, and those are opposite
facts — so a look carries a sentence when there is one worth saying, said once
when it starts being true rather than every ten minutes for as long as it stays
true.
The 76 refusals are the honest kind of evidence. That schedule read the whole
of main as one change, which on a busy day is several agents’ commits joined
together — too large to ask about, and unanswerable even when it fitted. It was
replaced on 26 September by one reading per task, and a person removed the old
one. The replacement has looked 512 times with no failures at all.
The one that is still wrong, and why
The 185 looks that asked GitHub about a commit nobody had pushed are a bug that was already fixed.
963dcb8, at 11:05 UTC on 26 September, put git first: a commit no ref of
origin reaches is not pushed, which is CI that has not run yet, and is not
a problem anybody has to do anything about. One rev-list walk, no network, no
fetch.
The window running these watches opened at 07:20 that morning and has not been restarted since. So it is still running the code from before the fix, and the newest of those 185 arrived while this was being written.
That is not a footnote, it is the design. There is no daemon. Schedules run
only while a window is open, the window is the program, and git pull is the
upgrade — which also means a window left open for two days is two days old.
Nothing hot-reloads behind anybody’s back.
What a watch is not allowed to decide
The scheduler asks about the host before it concludes anything, so a forge
having a bad afternoon never reads as this machine being offline. A watch with
network: true is held on a machine that cannot reach anything, rather than
finding out with a request that times out every ten minutes all night.
And the direction of that guard is a decision somebody had to make. When the network probe itself throws, the watch looks anyway — because pausing every watch on a bug inside the probe, silently, is the one failure worse than the noise the guard was written to stop. The commit says so in as many words: a machine that will not answer is not a machine that is offline.
That bug, incidentally, was found by the judge and not by a person. It is in the calibration table as one of nine.
npm i -g tade-sh
The watches are packages/extensions/*/src/ in
the repository; what each one costs to say
yes to is packages/core/src/watches.ts.