Skip to content

feat(goals): judge a goal on whether the number moved, not on whether work happened (v0.438.0) - #813

Merged
vikasprogrammer merged 1 commit into
mainfrom
feat/goal-metrics
Sep 10, 2026
Merged

feat(goals): judge a goal on whether the number moved, not on whether work happened (v0.438.0)#813
vikasprogrammer merged 1 commit into
mainfrom
feat/goal-metrics

Conversation

@vikasprogrammer

Copy link
Copy Markdown
Owner

Why

The Goals plane measures activity. stuckGoals asks whether a goal has had events lately — which is the wrong question, because a goal can be busy and failing, or quiet and succeeding.

The live fleet shows what that produces:

instawp:  3,722 tasks   14 goals   32 goal events   208 tasks linked to a goal (5.6%)

Goals get created, receive about two events, and go quiet. It's an outcome layer nobody uses, because nothing in it reads a real number.

What

A metric on the goal. Optional metric (name, unit, target, baseline, direction, expected interval) plus an append-only goal_readings table. GoalStore.metricStatus turns readings into a verdict: measuring · flat · regressing · achieved · unmeasured · new.

A deterministic review (src/edge/goal-review.ts), hourly off the scheduler tick. One standing card per goal, to its owner, raised only when the verdict changes.

The strategist is told the verdict, in words (metricBrief, in both the plan run and the goal room). A flat goal is instructed to say what isn't working and plan a different approach; an unmeasured goal is instructed to establish the measurement first.

goal_measure — an agent records a reading, so a run whose job is measuring closes the loop.

Console — the goal drawer shows the metric, verdict, movement, a sparkline, and one line to record the next reading.

The three judgement calls

It refuses to be confident on thin evidence. flat/regressing need ≥3 readings spanning the metric's own interval, and movement below 5% of the distance to target. One wobble, or three readings taken the same afternoon, reads as new. A review that cries wolf gets ignored, and then the metric was worse than useless.

It spawns nothing. The judgement is arithmetic. A review that cost a session every tick would bill a tenant for the privilege of being told nothing changed. The response — re-plan, retire, adjust the target — is the human's, through the button that already exists.

Agents report the number; they don't move the goalposts. goal_measure is the only goal write agents get besides proposing. Metric, target and status stay human-owned, and every reading records who took it, so a number reported by the same agent that did the work is visibly self-reported.

Also: direction is first-class because incidents, p95 and churn goals go down, and a reviewer that assumes up-is-better reads every one of those backwards. And unmeasured is deliberately separate from flat — "nobody is measuring this" and "this isn't working" call for opposite responses.

Test

scripts/goal-metric-review-test.cjs — 32 assertions, wired into test:governance. Most of them are about the review not lying: one reading isn't flat, three same-day readings aren't flat, a falling down-metric is progress, a stale goal is unmeasured rather than failing, and three consecutive reviews leave one card.

32 passed, 0 failed

Full npm run test:governance green (exit 0); typecheck and both bundles build. A goal with no metric behaves exactly as it did before — the review skips it and the strategist prompt says nothing.

Not in scope

Reading the number automatically from a source of truth (Ahrefs, GA4, an incident tracker). Readings arrive from a human or from an agent that measured — wiring a specific data source is a connector job, per goal, and belongs after we've seen which metrics people actually set.

🤖 Generated with Claude Code

https://claude.ai/code/session_014hX85iZ5BhHypPNrob8vnH

… work happened (v0.438.0)

The Goals plane measured activity. `stuck` asks whether a goal has had events lately, which is the
wrong question: a goal can be busy and failing, or quiet and succeeding. On the live fleet 5.6% of
tasks link to a goal and goals stall at ~2 events each — an outcome layer nobody uses, because
nothing in it reads a real number.

Goals gain an optional METRIC (name, unit, target, baseline, direction, expected interval) and an
append-only `goal_readings` table. `GoalStore.metricStatus` turns readings into a verdict:
measuring / flat / regressing / achieved / unmeasured / new.

The verdict refuses confidence on thin evidence. flat/regressing need >=3 readings spanning the
metric's own interval, and movement below 5% of the distance to target — so one wobble, or three
readings taken the same afternoon, reads as `new`, not failure. `direction` is first-class because
incidents / p95 / churn goals go DOWN and a reviewer assuming up-is-better reads them backwards.
`unmeasured` is distinct from `flat` on purpose: "nobody is measuring this" and "this isn't
working" call for opposite responses.

The review (src/edge/goal-review.ts) runs hourly off the scheduler tick and SPAWNS NOTHING — the
judgement is arithmetic, and a review costing a session per tick would bill a tenant for being told
nothing changed. One standing card per goal, to its owner, only when the verdict CHANGES (guard =
the `goal.reviewed` audit event, so a restart never re-alarms). The response stays human: the
existing Plan button, and `metricBrief` now puts the verdict into the strategist's prompt in words,
so a flat goal is told to try a DIFFERENT approach instead of filing more of the same.

`goal_measure` lets an agent record a reading — the one goal write agents get besides proposing.
They report the number; the metric, its target and the goal's status stay human-owned, and every
reading records who took it so a self-reported number is visibly self-reported.

Pinned by scripts/goal-metric-review-test.cjs (32 assertions, wired into test:governance).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014hX85iZ5BhHypPNrob8vnH
@vikasprogrammer
vikasprogrammer merged commit 98287e3 into main Sep 10, 2026
1 check passed
@vikasprogrammer
vikasprogrammer deleted the feat/goal-metrics branch September 10, 2026 12:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant