The sync loop
kronika sync closes the loop the single-shot verbs leave open: check
audits one change and write regenerates one document, but nothing
remembered where documentation last agreed with the source. Sync carries
that memory in the repository itself, in two committed files, so a scheduler
can run it forever and each tick does only the work the evidence demands.
Each noun has its own page — manifest,
state, baseline,
drift, audit,
finding,
blocker vs warning — and every action
below was captured running, tick by tick, in
walkthrough-sync-cycle.
The manifest: what is maintained
kronika.sync.json (override with --manifest) is the human's declaration
of which documents are maintained from which evidence:
{
"schemaVersion": 1,
"documents": [
{
"output": "docs/operations.md",
"sources": ["src/deploy", "src/monitor"],
"instruction": "Operator documentation; keep the existing section order."
}
]
}
Per document: output is the repository-relative documentation file;
sources are the repository-relative files or directories that are its
evidence — and also the pathspecs drift detection filters the Git diff by;
instruction is a standing documentation goal passed to both the audit and
the rewrite. Each entry may also override the run's budgets: model,
maxTokens, maxInputBytes, maxFileBytes, maxDiffBytes; anything not
overridden comes from the CLI flags (cli). schemaVersion must be
1, documents must be non-empty, and every entry must declare at least
one source — a manifest that fails these checks is an error, as is a
missing manifest.
The state: what was reconciled
kronika.sync-state.json (override with --state) records, per output
path, the last commit the document was reconciled against:
{
"schemaVersion": 1,
"documents": {
"docs/operations.md": {
"headSha": "<40-hex commit>",
"syncedAt": "<ISO-8601 timestamp>",
"lastAction": "checked-current"
}
}
}
A missing state file means no document has a baseline yet. Invalid JSON in the state file is an error with one remedy: fix or delete it to re-baseline. The state file is meant to be committed beside the manifest — it is the auditable record of the loop.
One tick
Sync resolves HEAD once, then decides per manifest document:
| Evidence | Action | Model calls |
|---|---|---|
| no state entry | baseline — record HEAD, generate nothing |
none |
| state already at HEAD | current |
none |
| no declared path changed since the baseline | advanced — baseline moves to HEAD |
none |
| declared paths changed, audit passes | checked-current — baseline moves |
one check |
| audit finds blockers | rewritten — document regenerated, baseline moves |
check + write |
| baseline cannot be diffed, or audit/rewrite errors | failed — baseline does not move |
varies |
Two deliberate choices define the loop:
- Baseline before generation. A first run that rewrote every document through a model would replace reviewed prose wholesale; sync maintains documents from the moment they enter the manifest, so the first tick only records where maintenance begins.
- A passing audit IS the update. Drift detection diffs
baseline...HEADrestricted to the document's declared sources and the document itself — a hand edit to the document advances its baseline exactly like a source change the audit passes. The audit runs over that same restricted diff, so one drifted document is never blocked by the size of unrelated changes, and the checker's own contract is "never require churn merely because source changed".
When the audit does find blockers, the rewrite is not free-form: the
document is regenerated through write --apply with an instruction built
from the audit's own blocker findings — preserve the document's existing
structure, voice, and correct content; correct exactly the audited defects;
do not re-author sections the audit did not name.
Failure and recovery
A failed outcome never advances the baseline, so the next tick retries the
same range. If the recorded baseline commit no longer exists in the
repository (rewritten history), the diff fails and the outcome says so;
delete that document's entry from the state file to re-baseline. Audit and
rewrite failures carry the underlying Brama or parsing error in their
detail. Sync exits 1 when any document failed, 0 otherwise.
Dry run, commit, push
--dry-run reports and audits but writes no file and no state — a document
that would be rewritten is reported with its findings instead. Without
--dry-run, the state file is written only when something actually
advanced. --commit stages exactly the rewritten documents plus the state
file and commits (kronika sync: reconcile <files>, or
kronika sync: advance documentation baselines when only baselines moved);
--push pushes that commit. This makes the loop schedulable: cron, launchd,
or any scheduler can run kronika sync --commit --push unattended and
documentation follows the repository by itself.
One observed detail: the state records the HEAD the tick ran at, and the
sync commit itself then moves HEAD by one — so the tick after a --commit
reports advanced (a free baseline move over the sync commit), not
current. current appears only when nothing was committed between two
ticks.