Read-only goal-drift auditor. Holds each project's immutable goal as a typed record derived only from real human prompts, inspects what the project actually produced (tickets first, then artifacts, commits, and absences), and labels every action SERVES_GOAL, SUPPORTS_INDIRECTLY, DECLARED_DRIFT, UNTICKETED_WORK, SCOPE_DRIFT or MISSING_EXPECTED with a reason. Runs nightly on cron and reports; it never edits, commits, fixes, or reprioritises. Use when asked whether work is on track, whether an a...
Scanned 9/11/2026
Install to Claude Code
npx -y skills add grahama1970/agent-skills --skill goal-drift --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Goal Drift?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/grahama1970-goal-drift)More formats (shields.io, HTML) on the badges page.
---
name: goal-drift
description: >
Read-only goal-drift auditor. Holds each project's immutable goal as a typed record
derived only from real human prompts, inspects what the project actually produced
(tickets first, then artifacts, commits, and absences), and labels every action
SERVES_GOAL, SUPPORTS_INDIRECTLY, DECLARED_DRIFT, UNTICKETED_WORK, SCOPE_DRIFT or
MISSING_EXPECTED with a reason. Runs nightly on cron and reports; it
never edits, commits, fixes, or reprioritises. Use when asked whether work is on track,
whether an agent drifted, to register or read a project's immutable goal, or to run the
goal-drift check.
allowed-tools:
- Bash
- Read
triggers:
- goal drift
- did we drift
- am I on track
- scope drift
- check goal drift
- drift verdict
- is this work serving the goal
- register immutable goal
- what is the immutable goal
- why did we drift
metadata:
short-description: Read-only goal-vs-actions drift audit on a nightly cron
author: Graham
version: "0.1.0"
inspired-by: "nicobailon/pi-subagents watchdog scope-drift (README verified 2026-08-02)"
runtime_self_improvement: basic
provides:
- goal-registry
- goal-drift-detection
- scope-drift-report
composes:
- ticket
- tau
- memory
- goal-helper
- scheduler
- task-monitor
- agentic-evals
complies:
- best-practices-skills
- best-practices-python
- best-practices-arangodb
taxonomy:
- operations
- validation
- precision
- resilience
- human-in-the-loop
disciplines:
- evaluation-quality
- observability-operations
---
# goal-drift
**Read-only.** Observes and reports. It cannot edit files, commit, or reprioritise. A
drift detector that can also act becomes one more lane to drift into.
## Related skills, easily confused
| Skill | Axis |
|---|---|
| `/goal-helper` | **authors** a goal: proof command, completion criteria, retry limits |
| `/project-drift` | **knowledge** drift: does transcript evidence stale-date `PROJECT_KNOWLEDGE.md`? |
| **`goal-drift`** | **goal** drift: does the work still serve the registered immutable goal? |
`/project-drift` asks *"is the documentation still true?"* This asks *"are we still doing the
right thing?"* Both are real; they fail independently. Documentation can be perfectly
accurate while the work wanders.
## Why it exists
An immutable goal written into a document does not prevent drift. Observed 2026-08-02: a
goal was recorded verbatim in a skill header, and the work still went to a compiler fix in
another repo, four unrelated documentation edits, and single-employer archaeology reported
as chat prose. Every step was defensible alone. None was the artifact the goal named. The
`PROJECT_KNOWLEDGE` claims stayed accurate throughout, so a knowledge-drift auditor would
have reported clean. Nobody noticed until the human asked.
The mechanism is ported from `nicobailon/pi-subagents`, whose watchdog keeps a
current-scope artifact from real user prompts and flags work that no longer serves it as
`scope-drift`. That project is a Pi extension (TypeScript), so the pattern is reimplemented
here rather than installed.
## The rule that makes it work
**Scope comes only from real human prompts.**
The agent's own intermediate goals, self-generated plans, and auto-follow steps are never
admitted as scope. Without this rule an agent invents a sub-goal, pursues it, and grades
itself compliant. pi-subagents makes the same exclusion: its watchdog auto-follow prompts
are explicitly not recorded as scope.
`register` stores `source: human_prompt` with the verbatim text. Anything marked
`agent_inferred` is **refused at registration**, not warned about.
## Commands
```bash
./run.sh register --project <name> --goal-file goal.txt # human text only
./run.sh goal --project <name> # read the goal back
./run.sh check --project <name> --since 24h [--json] # the audit
./run.sh schedule # nightly cron
./sanity.sh # behavioral gates
```
Nightly:
```bash
/scheduler add goal-drift --cron "0 6 * * *" --budget 5
```
06:00 deliberately — **after** nightly producers finish, so it grades a completed night
rather than one in progress.
## Evidence hierarchy — tickets first
| Rank | Evidence | Source | Why |
|---|---|---|---|
| 1 | **Tickets** | `/ticket` + `gh issue list` | declares intent **before** work starts |
| 2 | Artifacts | criterion `artifact_globs` | did the thing actually get produced? |
| 3 | Commits | `git log --since` | secondary; a commit citing **no** ticket is itself a signal |
| — | **Absence** | expected artifact never produced | the case a what-happened-only checker cannot see |
Tickets rank first because a ticket declares intent at filing time, so drift is catchable
the day it is declared rather than weeks later in commit forensics. A lease shows what is
being worked on *now*; attached proof is real acceptance evidence rather than a filename
guess. Verified: a ticket titled "Fix DAG reachability in tau compiler" against a
report-and-resume goal yields `DECLARED_DRIFT` with zero commits present.
**Absence remains the finding that matters most.** A night with twelve commits and zero
instances of the artifact the goal names *is* the drift case.
## Strict seam contracts
Every boundary-crossing artifact is validated at the producer with a typed
`@dataclass` + `validate()` (copy-safe, no pydantic). Exactly three outcomes: **pass,
self-heal-with-record, or raise** — never warn-and-continue. Validated artifacts carry a
`seam_validation` stamp so a reader can tell "validated" from "never checked".
Cross-field truth checks, because field presence alone does not catch a lying summary:
- `ON_GOAL` while findings contain drift markers → **refused**
- `NOT_ESTABLISHED` without a `GOAL_UNREGISTERED` finding → refused
- `indirect_share > indirect_cap` while claiming `ON_GOAL` → refused
- `read_only: false` → refused
The skill therefore cannot emit a verdict its own evidence contradicts.
## tau integration
The audit is handed to `/tau` as a skill node using **`tau.generic_dag_spec.v1`** — *not*
`tau.dag_contract.v1`, which tau skill nodes reject — with `tau.skill_work_order.v1` and
`tau.skill_dag_node.v1`. The node declares `read_only: true` and the work order's task text
carries "Never edit, commit, or reprioritise", so the constraint travels with the handoff.
**Goal identity is `goal_hash`**, canonical over the goal object the way tau hashes goals:
- hash covers goal **content**; `registered_at` is excluded, so re-registering identical
text and criteria reproduces it
- change one word of the goal and the hash changes
- a revision carries the prior hash as **`goal.parent_goal_hash`** — traceable lineage, not
a silent overwrite
"Immutable" stops being a promise in prose and becomes something a receipt can prove.
## Verdicts
| Verdict | Meaning |
|---|---|
| `SERVES_GOAL` | maps to an acceptance criterion |
| `SUPPORTS_INDIRECTLY` | plausible enabler — **capped**, see below |
| `SCOPE_DRIFT` | real work, no mapping to any criterion |
| `DECLARED_DRIFT` | a **ticket** declares work matching no criterion — caught at filing time |
| `UNTICKETED_WORK` | a commit cites no ticket and matches no criterion |
| `MISSING_EXPECTED` | a criterion produced nothing |
| `GOAL_UNREGISTERED` | no goal → report is `NOT_ESTABLISHED`, never "clean" |
Run verdict: `ON_GOAL`, `DRIFTED`, `NOT_ESTABLISHED`, or **`DEGRADED`** (an evidence source
failed; partial evidence can never read as on-goal).
`SERVES_GOAL` from a ticket requires **closed AND attached proof**. An on-criterion ticket
still open, or closed without proof, is `SUPPORTS_INDIRECTLY` and does **not** satisfy
absence — "in progress" is not done.
**`SUPPORTS_INDIRECTLY` is capped at 30% of actions.** Past the cap the run is `DRIFTED`,
because *"it was all necessary groundwork"* is precisely the story drift tells about
itself. Infrastructure that enables the goal is legitimate; infrastructure that replaces
producing the goal's artifact is not.
## Report contract
Leads with the verdict and the absences, never with the volume of work:
```
project: monitor-opportunities window: 24h verdict: DRIFTED
MISSING_EXPECTED daily interactive report (0 produced, >=1 required)
MISSING_EXPECTED tailored resume variant (0 produced)
SCOPE_DRIFT tau compiler fix (no criterion references tau)
SERVES_GOAL ATS tenant discovery -> criterion: discover opportunities
indirect 42% exceeds cap 30% -> DRIFTED
```
An honest `DRIFTED` verdict is a **success** for this skill. It must never soften a verdict
because the work looked productive.
## Non-negotiables
- **No mutation.** `sanity.sh` asserts the runtime cannot write to a project tree and that
no subcommand edits, commits, or reprioritises.
- **No self-certification.** `agent_inferred` goals are refused.
- **Absence is a finding.** Zero expected artifacts is `DRIFTED`, not silence.
- **No goal means `NOT_ESTABLISHED`** — never "on track by default".
- **Writes go through `/memory`**, never direct ArangoDB.
## References
- `references/verdict-contract.md` — verdict definitions and the indirect cap
- `fixtures/agentic_eval.json` — positive, negative, adversarial cases
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!