The .odd/ memory - its contract and one reference per kind. The contract states what every kind shares (where the memory lives, the frontmatter and the whole body, append-only reports versus living-source benchmarks, recall by frontmatter then by section, the no-secrets rule, the work branch and the lone commit, the reply that carries a synthesis and never the artifact); each reference says how to persist, recall and show its kind - observation reports, instrumentation reports, benchmarks, th...
Installs into .claude/skills of the current project.
Are you the author of Odd Memory?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/using-system-odd-memory)
---
name: odd-memory
description: The .odd/ memory - its contract and one reference per kind. The contract states what every kind shares (where the memory lives, the frontmatter and the whole body, append-only reports versus living-source benchmarks, recall by frontmatter then by section, the no-secrets rule, the work branch and the lone commit, the reply that carries a synthesis and never the artifact); each reference says how to persist, recall and show its kind - observation reports, instrumentation reports, benchmarks, the maintainer-ruling ledgers (finding decisions, tree-entry classifications), custom stacks. Read when a report, a benchmark, a custom stack or a decision is persisted, recalled, shown or inventoried, or when a finding is declined (wontfix) or such a decision reversed, or a tree entry is ruled runtime or non-runtime; never invoked on its own.
---
# The `.odd/` memory contract
The ODD loop's memory is a set of committed files in the observed
repository, under `.odd/`: git versions them, pull requests review them,
every user of the repository shares them — no side-channel storage,
nothing opaque. It has five kinds, each with the reference that owns
its specifics (its paths, its frontmatter fields, its recall matching,
its branch name, its commit subject, what its reply carries, and how a
stored one is shown):
| Kind | Store | Reference |
| --- | --- | --- |
| Observation reports | `.odd/observe-run-reports/` | [references/observe-run-report.md](references/observe-run-report.md) |
| Instrumentation reports | `.odd/otel-instrumentation-reports/` | [references/otel-instrumentation-report.md](references/otel-instrumentation-report.md) |
| Maintainer rulings | `.odd/decisions.md` (findings) and `.odd/entry-classifications.md` (tree entries), written by `scripts/odd_ledger.py` | [references/decisions.md](references/decisions.md) |
| Benchmarks | `.odd/benchmarks/<name>/` | [references/benchmark.md](references/benchmark.md) |
| Custom stacks | `.odd/observability-stacks/<name>/` (`guide.md` and `scripts/`) | [references/observability-stack.md](references/observability-stack.md) |
This file owns what they share; a reference states only its
specifics. A sixth kind is a new row in this table and a new
reference, never a sixth copy of these rules.
## Reading a reference by section
A reference is read like a stack reference — by the section a step
needs, never whole. To **persist** an artifact, read its naming and
format sections and its `## Rules`; to **recall** the baseline before
a run, its `## Recall`; to **show** a stored artifact at the end of a
mission, its `## Show` and nothing else; to **record** a decision, the
ledger reference's `## Recording a decision` and `## Rules`, and to
record a tree-entry classification, its
`## The entry-classification ledger`. The `## Return value` of a
report reference is what the persistence hands the caller, and what
`## Show` renders from.
## Where the memory lives
- In the **observed** (or investigated) repository, exactly where the
kind's reference says — never in the oddyssey package, a home directory,
or a temp path. Create the directory when it does not exist.
- **Committed**: the files stay tracked, never added to `.gitignore`.
- A report file is named `YYYY-MM-DD-HHmm-<run_name>.md`: the run's
**UTC** start time to the minute, so two same-day runs never collide
and a plain listing sorts chronologically — computed with `date -u`,
never from the local clock (a session crossing local midnight while
UTC has not names the wrong day) — then a short kebab-case slug
naming the content, not the date. The kind's reference adds its own
prefixes and variants.
## The frontmatter and the body
- A report opens with a YAML frontmatter so later runs can filter the
store **without parsing prose**. Every field mirrors the run as it
actually executed — defaults applied, not as requested. The kind's
reference lists the fields and their meaning.
- The body is the producing agent's judgment **as-is**, whole: what
each section carries is the agent's contract, stated once where the
kind's reference says — in that reference, beside the format, or in
the agent — and read at report time; the persistence adds nothing to
it beyond the numbered headings and the few machine-read shapes a
kind's script fixes and checks (its reference names them), and a
summary cannot feed a later diff.
## Append-only, with one exception
- The report stores and the ruling ledgers are **append-only
evidence**: one run, one file — never edit a stored report to
"update" it, a new run writes a new file and the diff lives there; a
decision is a row appended, never rewritten, and the latest row for a
finding wins, as does the latest row for a tree entry. A report is
never modified to carry a decision: the ledger is the decision's only
home — and a ledger is written by the script its reference names,
which checks the row before it lands, never by a file tool; the
reference's prose steps are the fallback where the script cannot
run.
- A **benchmark is living source**, not a run record: it is updated in
place through reviewed diffs, and git history, not file accumulation,
is its memory. It is never overwritten silently — an update is a diff
the maintainer reviews like any other committed change. A **custom
stack** is living source the same way.
- The consumers keep the two apart: `/odd-verify` and `/odd-status`
treat a commit that touches only the report stores or a ledger as
memory, not code, while a commit that changes a benchmark or a custom
stack is a code change.
## Recall: reading the memory
For the two report stores and the benchmarks — a custom stack is
recalled by name, each ledger is one file; their references own that:
- Run the recall script this skill carries,
`python3 <this skill's directory>/scripts/odd_recall.py --repo <path>`
with the mission's scope as flags — the kind's reference says which.
**The invocation may travel with the mission**: a caller's preflight
that already resolved the scope writes it into the mission block as
`Recall: python3 <this skill's directory>/scripts/odd_recall.py --repo <path> --service <name> [--service <name> ...] --stack <stack>`
(the observation form — one `--service` per name, `--env` left for
the agent to append once it has detected the environment; a benchmark
is recalled by `--kind benchmark --service <name>` alone, an
instrumentation report by `--kind instrumentation --project <scope>`),
and the agent runs that line as is — never re-derived, never a flag
guessed. The whole surface is `--repo`, `--kind <observation |
instrumentation | benchmark>` (default `observation`), `--service`,
`--stack`, `--env`, `--mode` (repeatable) and `--project`
— nothing else: `--help` has nothing to add and the file has nothing
to read. The script reads every stored frontmatter (a benchmark's manifest) in
Python and prints the matches newest first, one line each; a
report the frontmatter contract flags, or a benchmark whose manifest
it cannot read, is listed and reported, never skipped silently. No
frontmatter reaches the conversation until the baseline is chosen.
A missing or empty store is a first run — the script says so, never
fails. **By hand, only when the script cannot run**: for a report
store, list it newest first (the filenames sort chronologically) and
read **frontmatter blocks only**, never whole files; the benchmarks
are a directory store, so list the directories and read the few
manifest keys their reference names, never a whole manifest. Either
way apply the kind's matching rules yourself; a missing or empty
store is a first run then too — say so, never fail.
- The first line printed is the baseline: read it **by section, never
whole** — the kind's reference names the sections a mission needs,
and the command that prints them when it ships one.
- A replay's baseline is not recalled but resolved — the report the
caller named or the newest, its one-hop source, the mode to replay,
and whether the code changed since it — by the
report script's `baseline` and `boundary` commands, stated in the
observation reference's `## Resolving a replay`; the caller runs
them before the dispatch and the mission block carries their lines.
Reading beyond that set is the exception, for a stated need that the
calling agent records. A benchmark recall has no baseline: the whole
listing is the set the mission checks itself against, and its
reference says what each column answers.
- Older matches are history: read them only when a trend or the
evolution of one decision matters, and only the sections in question.
## No secrets, no real identifiers
Every file of the memory is made to be committed and shared. Never
write a token, credential, cookie, or connection string into one —
refer to access material by variable or secret name only. The same for
**real identifiers** that carry no access on their own: tenant,
workspace, subscription, resource-group or site names and GUIDs,
account or login names, home-directory paths — anything that identifies
a real customer, tenant or environment — and **every value persisted
under a remote stack's `stack_config`**, regions and the `local` stack
excepted: a log group or a profile name reads like a plain path and
identifies an account all the same; write the field's name in angle
brackets instead (`<log_group>`). A live CLI excerpt, a
configuration display, or a mission block's preflight handoff is the
likeliest source: every such value lands in the file as an obviously
fake placeholder (`Contoso`, a zeroed or patterned GUID,
`example-user`, `<scratchpad>`). On a host that runs the package's
lifecycle hooks, a hook flags what slipped through, after the write.
## The work branch and the lone commit
An observation report's `persist` (its reference's script) applies this
section; everywhere else it is applied by hand:
- **Never commit on the default branch.** Before committing, compare
`git branch --show-current` with the repository's default branch
(`git symbolic-ref --short refs/remotes/origin/HEAD` stripped of its
`origin/` prefix; if unset, `main` — or `master` when that is the
checked-out branch). Only when on the default branch, create and
switch to the work branch the kind's reference names (switching to it if
it already exists), commit there, and say so in the reply. If
switching is impossible, do not commit: state the path and leave the
commit to the caller. On a host that runs the package's lifecycle
hooks, a hook refuses the commit itself; this rule stays the
enforcement everywhere else.
- **Commit the artifact's files alone**: `git add` the files the
persistence just wrote, then `git commit` with the subject the
kind's reference gives (`docs(odd): ...`) — never stage anything else; a
dirty working tree stays untouched otherwise. If committing is
impossible (not a git repository, or the caller said not to), state
the path and leave the commit to the caller.
- Either way the reply states the stored path and the carrying commit
(`git rev-parse --short HEAD` right after the commit), or
`not committed` with the reason.
## The reply and the synthesis
- The persistence's return value carries the stored path, the
carrying commit, and what its `## Show` needs to render — the
**synthesis inputs**, quoted from the artifact where the artifact
carries the value, never rephrased, the kind's reference lists them;
or, when the kind ships a script that renders the synthesis from the
stored file, the headline alone, so the block never travels through
the reply — and never the artifact's body: an observation report
runs 300 to 500 lines, the reply travels back into the caller's
context, and the synthesis is its only reader. What the next wave
needs is in the file, at the stored path.
- `## Show` renders from that return value, or reads a stored
artifact the caller names from disk, by section, with its carrying
commit from git (`git log -1 --format=%h -- <path>`) — never from the
conversation's memory of the mission.
- Everything in the synthesis comes from the stored artifact: no
backend query, no doc fetch, no re-derivation, no invented value; the
carrying commit is the one value outside the file, and a value the
artifact does not carry is absent from the synthesis too.
- One screen, hard cap: prefer dropping rows behind a
`+N more in the report` marker over growing sections.
- The synthesis renders in the conversation's language; the stored
artifact itself stays English.
- The synthesis never replaces the artifact: the next wave consumes
the stored file — state its path, never re-inline the full body.