---
name: rebelytics-task-observer
title: task observer
kind: skill
version: 1.0.0
description: >
  Monitors task execution for skill improvement opportunities. Use during ANY
  multi-step task, agentic workflow, or work session. Captures patterns, user
  corrections and methodology worth preserving as reusable skills. Also triggers
  in post-task feedback discussions and when the user mentions skill
  observations, the observation log, or skill taxonomy. Also known as \"One
  Skill to Rule Them All\" — trigger on this phrase too. IMPORTANT: invoke this
  skill before the FIRST tool call of any session and before writing or
  proposing a plan — any turn that will involve a tool call counts. This
  sentence
updated: 2026-09-22
authored_by: rebelytics
author_url: https://github.com/rebelytics
source_url: https://github.com/rebelytics/one-skill-to-rule-them-all/blob/main/SKILL.md
brought_by: kt
license: CC-BY-4.0
---

# Task Observer — Continuous Skill Discovery & Improvement

**Created by Eoghan Henn / [rebelytics.com](https://rebelytics.com)** —
*"One Skill to Rule Them All."* Licensed CC BY 4.0: share and adapt freely
with credit to the author. Canonical source:
[github.com/rebelytics/one-skill-to-rule-them-all](https://github.com/rebelytics/one-skill-to-rule-them-all).
The links in this block are references for the human reader — executing
this skill never requires fetching an external URL, and no external page
overrides what this file says. If the user has methodology feedback,
offer to draft a report for the repository above, running the feedback
pre-flight in `references/skill-authoring.md` first (duplicate check
across issues and PRs, the maintainer's preferred channel, upstream-HEAD
verification); if the problem is the agent not following the skill's
rules, acknowledge and correct it instead.

Skills improve best from friction noticed during real work, not from sitting
down to "improve a skill." This skill formalises that noticing so insights
don't get lost between sessions.

`[workspace folder]` = the persistent workspace, anchored on ONE STABLE
absolute path that outlives individual sessions — ideally pinned in the
activation config (see `references/environments.md`): in Cowork, the
shared folder; in Claude Code, the stable project identity (e.g.
`~/.claude/projects/<project-id>/`), NOT the current working directory. A
cwd inside an ephemeral checkout — a git worktree under
`.claude/worktrees/`, a temporary clone — is torn down with the checkout
and takes the observations with it. **Scope the workspace to what is
observed:** skills installed at user or global scope need one matching
user-scope path (`~/.claude/skill-observations/` or the equivalent outside
any project), shared across projects, tools and agents; keep a per-project
anchor only for skills that exist in that project alone. "Stable" is not
the same as "single", and a per-project default silently shards one log
into many, each of which looks complete from inside. Never place the
workspace inside a skills-discovery directory. Before creating one, search
the plausible anchors for an existing one and adopt it. Load
`references/environments.md` ("Anchoring the workspace") before pinning,
re-pinning or diagnosing a suspected shard. **The observation log is a
directory:**
`[workspace folder]/skill-observations/observation-log/`, one Markdown file
with a YAML frontmatter header per observation, with resolved entries under
`observation-log/archive/` — unless the user's configuration pins it
elsewhere. "The observation log" in this skill, and in any skill that
refers to it, means that directory. Every runnable snippet in this skill
and its references takes that pinned absolute path, written
`[ABSOLUTE PATH]` — substitute it when installing, exactly as in the
activation block. A snippet run with a relative path from any other
directory does not fail: it reports an empty, clean backlog, which is the
one answer that never gets questioned. **The substituted path routinely
contains a space** — the default shared-folder name on at least one
common install does — so every expansion of it stays double-quoted, and
no snippet may feed it through word splitting (`for f in $(find …)`): a
sweep that splits its own path at the space examines zero files, prints
errors nobody reads, and lets the command it rides inside succeed.
**Every snippet here is bash, not POSIX `sh`** — the archival sweep's
`read -r -d ''` is a bash extension that `dash` and `ash` do not have, so
under `sh` it fails as a usage error or, worse, as a loop that reads
nothing and exits zero, which is the same silent success as the word
split. A `bash` code fence states that to a human reader and to nothing
else, so invoke the snippets with bash explicitly; where a loop happens to
be POSIX-safe as well (the session-start scan), that is incidental and not
a promise about the rest.

## Reference files — load on demand, not up front

Each pointer names its trigger. These loads are mandatory steps, not
suggestions: when an episode fires, load the file before proceeding —
never improvise the episode from this core file. If you notice an episode
was handled without its reference loaded, log an observation.

- `references/weekly-review.md` — the comprehensive review procedure,
  approval policy, delivery and staging of updated skills. **Load when a
  review triggers or the user asks for one.**
- `references/skill-authoring.md` — taxonomy in full, structure defaults,
  licensing, attribution, confidentiality layers, live-file editing and
  relocation-verification rules. **Load before creating or editing any
  skill.**
- `references/observation-log.md` — storage layout, frontmatter fields,
  helper snippets, archival details, and the reasoning behind the rules.
  **Load when setting up the log for the first time, when archiving, when
  an id or frontmatter looks wrong, or before changing how anything reads
  the log.**
  Also load it, as the pointers below direct, before resolving, dismissing
  or citing an observation, when setting `parked` or `reference:`, before
  adding or changing an enforcement trigger or writing a new instrument
  over the log, and whenever a scan, sweep or id probe comes back empty.
- `references/signals.md` — the full catalogue of what is and isn't worth
  logging. **Load when unsure whether something is an observation, or when
  sorting many candidates.**
  Also load it when a defect that is not the deliverable is consuming the
  session, and before writing any "later" into a recommendation.
- `references/environments.md` — activation and config setup, compaction
  behaviour, bundle manifest, handoff-doc mode for storage-less
  environments. **Load for setup questions, after compaction, or when
  there is no filesystem.**
- `references/migration.md` — the one-time scripted conversion of a
  pre-3.0 single-file `log.md`. **Load only when the Session Start
  Protocol detects a legacy log.** Fresh installs never read it.
- `references/starter-principles.md` — an optional, provenance-stripped
  seed set of generic cross-cutting principles. **Load only when the
  starter-set reconciliation is due** (Session Start step 1: the
  `starter-principles-reviewed.txt` marker is absent or names an older
  starter set) — never on an ordinary session start.

## Session Start Protocol

1. **Storage.** The existence check for `skill-observations/` is also
   the workspace-mount probe — one `ls` of the pinned path, run in this
   turn. If it fails, the first response is the environment's folder-picker
   tool (in Cowork, `request_cowork_directory`; elsewhere, its equivalent),
   not the "no filesystem" branch: handoff-doc mode
   (`references/environments.md`) is for environments that have no
   filesystem at all, and it is reached too easily when a missing mount is
   read as one — and that request can itself come back refused by the
   harness's permission classifier rather than by the user (a classifier
   denial names the classifier and carries a bracketed reason; a user
   decline does not), so retry it once with an identical call before the
   folder-picker path is treated as having failed and before the "no
   filesystem" branch is even considered; the rule under **How to Log** —
   consecutive denials from a probabilistic gatekeeper are noise, not a
   wall — is stated there about log writes but governs every gated call,
   this one included. Never assert the mount's state — connected or not —
   from an environment flag, a config file's presence in context, or memory
   of an earlier turn: a claim about mount state needs a probe in the same
   turn. **A successful probe does not mean the activation config fired.**
   On turn 1 a config that is merely late is indistinguishable from an
   absent one, so load the session-start skills directly rather than
   assuming the config did, and read the config file yourself if the mount
   resolves but its content is not in context. Why this guard is only the
   backup, and which channel the primary one belongs in, is in
   `references/environments.md` ("Activation config — late, intermittent,
   and why the guard cannot live inside it"); load it when setting up or
   diagnosing activation. Once the path resolves: if
   `skill-observations/observation-log/` (with its `archive/`
   subdirectory) or `skill-observations/cross-cutting-principles.md`
   don't exist, create them (principles template:
   `references/skill-authoring.md`). Then the **starter-set
   reconciliation**, due whenever
   `skill-observations/starter-principles-reviewed.txt` is absent or holds a
   starter-set version older than the one in
   `references/starter-principles.md` — a fresh install, an upgrade to a
   bundle that ships the file, and every later growth of the set. Load that
   file and follow its **Reconciliation** section: match by substance, offer
   once in one line, import only what the adopter picks, then write the
   shipped version into the marker file so the offer never repeats until the
   set changes. Never pre-populate silently.
   Create `skill-observations/last-review-date.txt` containing the literal
   value `never` if it doesn't exist — never write a date into it at setup;
   a date means a review actually ran. If a legacy single-file
   `skill-observations/log.md` exists and `observation-log/` does not, this
   is an upgrade from a pre-3.0 install: load `references/migration.md` and
   run the scripted conversion before writing anything else. Before
   creating or writing anything: if the resolved workspace folder sits
   under an ephemeral path (e.g. `.claude/worktrees/`, a temporary clone),
   warn the user and re-anchor on the stable project path first — state
   written to an ephemeral checkout is lost at teardown.
2. **Scan.** Read only the frontmatter of each file in `observation-log/`
   — the header block between the first two `---` lines, never the bodies
   — and build awareness from `status`, `skill`, `proposes_skill` and
   `title`; also read the active principles. Hold them in awareness, don't
   surface unprompted. Frontmatter-only is the whole point of the per-file
   format: the scan stays cheap once hundreds of observations exist.

   **This scan does not satisfy the per-skill check** (the grep run each
   time a skill loads — `references/environments.md`, activation block):
   different scope, depth and moment. Load `references/observation-log.md`
   ("Why the session-start scan does not satisfy the per-skill check")
   whenever the per-skill grep feels redundant because this scan already ran.

   **An empty scan in a log known to be non-empty is a broken command
   until proven otherwise** — the snippet's guard halts on it. When the guard
   fires, or before adapting the snippet, load `references/observation-log.md`
   ("An empty scan over a non-empty log is a broken command").

   ```bash
   d="[ABSOLUTE PATH]/skill-observations/observation-log"   # the pinned workspace path — re-derive in EVERY call, never relative to the cwd; run under bash, not sh
   n=$(find "[ABSOLUTE PATH]/skill-observations/observation-log" -maxdepth 1 -name '*.md' | wc -l | tr -d ' ')  # literal path: independent of $d
   parsed=$(find "$d" -maxdepth 1 -name '*.md' -exec awk 'FNR==1 {if (/^---[[:space:]]*$/) print FILENAME; nextfile}' {} + | wc -l | tr -d ' ')
   suspect=$(find "$d" -maxdepth 1 -name '*.md' -exec awk 'FNR==1 && /^---[[:space:]]*$/ {fm=1; next}
     fm && /^---[[:space:]]*$/ {fm=0; nextfile}
     fm && /^[a-z_]+: [^"\047[|>].*: / {print FILENAME; nextfile}' {} + | wc -l | tr -d ' ')   # values with an unquoted ": " — invalid YAML
   find "$d" -maxdepth 1 -name '*.md' | LC_ALL=C sort | while IFS= read -r f; do  # quote + IFS=: never word-split a path containing a space
     awk 'NR==1 && /^---[[:space:]]*$/ {fm=1; next}
          fm && /^---[[:space:]]*$/ {exit}
          fm' "$f"
     printf -- '---\n'
   done
   if [ "$n" -gt 0 ] && [ "$parsed" -eq 0 ]; then
     echo "SCAN COMMAND BROKEN — $n files present, 0 headers parsed"; exit 1
   fi
   [ "$suspect" -gt 0 ] && echo "NOTE: $suspect of $n headers carry an unquoted ': ' in a value — quote those values (File format)"
   printf 'files: %s  parsed: %s  suspect: %s\n' "$n" "$parsed" "$suspect"
   printf '%s [%s] session-start scan: files=%s parsed=%s\n' "$(date '+%F %H:%M')" "${PWD##*/}" "$n" "$parsed" \
     >> "[ABSOLUTE PATH]/skill-observations/checkpoints.log"   # date+time+source: one line per session, not per day
   ```

   **The scan ends in a write, not only a print** — the appended
   `checkpoints.log` line is the protocol's own trace (in a priced-write
   workspace, fold it into the session's first write instead). Load
   `references/observation-log.md` ("The scan ends in a write, not only a
   print") before removing, moving or replacing that line.
3. **Review trigger.** Read `skill-observations/last-review-date.txt`. The
   value carries the truth: a date = when the last review actually ran;
   `never` = no review has run yet. A missing file is abnormal (step 1
   creates it) — recreate it with `never`, don't invent a date. If the
   value is `never` or 7 or more days old AND there are OPEN observations:
   in an interactive session, offer the review in one line and proceed
   with the user's task unless they opt in; never gate their work on the
   review. Scale the offer's CONTENT with the backlog, never its
   frequency: up to ~15 open observations, offer the full review ("the
   backlog hasn't been reviewed [in N days / yet] — N open; run it now, or
   carry on?"); above that, offer a bounded slice whose unit of work stays
   constant as the backlog grows — "review the 10 oldest", "review just
   the ones targeting <the skill most named>" — and state both numbers,
   how many are open and roughly how many distinct findings they
   represent (cluster on the `title` and `skill` fields you just scanned).
   A backlog that is never drained does not fail loudly; it fails by
   becoming too expensive to drain, so the per-session behaviour that is
   correct (never block the user) sums to a review nobody accepts. Only a
   scheduled/autonomous run loads `references/weekly-review.md` and runs
   the review unprompted.
4. **Activation.** Once per session: if no CLAUDE.md (or equivalent)
   activation instruction for this skill exists, briefly suggest adding one
   (see `references/environments.md`). Skip if already configured. Be clear
   about what this step is: it runs only after the skill has been invoked,
   so it verifies a working setup and structurally cannot detect the
   missing one — it is not the safety net for a never-activated install.
   That case is caught only from outside the runtime: the install-time
   verification and the external diagnostic in `references/environments.md`
   (no observation-log directory after sessions of real work), and the
   review's regression check for a tier that was present and is gone.
5. **Concurrency.** There is no shared log file to guard: each observation
   is its own file, so creating one never collides with another session's
   entry. Before changing an existing observation's *status*, re-read that
   one file first — a parallel review may have resolved it.
6. **Targets and staged work.** Resolve each distinct `skill:` value in
   the scanned frontmatter against the installed skill set and mention, in
   one line, any that no longer resolve — a deleted skill can accumulate
   dozens of observations before a review notices. Say what you resolved
   against (this checkout, this install): an unresolved target is a fact
   about where you looked, not about the world.
   If `skill-updates/PENDING.md` lists staged updates, reconcile the list
   before announcing it — installation happens outside any session, so no
   session observes the install itself, and the session that reads the
   ledger owns its cleanup. `diff -rq` each staged copy against live and
   classify it; a bare "differs" is not a verdict, because live moves on
   legitimately. The classification and its cases are in
   `references/weekly-review.md` ("Staged-work reconciliation gate") —
   load it before judging any entry. Then say "N staged updates awaiting
   review" in one line.
7. **First run.** If the log is empty and the project has history (handover
   or decision docs, commit history, test scripts, an existing CLAUDE.md —
   largely a record of corrections nobody logged), offer a one-off backfill
   pass over those artefacts. Backfilled entries cite the durable artefact
   (file and section) in `session_context` instead of a session, and one
   batched write satisfies the same-turn rule. One-off; the scheduled
   review takes over afterwards.

## When to Observe

Active for the entire task session — execution, post-task feedback, review
discussion, meta-discussion about skills or methodology, and strategy
conversations about how work should be done. **The observation mindset
does not deactivate when the conversation shifts from doing the work to
discussing it**; review-phase feedback is often the highest-signal input.
Inactive only for casual conversation and quick factual questions with no
tools or deliverables involved.

## What to Watch For

**New skill:** a reusable multi-step workflow, a methodology the user
explains that no skill captures, a recurring task type, a process the user
describes as "I always do it this way". **Improve a skill:** the agent
violates a documented rule (the skill needs enforcement, not louder rules);
a user correction reveals a missing rule or edge case; a better workflow or
technique emerges than the skill recommends; a wrong assumption; new
tooling obsoletes a step; a principle that applies to other skills too.
**Simplify a skill:** a section never relevant across many sessions, a rule
from a single unvalidated observation, contradictory rules, a rule the
agent consistently fails to follow — convert to structural enforcement or
remove. Full catalogue with examples: `references/signals.md`.

**An unresolved defect is an observation, at a bounded point.** When a
defect that is not itself the deliverable takes a second hypothesis, load
`references/signals.md` ("An unresolved defect is an observation, at a
bounded point") and log the evidenced problem report instead of a fifth
probe.

**Do NOT log:** one-off corrections that don't generalise; preferences
already captured in a skill; tool bugs unrelated to methodology;
observations needing proprietary client information to be useful in an
open-source skill (unless an internal skill is the right home). The
generalisability test, when unsure: would this still make sense in another
project, and for another task using the same skill? Does it name a missing
rule, step or principle rather than fix this task? Is it likely to recur?
Mostly no → task context, not an observation. Before minting a
`proposes_skill` name, reuse a fitting existing candidate — independently
logged proposals for one skill rarely share a name.

**Check for a restatement before writing.** Before creating the file,
list the open observations that name the same target skill (the scan at
session start already holds their titles; otherwise `find observation-log
-name '*.md' -exec grep -l "skill:.*<skill>" {} +`) and read those titles.
If the finding is the same one restated — the same rule, the same failure
shape, a different example — extend the existing entry instead: append the
new instance to its body, add the session to `session_context`, edit that
one file. Duplication is only visible in aggregate (measured on one log:
roughly forty of ninety-one open entries were one finding restated), and a
near-duplicate costs a capture every session and a triage every review.

**Validate the target at write time.** `skill:` names a skill that exists
now. If the right home is not a skill — an instructions file, a memory
note, the register a routine reads — put that path in `target_file:`
instead of mapping the entry onto the nearest skill; if the skill does not
exist yet, use `proposes_skill:`.

**Check the target's siblings at write time, and record that you did.**
Before writing, resolve the target against the family registry
(`skill-observations/skill-families.md`) and for each sibling either add it
to `skill:` or say in the body why it does not apply. Fast test: **could
this sentence survive having the tool's or subject's name removed?** If
yes it belongs to every sibling. Record the outcome in the mandatory
`siblings_checked:` field, including the verdict "checked —
instance-specific, no propagation": a one-entry `skill:` list is
byte-identical whether the siblings were evaluated or never considered,
and only the recorded field makes the *absence* of the judgement visible.
Load `references/observation-log.md` ("Skill families and the sibling
check") for the registry spec, the coherence models and the fallback when
no registry exists.

**Log your own rule violations.** Breaking a rule that a skill or a project
instruction file documents is a first-class observation, not an
embarrassment to move past: it is the only evidence that the rule's
*enforcement* is too weak, and nobody but the agent can see it. Log it in
the same turn, and name which protection was actually in play — written
down, loaded into context, or backed by a checkpoint. The restatement check
above surfaces the earlier entry when one exists, so the count arrives
without extra work.

**Second violation of the same rule: stop proposing text.** A rule that has
failed twice with no intervening `actioned` fix has a protection problem,
not a wording problem. From the second occurrence the proposed improvement
must be a structural barrier — a hook that refuses the call, a lint rule, a
default that makes the wrong path unavailable — never a clearer sentence, a
bolder warning, or the same rule moved somewhere more prominent. Load
`references/signals.md` ("Second violation — why a barrier, not a
rewording") before proposing either.

## How to Log

Write the observation file **silently, within the same turn or the next** —
never batch mentally for later; the act of writing is the enforcement
mechanism.

**Mandatory checkpoint after every 3rd completed todo item.** After marking
the 3rd, 6th, 9th (etc.) item complete you must **write to disk** — not
merely ask yourself whether anything is pending. Write any pending
observation files, or append a one-line `no observations` acknowledgement
to `skill-observations/checkpoints.log`. The required action is a concrete
write; a remembered "ask whether" is not enforcement. Roughly every third
completion is the rule; the count need not be precise. (Exception for a
priced-write workspace: `references/environments.md`.)

**A denied or failed write is not a read-only log.** Retry once before
concluding the workspace is unwritable, and try a second tool reaching the
same path — a classifier can deny one interface while allowing another,
and consecutive denials from a probabilistic gatekeeper are noise, not a
wall. Report "failed N times", never "cannot be done", unless retries and
alternate interfaces are exhausted; otherwise observations are silently
lost for the rest of the session.

**Deliverable-event flush.** Whenever a unit of work is declared complete
to a human — a file handed over, a render, a staged skill file, a
completion notification, a final report, a finished todo batch — write any
pending observation files at that moment. These checkpoints already
involve a tool call, so the flush rides on work you were doing anyway.
(Why both checkpoints are writes rather than questions:
`references/observation-log.md`.)

**A failed write to an external system is a flush trigger in its own
right** — a tool result carrying `permission stream closed`, `permission
denied`, or a harness interrupt. Not a completion, but it has both
properties the flush needs: it is a literal string in the tool record
rather than a judgement about whether the moment qualifies, and it lands
where a run has just found something worth reporting and is most likely to
stop instead. Flush before doing anything else with the failure, including
deciding what to do about it. (Observed: an unattended run completed every
step, took a permission error on its first external write, and ended with
no report and no logged observation.)

**Two gaps this pairing still leaves:** the 3rd-completion checkpoint is
inert in a session using no todos, and "is this a major deliverable?" is a
self-assessment — so the deliverable flush is the only enforcement left
and must be applied deliberately. Before adding or changing any
enforcement trigger, load `references/observation-log.md` ("Two gaps the
checkpoint pairing still leaves — and the rule behind both").

**Id and filename.** Each observation is `NNNN-short-slug.md` (zero-padded
id + a kebab-case slug from the title). The id is the highest of three
values, plus one: the highest numeric prefix in `observation-log/`, the
highest in `observation-log/archive/`, and the number in
`observation-log/archive/.id-floor` (the highest id ever issued — update it
whenever you issue an id above it, so the counter can never restart from 1
when the active directory is empty). The same command first sweeps stale
resolved files into `archive/` — archival is a side effect of deriving the
id, not a separate duty (see Archival on Write):

```bash
d="[ABSOLUTE PATH]/skill-observations/observation-log"   # the pinned workspace path, never relative to the cwd; it may contain a space, so keep it quoted; bash, not sh
today=$(date +%F)          # archival rides inside this command (see below):
n_files=$(find "$d" -maxdepth 1 -name '*.md' | wc -l | tr -d ' ')
seen=$(find "$d" -maxdepth 1 -name '*.md' -print0 | { n=0   # -print0/-d '': never word-split a path containing a space — `read -d` is a bash extension, so this loop requires bash
  while IFS= read -r -d '' f; do   # stale resolved files move before the id is read
    n=$(( n + 1 ))
    hdr=$(awk 'NR==1 && /^---[[:space:]]*$/ {fm=1; next}
               fm && /^---[[:space:]]*$/ {exit} fm' "$f")
    case $hdr in   # patterns parenthesised: required inside $( ) on bash 3.2
      (*"status: actioned"*|*"status: declined"*|*"status: superseded"*) ;;
      (*) continue ;;
    esac
    r=$(printf '%s\n' "$hdr" | sed -n 's/^resolved:[[:space:]]*//p' | head -1)
    case $r in ([0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]) ;; (*) continue ;; esac
    [ "$r" != "$today" ] && \
      [ "$(printf '%s\n%s\n' "$r" "$today" | sort | head -1)" = "$r" ] && \
      mv "$f" "$d/archive/"
  done; printf %s "$n"; })
[ "$n_files" -gt 0 ] && [ "${seen:-0}" -eq 0 ] && { echo "ARCHIVAL SWEEP BROKEN — $n_files files present, 0 examined"; exit 1; }
hi=$( { ls "$d" "$d/archive" 2>/dev/null | grep -oE '^[0-9]+'; cat "$d/archive/.id-floor" 2>/dev/null; } \
     | sed 's/^0*\([0-9]\)/\1/' | sort -n | tail -1); : "${hi:=0}"
[ "$hi" -eq 0 ] && [ -n "$(find "$d" -maxdepth 1 -name '*.md')" ] && { echo "ID COMMAND BROKEN — log is non-empty but no ids extracted"; exit 1; }
next_id=$(( hi + 1 )); echo "$next_id" > "$d/archive/.id-floor"
f="$d/$(printf '%04d' "$next_id")-<slug>.md"      # the target path, built from the id just derived
[ -e "$f" ] && { echo "COLLISION — $f exists; re-derive the id"; exit 1; }
(set -C; : > "$f") || exit 1                        # noclobber: create, never truncate an existing file
```

The `sed` strips the filename prefixes' zero-padding before the
arithmetic — do not "simplify" it away: shell arithmetic reads a
leading-zero number as octal, so `$(( 0105 + 1 ))` yields 70, and a
prefix containing an 8 or 9 errors out.

The guard line distinguishes "the log says zero" from "I could not read
the log", the sweep's own count does the same for the archival loop, and
the `noclobber` create refuses an existing path — write the body only after
that create succeeds, and on a collision re-derive the id. Load
`references/observation-log.md` ("The guard line, the sweep's count and the
noclobber create") when any of the three fires.

**Run the snippet immediately before EVERY write, including the first and
only one of a session** — an earlier read of the log for any other purpose
is not a substitute. Load `references/observation-log.md` ("Run the snippet
immediately before every write") when skipping it feels reasonable, or when
two files turn out to share an id.

**Resolve each id at its own write time** — run the snippet before EACH
file, never pre-compute a range, and put the value it printed into both the
filename and the `id:` field. Load `references/observation-log.md`
("Resolve each id at its own write time") before batching or parallelising.

**Every instrument gets the same guard: an empty or zero result is a
claim about the instrument until an independent probe shows the
population is empty.** Load `references/observation-log.md` ("Every
instrument gets the same guard") before writing any new scan, count, grep
or query over the log.

**A structural probe that comes back empty where content existed before is
a stop signal, not a create** — HALT and re-probe; never let an append
recreate a missing target. Load `references/observation-log.md` ("A
structural probe that comes back empty is a stop signal") before writing
anything after such a probe.

**File format.** YAML frontmatter (the metadata every scan reads) followed
by the Issue → Improvement → Principle body. **The frontmatter is mandatory;
always write `status: open` and a non-empty `siblings_checked:` at creation
time** — an observation without a `status` field is treated as OPEN by
reviews, never as nonexistent, and one without `siblings_checked:` counts
as logged without a sibling check.

```markdown
---
id: 0
title: "Short descriptive title"
status: open            # open | actioned | declined | superseded | parked
type: open-source       # open-source | internal
skill: [skill-a, skill-b]        # existing skills this improves — always a
                                 # list, even with one entry; first entry is
                                 # primary; may be empty: []
proposes_skill: []               # new skills this argues for, by working
                                 # name; an observation can fill either
                                 # list or both
target_file: []                  # when the right home is not a skill at all:
                                 # the path the fix will be written to
siblings_checked: "family-name: a, b — shared, both added"
                                 # MANDATORY, never blank: the family name,
                                 # the members evaluated and the verdict —
                                 # or "family-name: a, b — instance-specific,
                                 # no propagation"; the literal none only
                                 # where the target belongs to no family
area: "which part of the skill or workflow"
date: YYYY-MM-DD
session_context: "what task was being worked on"
parked_until:           # MANDATORY when status is parked, empty otherwise:
                        #   one line naming the condition that unparks it
resolved:               # date resolved; leave empty while OPEN
resolution:             # what was done — set only when actioned/declined
reference:              # optional: path to saved session-local evidence
---

**Issue:** [What happened — specific enough to understand weeks later
without the original conversation.]

**Suggested improvement:** [Concrete change. For existing skills, name the
section or rule; for new skills, scope and key components.]

**Principle:** [The generalisable takeaway — the most important field.]
```

**Every prose value is double-quoted.** `title`, `siblings_checked`,
`area`, `session_context`, `resolution`, `parked_until` and `reference`
carry free text, and free text contains `: ` as the common case, not the
exotic one. Unquoted, that is invalid YAML: the frontmatter still
extracts, so the scan notices nothing, but every consumer that PARSES it
throws on the file. Quote the value (`"…"`, inner `"` as `\"`), keep
lists in `[]` with bare kebab-case names, leave dates and status words
bare. Load `references/observation-log.md` ("Frontmatter fields") for how
far this drifts unnoticed and what the scan's suspect count means.

**`parked` means decided, not pending:** sound but blocked on an external
precondition, out of the work queue, never archived, `parked_until:`
mandatory and naming a condition that can actually occur. Load
`references/observation-log.md` ("The `parked` status — decided, not
pending") before setting, reviewing or unparking a parked status.

**Context preservation:** if an observation depends on session-local data,
save it into the workspace first and set `reference:` to a durable path a
fresh session can resolve. Load `references/observation-log.md` ("Context
preservation — the `reference:` field") when setting `reference:`.

**Confidentiality at logging time:** for `type: open-source` observations,
the Issue/Improvement fields may reference specifics for context, but the
Principle must be fully generalised — no client names, domains, or details
traceable to a real project. Full confidentiality layers:
`references/skill-authoring.md`.

**Changing an existing observation:** re-read that one file, edit only the
frontmatter fields you are changing (`status`, `parked_until`, `resolved`,
`resolution`),
never batch-rewrite the directory. Archival is a plain `mv` (below).

## Referencing Observations

Cite an observation by the `id` field in its frontmatter (= the `NNNN-`
filename prefix), never a `grep -n` line number — those are positional
metadata, not identifiers. A cited id must fall inside the range across
`observation-log/`, `archive/` and `.id-floor`; one far outside it is
almost certainly a line number misread as an id.

## Taxonomy (quick version)

**Open-source** — client-agnostic, methodology-driven, useful to other
practitioners. **Internal** — contains user/client/project specifics or
personal preferences. Default to open-source when it could go either way,
stripping specifics. The boundary is also a confidentiality boundary and
the two errors are not symmetric — over-classifying as internal costs only
reach, under-classifying can leak — so when genuinely uncertain, prefer
internal and promote later. Full requirements (attribution, licensing,
structure): `references/skill-authoring.md`.

## Archival on Write

Archival is not a preamble duty to remember before writing — it rides
inside the id-derivation snippet above: the same command that computes the
next id first `mv`s already-resolved files from `observation-log/` to
`observation-log/archive/`, so the sweep runs whenever an id is issued and
cannot be skipped without failing the write. (The prose form — "on every
write, first archive" — under-fires: a duty attached as a preamble to
another action inherits none of that action's enforcement. If a step must
always accompany a tool call, put it inside the same command.) The
scheduled review archives too, at Step 1, as an independent backstop. "Already resolved" is read from the file's
own frontmatter: `status: actioned`, `declined` or `superseded` AND a
`resolved:` date **before today**. Files resolved today stay until the next
day, whichever session resolved them — the grace period lives in the file,
never in session memory. A resolved file with no readable `resolved:` date
gets today's date written to that field instead of being archived (the
snippet skips it; make that one-field edit separately). One
file per `mv`; no rewrite of anything else. Helper and rationale:
`references/observation-log.md`.

## Surfacing Protocol

Default: at end of session, as a grouped summary — improvements grouped by
skill, new-skill candidates listed separately; for each, one sentence plus
suggested type; ask which to act on. Surface earlier when an observation
needs user input to be complete, when a skill is actively producing wrong
output, or when observations cluster on one skill.

**Deferral wears a second disguise: not a promise, but an argument** ("let's
gather more data first"). Before writing any "later" into a
recommendation, load `references/signals.md` ("Deferral disguised as
diligence") and name which specific observation would change the decision
and when it could arrive.

**Default to log-and-defer.** Surfacing an observation is not an invitation
to act on it: state that it is logged for the next review, and stop.
Reserve in-session application strictly for the triggers under "Acting on
Observations". Do NOT routinely offer a binary "apply now vs leave for next
review" choice; for users who run regular reviews that offer is unwanted
friction, and if a user has said they always defer, suppress it entirely.

**Log-and-defer means the observation is the sole carrier of the change**,
and applying an insight to the work in front of you is not the same act.
Letting an open observation change what you do in THIS task is the point of
the log. Writing the rule anywhere a later run reads it — a state file the
skill loads (registry, dossier, config note), a prompt, a handoff doc, a
project instruction file — is acting on it. The test: **does it leave a
durable change outside the observation log?** The disguise is a bridge —
"the rule has to live somewhere until the review runs". It does, and that
somewhere is the observation; a parked copy has no update path, so when the
review edits the skill the next run reads both. A state file that genuinely
needs to point at the rule gets one line naming the owner and the pending
change, never a restatement. A harness's skill-save control is the sharper
version: it installs a copy rather than parking one, possibly over what a
parallel review has staged. See "Acting on Observations".

**Self-check before surfacing:** observations were logged throughout the
whole session (including discussion phases); logged silently; each follows
Issue → Improvement → Principle; each is typed; existing-skill items name
the section; no open-source Principle contains client-identifying info;
every observation file carries `status:` (`status: open` at write time) and
a non-empty `siblings_checked:` — if any lacks one, do the sibling check
now and record it rather than back-filling the field with `none`.

## Acting on Observations

Act only in three contexts: (1) the comprehensive review (load
`references/weekly-review.md`); (2) an explicit user request ("update X
skill", "act on observation #N"); (3) in-session correction when a skill is
producing wrong output the user should know about. Otherwise: log, don't
act.

**An outcome-level ask is not trigger (2).** "Make this part of the
workflow", "make sure it happens every week", "make sure this is included
going forward" name an outcome, not a mechanism. They are satisfied by
logging the observation and saying it will be applied at the review —
trigger (2) needs the user to name the change and the moment ("update skill
X now", "act on observation #N"). A request about an outcome is never a
licence to choose the change path: the workspace's own path — log, review,
stage, install — wins over any route that reaches the installed skill
sooner.

**A harness's own skill-proposal or skill-save control is an install path,
not a staging path.** Where the environment offers to save or propose a
skill from inside the conversation — and its tool description may well say
that is how a skill change is delivered — that control writes the installed
copy; it produces nothing under `skill-updates/`. Never use it in a
workspace that stages there. And before any in-session skill change, check
the staging manifest (`skill-updates/PENDING.md`) and today's
`skill-updates/<date>/` for the same skill: a review running in parallel
may already have staged it, built on the live file plus other changes.
Installing a conversation-side copy over that drops those changes, and
installing the review's copy afterwards silently reverts the one the
control installed — a shortcut taken while another session stages the same
artefact turns a one-line rule into a merge conflict the user has to catch.

**Read the full body before resolving, dismissing, fixing, or citing** — a
title is an index entry, not content. **A change you did not make resolves
an observation point by point, never title against title:** list the points
the body makes, name the line of the fix covering each, and any point
without a line stays open on a carrier. Load
`references/observation-log.md` ("Read the full body before resolving,
dismissing, fixing or citing") before any resolve, dismiss or cite step.

When acting: small, clearly-additive, low-risk changes (a new rule, a
clarification, a factual fix) may be applied without waiting for the next
review — "directly" means *now*, not *in place*: the edit is still made on
a staged copy based on a fresh read of the live file and handed to the user
to install, in every environment and every context. Staging-only has no
interactive exception; an exception the user has to remember is a gate
that eventually gets left open. Substantial changes (restructuring, new
capabilities, changed methodology) and all new-skill creation: load
`references/skill-authoring.md` first and follow its editing and staging
rules. A principle that applies to skills generally goes to the
cross-cutting principles file (same reference).

**Set the status in the same turn you act.** An observation acted on
in-session must have its frontmatter updated — `status: actioned`,
`resolved: YYYY-MM-DD`, `resolution: what was done` — before the turn
ends. The work and the bookkeeping are two acts, and the second is the one
that gets dropped; a stale `open` entry then invites redoing finished work
over a section that has since moved on. The write is the enforcement,
exactly as it is for logging. **"Acted on" includes a fix that lands as
ordinary work** — the rule written into the instructions file, the code
corrected — with the observation not in mind; and a later session finding
the remedy already in place closes the entry the same way. Neither looks
like acting on an observation, which is why both are missed (measured on
one first review: 10 of 27 entries were already applied while `open`).

**Acting on only a subset of a multi-skill observation's `skill:` list?**
Neither plain move is honest — left `open`, the finished portion gets
re-applied by another session; marked `actioned`, the unfinished portions
silently leave every future queue. Use the carrier pattern: mark the
observation `actioned` with a `resolution:` naming the portions applied,
then log a carrier holding the remainder, with only the outstanding skills
in its `skill:` list. Full protocol: `references/observation-log.md`.

## Quick Reference

| Question | Answer |
|----------|--------|
| When do I observe? | The whole session, including feedback and reflection phases |
| How do I log? | Silently, immediately, as one file per observation named `NNNN-slug.md`; id = max(active, archive, `.id-floor`) + 1, derived by running the snippet immediately before each write — an earlier read of the log for any other purpose is not a substitute |
| When do I surface? | End of session, or earlier if needed |
| Status field? | Mandatory `status: open` frontmatter on every new observation; reviews treat a missing status as OPEN, never as nonexistent. Five values: `open`, `actioned`, `declined`, `superseded`, `parked` — `parked` = decided but blocked on an external precondition, so it leaves the queue, requires `parked_until:`, and never archives |
| Does the target skill have siblings? | Resolve it against `skill-observations/skill-families.md` BEFORE writing; add every sibling the insight applies to to `skill:`, and record the verdict in the mandatory `siblings_checked:` field — including "checked, no propagation" |
| A scan or query came back empty? | Two possibilities, only one is a finding: guard every retrieval meant to prevent duplicate work with an independent existence check, and treat empty output over known content as a broken command |
| Citing an observation number? | From the `id:` frontmatter field (= the `NNNN-` filename prefix); never a `grep -n` line number; sanity-check against the known id range |
| Open-source or internal? | Default open-source; the boundary is confidential |
| Small fix or substantial? | Additive → apply directly; restructuring/new skill → `references/skill-authoring.md` |
| Same rule broken twice? | The fix is a structural barrier (hook, lint, default) — never a third rewording |
| Changing an observation (status/archival)? | Re-read that one file, edit only its frontmatter, or `mv` it to `observation-log/archive/` — no shared-file rewrite |
| Upgrading from a single-file `log.md`? | Scripted, once — `references/migration.md` |
| Weekly review? | Trigger check at session start; procedure in `references/weekly-review.md` |
| No filesystem? | Handoff-doc mode — `references/environments.md` |
