Skip to main content

Memory System

Overview

Crewship has a 3-tier memory hierarchy that gives agents persistent knowledge across sessions and shared context within crews. Each agent has its own private memory the LLM can read and write, every crew has a lead-owned shared memory all members read, and an ephemeral session tier lives only in the live context window. The three tiers compose into a single retrieval call at agent-run time — the orchestrator picks chunks from whichever tier fits within the per-prompt budget, ranks them by relevance, and stitches them into the system prompt before the LLM ever sees the user’s message. Memory is local-first with zero external dependencies. No embedding APIs, no vector databases, no cloud services. Retrieval is powered by SQLite FTS5 (BM25 ranking) running sub-millisecond on thousands of chunks, with the hybrid retrieval uplift from PRs #211/#212 layering dense scoring on top via RRF (reciprocal rank fusion) when an in-process embedding model is configured. The whole memory subsystem fits in internal/memory/ plus the consolidation worker in internal/consolidate/, both running inside crewshipd — no extra container, no extra port. The point of three tiers (instead of one big pile) is to keep the who-owns-what model clear. An agent can freely write to its own tier without affecting peers. The crew lead is the single writer for crew-shared memory, which means the FTS5 index has zero write contention and crew-wide context evolves under one editorial voice. And session memory is intentionally not persisted — context that’s irrelevant the next time the agent is invoked stays out of the index, keeping retrieval fast as the deployment ages.

When to use it

Turn memory on for any agent or crew whose value compounds across sessions. The system is opt-in (memory_enabled=false by default) precisely because the budget cost in the system prompt is real — only pay it where it earns its keep:
  • An agent has long-running user-specific preferences. Dark-mode preference, preferred PR style, favourite framework, “use PostgreSQL 16 for new DBs” — anything the user shouldn’t have to repeat on every new chat. Persist to AGENT.md once, retrieve forever.
  • A crew needs a single source of truth for conventions and decisions. Architecture decisions, deployment rules, security policies, “we always use Tailwind, never inline styles” — the lead curates CREW.md and every member-agent reads it. Beats stuffing the same instructions into each agent’s system prompt by hand.
  • An agent is accumulating project context. A code-review agent reading the same repo every day shouldn’t relearn the module layout each session. Daily logs (daily/2026-05-14.md) capture session observations; FTS5 search surfaces them when the same module comes up again.
  • A single-agent crew that should specialise over time. Without crew peers the budget is 100% agent-tier — the agent essentially gets a persistent notebook. Useful for “my personal research assistant” or “the on-call bot that remembers last week’s incidents.”
  • You’re domain-mining knowledge with topics/. When the crew has bounded subject areas (deployment, security, billing), populate topics/<name>.md files. Hybrid search ranks them above the daily noise on relevant queries.
Skip memory if the agent is single-use, the conversation never references prior sessions, or the user actively wants a stateless interaction. The budget cost of an enabled-but-unused memory layer is non-zero — it eats characters that could carry richer per-turn context instead.

Key concepts

Usage

The end-to-end loop from “memory off” to “agent recalls last week’s decision” is five steps. Each step links down to the deeper detail section below.
1

Enable memory on the agent

Memory is memory_enabled=false by default. Flip it on at create or update time:
The next time the agent runs in a session, its sidecar provisions ~/.memory/AGENT.md (and the crew tier if the agent belongs to one) and starts the FTS5 index.
2

Let the agent write to its own memory

No special API needed — the agent writes by running shell or file-tool commands inside its container. The two common patterns:
The crew lead writes to /crew/shared/.memory/CREW.md and /crew/shared/.memory/topics/<name>.md the same way — see File Structure for the full layout.
3

Subsequent sessions retrieve memory automatically

When the orchestrator builds the system prompt for the agent’s next session, BuildMemoryBlock selects the top-K chunks within budget and injects them between [AGENT MEMORY] / [CREW SHARED MEMORY] markers. The agent doesn’t have to ask — it reads the markers as part of normal context. See Reading Memory for the exact prompt shape.
4

Search explicitly when you need to

For mid-session lookups outside the initial prompt budget, the agent uses the memory.search MCP tool, which every adapter injects and which resolves the acting agent from its token. Normally the model just calls it; to drive the same transport by hand, POST a JSON-RPC tools/call to the sidecar’s memory MCP endpoint:
This works for every crew member: the transport resolves the acting agent from the token and searches that agent’s own tier (plus the crew tier). Omit tier to search everything accessible.
The legacy POST /memory/search route works the same way for every crew member — it resolves the acting agent from its own token and searches that agent’s tier, not always the sidecar’s boot agent (see the note at the end of Searching Memory).
scope is one of agent, crew, both. With both, results from the two engines are merged and sorted by BM25 score, each carrying its origin. (RRF fusion of sparse + dense scores applies on the hybrid=true path, when an embedding model is configured — not to the default both merge.)
5

Let consolidation tend the index

The Consolidation worker runs nightly: recurring observations from daily/*.md get promoted into the evergreen AGENT.md / CREW.md, and the daily logs get archived. You don’t run it by hand for normal operation — the workspace operator can trigger one off-cycle via crewship consolidate run --crew=<crew> (optionally scoped with --since) if a tier is visibly drifting.For development without a running server, the crewship memory CLI lets you reindex and search on-disk memory directly — useful for poking at a backup or a frozen container.

Examples

A research-assistant agent that remembers user preferences

You spin up researcher, ask it five PR reviews. Over the course of a week the agent picks up that you write tight commit messages and prefer the changelog updated in the same PR rather than later. On day one it appends:
On day eight, you ask for a review of an unrelated PR. The orchestrator’s BuildMemoryBlock injects AGENT.md into the system prompt before the LLM ever sees your message, so the agent’s first review comment already says “I noticed the CHANGELOG entry is missing from this PR” without having to be told.

A crew lead recording an architectural decision once

Your backend crew decides Postgres 16 is the new baseline. The lead writes it once:
Every other agent in the crew (migrations, api, tests) reads that line on their next session via the crew-tier system-prompt block — the lead doesn’t have to update three system prompts, and a new agent joining the crew picks up the same context for free.

Bounded subject expertise via topics/

A support crew handles deployment, billing, and security questions. Rather than letting CREW.md grow into a sprawling FAQ, the lead carves it up:
When a question lands that mentions “deployment”, hybrid retrieval ranks topics/deployment.md chunks at the top — the agent gets the right slice and not all 12kB at once. CREW.md stays small and editorial; the long-tail expertise lives in dedicated topic files that grow without bloating the budget.

Memory Tiers

Budget is dynamic. If crew memory is empty, the agent reclaims the full budget for the agent tier.
A workspace-scope memory tier (org-level strategy, cross-crew knowledge) is live: agents get a [WORKSPACE MEMORY] block (capped at 15% of the memory budget), and the CLI reads it via --scope workspace (or all).

Enabling Memory

Set memory_enabled to true when creating or updating an agent:
Memory is disabled by default. Each agent has its own isolated memory store — agents cannot read each other’s personal memory.

File Structure

Agent Memory

Lives in ~/.memory/ inside the agent’s container, mapped to /crew/agents/{slug}/.memory/ on the filesystem.
AGENT.md is the agent’s primary knowledge base — identity, learned facts, user preferences, project context. The agent is instructed to add new information and only remove entries that are explicitly outdated. Daily logs capture session-specific notes: what was done, decisions made, observations.

Crew Shared Memory

Lives at /crew/shared/.memory/ inside every agent container in the crew. The Lead agent writes crew-wide knowledge; all agents can read it.
CREW.md contains crew-level decisions, conventions, and shared context that all agents need. The Lead maintains this file. topics/ holds domain-specific knowledge files that grow over time as the crew learns.
The Lead’s sidecar owns the crew FTS5 index and reindexes it every 60 seconds automatically. Non-lead agents search crew memory via the sidecar HTTP API.

How It Works at Runtime

1. Writing Memory

Agents write memory by executing file operations inside their container — shell commands, file tools, or editors. No special API needed.
The preferred write surface is the memory.write tool the sidecar exposes over MCP. It is durable and fail-closed:
  • A successful memory.write is fsync’d and atomically renamed into place before it returns — a write the tool reports as done is on stable storage, never merely in the page cache.
  • If the persist cannot complete, the tool returns an isError result (the model sees the failure and reports it) instead of a false “done”. Success therefore means durability.
  • The tool is health-gated: memory.write / memory.read are only advertised to the model when the memory sidecar is actually reachable. If the sink is down, the model doesn’t see a memory tool at all and degrades explicitly (“I couldn’t persist that”) rather than calling a dead endpoint and getting a phantom success. Because sidecars are container-scoped and persistent, any warm crew container keeps the tools available across runs.
Recall (the [AGENT MEMORY] injection below) reads the file directly and does not depend on the sidecar, so an agent always reads back its durable memory even on a path that skips the sidecar. Writing mid-session is what needs the sink — see Memory observability for the authoritative way to verify a write landed (and why memory hybrid can lag it).

2. Reading Memory (System Prompt Injection)

When a session starts, the orchestrator reads memory files and injects them into the system prompt:

2a. Operator model (per-user)

Alongside the three memory tiers, Crewship keeps a small operator model for each person who works with a crew: their role, what they are responsible for, and the working preferences and standing constraints they have stated.

What it records, and what it refuses to

Only what the person actually said. Never what the system concluded from watching them work. An inference is often wrong, the person cannot rebut it because they do not know it was made, and once written it reads as fact in every later session — the prompt carries no notion of provenance to mark it otherwise. The cost is that the model grows slowly, and only when somebody says something explicitly. That is the intended trade. Professional facts, not sentiment. Think of it as a CRM record rather than a character study. There is no field for a mood, an attitude, or a personality read, so there is nowhere for one to be written even if a model proposes it. The fields are a closed set: role, owns, constraint, process, prefers, tooling, timezone, language, contact.

How the refusals are enforced

Not by asking the model firmly. Every candidate fact must arrive with the exact span of the person’s own words that states it, and that span is checked, byte for byte (whitespace-insensitive, nothing else), against the turns that person authored. The model chooses which span; it cannot write one. A quote that turns out to come from the agent’s own turn, or from a different human in a group chat, is refused with that reason rather than accepted. This matters because prompt instructions alone have been measured to be insufficient for exactly this: an independent audit of a comparable system found attribution laundering in 12 of 12 trials with its anti-attribution prompt in place. A short answer is still an answer. The span has to be long enough to support a claim, or a model could quote "I" and hang anything off it — but the information density of a real answer runs the other way, and "UTC+1." is the most precise timezone statement a person can make. So the length floor applies to a fragment, and a span that is a whole sentence the person finished is admitted however short it is: Nothing else is relaxed: the sentence still has to be one the person authored, matched byte for byte, under a key the profile has, phrased as a description.

Switching the mode

The extraction profile is an instance setting, read fresh on every sweep:
off stops extraction while leaving the sweep’s opt-out purge and index upkeep running. A name that is not one of these two is treated as off and logged — an unimplemented profile must never silently resolve to one that writes. Switching to off takes effect on the next sweep, not on the next server restart.

Where it lives

The model is keyed on (operator, workspace), independent of which agent is answering. Every agent in a crew reads the same model, stored crew-shared at:
user_slug is a one-way hash (sha256(user_id ‖ workspace_id)[:16]), so the filename never carries the operator’s identity into a directory listing or a stack trace. The same hash gives each workspace its own isolated model for the same person — a user who works in two workspaces gets two independent models. When a session opens, the orchestrator injects only the session opener’s model — never another operator’s, even if one exists on disk — as an [OPERATOR MODEL] block, placed before the per-agent [PEER CONTEXT] block so the broad working-style hint frames the narrower per-agent relationship hint:
Evolving, not overwriting. A daily background sweep refreshes the model from recent sessions. Refreshes merge: a field the latest session is silent about is preserved from the prior model rather than dropped, so a one-off session that only touches one aspect of how an operator works can’t erase the stable picture built up over time. A field the session does re-touch is updated to the newer value. The model is capped at 1.5 KB; a merge that would exceed the cap drops fields from the end (the newest, least-established ones) rather than failing the write, so the profile never silently freezes. Eligibility. An operator gets a model once their interaction with the crew crosses a threshold (≥10 messages or ≥5 minutes of session time). Below that, no model is written. Cost. The extraction runs on the curator auxiliary model slot, once per active operator per day. An unconfigured or unbuildable slot (no ANTHROPIC_API_KEY, for instance) means no extraction, not an error — the sweep still does its opt-out purge and index upkeep. Point the slot elsewhere with crewship keeper aux set curator. Telling whether it is working. An empty operator model is ambiguous on its face — nobody said anything durable, or every candidate was refused, or the slot never answered all look the same from outside. The sweep says which, once per operator per day, whether or not anything was written:
  • proposed=0 — the model found nothing worth recording in that conversation. This is the ordinary outcome for a session that was purely technical, and it is what most days look like.
  • proposed>0 with written=0 — the model proposed facts and the gate refused all of them. reasons says why; quote_not_in_transcript dominating means it is paraphrasing rather than quoting, which is a prompt problem and not a reason to loosen the check. evidence_quote_too_short means it is quoting fragments instead of whole sentences; evidence_quote_states_nothing means it is quoting the operator’s assent to something the agent proposed, and the fact it was hanging off that is worth reading sceptically.
  • no line at all — the extraction never reached the model: the operator is below the interaction threshold, the profile is off, the person opted out, they spoke in no chat they opened, or the curator slot could not be built. Check crewship keeper aux test curator first — it calls the slot’s model once and reports what happened.

Reading and correcting your own model

A memory about you that you cannot see is a poor default, so you can read it, drop one wrong entry, or drop all of it — without turning the feature off:
The same three actions are GET /api/v1/users/me/user-model, DELETE /api/v1/users/me/user-model/facts/{key} and DELETE /api/v1/users/me/user-model. Each acts only on your own record — the server reads your identity from the auth context, so no workspace-admin role is needed and no route can reach anyone else’s model. Reads and deletions are recorded in the peer-card audit log.
list shows what is stored, not where each entry came from. Extraction verifies every fact against a verbatim span of your own words, but that span is not kept — the file has a 1.5 KB budget that is read into every prompt, and carrying provenance inline would halve how much can be recorded. Per-entry provenance is tracked as follow-up work.
Opt-out and deletion. The operator model reuses the same per-(operator, workspace) consent flag as peer cards: opting out of one opts out of both. Opting out purges any existing model from disk and its index row immediately — on the same request, alongside your peer cards — and records the deletion in the audit log. Deleting the user cascades the index row away as well.

3. Searching Memory

Most agents should search through the memory MCP tools, which every adapter injects automatically — memory.search resolves the acting agent from its token and searches that agent’s own tier (plus the crew tier, for a lead). memory.search is BM25-ranked against the same FTS5 index the HTTP routes use, so a query matches a chunk rather than a line: terms in a different order than the file wrote them, or spread across two lines of the same section, still hit. Each result carries a source tier label, a score, and the line the chunk starts on, so the model can follow up with memory.read. A write through memory.write / memory.append_daily re-indexes that one file immediately, so notes are searchable in the same session they were written.
If the sidecar could not open the tier’s index.sqlite, memory.search degrades to an unranked substring scan over the tier files instead of failing — results carry no score. The sidecar logs the fallback.

How a question becomes a query

Ask in plain language. The engine turns the question into an FTS5 expression for you, and the rules are worth knowing because they decide what comes back:
  • Any word is enough, the whole phrase ranks best. journal retention policy becomes "journal retention policy" OR "journal" OR "retention" OR "policy" — a chunk with the exact phrase ranks first, a chunk with one of the words still comes back. It is not an AND: a seven-word question does not have to find all seven words inside one ~500-character chunk.
  • Common function words are dropped from the term list, in English and Czech both — what, the, how, se, na, který. They match nearly every chunk and would bury the words that carried the question. A query made entirely of function words keeps them rather than searching for nothing.
  • File paths are not searched. Searching daily or md matches notes that say those words, not every file under daily/ or every .md. To scope by location, pass the tier argument — that is what it is for.
  • Write real FTS5 and it is passed through. If your query contains AND, OR, NOT, a "quoted phrase" or a prefix*, the engine assumes you meant it and does not rewrite. gatekeeper AND credential really is an AND.
  • Matching ignores accents but not endings. rozhodnuti finds rozhodnutí, but žurnál does not find žurnálu — there is no stemming on this index. If a Czech query comes back empty, try the stem, or a prefix*.
The sidecar HTTP API below takes a scope parameter and resolves scope=agent to the acting agent’s own tier — the caller identified by its Authorization: Bearer $CREWSHIP_AGENT_TOKEN header, not always the agent the sidecar was originally booted for. Several crew members can share one sidecar; each gets its own tier here exactly as it would through /mcp/memory/<slug> — see the note at the end of this section.
Every memory route requires a valid per-agent token. The whole memory surface — /memory/read, /memory/write, /memory/search, /memory/status, /memory/reindex, and the MCP transports /mcp/memory and /mcp/memory/<slug> — is gated on the Authorization: Bearer $CREWSHIP_AGENT_TOKEN header whenever the crew has per-agent tokens provisioned (every crew started by a current orchestrator). Several agents share one sidecar, so a call that cannot be attributed to a member is refused:
  • no header → 403 {"error":"per-agent token required"}
  • a token matching no crew member → 403 {"error":"unrecognized agent token"}
Crews that predate per-agent tokens keep working unauthenticated (the token-less fallback resolves to the sidecar’s boot agent, matching pre-token behavior). The token is in each agent’s environment, so -H "Authorization: Bearer $CREWSHIP_AGENT_TOKEN" is all that is needed. See Per-agent identity.
The legacy /memory/* routes serve each caller’s own agent tier. scope=agent (the default) resolves from the caller’s own bearer token — the same identity /mcp/memory/<slug> resolves — so any crew member reaches its own AGENT.md, pins.md, and daily logs through these routes, not just the sidecar’s boot agent. scope=crew is unaffected by identity: it is one directory shared by the whole crew by construction. /mcp/memory/<slug> (or the memory MCP tools, which every adapter injects automatically) remains the recommended surface for new integrations — it has a narrower, closed tool vocabulary — but the legacy HTTP routes are no longer a boot-agent-only surface.

Dynamic Budget Allocation

The orchestrator allocates a character budget (default 15,000 chars) across memory tiers. Empty tiers reclaim their budget for lower tiers.
Lead with small crew memory:
  • Crew: 2,000 chars (actual content, under 40% cap)
  • Agent: 13,000 chars (reclaimed from crew)
Single-agent crew (no peers, empty crew memory):
  • Agent: 15,000 chars (full budget)

API reference

The memory subsystem is reachable on the agent’s sidecar at localhost:9119 inside the container. There is no public REST endpoint — these calls are intentionally container-local so an agent can only ever query its own tiers (and, via the lead’s sidecar proxy, the crew tier it’s a member of). The schemas below are the source of truth; for the database-side view of the underlying tables see /api-reference/journal (journal-backed retrieval) and the SQLite migration log under internal/database/migrate.go (v54/v55 introduced the importance + FTS5 columns these endpoints read).

POST /memory/search

Search indexed memory with scope control. Request:
Response (200):
When scope=both, results from both engines are merged and sorted by BM25 score. Each result includes a source field indicating its origin.

GET /memory/status?scope=agent|crew

Check the state of a memory index. Response (200):

POST /memory/reindex?scope=agent|crew

Trigger a full reindex. Context-aware — responds to client disconnect and SIGTERM. Response (200): Returns status object after reindexing.

CLI (Development & Debugging)

The crewship memory command provides direct filesystem access to memory indexes without a running server.

Access Control

Agents in one crew cannot access another crew’s shared memory. Container bind mounts enforce this isolation at the filesystem level.

Limits

Load-time injection scan

Memory files are authored by prior agent runs, so a file on disk can carry an indirect-injection payload (text ingested from a web fetch, a peer message, or a file read, then persisted). Every memory tier is scanned again at prompt assembly time, immediately before its content is placed into the system prompt — independent of the write-path scan. The scan runs per section. If a section’s body trips the scanner, only that section’s body is replaced with a deterministic notice; clean sibling sections in the same block are untouched:
What this means for operators:
  • The live file is never modified. The substitution happens only in the assembled prompt. The file on disk is left exactly as written so you can open it and judge the content yourself — the notice points you at the file rather than discarding anything.
  • The label is preserved so you can tell which tier and which file produced the hit, and the category / pattern map back to the scanner rule.
  • It is deterministic. The same file content always yields the same notice (first-hit, fixed rule order), so a blocked section won’t flicker between runs.

Memory write overflow guidance

The memory.write tool is a pure bounded store — each tier has a fixed byte cap and the store never silently evicts or rewrites entries for the agent. When a write would cross a cap, the tool now hands the agent enough to fix it within the same turn instead of just rejecting the call:
  • Hard cap exceeded (the write would push the tier past its cap): the result is an error, nothing is written, and the result metadata carries current_entries (the current on-disk body) and usage (e.g. 3900 of 4000 bytes, 97%). The message instructs the agent to consolidate the current entries — merge duplicates, drop stale lines, summarize — and retry the write in this turn with mode='replace' carrying the consolidated body.
  • Soft cap (80%) crossed (the write succeeds but is close to the cap): the same current_entries + usage are attached and the warning steers the agent to consolidate and rewrite the consolidated body in this turn, before the next append is rejected.
append and replace are at parity — both surface current_entries + usage on overflow. This keeps the store dumb (it does not consolidate for the agent) while giving the agent the material to self-curate without losing the turn.

Pinned facts (tier=pins)

A memory.write with tier=pins appends to a dedicated ~/.memory/pins.md file — the agent’s list of facts that must be always in context, not just searchable. Use it for the handful of things a session must never miss: an on-call code, a hard constraint (“migrations are forward-only”), a “remember this” the operator flagged as load-bearing. The guarantee: pins.md is force-injected at the top of the [AGENT MEMORY] block on every session start — deterministically, before the agent takes its first turn, without the model having to memory.read it. Because it’s the first section in the block, it also survives an aggressive budget-truncation pass ahead of AGENT.md and the daily logs. That’s the difference from ordinary agent memory, which is retrieved by relevance and can be crowded out.
Keep pins short. They spend from the same agent-tier budget as AGENT.md, and being always-on they cost tokens on every run. A few lines is right; a full notebook belongs in AGENT.md, which is retrieved on relevance.
Two unrelated things share the word “pin” — don’t confuse them:
  • Agent pins (memory.write tier=pins~/.memory/pins.md) — described above, injected inside [AGENT MEMORY].
  • Operator journal pins — an operator marking a journal entry as a priority pin; the Consolidation worker snapshots those into a separate crew file that renders as its own [PINS] block. That path is crew-scoped and operator-driven, not the agent’s memory tool.

Best Practices

  • Use clear headings (## Identity, ## Learned Facts, ## Preferences, ## Project Context)
  • Be specific. “Use driver name sqlite not sqlite3” beats “there’s a sqlite driver thing”
  • Keep it under 5 KB. Concise memory leaves budget for daily logs and crew context
  • Prune outdated facts. Stale memory is worse than no memory
  • Lead curates CREW.md with crew-wide conventions, architecture decisions, and policies
  • Use topics/ for domains. topics/deployment.md, topics/security.md keep CREW.md focused
  • Agents don’t write to crew memory — they write personal notes to their own AGENT.md
  • Don’t duplicate facts across agent and crew memory. Crew memory is for shared knowledge only
  • Use consistent terminology. FTS5 is keyword-based, not semantic — and it does not stem, so deploys will not find deployed
  • Ask the whole question. Extra words no longer narrow the result to nothing; the phrase is what ranks an exact match to the top
  • Scope with tier, not with a path word. File paths are not part of the match — see How a question becomes a query
  • Trigger reindex after bulk writes. Agent memory reindexes on startup; crew memory reindexes every 60s
  • Use scope=both to search across personal and crew knowledge simultaneously

Common pitfalls

Memory is memory_enabled=false by default. The most common “my agent isn’t remembering anything” report is just an agent that was never opted in. Check crewship agent get <slug> and flip the flag if needed.
Memory files are not encrypted at rest. AGENT.md, CREW.md, daily logs, and the index.sqlite files live as plain on-disk bytes inside the container. Never write secrets, tokens, or PII into memory — credentials belong in Keeper, not in ~/.memory/.
  • Crew reindex lags up to 60 seconds. The lead sidecar reindexes the crew tier on a 60-second tick. Writes to CREW.md or topics/*.md are searchable on the next tick, not immediately — if you need instant visibility, hit POST /memory/reindex?scope=crew. The crew tier is shared, so this works from any member of the crew, same as scope=agent on that route now resolving to the caller’s own tier.
  • Agent-tier reindex after /memory/write is asynchronous. A successful POST /memory/write returns 201 the moment the bytes are durable on disk; the FTS5 reindex (and the memory.updated journal entry) then run on a single-worker background queue rather than blocking the response. The lag is sub-second in practice. The queue is strict FIFO, so consecutive writes to the same file reindex in write order — turn N always lands before turn N+1, never a stale earlier write winning a race. Writes are still searchable essentially immediately; if you need a hard barrier (e.g. write-then-search in a tight loop), the queue is drained on sidecar shutdown so nothing is lost, but for instant in-session visibility you can hit POST /memory/reindex?scope=agent.
  • Single-agent crews have no crew tier. The crew scope returns 503 for an agent that isn’t a member of a crew (or whose crew has only itself). The budget for the agent tier is 100% in that case; no crew context to retrieve.
  • An agent’s personal memory is not reachable by its crewmates through the sidecar API. Note what this is not: within a crew there is no filesystem-level isolation to rely on. A crew shares one container and one mount, so every agent tier is on the same disk, reachable by any process in that container. The boundary is enforced by the sidecar — a per-agent bearer token, checked at the memory chokepoint before the route switch, with the acting agent resolved from the token rather than from a caller-supplied slug. An agent with shell access in the container can still read its crewmates’ files directly; the guarantee covers the API surface, not the filesystem. If a workflow needs cross-agent state, the right surface is CREW.md or topics/, written by the lead.
  • Budget is measured in characters, not tokens. The default 15,000-character budget is ~3,500–4,500 tokens depending on language and content. Don’t reason about budget as if it were tokens — the orchestrator slices on character boundaries.
  • The 10 MB per-agent memory cap is a hard limit. Once a directory exceeds it, new writes fail rather than evicting old ones — consolidation isn’t automatic eviction. Curated facts only, not raw logs.
  • Don’t delete AGENT.md entries casually. Memory is append-mostly. Remove only entries that are explicitly outdated — pruning live knowledge is what makes an agent feel “dumber than yesterday”.
  • Migration v54/v55 must be applied for hybrid retrieval. Older databases that predate the Crew Journal era (PRs #211/#212) don’t have the importance_score, reference_count, or journal_entries_fts columns these endpoints read. Migrations apply automatically on crewship start, so start the upgraded binary once before depending on RRF scoring.
  • Docker network Internal: true blocks sidecar → embedding model. If the agents network is Internal: true, the sidecar can read its on-disk FTS5 index but cannot reach a remote embedding service if one is configured, silently degrading hybrid retrieval to BM25-only. The fix lives in internal/provider/docker/docker.go:ensureNetwork — the network must be Internal: false with a gateway.
  • Episodic memory — the hybrid retrieval (RRF) + importance/decay/reinforce uplift from PRs #211/#212 that this guide builds on.
  • Consolidate — the nightly worker that promotes recurring daily/*.md observations into AGENT.md / CREW.md and archives the original logs.
  • Crew Journal — the event log behind the FTS5 mirror and the journal_embeddings table memory reads from.
  • Keeper — where secrets belong instead of memory files (memory is not encrypted at rest).
  • Orchestration — the runtime that calls BuildMemoryBlock to inject memory into system prompts at agent-run time.
  • Skills — for agent-side packaged knowledge that doesn’t change per-user/per-crew; complementary to memory rather than overlapping.