C-11: Context operations
Summary
Every change to a project's context is an operation: an actor, a kind, a subject, the evidence behind it, the governance it was made against, and a timestamp from Go's clock. Operations are appended to the workspace's operation log (C-03) and never edited. A status change is a new operation naming the earlier one.
A suggested rule takes effect at once as advice. Checks report it with no weight in the score, so a suggestion shows up wherever a rule would and can never fail a check. A suggestion is established when at least one signal from a person backs it and nothing open contradicts it: a person keeping it, a correction toward it, or the change reaching the default branch. Settling is a function of the log alone, so every machine holding the same operations reaches the same answer. An established rule fails a check unless it is marked advisory, and is written into the subsystem that already reads it: the terms store or the content memory. The log is the history and the source of suggestions; established rules live in the stores.
contextop.PersonDecides defines the permission policy. An agent or a tool
may observe, record a correction, and withdraw its own suggestion in the
session that recorded it. Any actor may record a check run (check), which
changes nothing the context holds. Only a tool records evidence (signal) and
the establishments settling derives from it (establish). Only a person keeps,
edits, drops, imports, widens, or resets the context to an earlier point.
The command line splits along the same line. kapi context carries a
person's verbs (<path>, search, review, reset, sync) and lists the
two an assistant uses, note and log, under a heading of their own, For
assistants. Maintenance of the store itself (import, export, rebuild,
locales) is a separate command, kapi store.
Context
Projects accumulate context through observations and corrections made during normal writing work. Agents can propose rules with evidence; people review those proposals before checks enforce them.
Notes about what the files do and what a person changed become suggestions. A person keeps a suggestion in review before checks enforce it.
Suggestions must not fail builds before review. Operations must also be
attributable and reversible. The design follows the unit-decision model in
core/state (C-04): an append-only ledger
and a shared policy function for all writers.
Decision
An operation is the unit
contextop.Record is one recorded change:
type Record struct {
ID string // the operation id, the same in every log that holds it
Short string // its first ten characters, as a log line shows it
Project workspace.ProjectKey
Actor Actor // person | agent | tool, a name, an agent's session
Kind Kind // observe | correct | import | edit | keep | drop | withdraw | reset | widen | signal | establish
Subject Subject // a term rule, a content-memory pair, a note
Correction *Correction // the wording before and the wording after
Evidence []Evidence // file, unit, quotation: where this was seen
Basis Basis // the governance in force when it was recorded
Scope Scope // where it applies, and for how long
Target string // the operation this one acts on
Before string // the first operation a reset sets aside
Note string // why, in the recorder's words
At time.Time
Status Status // folded from the log, never stored
ContestedBy []string // the operations on the other side of a disagreement
Established bool // kept, imported, written, or settled on a person's signal
Signal *Signal // the evidence a signal carries
Because []string // the signals an establishment rested on
Standing *Standing // the counts for and against, folded from the log
}
The kinds, persisted as context.<kind>, divide into those that carry a
subject, those that act on one, and check, which does neither.
observerecords something somebody noticed. Stated in prose, it is a note, such as who the documents address. Stated as the form the project uses for a word and the forms it avoids, it is a term rule.correctrecords that someone changed wording from one form to another at a location, carries both wordings inCorrection, and may carry the term rule the change implies as its subject.importrecords one context file a person read into the project's stores, andeditrecords a rule a person wrote directly, withkapi applyor by editing the voice profile. Both are established from the start.keep,drop,withdrawandwidenname an earlier operation and say what became of it.resetnames a point in the log, the first operation it sets aside, and rewinds the project's context to how it stood there (below).signalrecords evidence about a suggestion that nobody wrote down by hand, andestablishrecords that settling established one (below).checkrecords one run of the project's checks for an assistant: the Claude Code Stop hook runningkapi check --shipas a turn ends (CheckRun.Hookstop), or its PostToolUse hook runningkapi check <file>on the one file an edit wrote (edit). ItsCheckRunholds the files the turn edited, the failing findings by file, rule id and quote, and whether the run was clean. Its id is derived from the session and the event the run answered: for a Stop hook run, the id of the transcript's last entry, which Claude Code writes after every Stop hook, so each Stop is a run of its own; for an edit hook run, the id of the tool call. A hook running twice for one event records one operation. A check run stays on the machine that recorded it: sharing the context leaves it out (projector.LocalKinds). A failing finding that one run reports and the session's next run on the same file does not find counts as caught. A Stop hook run covers every file; an edit hook run covers its one file and the findings whose rule the writing check reports (CheckRun.Covers), so a finding on another file waits for the next run that checks that file;host.SessionOutcomesderives the count from the log and nothing stores it.
contextop.Ledger derives status by replaying the log in order and applying
subsequent operations to each subject:
| Status | What it means | Answers |
|---|---|---|
suggested | nothing has acted on it | advises |
established | a person kept, imported or wrote it, or a person's signal settled it | fails a check unless advisory |
contested | it disagrees with another rule, named in ContestedBy | advises |
withdrawn | its author took it back in the session that recorded it | silent |
dropped | a person set it aside, as a suggestion or as a rule in force | silent |
reset | a reset set it aside | silent |
An observation states a term rule by its forms
kapi context note --term Quickcast --instead-of "Quick cast" records the
form the project uses and a form it avoids. contextop.AvoidedForms derives
the rest deterministically, with no model: the --instead-of forms, then the
spacing, hyphen and capitalisation variants of a compound. The rule above
avoids Quick cast, Quick-cast and QuickCast.
A compound's parts come from the term's own spaces, hyphens and case changes,
or, for a closed word such as Quickcast, from an --instead-of form that
breaks it. A lower-case phrase such as content memory yields only
content-memory, without generating a closed compound. A word with neither has no
parts to vary, and its rule avoids what --instead-of lists. The first avoided
form becomes the rule's term and the rest its forms. A rule whose avoided forms
differ from the term only in case matches as written, so the rule about
quickcast never fires on Quickcast.
Suggestions advise, established rules bind
A suggestion or a contested rule is projected into checks as a suggested rule
set (profile.TermRuleSet.Suggested). The matcher raises every hit against such
a set with Fails false, whatever the rule declares, and stamps Suggested on
the hit, the finding and the diagnostic. A suggested finding weighs zero in the
score and counts into Summary.Reporting, so a suggestion cannot fail a check.
The flag is what a surface reads to show the finding as a suggestion rather than
a broken rule, and HitsToFindings words the message accordingly: Suggested
rule about "utilise", not yet established.
Suggested-rule checks run before and independently of the terms analyzer.
This allows projects without a voice profile or terms store to report
suggestions. Suggested findings have zero scoring weight and do not affect
gates or the analyzer's canary validation.
Keeping a rule writes it into the project's stores. Subsequent checks use the rule's advisory setting: violations of an established rule fail unless it is marked advisory.
Suggestions settle by evidence
contextop.settle runs inside the fold, after every person's decision has been
applied and before disagreements are marked. It groups the suggestions that
state one rule (the same form to use, a form to avoid in common, at points that
meet) and reads the evidence for the group from the log:
| Signal | Recorded as | Effect |
|---|---|---|
| A person keeps it | keep | Establishes it at once |
| A correction toward it, by a person or by an agent recording what the person changed | correct | Person signal |
| The change reached the default branch | signal with source merge | Person signal |
| Another session records the same rule | observe | Standing only |
| The content writes the preferred form | signal with source usage | Standing only |
| An agent's applied edit writes the preferred form | signal with source applied | Standing only |
| A correction away from it | correct | Against |
| Its author withdraws it | withdraw | Against |
| The content moves to a rejected form after it was recorded | signal with source usage | Against |
The group shares one standing, and each member is settled on the person
signals seen where that member holds. A correction counts for a member whose
scope covers the point the correction was recorded at (seenWithin), and a
merge signal names the member whose scope the change's files sat in, so it
counts for that member alone. Suggestions at a point with no coordinates meet
every other point, so a group can span the help pages and the legal terms
through one of them; the evidence is still read per member, and a correction
in the help pages establishes the help pages' suggestion and leaves the legal
terms' suggestion as it was.
A member with a person signal and nothing against it is established. A member with a person signal and a signal against it, or a rival rule that disagrees with it, is contested and waits for a person. A member without a person signal stays a suggestion whatever its standing, and time alone settles nothing. The correction a suggestion was drawn from is not evidence for that suggestion alone. A person dropping the rule sets aside what was recorded before the drop, together with the evidence gathered before it.
An agent's suggestion applies only where it was seen until a person widens it
in review (placed). One that names no file it was seen in has no place, so
settling never establishes it; a person keeps it and chooses where it holds.
Settling records an establish with the suggestion's own scope and never
changes it: evidence establishes a rule where it was seen and widens nothing.
When the log supports establishing a suggestion that no establish names yet,
Ledger.Settle records one: actor tool settle, the suggestion as its target,
and the person signals it rested on in Because. Its content address is
derived from the target and that evidence, so two machines settling the same
log record one operation. The host lands the rule the way a keep does, and a
later signal against it takes it back out of the store through the same
reconciliation a contesting correction uses. Because the fold reads the
evidence again on every read, an establish whose evidence fails to hold
stops binding, and the outcome is the same whatever order two logs were merged
in.
Record.Standing carries the counts behind each suggestion, and every surface
shows them as counts, never as a score: seen in 3 sessions · 14 of 15 uses in docs/ · merged in #412.
kapi context sync --merged <range> records the merge signal
(host.App.SettleContext, which SyncProjectContext runs between its pull and
its push, and alone in a project that shares its context nowhere). It reads the
diff the range made inside the project, and for each suggestion whose scope
covers a changed file it counts the added lines that write the preferred form
and the removed lines that held a rejected one. A suggestion with either count
above zero gets a signal naming the commit, the pull request (read from a
squash or merge commit's subject when not given) and the merger. The signal's
address covers the suggestion, the commit and the counts, so settling one range
twice records nothing new. CI runs it after a merge or a push to the default
branch (Convergence in CI).
The other signals are recorded where the content is read. A whole-project
kapi check and a kapi up run count, for each suggestion and each
established rule whose scope covers the content, the uses of its preferred form
and of the forms it avoids in the source they read, and record a usage signal
as tool check; a check of named files or of a diff records none, because part
of the content says nothing about how the project writes. A rule's counts are
recorded only when they differ from the latest recorded for it, so a run over
unchanged content adds nothing to the log. For a suggestion the
count is standing. For an established rule, a later count that writes a
rejected form more often than the count taken when the rule came into force is
drift, which the review digest reports (S-07).
An agent's content edit applied through kapi apply or the apply_edits tool
records an applied signal as tool apply for each suggestion whose preferred
form the edit writes, naming the agent's session.
Disagreements are contested until a person chooses
Two rules disagree when they share a form to avoid and name different forms to
use, or when one avoids the form the other prescribes. Two rules disagree
only if both can apply at the same point and instant. Rules in different
projects, rules at coordinates that cannot overlap, and rules whose lifetime
windows share no instant never disagree. A rename that ends on the day its
replacement begins is therefore two rules in sequence.
The fold marks a disagreement contested and names the other side in
ContestedBy:
- Two suggestions about the same word are both contested, each naming the
other. Both advise, and neither can be kept until a person chooses one:
host.App.ChooseContextSide(kapi context review --choose) drops the rivals and keeps the chosen side, one operation per step. - A suggestion that contradicts an established rule is contested by the rule, and the rule stays in force.
- A person's correction that reverses an established rule contests the rule,
and the rule contests the correction. The rule is taken out of the terms
store and reports instead of failing, until a person keeps it again or drops
the correction, so a person's own edit never fails their build. Keeping the
correction is a choice like any other side's:
review --keeprefuses it and names the rule, andreview --choosedrops the rule and keeps the correction.
kapi context review --session <id> --keep all keeps everything one session
suggested and leaves each contested suggestion for later, naming the other
side.
Review is where a person decides
host.App.DecideContextReview takes one round of decisions
(ContextReviewRequest: Keep, Drop, Choose, Session, WidenTo,
Replacement, Advisory, ValidFrom, ValidTo, Note) and records them in a fixed order: the
choice, then the drops, then the keeps, so a keep never meets a rival the same
round drops. kapi context review reaches it two ways. In a terminal it walks
the digest one item at a time and asks; elsewhere it prints the digest, and
the decisions arrive as flags. Both record the same operations.
A digest item says what a person may do with it: Droppable holds for a
suggestion and for a rule in force, and Widenable for a rule in force that
can apply more widely. Dropping a rule in force records a drop and takes the
rule back out of the stores keeping wrote it to
(host.App.DropContextOperation). There is no undo for a single decision:
changing your mind about a rule is a new decision, recorded beside the one it
replaces.
Keeping writes through the existing appliers
kapi apply is the one write verb, and its asset operations write the project's
terms store or content memory (C-08,
C-09). A word rule is a term, so keeping one writes a
concept, marked advisory when the rule is. Keeping a
rule builds the same change-set entry and runs the same applier, so retrieval,
checks, drafting, the governing fingerprint and every machine that pulls all see
it with no second code path. A term rule with several forms to avoid lands each
form, and a form that differs from the form to use only in case stays out of
the store, which folds case.
A note in prose states no rule, so keeping one writes to no store. The keep
operation in the log is the only record of it. Every by-location answer whose
point the note's scope and window cover lists it under Established notes
(C-06). The rules file of each folder at such a point
lists it under Notes a person kept
(C-06). The keep reports the operation log as
where the note was written. The widest scope a note can have is its project,
because the workspace's rule store holds only term rules.
ContextWidenOptions offers a note only its axes, and a request to widen a
note to the workspace is refused before anything is recorded.
Dropping an established rule reverses it against the same stores: the term is deleted from the terms store, or the pair from the content memory. Other terms in the concept are preserved because deletion targets the term.
Every one of these store writes goes through the projector
(C-03),
so the log holds each rule twice over: the context operation that decided it,
and the terms.write, memory.write, voice.write or rules.write operation
carrying the rows the decision wrote, whose origin names the operation it
carries out. The context operations fold into statuses; the store operations
replay into the stores.
A call that writes a rule to a store, removes one, or keeps or drops a note
also refreshes the project's rules files when it ends. The AGENTS.md and
CLAUDE.md an agent loads then list the rules and notes in force
(C-06).
kapi apply records one edit operation for each term or content-memory
operation it applies, established from the start and attributed to the person
who ran the command. The transport stamps the actor and a change set has no
field for one: kapi apply stamps the actor the environment names, as every kapi context command
does, and apply_edits stamps the calling agent and the server's session. The
policy refuses a term, content-memory or recipe entry from an agent before
anything is written, so an agent records a note instead.
Reading a checkout's context files is an operation too
kapi store import is the one command that opens a context file in a
checkout (C-01). What it reads it records: one
import operation per file, established from the start, with an actor of kind
person, carrying the
file's project-relative path and the SHA-256 of the bytes it read as evidence.
kapi context log then shows an import beside every other change to the
project's context, and a reader can tell which rules came from a file and which
from a decision somebody took. What the file held travels in the log as well:
each file is read as one projector batch, recorded as one store operation per
store it reaches, with the parsed concepts, entries or profile in a blob when
they are large. A second machine replaying the log therefore gets the imported
content, and needs neither the file nor its bytes.
A person runs it. The policy function below refuses an agent's import, naming
the command for the person to run, because reading a checkout's files puts them
in force for everyone working in the project. kapi voice edit records one
edit operation whose note says the profile was edited whole.
The skip stamps live in the workspace, keyed by the normalized checkout path and
the file's project-relative path, so two checkouts of one project never skip
each other's files and a second import of unmoved bytes reports that it read
nothing. --force reads regardless.
host.ContextFilesUnread detects a checkout with context files and an empty
project context store. It checks file metadata and whether the store contains
any concept, memory entry, voice profile or decision, without reading the
context files. It returns the file list and import command.
Every surface uses this result (S-02,
S-03). kapi check reports it as a
configuration warning that affects neither scores nor verdicts.
Scope is set by evidence, and widened deliberately
An operation is scoped to the point its evidence was seen at: the coordinates
ResolveGovernanceFor resolves for the file it was seen in
(C-02), which for a new project with no
declared axes is that project. A rule answers where its scope covers the point
being checked, so a rule seen in one product's reference pages says nothing
about another product's tutorials.
A rule lands in the terms store at its point too. Keeping or settling a rule
whose evidence was seen where a profile governs writes its concept with that
profile in terms.PropProfile and the point's coordinates in
terms.PropCoordinates (channel=app,product=quickcast). kapi store import
scopes the word rules in a profile's own voice or terms file under
.kapi/profiles/<name>/ to that profile. Both writes go through the projector
like every other store write. projectConcepts keeps, at a point, the concepts
that hold there (terms.AtPoint): scoped to no profile or to the profile
governing there, and to no coordinates or to coordinates the point sits at. A
rule kept from an app's strings therefore says nothing about the same
product's documentation, and checks, retrieval and the translation tools all
read the same filter. The project-wide answer, asked with no point, lists every
concept.
An answer for a point names the rules held at the project's other places and not there, as the wording that stays correct (C-06). Without that line an agent told to rename something everywhere renames it at a point the rule never reached, and its note of the rename there would then be a suggestion at that point too.
Keeping is also the moment a person may widen, with
kapi context review --keep <id> --widen-to <where>; the same flags on a rule
already in force record a widen. The steps nest:
--widen-to | What the rule's scope drops | Where it then holds |
|---|---|---|
an axis, such as channel | that axis; product drops the profile too | every value of the axis, at the rest of the point |
project | the profile and the axes it derives (product, channel) | every point of the project |
workspace | the profile, its axes and the project | every project of the workspace |
An axis no profile derives, such as a brand the recipe declares, stays through
project and workspace, so a rule widened from one brand's project holds
wherever that brand does. Widening moves the rule's concept with it: the
concept a keep wrote is rescoped to the wider point in the same write.
Workspace-wide rules use workspace.Rule rows in workspace.db. Each row
contains an id, kind, source project and opaque JSON payload. This keeps
workspace storage independent of the rule type. Project-specific terms take
precedence over workspace rules for the same term, and kapi context search
reports a workspace rule beside the project's terms, marked as holding across
the workspace.
A widened rule is written through the projector as a rules.write operation
recorded under the project that decided it, so it travels like every other
operation of the project: a push carries it, a pull or an import applies it to
the receiving machine's workspace, and a checkpoint holds the project's widened
rules. Narrowing it, by dropping the rule or contesting it, travels the same
way.
Coverage reads the project's typed axes
A rule applies at a point when its scope covers the point. The project's
axes: block defines coverage on each axis
(C-02).
Scope.CoversIn(space, point) is true when, for every axis the scope names,
the point's value sits within the scope's value (axis.Space.Contains). A rule
scoped to market: nordics applies at market: no and not at market: de. A
rule scoped to age: 13-17 applies at 14-16 and not at 18-. On a geo axis
the comparison uses the project's gazetteer (C-12), so a rule
at market: no applies at market: oslo when the recipe declares Oslo within
the county no-03. An axis the space does not type compares by equality, and
Scope.Covers(point) is CoversIn(nil, point). ResolveRequest.Space passes
the space to Resolve. Every host filter at a point uses the same space: the
check's suggested and binding rules, the by-location answer's notes, the usage
counts and merge signals used in settling, and the terms store's concepts
(terms.AtPointIn, through StoreSelection.Holding).
The fold also uses each project's space. Ledger.WithAxes supplies a function
from a project to its axis space. The fold stamps every record with its
project's space before settling and contest detection compare scopes. Two
rules of one project overlap where their values on every shared axis overlap
(axis.Space.Overlaps). A suggestion at market: nordics overlaps one at
market: no. A suggestion at market: greater-oslo overlaps one at
market: oslo, and age: 13-17 overlaps age: 16-20. A correction recorded
at market: no counts for a suggestion scoped to the Nordics (seenWithin
calls CoversIn). Rules of two projects can overlap only when one is widened
to the workspace, and they compare by equality.
The host builds every ledger through one helper (App.contextLedger). It
types a project's axes from the recipe of a checkout that the workspace
registry lists for the project on this machine, and caches the result by the
recipe's modification time. Recording an operation from a checkout registers
that checkout. Every later reader of a project recorded on this machine
therefore uses its typed axes, and the digest, the log, the check and the atlas
fold the log in the same way. For a project with no checkout on this machine,
coordinates compare by equality.
A submission may narrow its point
A note, a correction and a suggested rule take the point that their evidence
resolves to (basisAt). A submission may also give a point explicitly:
kapi context note --point axis=value (repeatable), point on MCP
context_note, and ContextObserveRequest.Point and
ContextCorrectRequest.Point in the host. The explicit point can only narrow
the evidence's point. For each axis it names (narrowScope):
- the value must be legal for the axis as the recipe types it;
- where the evidence's point already sets the axis, the new value must sit
within that value. Evidence from the Nordic pages can narrow a note to
market: no, and cannot move it tomarket: deor widen it toeurope; - where the evidence's point does not set the axis, the axis is added;
productandchannelare rejected, because they come from the file the evidence is in. The error names that file as the way to set them.
Only a person can widen a rule, in review (--widen-to). The error for a
widening point names that command.
A rule holds for a window of time
Scope carries a lifetime window, ValidFrom (inclusive) and ValidTo
(exclusive), the half-open model that profiles, terms and graph edges use.
Each bound is a bare date or an RFC3339 instant, parsed like a profile's window
(project.ParseValidityBound), so a valid_to date excludes that whole day. A
window is set when a rule is recorded (--valid-from and --valid-to on
kapi context note, valid_from and valid_to on MCP context_note) or when
a person keeps it (kapi context review --keep <id> --valid-from/--valid-to,
ContextKeepRequest). In the fold, a keep with a window replaces the rule's
window, in the same way that a keep with a scope replaces its scope.
Scope.InForce(at) reports whether the window includes an instant.
ResolveRequest.At is the instant a resolution is computed for. A rule outside
its window does not apply, in the same way that a profile outside its window
does not govern (C-02).
A check resolves at the run's governance instant (App.GovernanceInstant).
The zero instant is the as-declared view, in which every window holds. Keeping
a rule with a window writes the window onto the resulting term (terms.Term's
Validity). terms.InForceAt drops a term outside its window at the point's
instant, so a check, the by-location answer and drafting all stop using the
rule at the same moment.
A window describes the rule. It never describes the operation. No agent
surface accepts a recorded time: the log's clock stamps At on every
operation, and a window only states when the recorded rule holds.
The log can be read as of an instant
Ledger.RecordsAsOf(ctx, at, f) folds only the operations the log accepted at
or before at (Record.At), in id order, with workspace.SetAside computed
over that prefix, and returns the records the filter matches. A keep recorded
after the instant is excluded, and a reset after it sets nothing aside, so the
fold returns the status each subject had at that instant. The instant is a
ledger argument rather than a Filter field because it changes which
operations are folded. A filter only narrows what a fold returns.
Ledger.Histories(ctx, f, limit) reads the log once and folds it as of every
distinct instant at which the log accepted an operation of the filtered
project. For each operation that has a subject, it reports the initial status
and every later change, each at the instant of the operation that made it.
Keeps, drops, contests, settling and resets are all included. The cost grows
with the square of the log length, so it is bounded. Past limit instants
(HistoryLimit, 500, when none is given), each subject reports only its first
and current status, and the result marks the history as incomplete.
The atlas reads the whole context at once
host.App.ContextAtlas (kapi context atlas, the desktop's Explore view)
returns a project's whole context in one document. It contains each rule, note
and content-memory pair the log records, with its history from
Ledger.Histories, and each concept in the terms store. On request it also
contains content cells that count the project's units by collection, language
and state. Every record has the coordinates of its point and the derived axes:
family, actor, locale, region, and the project when the atlas covers every
project. Every operation is an event on the recorded clock, and window
boundaries are events on the effective clock. The atlas also includes the
places its geo values name and the places that contain them, so a map can draw
and filter them by the gazetteer's rules. The document is a contract with the
views that draw it, specified in
Context atlas.
One policy function
type Transition struct {
Actor Actor
Kind Kind
Subject SubjectKind
Target Record
Targeted bool
Widening bool
Editing bool
}
type Policy func(Transition) error
PersonDecides is what ships. An agent or a tool may observe and record a
correction, and each takes effect as a suggestion. A tool records evidence and
the establishments settling derives from it; an agent may record neither,
because an agent's statement is a suggestion and never evidence for one. The author of a suggestion
may withdraw it in the session that recorded it, which is how a session cleans
up after itself. Everything that turns advice into a rule, or sets another
actor's advice aside, belongs to a person: keeping, editing what another actor
suggested, importing, writing a rule directly, dropping a suggestion or a rule,
widening, and resetting.
drop and withdraw both set something aside, with different permissions. A
person drops a suggestion or a rule in force, and a dropped rule leaves the
stores. Its author withdraws a suggestion, and only while it is still one.
Widening is a property of the transition rather than of the verb, so the check
covers every route to it. Ledger.Append raises Widening for a widen and
equally for a keep that names the workspace, and a Widening transition is
refused for any actor but a person. Two sessions of one agent count as two
parties, so an agent acts on its own suggestion and on nobody else's.
Every writer goes through it: kapi context, kapi apply, the agent tools, the
desktop feed. Agent roles with wider rights are a change to this function rather
than to each call site.
A reset goes back to an earlier point
host.App.ResetContext (kapi context reset --before <point>) rewinds a
project's context to how it stood at a point in its history: before an agent
session's first operation, before a date or an instant, or before one
operation, named by id or by an unambiguous start of one. The request resolves
to the first operation set aside, and the reset operation records it in
Before. ContextResetScope answers the same request without recording
anything, which is --dry-run.
workspace.SetAside decides what a reset sets aside, and both readers of the
log call it, so they agree. It reads a project's operations newest first, in id
order. A reset still in force sets aside every operation of a context kind in
its project from the operation it names up to itself: the context.*
operations, and the terms.write, memory.write, voice.write and
rules.write operations that fill the stores. Reading resumes below that
point, so a reset inside the range a later reset covers is itself set aside
and has no effect. A document's edits, adoptions and review decisions are the
project's content, and a reset leaves them alone.
What a reset sets aside stays in the log:
- The ledger folds every set-aside record to status
reset. It stops answering, andkapi context log --status resetlists it. - A projector rebuild skips the set-aside operations, so the stores stand as
they did before the point.
ResetContextruns that rebuild once the reset is recorded. - A rebuild starts from no checkpoint that a reset was received after, because the reset changes what the operations before it apply. It starts from an older checkpoint, or from an empty store.
- A catch-up that meets a reset, or an operation that sorts before a reset the log already holds, rebuilds instead of applying operations one by one. This covers an operation merged in from another machine that falls inside what a reset set aside.
A reset is itself an operation, so a later reset to before it sets it aside
and brings back what it set aside: kapi context reset --before <reset-id>
takes one back. ContextResetResult reports the subject-bearing operations set
aside, a count of the decisions set aside with them, and the ones an earlier
reset had set aside that answer again.
A reset is for going back to a known good state. A single decision has no undo: changing your mind about a rule is a new decision in review.
Nothing is erased. A set-aside operation stays in the log with its evidence, so the same suggestion is recognisable the next time it is made.
Consequences
Agents can record suggestions during normal work without affecting build results. People can review operations together, keep suggestions by session, and reset the context to before a session when necessary.
The log is folded on every read rather than indexed. The ledger selects the
context.* operations alone (Backend.Select with a kind prefix, answered from
an index on the kind), so whatever else shares the log costs a fold nothing, and
contest detection compares only rules that share a form. With 13,000 other
operations and 500 to 800 context operations in the log, sixteen concurrent
agents record an observation with a p99 of 32 ms. An implementation that needs
more adds an index behind Ledger without moving the model.
Operation ids
An operation id (workspace.NewOpID) is 24 characters of lower-case Crockford
base32: eight for the moment the log accepted the operation, in milliseconds
since the start of 2026, and sixteen random ones. The time comes first, so ids
sort in the order operations were accepted, and the fold reads the log in id
order. A log never mints an id that sorts before one it already holds: where the
clock has not moved past the newest id, the time part advances one millisecond
past it, so an operation recorded after another was seen sorts after it, on
whichever machine either was recorded.
The id is the same in every log that holds the operation, so two logs merge by
union (workspace.Merge): recording an id the log already holds changes
nothing, and two logs merged into each other hold the same operations in the
same order, whichever was merged first. An operation may also carry a content
address; a log holds one operation per address, and where two machines recorded
one address under different ids the older id stands in both. The local backend
keeps a per-log arrival position beside the id. It is what Head returns and
what Since reads from, so a surface watching for change polls one number and
an operation merged in from elsewhere moves it like one recorded here.
A log line shows the first ten characters (workspace.ShortOpID): the time part
and two random characters, which one machine never repeats because it never
accepts two operations in one millisecond. Every verb resolves any prefix that
starts exactly one id; a prefix that starts several is refused with the
candidates listed (workspace.AmbiguousOpIDError).
Surfaces
The command line divides by audience:
| Audience | Commands | What they do |
|---|---|---|
| A person | kapi context <path>, search | read what applies |
| A person | kapi context atlas | list the whole context, over time and across its axes |
| A person | kapi context review | keep, drop, choose a side, apply more widely |
| A person | kapi context reset --before <point> | go back to an earlier point |
| A person | kapi context sync | pull, settle a merge, push |
| An assistant (For assistants) | kapi context note, log | record, and read back |
| Maintenance | kapi store import, export, rebuild, locales | the store itself |
kapi context note records an observe for something noticed (--term with
--instead-of, or a fact in prose), a correct for a person's change
(--from, --to, and --suggest for the rule it implies), and a withdraw
for a note of the caller's own (--withdraw <id>). --point narrows where a
note holds, and --valid-from and --valid-to bound when it holds
(above). kapi context log --status filters by any of the six statuses, and --session this reads back
what the calling session recorded. A log line prints the kind once, marks a
contested entry [contested by #0n79tw5k9s] and ends a suggestion's line with
its standing. The host API (host/contextops.go, host/contextreview.go,
host/contextreset.go) is typed requests and results with no flag sets, so the
agent tools and the desktop drive the same loop.
Operations are what travel between machines. host.App.SyncProjectContext
(kapi context sync) pulls another machine's operations from the backend
context.backend in kapi.yaml declares and merges them by id, settles a
merge when --merged names one, and pushes this machine's operations, unless
--no-push asks it only to read. The recipe is the only place the backend is
set. kapi store export carries the
operations in one file, and kapi store import merges such a file the same
way (C-03). A suggestion recorded on one
machine and kept on another is therefore one history, whichever pulled first.
The agent surface records and reads with two tools: context_note, which
records what the session noticed, a person's change (from, to, suggest),
or the withdrawal of the session's own note (withdraw), with the same
point, valid_from and valid_to the command line takes, and
context_session_summary, which reports what one session recorded and what
became of it and ends with the kapi context review --session <id> command a
person reviews it with. Each wraps a host call and adds no rule of its own.
Keeping, dropping, widening and resetting are reserved for people and are
excluded from the agent tool set (S-03).
The server assigns the actor identity. An MCP tool takes no actor
argument and refuses one: the kind is agent, the name comes from the client's
own initialize, and the session is minted once per server process, so
operations from one run can be read together, and the context reset to before
them. Shell commands use
environment-based actor detection to distinguish people from supported agent
hosts. A person is named by their git identity (user.email, or user.name
when no email is set), so two people on two machines of one team are two
actors: kapi context log --actor tells them apart, and the digest shows a
teammate's operation under the teammate's name and the reader's own as "you".
A machine with no git identity records the person unnamed.
Evidence is required where a rule is stated. context_note refuses a term
rule or a person's change that names neither a path nor a quote, so recorded rules have
traceable evidence.