Project Log
oat project log manages an optional, append-only project-log.md artifact for
workflow observations and lifecycle events. Use it to preserve evidence that
would otherwise remain in agent transcripts, then roll that evidence into
tracked project and repository references before archiving the project.
The command group is the only supported writer for the artifact. Do not edit
entries or the synthesis section by hand. A new log starts with a two-line
header that links to this page; the entry contract lives here and in
oat project log append --help rather than in every log.
Configure project logs
Four workflow keys control the feature:
| Key | Values | Default |
|---|---|---|
workflow.projectLog | auto, true, false | auto |
workflow.projectLogLedgerPath | repository-relative path | .oat/repo/reference/project-observations.md |
workflow.projectLogDigest.maxEntries | positive integer | 12 |
workflow.projectLogDigest.maxLines | positive integer | 60 |
oat config set workflow.projectLog auto --shared
oat config set workflow.projectLogLedgerPath .oat/repo/reference/project-observations.md --sharedauto creates the log on the first append. true also lets
oat project new scaffold the log up front. false skips an append when no log
exists. An existing project-log.md always accepts appends, regardless of the
current config value.
For one scaffold operation, oat project new --with-project-log forces
creation and --no-project-log suppresses it.
The two workflow.projectLogDigest keys bound the Workflow Observations digest
that roll-up writes into summary.md (see
Promote and roll up observations). They
are siblings of workflow.projectLog, resolve independently of it, and accept
only positive integers.
Append entries
Judgment entries capture evidence about the project or reusable workflow:
oat project log append \
--project .oat/projects/shared/example \
--type friction \
--scope project \
--area "gate review handoff" \
--body "The gate passed but the handoff was missing, so review receipt stopped."Judgment flags:
--type <bug|friction|worked-well|feedback>--scope <project|general>--area <text>— one line, at most 120 characters--body <text>— use--body -to read from stdin--version-note <text>— adds anobserved onclause--project <path>— optional when an active project resolves
Learning entries
A judgment entry can carry a learning category and a stable source identity.
Autonomous, cloud and goal runs record their learnings this way, so the log is
the one place learnings live; summary and retro read these entries first and a
legacy oat-execution-learnings.md second.
oat project log append \
--project .oat/projects/shared/example \
--type friction --scope project --area "cloud cache misses" \
--category environment-limited \
--source obs-2026-10-11-cache \
--origin oat-project-autonomous \
--idempotency-key obs-2026-10-11-cache \
--body "Observation: the runner cache missed on every run. Impact: builds doubled. Recommendation: warm the cache in setup."--category <category>— one ofgotcha,efficiency,documentation-gap,candidate-skill-content,decision, orenvironment-limited, carried verbatim. Any other value is refused, and--categorywithout--sourceis refused.--source <id>— the stable per-observation source id, one word. Pass the same id as--idempotency-keyand a retried write appends nothing; the key is found in the**Source:**line, so the body need not repeat it.--origin <producer>— the producer that recorded the learning.
These flags write a body head above the body:
**Category:** environment-limited
**Source:** obs-2026-10-11-cache
**Origin:** oat-project-autonomous
**Recorded:** 2026-10-11T14:03:22ZThe heading grammar and the four judgment types are unchanged, and an entry written without these flags is stored exactly as before. The flags are refused on structural entries.
Structural entries
Structural entries record lifecycle events without duplicating larger artifacts:
oat project log append \
--structural \
--producer oat-project-implement \
--ref p03 \
--body "Phase passed; details: .oat/projects/shared/example/implementation.md#run-3"Structural entries require --structural, --producer, --ref, and --body.
Do not combine structural flags with judgment flags.
Preview an append
Validate an entry against the current project state before appending it:
oat project log append --dry-run \
--project .oat/projects/shared/example \
--type friction --scope project --area "gate review handoff" \
--body "The gate passed but the handoff was missing, so review receipt stopped."The preview reports whether it would append, create the log, recognize a replay,
or skip a disabled missing log. It writes no files, acquires no locks, and makes
no Git changes. --dry-run --commit also validates an existing recovery receipt
without committing or removing it. A would-create preview checks compatible
bundled assets and validates the template in memory; an existing log does not
need assets. Sealed logs and invalid entries retain normal append diagnostics.
This is advisory validation of current state. It reserves nothing and does not guarantee later permissions, disk space, hook success, or the absence of other writers. A normal append validates again under its required lock.
Idempotent append and commit
Two optional flags let an interrupted append be replayed safely:
--idempotency-key <key>— skip the append when an existing entry body already carrieskey, reportingalready-appendedinstead of writing a duplicate. The key must appear in--bodyas its own whitespace-delimited word, since that is how a replay recognizes the entry it already wrote; a key glued to varying text such as a timestamp never matches its earlier self. Deduplication does not require--commit.--commit— stage and commit the log after appending, retrying a bounded three attempts when the failure is a transient.git/index.lock.
Together they are the recovery entry point for a gate whose project-log finalization could not commit. See Workflow Gates for the receipt and the exact recovery command.
The helper produces these heading grammars in UTC:
### YYYY-MM-DD · <project|general> · <bug|friction|worked-well|feedback> · <area>
### YYYY-MM-DD · structural · <producer> · <ref>Prior entries are never edited or struck through. Append corrections as new judgments that reference the original heading. Never record secret values such as tokens, credentials, keys, or signed URLs; reference their name or source instead.
Run oat project log append --help for the complete entry contract.
Bodies may use line feeds or CRLF line endings. A CRLF body is accepted and
stored normalized to line feeds, and the result then carries
normalizedLineEndings: true, so the bytes on disk are known to differ from the
bytes passed. A lone carriage return, U+2028, or U+2029 anywhere in a body — including
at its very start or end — is refused, because those are the terminators the
log's readers do not all agree about. Structural entries take one line and
accept no line terminator at all.
Automatic workflow integration
When project logging is enabled, OAT lifecycle surfaces append structural
entries without asking agents to edit project-log.md:
oat-project-implementrecords accepted subagent dispatches, STOP or park events, phase outcomes, and parallel-group merge results. These entries point to the correspondingimplementation.mdrecord instead of copying it.- The
oat-project-implementroot also appends root-judgment entries when breaks, surprises, workarounds, or notable successes surface, including observations relayed from subagent reports. An entry that surfaces while a child owns the worktree is queued and appended at the next bookkeeping boundary. Phase implementers and other dispatched subagents have no logging duties. - When
oat-project-implementoroat-project-review-providereceives an artifact-mode review, the root workflow first consumes exactly one**Reconnaissance:** attemptedor**Reconnaissance:** not-attemptedsignal from the reviewer's brief confirmation. A missing, duplicate, or invalid signal fails closed before artifact validation or bookkeeping.attemptedrequires a completeReview Orchestrationsection recording waves, task classes, classification rationale, selected targets, acceptance and outcomes, floor satisfaction, fallback, and primary reconciliation. The root then invokesoat project log appendexactly once for one structural entry referencing the review artifact instead of copying worker records.not-attemptedforbids aReview Orchestrationsection and does not invokeoat project log appendfor review orchestration. Reviewers and reconnaissance workers never writeproject-log.mdthemselves.
oat gate reviewrecords exactly one entry for every terminal outcome, including successful and blocking verdicts, child failure, timeout, targeting-correlation failure, and artifact-validation failure. A log append failure produces a warning but never changes the gate result.oat-project-summarychecks for entries, offers append-only promotion of reusable project judgments, and invokesrollupafter authoringsummary.md.oat-project-completewarns when synthesis is pending, requires a successful roll-up for a populated log, and appends the final seal entry before archive. No project-log entry may follow the seal.
With the default auto setting, the first of these append points creates the
log. With false and no existing artifact, automatic appends are no-ops.
Inspect status
oat project log check --project .oat/projects/shared/example --jsoncheck reports whether the log is absent, ready, or still awaiting synthesis;
entry counts by class, type, and scope; the last entry date; and invalid
hand-written headings. It reads project-log.md and checks only for the
presence of the legacy learnings file: learnings is legacy when the project
still has oat-execution-learnings.md (summary and retro read it after the
log) and none otherwise, in every status including absent.
It also reports whether the log carries a completion seal:
sealed:trueonce the log holds a structural entry whose producer isoat-project-completeand whose ref isseal. Both halves are required — asealref from another producer is an ordinary entry.seal:nullwhen unsealed, otherwise the first seal'sheadinganddate, whether it iskeyed, and thecountof seal entries. Acountabove one is a log sealed twice before the seal append became idempotent.
status keeps its ok / absent / synthesis_pending values on a sealed log;
sealing is reported alongside the status, not as a status value. The one
additional status is ambiguous: a ## heading, or a dated ### entry
heading, starts a line after a lone carriage return, U+2028, or U+2029 rather
than a line feed, so the log's structure can be read two ways; or a seal is
physically present outside the parseable ## Entries region. check then
exits 1 and reports an ambiguity reason instead of a clean verdict, every
writing command (append, synthesize, rollup) refuses the same file, and
the lifecycle skills stop rather than treat the log as empty.
Use --require-synthesis to exit with status 1 while synthesis is pending:
oat project log check --require-synthesisWithout that flag, normal absent, ok, and synthesis_pending results exit
successfully so lifecycle skills can decide whether to warn or enforce;
ambiguous always exits 1.
Complete the synthesis
Write the end-of-run synthesis through the command:
oat project log synthesize \
--project .oat/projects/shared/example \
--body "The workflow was effective; preserve the gate handoff checks."Use --body - for stdin. synthesize replaces the pending synthesis section
without changing entries. It fails when the log is absent or the synthesis is
already complete; append a correction judgment instead of replacing a completed
synthesis.
Promote and roll up observations
Before roll-up, promote a reusable project judgment by appending a new
general judgment. Its body must reference the original heading. Never mutate
or annotate the original entry.
oat project log append \
--type friction \
--scope general \
--area "gate review handoff" \
--body "Promotes '### 2026-07-18 · project · friction · gate review handoff': this applies to all gate-driven reviews."After summary.md exists, roll up the log:
oat project log rollup \
--project .oat/projects/shared/example \
--jsonrollup writes a bounded observations digest into summary.md and appends
general judgments to workflow.projectLogLedgerPath. Ledger entries
deduplicate by date and area. The command is idempotent.
The digest is a projection, not a copy:
- Only judgment entries qualify. Structural entries are provenance and stay in the log; they are counted, never rendered.
- Entries are ordered newest first, then
decisionandgotchacategories ahead of the rest, then by heading text, and taken while both bounds hold: at mostworkflow.projectLogDigest.maxEntriesitems and at mostworkflow.projectLogDigest.maxLineslines for the whole section. Entries beyond the bound are counted in a one-line trailer, never copied. - Each item is concise: a line with date, type, area, scope, category and the entry's source locator, then the first observation sentence and any limitation or uncertainty line. A recommendation is rendered only for an uncategorized entry; a categorized learning's recommendation belongs to the summary's Autonomous Execution Learnings section, and the digest adds a one-line cross-reference to it.
- The locator is a plain token, never a link:
oat-project:<slug>#<entry-id>, where the entry id is<date>-<type>-<area slug>-<n>andncounts entries sharing that date, type and area slug in log order. Archive export finalizes the token to the archive identity. - Live
.oat/projects/<scope>/<slug>/…paths in rendered text are replaced by a(project file …)label that keeps the project-relative remainder. - When no judgment entry qualifies, the section is omitted: an existing Workflow Observations section is removed and none is created.
The digest never feeds the ledger. Ledger graduation reads the complete
judgment set, so a general lesson the digest bound left out still graduates.
The structured result contains:
status:ok,failed, orambiguoussummarySection:written,updated, oromittedledgerOutcome:appended,deduplicated,skipped_permitted, orfailedentriesRolledUp: number of log entries read, structural and judgmentdigest: the bounds used (maxEntries,maxLines), therenderedcount,omittedcounts (overBound,structural, andmalformedfor a heading that matches neither grammar and was skipped individually),omittedEntries(the locator tokens of over-bound entries, no bodies), andbyProducerrendered/omitted counts keyed by a judgment's**Origin:**(orunattributed) or a structural entry's producer
An ambiguous result carries only status and an ambiguity reason, and exits
1: the log's structure has two readings, so no count would mean anything and
neither summary.md nor the ledger is written. The other fields are absent
rather than zeroed, because entriesRolledUp: 0 for an unreadable log is
indistinguishable from the same result for an empty one.
skipped_permitted means the default repository reference layer is absent and
no ledger path was explicitly configured; status remains ok. An explicitly
configured ledger write failure returns status: "failed". Completion must not
seal or archive a project with entries until roll-up reports status: "ok".
The completion seal
The seal is the last entry a project log may ever receive, and append
enforces that rather than leaving it to convention:
- Replaying the seal reports
already-appendedand leaves exactly one seal entry. This holds for a seal carrying the completion skill'soat-seal:<project>key and for an unkeyed seal written before that convention, which is recognized by its heading instead. - Every append carrying new content is refused with
status: "sealed"and a non-zero exit, naming the seal that closed the log. - A replay that
--idempotency-keyrecognizes as its own earlier entry is the one exception: it reportsalready-appendedand exits 0 even on a sealed log. Nothing is appended — the entry it finds necessarily predates the seal — so the seal stays the final entry. This is what keeps a gate partial-finalization receipt replayable after the project is completed. The key matches a whole word in an existing entry body, so an unrelated append whose key happens to occur in some earlier entry is reported the same way and likewise writes nothing.
A resumed completion therefore reads sealed from check and skips the
roll-up and the seal instead of duplicating them.
rollup requires an existing summary.md; summary authoring remains the
responsibility of the project summary workflow.