# Exocortex Agent Playbook v1.0.0
You have access to `exocortex.*` MCP tools connecting you to a typed,
deterministic memory graph. Sessions compound across time because you
write to it. Follow these rules exactly; the backend enforces them.
## When to write — the end-of-turn checklist
**Trigger:** end of every turn. Before sending your final message,
evaluate the checklist. If ANY item fired during this turn, call
`exocortex.end_session` — it validates locally before writing and tells
you exactly what to fix. If none fired, do nothing — empty turns are
the default and desirable.
**Write if this turn:**
1. Made a code edit the user accepted (see harness appendix for what
"accepted" means in your harness — Claude Code: user pressed accept
or auto-accept landed on disk; Codex: task completed non-error;
Cursor: user did not revert within the same turn).
2. Ran a command whose incantation was non-obvious (unusual flags,
environment, or ordering) AND it produced the intended result. Your
routine build/test loop does not count — only commands a future
session would need to reconstruct.
3. Answered a "why" or "how" question with a claim about the codebase
(a claim that would benefit a future session to know).
4. Decided against a stated alternative for a stated reason.
5. The user said "remember this" or equivalent.
6. Identified a problem — solved or not. An unsolved `Problem` memory
is high-value: it is exactly what a future session's `Solves` edge
needs. Do not wait for the fix to record the problem.
**Sessions are a backend concept.** You do NOT need to detect "session
end," and you do not manage session ids — the client mints one per
conversation and stamps it on every write automatically. Pass your own
`session_id` only when deliberately sharing a conversation with another
agent. Your job is turn-level: check the boxes, write if any fired,
move on.
Do NOT write on:
- Empty acknowledgments ("thanks", "ok")
- Failed experiments where the failure isn't novel
- Mid-work state (a half-done refactor is not a memory — wait for the
turn where it lands)
- Chit-chat
- Turns where you only read or searched
## Supersession — keeping the graph true
Memory is only useful if it can change its mind.
- When you observe an existing memory is **wrong or outdated**, write
the corrected memory and link it with `Replaces` (same subject, new
state) or `Contradicts` (the old claim is false).
- `end_session` flags near-duplicates of existing memories in its
`similar_to` ack field with a suggested edge (`replaces`,
`contradicts`, `duplicate`, `distinct`). Act on it: add the suggested
edge, or write `distinct` reasoning — do not ignore it and do not
restate what already exists.
- Search results mark superseded memories with `superseded_by` and
rank them below their successors. Cite the successor, not the
superseded memory, in your reasoning.
- Do NOT invent numeric confidence scores for memories. The backend
derives confidence from evidence; your job is verifiable specifics —
real file paths, exact error strings, precise values.
## What to write
1–5 memories per batch. If you have more, pick the 5 highest-signal.
| Fixed a bug | `Fix` | "Fixed FalkorDB pool exhaustion under concurrent writes" |
| Solved a design question | `Solution` | "Decided to use ArcSwap over RwLock for cache snapshots" |
| Documented a tech quirk | `Technology` | "FalkorDB rejects Cypher CREATE with reserved property names" |
| Extracted a reusable pattern | `CodePattern` | "Idempotent producer submit via (producer_id, batch_id) dedupe" |
| Ran a working command | `Command` | "cargo test -p exocortex-storage --features falkor" |
| Identified a problem (even unsolved) | `Problem` | "Cache reseed misses writes during subscribe gap" |
| Encountered an error | `Error` | "FalkorDB returns 'unknown property' on version-mismatched clients" |
## How to link
Edges are typed. The 8 buckets (48 kinds):
- **Causal (7):** `Blocks`, `Causes`, `Enables`, `Fixes`, `LeadsTo`, `Prevents`, `Triggers`
- **Context (14):** `ConcerningApplication`, `Contains`, `DependsOn`, `ForApplicant`, `Governs`, `InProject`, `InSession`, `Modifies`, `PartOf`, `PartOfApplication`, `Requires`, `UnderRule`, `Uses`, `WrittenIn`
- **Integration (7):** `Bridges`, `Consumes`, `Exposes`, `IntegratesWith`, `Produces`, `ReportedBy`, `Wraps`
- **Learning (7):** `BuildsOn`, `Confirms`, `Contradicts`, `Demonstrates`, `Specializes`, `SupersedesRule`, `Teaches`
- **Quality (8):** `Categorizes`, `Documents`, `Measures`, `SupportsIncome`, `Tests`, `Validates`, `VerifiedByOfficer`, `Verifies`
- **Similarity (5):** `AnalogousTo`, `DifferentFrom`, `MergeDuplicateApplicant` †, `RelatedTo`, `SimilarTo` †
- **Solution (5):** `Addresses`, `AlternativeTo`, `Improves`, `Replaces`, `Solves`
- **Workflow (7):** `Automates`, `Configures`, `Creates`, `Executes`, `ParallelTo`, `Precedes`, `PrecedesClosing`
† Computed-only (R-T14): the consolidation cycle asserts it, producers may
not. 60 kinds are declared; **58 are yours to use.** Asserting one
is rejected with `ComputedKindRejected`.
Two ways to target an edge:
- **Within the batch:** reference the other draft's `draft_key`.
- **To an existing memory:** set `to_memory_id` to its 32-hex id (you
have ids from `search_memories` / `get_memory` / `find_related`).
Today's fix can link yesterday's problem — prefer this over
re-describing an existing memory.
If in doubt, use `RelatedTo`. It's low-strength but always valid.
**Type triples matter.** `Fixes` requires `(Fix, Error | Problem)`.
`Solves`/`Addresses` require `(Solution | Fix, Problem | Error)`. `Uses`
requires the to-side to be `Technology | Command`. If your edge violates
a triple, the backend rejects it. `end_session` checks locally first and
tells you before anything is written.
## Titles
- ≤200 characters, not empty.
- Subject-verb-object.
- Include specifics (function name, error code, tech name).
- Bad: "fixed a bug", "auth stuff"
- Good: "Fixed OAuth token refresh race in RefreshMiddleware.exchange()"
## Visibility
All four labels are accepted: `private`, `project`, `team`, `org`.
Default to `project`. Escalate to `org` for cross-project knowledge (a
technology quirk that applies everywhere). Use `private` for user
preferences ("Gregory prefers 2-space indent for TypeScript"). `team`
is rarely correct — leave it unless you're sure.
You do not label edges. Edge visibility is derived from the two memories
an edge connects (the narrower of the two), so an edge is never more
visible than either end.
## When to read
- **Session start**, once: `search_memories("<project-context-terms>",
limit=10)`. Grounds you in prior decisions.
- **When stuck**: `search_memories("<exact error message>")` or
`find_related(<id-of-relevant-memory>)`.
- **Before writing a possibly-duplicate memory**:
`find_related(<closest-existing-id>)` to check.
Do NOT search on every turn. It's cheap but not free, and the results
inflate your context.
## Rejections
`end_session` returns an ack with `rejections[]`; each row has `code`
and `detail`. Read `detail` first — it names the exact problem.
| `InvalidTypeTriple` | Kind doesn't fit the (from, to) types | Kind does not fit the (from, to) memory types. Check the kind catalogue; `RelatedTo` is the always-valid fallback. |
| `UnknownKind` | Kind name typo or not in this pack | Kind name typo or not in this pack. Fix the spelling; use only the assertable kinds the playbook lists. |
| `UnknownMemoryType` | Memory type not in this pack | Memory type not in this pack. Fix the type name; use only the types the playbook lists. |
| `Unknown` | Title empty/>200 chars, content empty, or an atomic-batch reject | Generic validation failure (often title empty/>200 chars or empty content). Read `detail`; usually trim the title. |
| `VisibilityWidening` | Visibility above the source ceiling (rare for session wrapups) | Visibility above the source ceiling. Drop to a lower label (`project` is the default). |
| `ComputedKindRejected` | You asserted a computed-only kind (R-T14) | You asserted a computed-only kind (e.g. `SimilarTo`); only the consolidation cycle may. Use `RelatedTo` or `AnalogousTo`. |
| `DuplicateBatch` | Transport replayed the same batch id | Transport replayed the same batch id. Harmless — do NOT resubmit. |
| `RateLimited` | Backend is shedding load | Backend is shedding load. Transient; the client retries. Do not resubmit by hand. |
| `MissingExternalKey` | External-snapshot coordinates missing | External-snapshot batch without ExternalKey coordinates. Cannot occur for session wrapups; if seen, report it as a bug. |
| `InvalidExternalKey` | External-snapshot coordinates malformed | Malformed external coordinates (table_uuid must be 16 bytes, schema_hash 32). Cannot occur for session wrapups; report it as a bug. |
| `Unauthorized` | Credentials rejected (HMAC) | Credentials rejected (HMAC missing/invalid). Surface to the user; not fixable by you. |
| `BadChecksum` | Batch checksum mismatch | Batch checksum mismatch. The batch was mutated after signing; rebuild it from the drafts. |
| `IncompatibleOntology` | Ontology fingerprint mismatch | Ontology fingerprint mismatch — client and backend run different packs. Surface to the user; not fixable by you. |
| `UnknownSource` | Producer not registered / ceiling mismatch / wrong org | Producer not registered, ceiling mismatch, or wrong org. Surface to the user; not fixable by you. |
### exocortex-pack-mortgage-v1
- **RuleDefinition** (memory type): when authoring or changing a lender rule. link `Governs => LenderConfiguration`. link `SupersedesRule => RuleDefinition`.
- **RuleFinding** (memory type): when recording a rule's verdict on a loan file. link `UnderRule => RuleDefinition`. link `ConcerningApplication => LoanApplication`.
- **Transaction** (memory type): when categorizing a bank transaction into an income source. link `SupportsIncome => IncomeSource`.
3 guidance entries; computed-only kinds are never authoring advice.
All 18 `RejectCode` variants appear above. If you meet one that doesn't,
the generator drifted — say so in your final message.
Separately, the client itself refuses (before any wire call) batches
with an unknown visibility label, a memory count outside 1–5, or an edge
referencing a `draft_key` that isn't in the batch. Fix the argument and
retry.
1. If you can self-correct, resubmit within the same turn with fixes.
2. If you can't, tell the user before continuing. Silent rejection is
worse than no write.
## Preflight
`end_session` runs the same local validation before dispatching: an
invalid batch comes back with rejections and correction hints without
a wire call, so one call per write is enough. Call `preflight_wrapup`
directly when you want to check drafts without attempting a write —
mid-conversation, before you've decided what to keep.
A clean local pass (from either tool) means *no problem the client
can see* — not a guarantee of commit. Some checks only the backend can
run: ontology drift, source registration, ceiling changes, and the
type of a `to_memory_id` target that isn't in your local cache. Both
tools name those in an `unverified` list. Read it; if a cross-batch
edge is listed there, expect that one to be checked server-side.
## Cost & etiquette
- Each `end_session` is one write; keep to ≤5 memories.
- End-of-turn writes are cheap because most turns fail the checklist
and write nothing. Don't force a wrapup on a turn that didn't earn it.
- Multiple productive turns in one conversation produce multiple wrapup
batches — that's fine; the backend groups them by session id.
- Don't `search_memories` in a tight loop; the local cache is fast but
not free.