# 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.

Published 2026-09-28 · by The Tade project · tagged watches, safety, sentry, dependencies.
Published at https://tade.sh/blog/a-watch-may-only-ever-add-work/ — part of https://tade.sh/blog/.

---

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.

> **A screen from Tade — `a-watch`.** A watch: every hour it looks, starts an agent on each new issue, and keeps what it found and every look.
>
> A watch: how often it looks, what being on costs, at most two agents a look,
> what it found and what became of each one — and every look, including the ones
> that could not look.

## 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:

```ts
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](/blog/forty-eight-findings-nine-of-them-real/) as one of nine.

```sh
npm i -g tade-sh
```

The watches are `packages/extensions/*/src/` in
[the repository](https://github.com/mujacica/tade); what each one costs to say
yes to is `packages/core/src/watches.ts`.
