standing

package
v0.4.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 22, 2026 License: Apache-2.0 Imports: 23 Imported by: 0

Documentation

Overview

Package standing is the ambient side of v3: the things a conversation leaves behind that keep working after the window is closed — a reminder, a watch, a rule, an overnight job — and the small, honest machinery that wakes them.

THIS FILE IS THE CONTRACT. It was written by hand before any lane started, and every lane codes against it: the core lane fills in the store and the ticker, the session lane supplies the sentinel and the runner and arms the belt tool, the home lane draws items, the errand lane hosts the exchange at home. A field or a method added here mid-build is a change every lane has to hear about, so the shape is deliberately small and deliberately complete.

── THE LAWS ──

  • ONE OBJECT, MANY SHAPES. A reminder, a routine, a watch, a rule, an overnight job and a self-proposed follow-up are all an Item: the person's words, what wakes it (When), what it does (Action), and what bounds it (Rails). The product's variety is in two fields, not in six mechanisms.

  • THE MECHANISMS ARE CLOSED; THE CONDITION IS OPEN. There are five ways an item can be woken (WhenKind) and that list does not grow per feature. But WhenProbe is general: the model writes the probe — any shell command, or any tool on the belt with any arguments, including a tool a connected account brought — and a cheap yes/no judgment (Sentinel) reads its output against the person's words. "Is CI red", "did Priya reply", "is the cert under 14 days" are all probes; none of them is a kind.

  • FILES, NOT A DATABASE. One JSON document per item, written temp+rename under a per-item flock; an append-only daily ledger for the rails; one folder per run. docs/AMBIENT.md Part 4 has the numbers. Every path is answered by this package and nowhere else, so an index could be added behind it later without a caller changing.

  • NOTHING STANDS UNTIL THE PERSON SAYS YES. Store.Create is only ever called after a ratification card was answered yes (a StandingProposal in internal/session). There is no path that arms an item silently.

  • UNATTENDED MEANS WHAT WAS ALREADY ALLOWED. A firing runs under the person's banked approval rules with nobody to ask; anything that would have asked stops the run as "needs your look". This paragraph used to finish "there are no probation counters: the rules are the tenure", and the second half of that is no longer true: Item.CleanRuns counts clean firings in a row, and the rope column reads it (RopeWord). The first half still is, and it is the part that mattered — the counter changes WHAT A PERSON IS TOLD about an item, never what a firing is allowed to do. What is allowed is the banked rules and only ever the banked rules; nothing here widens with a count.

  • QUIET IS THE DESIGN. A check that found nothing rewrites LastChecked and LastCheckLine in the item and writes NO line anywhere else. A run that delivered nothing is reaped after RunKeep.

  • STATUS IS DERIVED, NEVER ASSERTED. Whether the OS timer is installed is whether its definition file still matches byte for byte what this build would write; last wake and next due come from the wake log and the fixed cadence. Nothing shells out to ask.

── WHERE THE BODIES ARE ──

This file is the shape. The work is in store.go (the documents, the item log, the locks), ledger.go (the daily lines the rails are summed from), inbox.go (news for a window that is not open), every.go (the rhythm), tick.go (the pass) and watch.go (the operating system's timer).

Every name a caller holds is declared here, with ONE exception the contract could not carry: Watch is an interface with no way to make one, so watch.go adds NewWatch and the WatchOptions it takes. Nothing else outside this file is reachable.

Index

Constants

View Source
const (
	RunningChecking = "checking"
	RunningFiring   = "firing"
)

The two things a pass can be doing to one item, and the whole of what a marker's What may say. Checking is the look — a probe, a fingerprint, the sentinel's yes-or-no; firing is the work that follows a yes.

View Source
const (
	// OutcomeNeedsYou is a firing that stopped on a question for the person.
	OutcomeNeedsYou = "needs-you"
	// OutcomeFailed is a firing that could not finish.
	OutcomeFailed = "failed"
)

The two outcomes that mean a firing did NOT come back clean, spelled once here because three places now test for them — the clause a card reads ([outcomeClause]), the recorder ([Ticker.fire]) and the trust counter that recorder keeps. They were string literals in each, which is a word spelled three times and therefore a word that will drift.

View Source
const (
	DarwinTickLabel = "ai.agentfield.codeaf.tick"
	LinuxTickTimer  = "codeaf-tick.timer"
)

DarwinTickLabel and LinuxTickTimer are what this machine's own scheduler calls the timer.

THEY ARE EXPORTED BECAUSE THE SETTINGS ROW SAYS THEM OUT LOUD. "codeaf installs a launchd agent" is a sentence nobody can check; the label is what `launchctl list` and `systemctl --user list-timers` answer to, and a person deciding whether to leave background checks on is entitled to the name they would have to type to go and look.

View Source
const (
	// RopeAsksFirst is an item that has been given no licence to act unasked.
	RopeAsksFirst = "asks first"
	// RopeTrusted is an item with a grant that has since fired [TrustAfter]
	// times in a row without needing anybody.
	RopeTrusted = "trusted alone"
)

The words the rope column is allowed to say, and no others.

View Source
const CameTo = "came-to"

CameTo is the one-word file a firing leaves in its run folder saying what that run came to — the same word as Outcome.Kind. The item's own LastOutcome is overwritten by the next firing, so without this nothing on disk would say which of a hundred run folders delivered anything.

View Source
const DefaultPerRunUSD = 5.0

DefaultPerRunUSD is what ONE FIRING of a standing item may spend when nobody named a figure — the probe, the sentinel's judgment and the work itself.

IT IS THE ONE PLACE THIS NUMBER LIVES. It was written out three times once — here in the store that creates a charter, in the proposal that quotes a price to the person, and in the belt tool's own schema — and three copies of one fact is the drift this codebase has a law against. Every reader resolves it from this constant.

Fifteen cents was the old figure and it was a rail rather than a backstop: a cheap look plus a small model's answer and nothing else, so the first standing order anybody wrote that did real work stopped halfway through its first firing. Five dollars is a whole piece of work on a good model, which is what a person who says "keep an eye on this" is actually asking for. The protection that matters is still the machine-wide daily rail plus the max-per-day count, not this.

View Source
const Interval = 5 * time.Minute

Interval is how often a pass runs, whether a window runs it or the OS timer does. It is the cadence the ratification card quotes for "checked every …".

View Source
const NeedsPermissionLead = "stopped: it needed your ok to run "

NeedsPermissionLead opens the one line a firing leaves when it stopped because a call needed permission and nobody was there to give it. It is declared here, beside the field, because two packages must agree on it: the session writes it and IsPermissionLine recognises it.

View Source
const OutcomeNothing = "nothing"

OutcomeNothing is the Outcome.Kind of a run that delivered nothing at all: no line, no landing, nothing waiting for the person. It is the ONE outcome whose run folder the sweep may reap after RunKeep, so it is a constant rather than a word spelled twice in two packages.

View Source
const Previous = 5

Previous is how many of an item's last sentinel judgments ride in the next judgment's prompt. It is what stops a declined firing being proposed again every wake forever: the sentinel sees what it said last time and what came of it.

View Source
const ProbeClip = 8 * 1024

ProbeClip bounds what one probe may put in front of the sentinel.

View Source
const RunKeep = 7 * 24 * time.Hour

RunKeep is how long a run that delivered nothing is kept before the sweep reaps it. A run that delivered something — a note, a task landing, a needs-your-look — is kept like any session.

View Source
const RunningFile = "running"

RunningFile is the marker's name inside the item's own folder (Store.RunningPath).

View Source
const Schema = 1

Schema is the document version every Item carries. Bump it when a field changes meaning; a reader that meets a newer schema than it knows skips the document and says so in the pass.

View Source
const TickWindow = 120 * time.Second

TickWindow bounds ONE pass, wherever the pass is run from. It is generous for a pass that found nothing (a stat per item) and short enough that a wedged probe cannot hold the store's lock against every other window on the machine.

IT IS ONE NUMBER BECAUSE IT IS ONE QUESTION. A window's own goroutine and `codeaf tick` each used to name their own 120 seconds, and Store.Running needs a third reading of the same figure — how long a pass may last is how long a marker may be believed. Three copies of a ceiling is three chances for one of them to move.

View Source
const TrustAfter = 5

TrustAfter is how many clean firings in a row earn an item the top rung. It is exported because it is the denominator a person reads — `earning trust 3/5` — and a page that spelled the 5 itself would be a second answer to a number this package owns.

Variables

View Source
var ErrHeld = errors.New("standing: another codeaf is ticking")

ErrHeld is Tick's answer when another process holds the lock.

View Source
var ErrNotFound = errors.New("standing: no such item")

ErrNotFound is Get's answer for an id that is not here.

Functions

func CostPerRunWord

func CostPerRunWord(spend Spend) string

CostPerRunWord is what one firing of this item costs, in words: "under a cent", "$0.31", or NOTHING AT ALL where nobody measured it.

It is the rate and not the total: Spend.USD over Spend.Fired, which is the only per-firing figure this package can honestly produce. Two things about that are worth a caller knowing, because both make the figure an over-estimate rather than an under-estimate:

  • A CHECK'S MONEY IS IN THE NUMERATOR AND ITS FIRING IS NOT. [Spend.count] adds what a quiet check cost and does not count it as a firing, because a check that came to nothing is not a firing — so an item that looks thirty times to fire twice carries thirty looks' worth of sentinel calls across two firings.
  • THERE IS NO EXACT LAST-FIRING COST. Nothing on the item records what the last firing alone came to, and every exported reader of the daily ledger sums rather than handing back rows, so an average is the honest answer and the only one.

NOTHING FIRED IS NOTHING MEASURED, and so is a run nobody could price: both answer "" rather than "$0.00", by the emptiness law — zero here means nobody could say, never that the work was free.

The word is the FIGURE alone. "a run" is the page's to append, because the same rate reads as "under a cent a run" in a sentence and as a bare cell in a column, and a formatter that decided which was a formatter drawing the page.

func Deliver

func Deliver(sessionDir string, note Note) error

Deliver appends a note to a session's inbox.

func DeliverProject

func DeliverProject(root, workspace string, note Note) error

DeliverProject appends a note to a project's inbox. It is Deliver with the address worked out, so the two inboxes cannot drift on their line shape.

func ExchangesRoot

func ExchangesRoot(root string) string

ExchangesRoot is where an errand said at home keeps its folder BEFORE anything stands: <root>/exchanges/<session id>/. Home's own `ask here` makes one there so that home never lists it, the session lane reads it to know that a conversation IS an errand, and the sweep reaps the ones that came to nothing after RunKeep.

It takes the root rather than hanging off the store because two of those three callers hold a path and not a store, and opening one to ask a question about a directory would create the directory.

func InboxPath

func InboxPath(sessionDir string) string

InboxPath is the inbox inside a session folder.

func IntervalWords

func IntervalWords() string

IntervalWords is Interval the way a person says it — `5 minutes`.

ONE SOURCE OF TRUTH FOR THE CADENCE. Three sentences a person reads name it — the line the conversation says the first time something stands, the settings row's hint, and the manual — and a figure typed into any of them is a figure that will disagree with the timer the day this constant moves.

func IsPermissionLine added in v0.4.0

func IsPermissionLine(line string) bool

IsPermissionLine reports whether a line on an item is about a permission the firing could not get, rather than a QUESTION it put to the person. It is the ONE predicate for that, asked by the store when a person changes an item and by the surface when it decides which door a row takes, so a line cannot be a permission in one place and a question in another.

IT KNOWS THE OLD SPELLING AS WELL AS THE NEW ONE, and that is not tidiness. Builds before this one put the engine's own refusal on the item verbatim, and those items are on disk now: a watch stuck for days carries `refused in a task: default — nobody to ask` and will carry it until it fires again, which an item that has spent its allowance for the day cannot do. A predicate that knew only the new lead would leave every row that provoked this exactly as it was, which is the one outcome that would make the change pointless.

func LastLookLine

func LastLookLine(item Item, now time.Time) string

LastLookLine is what this item did the last time it looked, in one sentence.

It is the line screen 2f draws under a card, and the whole reason it can be written at all is that the item keeps the three facts a firing leaves behind: Item.LastFired, Item.LastOutcome, and Item.LastCheckLine — the field whose own comment says it exists so *a watch that checked faithfully for thirty mornings and found nothing reads differently from one that never ran*.

THIS IS THE ONE HONEST ROUTE TO THAT SENTENCE. The inbox cannot deliver it: a run that came to nothing writes NO note anywhere, by contract (standing.go's QUIET IS THE DESIGN), so the line the design most wants is precisely the line the inbox refuses to carry. It is composed from the item instead, which every surface already holds.

AN ITEM THAT HAS NEVER BEEN LOOKED AT SAYS NOTHING — the emptiness law, and the same nothing a WhenHold rule always says, since nothing examines a rule and it will never have a look to report.

The item's OWN WORDS lead wherever it has them. `LastCheckLine` is written by the thing that looked — "it was the time you asked for", a sentinel's own sentence, "could not check: …" — and a page that replaced it with a phrase of its own would be throwing away the only account of what was actually seen.

func ParseEvery

func ParseEvery(every string) (func(now time.Time) time.Time, error)

ParseEvery reads a WhenEvery's Every: a five-field cron line, or a Go duration of at least one minute. It answers a function from "now" to the next moment.

The two dialects are told apart by a space, which a duration never has and a cron line always does.

func ProjectInboxDir

func ProjectInboxDir(root, workspace string) string

ProjectInboxDir is the folder one project's inbox sits in.

func ProjectInboxPath

func ProjectInboxPath(root, workspace string) string

ProjectInboxPath is that folder's inbox.jsonl.

func ProjectKey

func ProjectKey(workspace string) string

ProjectKey is the one name a workspace has under [projectsDirName]: the path with its separators turned to dashes, exactly the dumb one-way spelling cmd/codeaf gives a session bucket under v3/projects, so a person who goes looking recognises the folder names from the ones they already know.

IT IS NEVER DECODED AND NEVER JOINED TO A BUCKET. Decoding would be guessing which dashes were separators (internal/session's world.go says so about the bucket), and nothing here reads a bucket name or hands one out — every caller on all three sides holds the workspace path itself, so this is a key and not an address anybody has to reverse.

func RopeWord

func RopeWord(item Item) string

RopeWord is HOW MUCH ROPE this item has, in three rungs: RopeAsksFirst, `earning trust 3/5`, or RopeTrusted. It is the only derivation of that answer in the program, and every surface that draws the column reads it.

── THE RULE, AND WHY IT IS THIS ONE ──

Item.Grant is the only record in the whole program of a person letting a standing item act unattended: one sentence, in their own words, of *what acting on this may do without asking* — "open a pull request but never merge it" — written only when they actually said something like it, and its own documentation finishes the thought: **with none, it may only tell them things**. So a grant is the FLOOR: with none, nothing has been agreed and anything past telling somebody things is theirs to allow.

THIS IS THE OPPOSITE POLARITY TO THE ONE THE PLAN'S PARENTHETICAL ASSUMED. docs/design/home-rethink/LANES.md decision 5 glosses "asks first" as *the item's grant names something it must ask for*, which reads the field backwards: a grant names what it may do WITHOUT asking, so a grant is more rope and not less.

AND ABOVE THE FLOOR THERE ARE TWO RUNGS AND NOT ONE, WHICH IS A DECISION AND NOT A DERIVATION. This function argued at length, and until 2026-08-25 correctly, that there was no third rung — quoting standing.go's own *there are no probation counters: the rules are the tenure*. The owner has since ordered the counter ("follow the exact design please", recorded in docs/design/home-rethink/FIDELITY.md item 6), so the counter exists: an item is trusted alone once Item.CleanRuns reaches TrustAfter, and until then the column says how far along it is. The old argument is gone rather than left standing beside code that contradicts it — a comment arguing against the function under it is worse than no comment at all.

The design's own word is *tenured*. THE WORD SHIPPED IS *trust*, and the reason is not the one FIDELITY gives. That doc says the resident-separation test bans "tenure"; it does not — internal/tui3's manual_test.go bans five phrases and that is not among them, and internal/manual/chat/commands.md already ships the words "tenure after" for a settings row. The true reasons are better ones. First, *tenure* is the RESIDENT's own name for this exact mechanism — config.KeyTenureAfter, internal/resident/tenure.go, a charter earning tenure after so many clean firings — and CLAUDE.md's rule about the two products is that vocabulary does not travel between them, whether or not a test happens to catch a given word. Second, it is an employment term for a thing a person thinks of as trust. FIDELITY records this as a deviation in WORDING ONLY, which is right; only its stated cause needs correcting.

AND THE RESIDENT'S THRESHOLD IS NOT THIS ONE. config.TenureAfterAt is a setting over CHARTERS in that other product; TrustAfter is a constant over standing items in this one. They are the same idea about different objects, so this package does not read that setting — a data package reaching into the other product's configuration to answer a question about its own records would be the assumption-carrying CLAUDE.md forbids.

WHAT IS DELIBERATELY NOT IN THE RULE, because both would make the column say something it does not mean:

  • Item.NeedsPerson is a STATE and not rope. It is set while the latest run is stopped on a question and cleared by the next firing, so an item that walked into something this morning would flip its RUNG twice in a day — and how much rope a thing has is not a thing that changes while you are asleep. It does reset Item.CleanRuns, which is a different claim: the streak starts again, and the column says so by counting from nothing rather than by changing what it means.
  • Item.Exceptions narrow WHERE an item reaches — a workspace it skips, a conversation it stays out of — and not what it may do when it does reach. An item that runs everywhere but one repository has the same rope in the repositories it does run in.

func RunCameToNothing

func RunCameToNothing(runDir string) bool

RunCameToNothing answers whether a run folder's own marker says the run delivered nothing, which is the whole of the sweep's licence over it.

EVERYTHING ELSE ANSWERS FALSE: a run that said something, landed something or is waiting for the person; a marker that cannot be read; and a run with no marker at all. A folder that cannot say what it came to is a folder nobody may remove.

Types

type Action

type Action struct {
	Kind       ActionKind `json:"kind"`
	Say        string     `json:"say,omitempty"`
	Brief      string     `json:"brief,omitempty"`
	Acceptance string     `json:"acceptance,omitempty"`
	Model      string     `json:"model,omitempty"`

	// Effort is the rung on the effort ladder this item's firings and its checks
	// ask for (internal/effort), and empty on almost every item.
	//
	// EMPTY IS NOT THE CHEAPEST RUNG, IT IS "NOBODY SAID". An item that says
	// nothing runs at the standing role's own floor — low — because a firing is
	// unattended and repeats forever, and an install dialled deep must not turn
	// every check on the machine into a deep pass. This field is how the one
	// item that genuinely needs thinking says so once and gets it every time.
	Effort string `json:"effort,omitempty"`

	MaxSteps int `json:"maxSteps,omitempty"`
}

Action is what a firing does. Say is read for ActionSay; Brief, Acceptance, Model, Effort and MaxSteps for ActionTask. Either kind may template the probe's evidence into its text with {{evidence}}.

type ActionKind

type ActionKind string

ActionKind is what a firing does.

const (
	// ActionSay delivers one line to the person — into the conversation that
	// asked, and onto home. A reminder, "CI is red", "Priya replied".
	ActionSay ActionKind = "say"
	// ActionTask runs work: the brief is carried out in the item's workspace by
	// a fresh unattended session of its own, bounded by the item's rails, and
	// what it came to is written down with a cost row.
	//
	// IT IS A SESSION AND NOT A WORKTREE, which is the runner's own statement of
	// itself (internal/session's standing_run.go) and worth saying here because
	// this constant used to promise otherwise. A firing is one turn in the
	// project the person pointed it at — it has no branch, no landing to accept,
	// and no hands to give work to.
	ActionTask ActionKind = "task"
)

type Altitude

type Altitude string

Altitude is an item's reach — which work it governs and which surfaces list it. It is decided on the ratification card and it never drifts afterward; widening it is a new card. docs/STANDING-ORDERS.md is the design.

const (
	// AltitudeConversation governs one conversation and dies with it. It
	// requires Origin.SessionID: a reach with no place to reach is an error.
	AltitudeConversation Altitude = "conversation"
	// AltitudeProject governs every conversation and every task in one
	// workspace. IT IS WHAT THE ZERO VALUE MEANS: every item made before
	// altitudes were spelled was workspace-scoped, so an empty altitude reads
	// as this and nothing migrates.
	AltitudeProject Altitude = "project"
	// AltitudeMachine governs everything the person does on this machine. It
	// keeps the existing convention that a machine-wide item's workspace is
	// the person's home.
	AltitudeMachine Altitude = "machine"
)

type Brief

type Brief struct {
	Title  string `json:"title,omitempty"`
	Prompt string `json:"prompt,omitempty"`
}

Brief is the working half of an item: a short title for rows too narrow for a sentence, and the compiled prompt the machinery follows. The person's Words are NEVER rewritten — they are the reason the item exists, and every surface that opens the item shows both halves, words first. An empty Brief reads as the Words themselves. Editing a brief down (narrower, gentler) is free; editing it up (more reach, more action) is a new ratification card — the session lane enforces that law, not this package.

type Entry

type Entry struct {
	At     time.Time `json:"at"`
	ItemID string    `json:"item"`
	// Kind is "check" (a probe + sentinel), "say", or "task".
	Kind string  `json:"kind"`
	USD  float64 `json:"usd"`
	// Run is the run folder, for a firing.
	Run string `json:"run,omitempty"`
}

Entry is one line of the daily ledger: one firing, or one sentinel call, with what it cost. The rails are sums over today's lines.

type Exception

type Exception struct {
	Workspace string    `json:"workspace,omitempty"`
	SessionID string    `json:"sessionId,omitempty"`
	At        time.Time `json:"at"`
}

Exception is one place an item deliberately does not reach: a workspace, or a single conversation. Exactly one field is set. Exceptions are made by the person — from the place ("not here"), or from the item's own record pointing at a place — and never by the machinery. Both gestures write the same fact.

type Idle

type Idle func(for_ time.Duration) bool

Idle answers whether the machine is quiet enough for a WhenIdle: no live presence file says busy, and the last person activity anywhere is older than the given duration. The session lane supplies it from the world reader.

type Item

type Item struct {
	Schema int    `json:"schema"`
	ID     string `json:"id"`
	// Words are the person's verbatim sentence. Permanent anchor; every
	// surface leads with it.
	Words string `json:"words"`
	// Workspace is the REAL project root the item belongs to — the resolved git
	// root, an owned work/ directory, or the person's home for a machine-wide
	// item. It is what home groups by and where a task firing runs.
	Workspace string `json:"workspace"`
	Origin    Origin `json:"origin"`
	When      When   `json:"when"`
	Does      Action `json:"does"`
	Rails     Rails  `json:"rails"`
	// Altitude is the item's reach (see [Altitude]); empty reads as project.
	Altitude Altitude `json:"altitude,omitempty"`
	// Brief is the working title and compiled prompt; empty reads as Words.
	Brief Brief `json:"brief,omitempty"`
	// Grant is one sentence of what acting on this item may do without asking,
	// quoted on the card that ratified it. Empty means say-only, which is what
	// every item made before grants were spelled could do.
	Grant string `json:"grant,omitempty"`
	// Exceptions are the places this item deliberately does not reach.
	Exceptions []Exception `json:"exceptions,omitempty"`

	Status  Status    `json:"status"`
	Created time.Time `json:"created"`
	Updated time.Time `json:"updated"`
	// RetiredWhy says why a retired item retired: "fired", "expired",
	// "stopped by you", or the sentence the last failure left.
	RetiredWhy string `json:"retiredWhy,omitempty"`

	// The quiet half. LastChecked and LastCheckLine are what let a watch that
	// checked faithfully for thirty mornings and found nothing read differently
	// from one that never ran.
	LastChecked   time.Time `json:"lastChecked,omitempty"`
	LastCheckLine string    `json:"lastCheckLine,omitempty"`
	NextDue       time.Time `json:"nextDue,omitempty"`
	// Fingerprint is a WhenFile's last reading.
	Fingerprint string `json:"fingerprint,omitempty"`
	// Previous are the last [Previous] sentinel lines, newest first, each with
	// what came of it.
	Previous []string `json:"previous,omitempty"`

	// The ledger half, kept on the item for the card; the daily ledger is the
	// truth for the rails.
	Runs        int       `json:"runs"`
	LastFired   time.Time `json:"lastFired,omitempty"`
	LastOutcome string    `json:"lastOutcome,omitempty"`
	LastRun     string    `json:"lastRun,omitempty"`
	SpentUSD    float64   `json:"spentUsd"`
	// NeedsPerson is set while the latest run is stopped waiting on the person,
	// with the one line it is stopped on. Home sorts on it.
	//
	// IT CARRIES TWO DIFFERENT THINGS, and a reader has to know which. One is a
	// QUESTION the firing put to the person, in its own words. The other is the
	// line written when a call was refused for want of somebody to allow it,
	// which opens with [NeedsPermissionLead].
	//
	// The pass writes this field whole on the next firing. The PERSON can put
	// down the permission line by changing the item ([Item.ClearNeedsPerson]),
	// and can never lose a question that way. With only the pass, an item that
	// could not fire again — one that had spent its allowance for the day — kept
	// a row on home saying it needed somebody for as long as that stayed true,
	// with no act of theirs able to put it down.
	NeedsPerson string `json:"needsPerson,omitempty"`
	// CleanRuns is HOW MANY FIRINGS IN A ROW CAME BACK CLEAN — fired with
	// nothing waiting for the person and no failure. It is the count the rope
	// column's middle rung is drawn from ([RopeWord]), and a firing that stops
	// on a question or fails puts it back to nothing.
	//
	// IT IS RECORDED AND NOT DERIVED, and that is a deliberate loss of
	// elegance. Everything else this struct keeps about a firing is the LATEST
	// one — LastOutcome is overwritten every time — so a streak simply is not in
	// the record. The one field that looks like it might do is [Item.Previous],
	// which holds the last few sentinel judgments with `" → " + outcome.Kind`
	// glued on the end; reading a streak out of that would mean splitting
	// model-authored prose on an arrow, and it would silently tie a product
	// threshold to [Previous], whose size exists to bound a PROMPT. Somebody
	// trimming that prompt by two lines would quietly make trust unreachable.
	// So the count is kept at the one site that records a firing
	// ([Ticker.fire]), which is the same place [Item.Runs] is kept.
	//
	// AN ITEM WRITTEN BEFORE THIS FIELD EXISTED DECODES AS ZERO and starts
	// earning trust again. That is the conservative direction and the only
	// honest one: nothing on disk says those firings were clean, and a column
	// that assumed they were would be granting rope nobody measured.
	CleanRuns int `json:"cleanRuns,omitempty"`
}

Item is one standing thing. The top half is what the person agreed to and never changes without another card; the bottom half is the item's own present, rewritten on every check.

func (Item) AppliesTo

func (it Item) AppliesTo(workspace, sessionID string) bool

AppliesTo answers whether this item governs the given place: its reach, minus the places the person kept it out of. An exception beats every altitude.

func (Item) ClearNeedsPerson added in v0.4.0

func (it Item) ClearNeedsPerson() Item

ClearNeedsPerson puts down the line about a permission a firing could not get, on the person's own act of changing the item. The acts are the ones this build has: pausing it, stopping it, and letting it go again. What a run before that could not be allowed to do is no longer news about what this item will do next.

IT LEAVES A QUESTION ALONE, and that is the whole of why it reads the line before clearing it. This field carries two different things. One is a QUESTION the firing actually put to the person, in its own words, which is theirs to answer and which nothing may throw away behind their back — pausing a watch is not answering it. The other is the line this build writes when a call was refused for want of somebody to allow it, which goes stale the moment the item changes. Only the second is put down.

IT IS A CHANGE AND NOT A LOOK. Opening an item and closing it again leaves the row exactly as it was, because nothing about the item moved and the reason it stopped is still true.

func (Item) ExceptedFrom

func (it Item) ExceptedFrom(workspace, sessionID string) bool

ExceptedFrom answers whether the person excepted this item from the place.

func (Item) Glyph

func (it Item) Glyph(running bool) string

Glyph is the one character a row leads with, decided here so every surface agrees: ▲ needs you, ● a pass has it in its hands right now — checking it or firing it — ◦ waiting for its time, ∙ paused or retired. Running is the store's knowledge and not the item's, so it is passed (Store.Running).

func (Item) Level

func (it Item) Level() Altitude

Level is the altitude with the zero value resolved to its meaning.

func (Item) Prompt

func (it Item) Prompt() string

Prompt is the instruction the machinery follows: the compiled brief, or the person's words themselves when nobody compiled one.

func (Item) Reaches

func (it Item) Reaches(workspace, sessionID string) bool

Reaches answers whether this item's altitude covers the given place, WITH EXCEPTIONS IGNORED. It exists because a page drawing its dim "not here" lines asks exactly "would this have applied but for the person keeping it out" — and before it was in the contract, the one caller answered that by copying the item and clearing its exceptions, which is the contract's own arithmetic written a second time. Callers pass what they know; an empty sessionID is a place with no conversation (a task's worktree, a firing).

func (Item) Spends

func (it Item) Spends() bool

Spends answers whether anything about this item can ever cost money, and it is the ONE PLACE that question is decided.

EVERY WAKING KIND CAN. A probe runs, the sentinel judges it, a firing works — all three are billed, which is why Item.Validate refuses one of them with a zero budget. A HOLD CANNOT: nothing wakes it, so nothing about it is ever bought. That is why it alone may carry no rails, and it is why the ratification card draws no cost band for a rule — THE EMPTINESS LAW IS THE OTHER HALF OF THE SENTENCE, and a figure nobody can spend is a figure no surface may print.

func (Item) Title

func (it Item) Title() string

Title is what a row too narrow for a sentence leads with: the brief's title, or the words themselves when nobody wrote one.

func (Item) Validate

func (it Item) Validate() error

Validate is what Store.Create and Store.Save refuse on. It is the whole admission law in one place: words, a workspace, a kind with its fields, an action with its text, and rails that are not zero.

type Judgment

type Judgment struct {
	Item     Item
	Evidence string
	Previous []string
}

Judgment is what the sentinel is asked: the person's words, the hint, the evidence a probe gathered, and what the sentinel said the last few times.

type Note

type Note struct {
	At     time.Time `json:"at"`
	ItemID string    `json:"item"`
	Words  string    `json:"words"`
	// Kind is "said", "landed", "needs-you", or "failed".
	Kind string `json:"kind"`
	Text string `json:"text"`
	// Run is the run folder a person can open for the whole story.
	Run string `json:"run,omitempty"`
}

Note is one line of news for a conversation: a firing's delivery, a needs-your-look, a failure. It is appended to <session dir>/inbox.jsonl when the conversation's window is not open, and drained into one "while you were away" fold the next time it is.

func Drain

func Drain(sessionDir string) ([]Note, error)

Drain reads and removes a session's inbox, oldest first. An absent inbox is an empty slice and no error.

func DrainProject

func DrainProject(root, workspace string) ([]Note, error)

DrainProject reads and removes a project's inbox, oldest first — Drain at the project's address.

func PeekProjectInbox

func PeekProjectInbox(root, workspace string) []Note

PeekProjectInbox reads a project's inbox WITHOUT emptying it, oldest first.

It is what a screen calls. Home draws what is waiting every time it redraws, and a read that emptied the file would mean the first draw of a project card consumed the news the conversation was supposed to fold in. Draining is the conversation's act and this is the looking.

type Origin

type Origin struct {
	// SessionID and Transcript name the conversation the card was answered in.
	SessionID  string `json:"sessionId,omitempty"`
	Transcript string `json:"transcript,omitempty"`
	// Exchange is set instead when the item was made from home's own box: the
	// short exchange that produced it is kept under the item's folder
	// ([Store.ExchangeDir]) and not as a project session. Promoting it to a
	// conversation moves the folder and fills SessionID.
	Exchange string `json:"exchange,omitempty"`
	// TaskID is set when a finishing task proposed the item itself.
	TaskID int `json:"taskId,omitempty"`
	// TurnIDs are the turns of the origin session that were about making or
	// changing this item. Home uses them to tell a conversation that was only
	// ever about this item from one that merely contains it.
	TurnIDs []string `json:"turnIds,omitempty"`
}

Origin is where an item was asked for. It is provenance and it is the door home opens: "why did I get this?" opens the conversation that made it.

type Outcome

type Outcome struct {
	// Kind is "said", "landed", "needs-you", "failed", or [OutcomeNothing].
	Kind string
	Text string
	USD  float64
	// NeedsPerson is the one line the run stopped on, when Kind is needs-you.
	NeedsPerson string
}

Outcome is what a run came to.

type Pass

type Pass struct {
	At       time.Time
	Examined int
	Checked  int
	Fired    int
	Said     int
	NeedsYou int
	Skipped  int
	Errors   int
	// Tidied is how many remembered lines the consolidation pass moved, which
	// is zero on all but a handful of passes a day (see [Tidy]).
	Tidied int
	// Notes are one sentence per thing worth saying, for the log.
	Notes []string
}

Pass is what one tick decided, for the wake log and for /status.

type Probe

type Probe struct {
	Command string          `json:"command,omitempty"`
	Tool    string          `json:"tool,omitempty"`
	Args    json.RawMessage `json:"args,omitempty"`
}

Probe is one look at the world: a shell command in the workspace, OR a belt tool with arguments. Exactly one is set. Output is what the sentinel reads, clipped to ProbeClip bytes from the tail.

type Rails

type Rails struct {
	// PerRunUSD is the most one firing may spend, probe and sentinel included.
	//
	// ZERO IS NO LIMIT, and it always was at the place that enforces it —
	// internal/session's standing_run.go has only ever stopped a firing when
	// `PerRunUSD > 0` — so a person who wants a standing order bounded by
	// nothing but the daily rail writes 0 here and gets exactly that.
	// [Item.Validate] used to refuse that number, which made the enforcement
	// site's own contract unreachable.
	PerRunUSD float64 `json:"perRunUsd"`
	// MaxPerDay is how many times it may fire in one local day.
	MaxPerDay int `json:"maxPerDay"`
	// Expires retires the item at that moment. Zero is never. A WhenAt item
	// expires a day after its moment whatever this says.
	Expires time.Time `json:"expires,omitempty"`
}

Rails bound an item. MaxPerDay is mandatory by construction: Store.Create refuses an item that may fire zero times a day, which is an item that would never fire at all. They stay quiet on the proposal card unless the person named money themselves, because the ordinary promise is the machine-wide daily allowance.

type Runner

type Runner interface {
	// Probe runs the item's probe and answers its output, clipped.
	Probe(ctx context.Context, item Item) (string, error)
	// Say delivers one line: into the origin conversation if it is open in
	// this process, else into its inbox, and always onto the item.
	Say(ctx context.Context, item Item, text string) (Outcome, error)
	// Run runs the item's task brief in a fresh headless session at runDir,
	// with the evidence available to the brief, bounded by the item's rails.
	Run(ctx context.Context, item Item, runDir, evidence string) (Outcome, error)
}

Runner is supplied by the session lane. It is how a firing touches the world: a probe run in the item's workspace, a line delivered, a task run headless under the person's banked rules in a fresh session folder at runDir.

type RunningMark

type RunningMark struct {
	PID   int       `json:"pid"`
	Since time.Time `json:"since"`
	// What is [RunningChecking] or [RunningFiring]. A surface says it in those
	// words — "checking now", "firing now" — so it is the person's vocabulary
	// and not a state name.
	What string `json:"what"`
}

RunningMark is what a pass leaves behind while it has one item in its hands: which process is doing it, since when, and which half of a pass it is in. It is the answer Store.Running gives and the whole of what running.go writes.

IT IS A CLAIM ABOUT NOW AND IT IS ALWAYS DOUBTED. A process that was killed mid-firing leaves its marker behind, so every reader treats a dead pid or an age past TickWindow as no marker at all (running.go's markLive).

type Sentinel

type Sentinel func(ctx context.Context, judgment Judgment) (yes bool, line string, usd float64, err error)

Sentinel is one cheap yes/no call. The line is kept on the item and in the log; it is read by the person, so it is one plain sentence.

type Spend

type Spend struct {
	USD   float64
	Fired int
}

Spend is what today's ledger says, for one item or for all.

type Status

type Status string

Status is where an item is in its life. There is no "proposed": a proposal is a card in a conversation, and only a yes makes an item.

const (
	StatusActive  Status = "active"
	StatusPaused  Status = "paused"
	StatusRetired Status = "retired"
)

type Store

type Store struct {
	// contains filtered or unexported fields
}

Root is where everything standing lives: <codeaf home>/v3/standing. Callers pass it in rather than this package reading internal/home, so a test's store is a temp dir and nothing else.

func Open

func Open(root string) (*Store, error)

Open answers the store at root, creating the directory. It holds no handles.

func (*Store) Append

func (s *Store) Append(entry Entry) error

Append writes one entry to today's ledger with O_APPEND.

func (*Store) Applicable

func (s *Store) Applicable(workspace, sessionID string) ([]Item, error)

Applicable is THE ONE RESOLVER: which standing things reach this place, in the order a person reads them. Every seam that asks "what stands over here?" asks this and nothing else, so a conversation, a page and a task's world can never come to three different answers about one item.

IT IS THREE SHELVES AND NOT A SORT KEY. This conversation's own orders lead, then this project's, then the machine's — narrowest first, because the nearest one is the one somebody just made and the one they mean when they say "not here". Within a shelf the most recently touched leads, and that is Item.Updated rather than Created: an order paused, excepted or edited this morning is the one they are thinking about.

ONLY WHAT IS ACTUALLY STANDING. A paused order governs nothing while it is paused and a retired one is over, so neither is here — a list that included them would be saying something is true of this place that is not.

Callers pass what they know; an empty sessionID is a place with no conversation (Item.AppliesTo), which is what a task's worktree and a firing both are.

func (*Store) Create

func (s *Store) Create(item Item) (Item, error)

Create validates, assigns an id when there is none, stamps Created, Updated and Status active, writes the document, and answers the item as written. IT IS ONLY CALLED AFTER A YES.

func (*Store) ExchangeDir

func (s *Store) ExchangeDir(id string) string

ExchangeDir is where a home-made item's origin exchange is kept.

func (*Store) ForWorkspace

func (s *Store) ForWorkspace(workspace string) ([]Item, error)

ForWorkspace is List filtered to one project, the grouping home draws.

func (*Store) Get

func (s *Store) Get(id string) (Item, error)

Get reads one item. A missing id is ErrNotFound.

func (*Store) ItemDir

func (s *Store) ItemDir(id string) string

ItemDir is <root>/<id>/ — the item's own folder: runs/, exchange/, log.

func (*Store) ItemPath

func (s *Store) ItemPath(id string) string

ItemPath is <root>/<id>.json.

func (*Store) LedgerPath

func (s *Store) LedgerPath(day time.Time) string

LedgerPath is the append-only daily ledger the rails are summed from.

func (*Store) List

func (s *Store) List() ([]Item, error)

List reads every item, newest first. An unreadable or newer-schema document is skipped, not fatal.

SKIPPING IS THE POINT. One document written by a build from the future, or one truncated by a full disk, must not be able to stop every other standing thing a person owns from being checked.

func (*Store) LockPath

func (s *Store) LockPath() string

LockPath is the flock one ticker at a time holds. A window takes it for the length of a pass; `codeaf tick` refuses when it is held.

func (*Store) Log

func (s *Store) Log(id, line string) error

Log adds one line to an item's own log: a check that found something, a firing, a pause, a stop. NEVER A LINE PER QUIET CHECK — a watch that looks every five minutes for a year would otherwise leave a hundred thousand lines saying nothing happened.

func (*Store) LogPath

func (s *Store) LogPath(id string) string

LogPath is the item's own one-line-per-event log: checks that found something, firings, pauses. Never a line per quiet check.

func (*Store) Root

func (s *Store) Root() string

Root is the directory the store was opened on.

func (*Store) Running

func (s *Store) Running(id string) (RunningMark, bool)

Running answers whether a pass has this item in its hands at this instant, and what it is doing with it.

FALSE IS THE ANSWER TO EVERY DOUBT: no marker, a marker that cannot be read, a marker with nothing to say, a marker whose process is gone, and a marker older than one pass may last. A surface draws `●` on a true and nothing at all on a false, so every uncertainty here is a glyph that stays still.

func (*Store) RunningPath

func (s *Store) RunningPath(id string) string

RunningPath is the marker's place: <root>/<id>/running, inside the item's own folder beside its runs and its log. It is NOT a `.json` beside the document, because Store.List reads that directory by suffix and a second document shape there would be one more thing every reader has to skip.

func (*Store) RunsDir

func (s *Store) RunsDir(id string) string

RunsDir is where an item's firings live, one session folder each, numbered. They live here and NOT under v3/projects so home never scans them.

func (*Store) RunsSince

func (s *Store) RunsSince(from time.Time) (map[string]Spend, error)

RunsSince sums the ledger from a moment until now, PER ITEM: how many times each thing fired, and what it spent doing so. It is what a card means by `3 runs this week · $0.04`.

ONE WALK ANSWERS EVERY ITEM, and that is the whole reason it answers a map rather than one item's figure. The ledger is one file per local day, so a surface asking item by item would open the same seven files once per item on every card it draws; here they are read once and the caller sums whichever ids its subject owns.

A day with no file is a day on which nothing fired, which is not a failure — the same reading Store.Today takes of the same absence. A day whose file cannot be read at all IS reported, because a total silently missing a day is a rail quoting a number that is too small.

The moment is inclusive and entries before it are skipped: the day file it lands in holds the hours on either side of it.

func (*Store) Save

func (s *Store) Save(item Item) error

Save rewrites one item's document, temp+rename under its flock, and stamps Updated. It validates first.

func (*Store) SetStandingEffort

func (s *Store) SetStandingEffort(id string, rung effort.Rung) error

SetStandingEffort sets how hard one item's firings and its checks think, and is the door a surface calls to move that rung.

It is a read-modify-write under the item's own flock rather than a field on a whole item somebody hands back, for the reason Store.Save takes a whole document and this does not: a surface holding an item it read a minute ago would write back the check results, the spend and the next-due that the ticker has moved since, and quietly undo a firing.

The rung is validated against the ladder here, so a word nothing can parse is refused at the door instead of landing on disk and reading back as absence forever. Absence itself IS settable — "off" and "" both clear the field — and clearing it puts the item back on the standing role's own floor.

func (*Store) Today

func (s *Store) Today(itemID string, now time.Time) (Spend, error)

Today sums today's ledger. An empty itemID sums everything, which is what the daily rail reads.

func (*Store) WakeLogPath

func (s *Store) WakeLogPath() string

WakeLogPath is where every pass writes one line, and where "last wake" is read from.

type Ticker

type Ticker struct {
	Store    *Store
	Sentinel Sentinel
	Runner   Runner
	Idle     Idle
	// Tidy is the consolidation pass over what is remembered, run once at the
	// end of a pass and only when the session lane supplied one.
	Tidy Tidy
	// DailyRailUSD is the ceiling on everything standing spends in one day,
	// from settings. Zero is no rail, which the card says out loud.
	DailyRailUSD float64
	// Now is the clock, injectable for tests.
	Now func() time.Time
}

Ticker runs passes. One is built per process that may tick — a window, or `codeaf tick` — and Ticker.Tick is what both call.

func (*Ticker) Tick

func (t *Ticker) Tick(ctx context.Context) (Pass, error)

Tick runs one pass: take the lock (or decline), walk every active item, wake the due ones, judge, fire within the rails, write the wake log, release. It never blocks on a person and never runs past ctx.

type Tidied

type Tidied struct {
	Merged     int
	Superseded int
	USD        float64
}

Tidied is what one consolidation pass over what is remembered came to (internal/session's memory_consolidate.go): how many lines were merged into one clearer line, how many were retired in favour of one that replaced them, and what the single call cost.

It is declared HERE rather than in the session lane because the two things a pass owes the person about it — the money on the day's rail and the line in the wake log — are both this package's business.

func (Tidied) Changed

func (t Tidied) Changed() int

Changed is how many remembered lines the pass actually moved. Zero is a pass that read fifty lines and decided every one of them was already right, which is the ordinary answer.

func (Tidied) Line

func (t Tidied) Line() string

Line is the pass's own account of a tidy, THE EMPTINESS LAW APPLIED: a part that is zero is absent rather than printed as a zero, and a pass that changed nothing and spent nothing is no line at all.

type Tidy

type Tidy func(ctx context.Context) (Tidied, error)

Tidy is the one piece of work in a pass that nobody armed: the call that reads what is remembered and answers with the duplicates merged and the replaced lines retired. The session lane supplies it, and a NIL Tidy is the whole of "memory is off on this machine" — a capability that cannot work is absent rather than present and refusing.

type Timer

type Timer struct {
	// contains filtered or unexported fields
}

Timer is the Watch this package installs. It holds no state of its own: everything it answers is read from the filesystem at the moment it is asked.

func NewWatch

func NewWatch(options WatchOptions) (*Timer, error)

NewWatch resolves the defaults and validates the immutable inputs once.

func (*Timer) Drift

func (w *Timer) Drift() (WatchDrift, error)

Drift reads that. A machine with no definition answers a zero value and no error: nothing to repair is not a fault.

func (*Timer) Install

func (w *Timer) Install(ctx context.Context) error

Install writes the exact definition and asks this user's operating system to use it now. Installing over an existing one repairs drift and is safe.

func (*Timer) Status

func (w *Timer) Status() (WatchStatus, error)

Status derives everything: installation from the definition's own bytes, the last wake from the wake log's last line, the next check from the cadence.

func (*Timer) Uninstall

func (w *Timer) Uninstall(ctx context.Context) error

Uninstall stops the timer and removes its definition. A definition that is not there is already uninstalled, and says so without touching the host.

type Watch

type Watch interface {
	Install(ctx context.Context) error
	Uninstall(ctx context.Context) error
	Status() (WatchStatus, error)
}

Watch is the OS timer: a launchd agent or a systemd user timer running `codeaf tick` every Interval. The core lane builds it on internal/watchdog's shape with its own unit names, so it can coexist with v1's.

type WatchDrift

type WatchDrift struct {
	// Present is a definition file on disk, whatever it says.
	Present bool
	// Executable is the program that definition names, or empty when the file
	// could not be parsed for one. It is read for the log line the repair
	// writes; nothing decides on it.
	Executable string
	// Gone is Executable naming a path with no program on it — the build was
	// deleted, or moved and the old path left empty.
	Gone bool
	// Stale is a definition for THIS timer's home that this launch may repair:
	// the program it names is gone, or the bytes are not what this build writes
	// for that pair (an older build wrote them, or somebody edited them).
	//
	// IT IS NEVER TRUE FOR A TIMER THAT IS SOMEBODY ELSE'S: one naming another
	// home, or one naming another program that can still run. Repairing either
	// would be this launch taking the machine's one timer away from a pair that
	// was serving it, which is the flip this law exists to end. Turning the
	// settings row off and on is the one hand that moves it on purpose.
	Stale bool
}

WatchDrift is what Timer.Drift answers: a definition already on this machine, and whether the launch that asked may put it back.

IT IS A SEPARATE READING FROM WatchStatus ON PURPOSE. Status answers the only question a person asks — is anything checking — and a definition pointing at a binary that has been deleted is not checking, so Status says no. This says WHY it said no, which is a different question with exactly one caller: the launch that repairs the drift (Timer.Install rewrites it).

type WatchOptions

type WatchOptions struct {
	Platform   string
	HomeDir    string
	Executable string
	// StateRoot is the home the timer ticks — CODEAF_HOME when set, the
	// login's default otherwise — and the half of the pair a test has to name
	// for a home it is not running under. Empty is this process's own.
	StateRoot string
	UID       int
	Runner    WatchRunner
	// WakeLog is where passes leave their one line each — [Store.WakeLogPath].
	// It is a path rather than a store because the timer has no business
	// reading items, only proof that something woke.
	WakeLog string
	Now     func() time.Time
}

WatchOptions makes every host-specific input injectable, so a test can install a timer for a machine it is not running on. Empty values are filled from the current process by NewWatch.

type WatchRunner

type WatchRunner interface {
	Run(ctx context.Context, name string, args ...string) error
}

WatchRunner is the whole process boundary the timer needs. Tests hand it a recorder; only execRunner reaches os/exec.

type WatchStatus

type WatchStatus struct {
	// Installed means the OS timer's definition on disk matches byte for byte
	// what this build writes.
	Installed bool
	LastWake  time.Time
	NextDue   time.Time
}

WatchStatus is what /status prints, derived and never asserted.

type When

type When struct {
	Kind  WhenKind `json:"kind"`
	Words string   `json:"words,omitempty"`
	// At is the one moment of a WhenAt.
	At time.Time `json:"at,omitempty"`
	// Every is a WhenEvery's rhythm: a five-field cron line ("0 9 * * 1") or
	// a Go duration ("20m", "2h"). [ParseEvery] is the one reader of it.
	Every string `json:"every,omitempty"`
	// Glob is a WhenFile's pattern, relative to the item's workspace.
	Glob string `json:"glob,omitempty"`
	// IdleFor is how quiet the machine must have been for a WhenIdle.
	IdleFor time.Duration `json:"idleFor,omitempty"`
	// Probe is a WhenProbe's look at the world, taken every ProbeEvery.
	Probe      Probe         `json:"probe,omitempty"`
	ProbeEvery time.Duration `json:"probeEvery,omitempty"`
	// Hint tells the sentinel what a yes looks like, in the model's words at
	// proposal time: "yes when any run on main shows conclusion=failure".
	Hint string `json:"hint,omitempty"`
}

When is what wakes an item. Exactly the fields its Kind names are read; the rest are left empty and never consulted. Words are always kept: they are the person's own cadence or condition, and every surface speaks them back rather than the spec.

type WhenKind

type WhenKind string

WhenKind is one of the six shapes of an item — five ways to be woken, and one that never wakes (WhenHold). The list is closed.

const (
	// WhenAt fires once, at a moment, then retires. A reminder is this.
	WhenAt WhenKind = "at"
	// WhenEvery fires on a rhythm — a cron line or an interval — forever.
	WhenEvery WhenKind = "every"
	// WhenFile fires when files matching a glob change (a fingerprint of
	// names, sizes and mtimes, as v1's file watch did).
	WhenFile WhenKind = "file"
	// WhenIdle fires when the machine has been quiet — no window busy, no
	// task running anywhere — for [When.IdleFor]. "Learn this later" is this.
	WhenIdle WhenKind = "idle"
	// WhenProbe fires when a probe's output, judged by the sentinel against
	// the person's words, says yes. Anything the belt can do is a probe.
	WhenProbe WhenKind = "probe"
	// WhenHold never wakes. A rule — "always use tabs here", "never touch the
	// public API" — has no moment, no rhythm and no probe: its whole work is
	// done at birth, riding into the world of every conversation and task it
	// reaches (docs/STANDING-ORDERS.md, the birth seam). The pass walks past
	// it; it cannot fire, so it cannot spend, so it alone needs no rails and
	// no action.
	WhenHold WhenKind = "hold"
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL