Skip to content
E. AguilarKeel Skillsdocs
Menu · The operating loop

Concepts

The operating loop

The permission model decides what an agent may do. This is the loop it runs inside: where state comes from at the start, what catches drift while you work, what gets written back at the end, and how repeated work turns into tooling instead of habit.

A brake on its own is a good day's protection. What breaks a project over months is quieter: documents that were true in March and are confidently wrong in July, state that lives in a chat window and disappears with it, and the same manual sequence done for the ninth time because nobody counted.

Key idea

State is read to work; history is read to understand why. They are not read at the same frequency, so they must not cost the same to read. Almost everything on this page follows from that one line.

The two ends of a session#

A session that reads state at the start and writes it back at the end is what makes “files are the source of truth” actually hold. Both ends matter, and the opening one is the one people skip.

Opening#

Read the rules, then the state file, then only the docs for the area you are about to touch — not all of them. Then run the project's checks before doing any work.

The order is the whole point. If the last session left drift, you want it now, while it is cheap and clearly not yours. Finding it after two hours of building on top of it means choosing between rework and knowingly shipping on a bad foundation.

shell
/keel-skills:session-start

Closing#

Reconcile what happened into the files: resolved items move to history, new verified facts go where they belong, open questions get recorded as open. Re-run the checks. Leave a short handoff.

shell
/keel-skills:session-close

Two rules keep the close from becoming its own problem:

  • One line of history per close. The reasoning belongs in the document it is about — the decision, the process doc, the draft — not in the history entry. Entries that grow into essays are how a changelog gains tens of kilobytes a month and stops being read.
  • The close records; it does not build. No new work, no reopening decisions. If it finds something worth doing, it writes it down as open and stops.

Note

The test for whether you wrote enough: a fresh session with no access to the conversation should be able to pick the work up from the files alone. If it couldn't, the state is still living in the transcript — and the transcript is about to disappear.

Documents that don't age#

Written rules decay — not because anyone disagrees with them, but because the world moves and the document doesn't. Three shapes stop that.

Split state from history#

What it holdsRead when
StateWhat is true now, what is openEvery session
HistoryWhat happened and why, datedRarely, on purpose

When something closes, its body moves to history and the state keeps one dated line. Resolved items left in the state file are the most common way a state file quietly triples in size.

Cut history out of a document when any one of these is true — each bar exists because the one before it let a real case through:

  1. Size. The history section passes roughly 30 KB.
  2. Proportion. History passes about two thirds of the file, even if it is small. You pay for what is read, not for what the file measures.
  3. Nature. A closed item is history even when it lives in a section not called “History”. The first two bars only look at the labelled section, so a “Pending” section can be more than half closed items and neither one sees it.

A bootstrap carries no state#

The file an agent reads first must point at where state lives, not contain it. State written into a bootstrap ages invisibly and, worse, gets read as criteria rather than as a fact — nobody re-checks it, because it looks like a rule.

The same failure hits numbers written into method documents. “Runs 14 checks”, “takes about 1.5 seconds” — each one is a fact filed inside a rule, and no one looks at it again. Either keep the number where it can be verified, or don't write it.

Date every snapshot, and prefer generated over maintained#

When a document genuinely must hold a picture of the world, mark it Current state (2026-08-14) and say it is a snapshot. A dated snapshot can be measured; an undated one just rots.

Better still: if a listing can be generated from the filesystem at run time, generate it and delete the copy. An index built at run time cannot go stale.

Checks that catch what the rules didn't#

A rule tells a careful reader what to do. A check notices when it didn't happen. You want both, and they fail differently.

Add a check when a real drift already got through undetected — not speculatively. That keeps the suite small enough that every failure means something and nobody learns to ignore it. Write down why each one exists, right next to it.

Three levels, not pass/fail#

A finding means different things depending on whether someone has that file open right now, so version control decides:

LevelThe file is…What to do
FAILcommitted and cleanReal drift — it survived a session close. Safe to fix: nobody has it in flight
WARNmodified or untrackedSomeone is working on it. Report; don't fix what isn't yours
infoNeeds human judgment. Look, don't auto-correct

Watch out

Skipping this split has a specific consequence. With several sessions open, “fix all findings before closing” tells one session to edit another session's in-flight files — the exact collision the checks were meant to prevent.

Ratchets#

For anything that must not grow — startup context cost, bundle size, an index loaded every session — check against the last recorded value, not a generous ceiling. When a ratchet fails, remove content; do not raise the ceiling. A ceiling raised on every failure is a log, not a limit.

A runnable starter — five universal checks, the FAIL/WARN split, and a documented extension point — ships with the plugin:

shell
cp "$CLAUDE_PLUGIN_ROOT/templates/checks/keel_checks.py" scripts/
python scripts/keel_checks.py

Unattended runs#

A scheduled sweep, a CI job, a background agent. Here the rule doesn't get looser — it gets stricter:

Key idea

With no human present, there is no one who could give a green light — so anything that needs one cannot happen. Not “proceed carefully”. It does not happen.

An unattended run may look, measure, and report with a recommendation. It must not commit, push, deploy, send, delete by inference, mark anything approved, or “fix” what it finds broken. If the job needs any of those, it was scoped wrong.

Write those limits into the routine's own prompt. Every run starts with no memory of any conversation, so a limit agreed once in chat does not exist for it. The enforcement hook does the mechanical half: a hot action degrades from ask to deny when nobody can answer.

command /keel-skills:hygiene — a read-only sweep for uncommitted work, leaked secrets, state drift and stale drafts. Run it on demand, or schedule it from templates/routines/weekly-hygiene.md.

Turning repetition into tools#

Encapsulate what has already been done three times. Fewer is premature abstraction: you pay maintenance forever and — the real cost — one or two occurrences don't show you what varies, so you build the wrong shape and then work around it.

Count carefully, because the number lies in both directions:

  • Normalize before counting. The same command with a different id inside is the same command. Counting raw text splits one repetition into five.
  • Then check the opposite. A form appearing 200 times may be a truncation artifact. Look at whether the occurrences do the same thing or merely start the same.

Test banks need a case that must fail#

A script that touches a live system can only be exercised on the state things happen to be in — which is the state where nothing is wrong. Build a bank that substitutes the reads, runs the real routine, and captures what would have been sent.

Watch out

Include a negative control: a case where the code must act, and the bank fails if it doesn't. Without one, a suite that only checks “nothing bad happened” passes just as green with the entire engine commented out — measuring nothing while looking healthy.

Capture is free, adopting is not#

Repetition is invisible from inside a session: each one starts blind, so nobody looks across them. The loop has three separate times:

TimeWhenWho
Captureduring real workthe agent — one line, then keep going
Harvestwhen capacity is spareits own session
Adopton reviewthe human only

Capture is the asset. If the harvest has to begin by thinking of what to improve, it doesn't happen. So: notice the friction, write one line, keep working. Do not design the fix in the moment — that derails the task, and it is exactly why nothing ever gets written down.

Adopting is the green light. A backlog file decides nothing and deleting it breaks nothing, so writing to it is free. The moment an item becomes a tool, a command, or a rule something obeys, it changes how the system works. The agent fills the funnel; the human opens the gate.

Record discards with their reason — a discard without one gets proposed again next quarter.

shell
/keel-skills:harvest