Cerebro
Persistent institutional memory, exposed over MCP.
1. What Cerebro is
Cerebro is a knowledge layer that both people and AI agents read from and write to. It stores organizational knowledge — decisions, rules, procedures, product behavior, customer specifics, past resolutions — as retrievable memories, each with attribution and traceability.
1.1 The problem it solves
Every new conversation with an agent starts from zero. Someone has to re-explain which customer this is, what was decided last quarter, why the workaround exists, which rule forbids the obvious approach. That re-explaining is expensive, it's inconsistent between people, and it silently fails when the person who knew is unavailable.
Cerebro moves that context out of individual heads and chat logs into a queryable store. An agent that calls Cerebro first arrives already informed. An agent that doesn't is guessing — confidently, and in a house style that makes the guess hard to spot.
Concretely. Someone discovers that deleting from the Data Sources panel is a hard delete, not a detach. They save it once, into arpia_product, as Product Knowledge. Six weeks later a different person asks a different agent "what happens if I drop a data source?" — and the answer comes back correct, with a link to the memory and the doc it came from. Nobody re-derived it. Nobody had to remember who knew.
1.2 Mental model
initis the map ·loadandsearchare the query ·savewrites ·updatecorrects ·configis the structure.
Two design principles govern the whole API:
Organizational, not personal. Cerebro holds knowledge the organization needs, not user configuration. "We markup LLM pricing at cost × 1.30" belongs in Cerebro. "I prefer bullet points" does not.
Discovery before action. init returns the map — which domains exist, which knowledge types are valid, which org-wide rules are in force. Every other call depends on that map. Init without Load is just discovery; Load without Init is guessing.
1.3 What Cerebro is not
| Not | Why it matters |
|---|---|
| A document store | It holds distilled knowledge, not files. Link to the document via references[]; don't paste it into content |
| A search engine | General knowledge doesn't belong here. "How do I parse JSON in Python" is not a Cerebro question |
| A chat log | Nothing is captured automatically. Every write is a deliberate, explicitly requested act |
| A preferences store | Explicitly out of scope, by design |
| Editable indefinitely | A memory is editable for 15 days, then frozen (§3.3). Write carefully the first time |
1.4 How to read this document
It draws on two sources of truth, and they disagree in places:
- The MCP tool descriptions and API schemas — authoritative for the contract: fields, formats, windows, rejections. Sections 3–8.
- Observed behavior in live sessions — authoritative for defects. Section 9 is entirely empirical and appears in no schema. An agent reading only the tool descriptions will not know any of it.
Where the two conflict, this document states both and marks the resolution as pending. It does not average them.
2. Quick start
The shortest useful path, three calls:
// 1. Get the map. Once per conversation, before anything else.
{ "action": "initalize" }
// 2. See what already exists in a scope, without pulling content.
{ "mode": "list", "domain": ["arpia_product", "arpia_docs"] }
// 3. Ask a specific question and get a sourced answer.
{ "query": "what is the difference between a Repository and a Data Source?",
"domain": ["arpia_product", "arpia_docs"] }
Four rules that prevent most errors:
- Init first. Without it, you don't know which domains or types are valid, and org-wide rules never load.
- Never infer intent to write.
saveandupdaterun only when the user explicitly asks. - Never guess a domain, type, or
mid. If it isn't in loaded context, look it up or ask. - Never pass exactly one domain. A single-element array silently excludes
global, so the immutable rules don't fire (§9.3). Pass two or more, or omitdomainentirely.
One thing to know up front: all six tools declare additionalProperties: true with no enumerated schema properties. Validation is server-side only — malformed payloads fail at runtime, not at the MCP layer. There is no safety net.
3. Core concepts
3.1 Hierarchy
Organization
└── Domain (arpia_product, arpia_ciso, global, ...)
└── Memory (mid_xxxxxx)
├── knowledge_type_id (Procedure, Immutable Rule, ...)
├── parent_mid (causal antecedent)
├── references[] (external URLs with a role)
├── expires_at (optional expiration)
└── status (APPROVED | INACTIVE)
mid format is not documented. Schema examples show both 6 and 8 hexadecimal characters (mid_1df233, mid_a1b2c3d4). Do not infer a fixed length, and never construct a mid — copy it verbatim from an Init, Load, or Search result.
3.2 Object reference
| Object | What it is | Critical consideration |
|---|---|---|
| Organization | Multi-tenant isolation boundary | No memory crosses organizations. Returned as organization_id / organization_name |
| Domain | Grouping and permission scope | Every save and retrieval is bound to a domain. Has a domain_slug (lowercase, underscores) and a human domain_name — not interchangeable across actions (§4.6) |
Memory (mid) | Atomic unit of knowledge | Editable for 15 days from creation, then frozen |
knowledge_type_id | Semantic classification | Valid set is per-organization, from Init's available_types. Do not invent values |
parent_mid | Causal link to an antecedent | Only the primary, canonical antecedent — never "related to." Memories form a tree |
references[] | External links: { url, title, role } | Powers attribution in Search. Replaced wholesale on update, never merged |
expires_at | Auto-expiration, YYYY-MM-DD HH:MM:SS | The only automatic hygiene mechanism |
status | APPROVED or INACTIVE | Case-insensitive input, normalized to uppercase. No other value accepted. INACTIVE retires a memory |
| Scope (access) | read_only or read_write | Per user, per domain. Grantable but not revocable (§4.6) |
3.3 Memory lifecycle
save
│
▼
APPROVED ──── editable window: 15 days ────► FROZEN
│ (title, content, domain, │
│ type, references, │ no further edits
│ expires_at, parent_mid, │
│ status) │
│ │
├──► expires_at reached ──► auto-expired │
│ │
└──► status: INACTIVE ──► retired ◄─────────┘ (see Gap 7)
The window is the single most consequential fact in this document. After 15 days a memory cannot be corrected. The documented recovery — retire it and write a replacement — may itself be blocked by the freeze; see Gap 7 in §12. Plan on writes being permanent.
Practical consequences:
- Set
expires_atat creation for anything provisional. Adding it later is only possible inside the window. - Run a
mode:"list"check before saving. A duplicate you catch today is an edit; a duplicate you catch in a month is permanent. - Draft in the conversation, get confirmation, then save. There is no cheap undo.
3.4 Knowledge types
The authoritative list is available_types from cerebro_init. The table below is a convenience reference and may lag — if Init disagrees, Init wins.
| Type | Intended use | Example |
|---|---|---|
Immutable Rule | Non-negotiable organizational rule | Prohibition of npm install without safeguards |
Business Rule | Business rule, may change | Markup formula for LLM model pricing |
Procedure | Repeatable sequence of steps | Committee Minutes (Actas) generation flow |
Customer Knowledge | Customer-specific context | ELCONIX configuration particularities |
Product Knowledge | Product behavior | Repository vs. Data Source distinction |
Fact | Verified, stable data point | Oria = Ontology Reasoning Interactive Agent |
Tip | Practical learning | Operational shortcut discovered in the field |
Todo Item | Pending work | Open [CONFIRMAR] fields in an Acta |
Type is not cosmetic — it determines how a memory is prioritized during retrieval.
4. The six tools
4.1 cerebro_init — discovery
cerebro_init — discoveryWhen: once per conversation, before any Load, Search, Save, or Update.
Invocation posture — the schema and ARPIA policy disagree.
| Posture | Behavior | Authority |
|---|---|---|
| Context-triggered | Init fires automatically when the first message names a customer/project/product/team, implies scoped work ("I'm working on…"), or asks about organizational knowledge | The tool description. The documented default |
| Explicit-invocation-only | No Cerebro tool fires — including Init — until the user asks ("check Cerebro," "load the context") | ARPIA operating policy for interactive agent sessions, overriding the above |
Policy is stricter than the schema, so following policy never violates the contract. Under explicit-invocation-only, an agent that needs Cerebro should say so and wait. Rules 2–4 in §2 hold under both.
Do not call: Init already ran this conversation; the question is general knowledge; the conversation won't touch institutional knowledge.
{ "action": "initalize" }
The literal value is
initalize— missing the secondi. The correctly spelledinitializeis not documented as supported. Do not "fix" it.
Response fields
| Field | Meaning |
|---|---|
user | Authenticated user's email |
organization_id | Tenant identifier |
organization_name | Human-readable org name |
available_types | Knowledge types this user may save |
domain | Domains this user can save to and retrieve from (field is singular) |
last_saved_memories | Most recent memories — useful for inferring active work |
global_immutable_knowledge | Org-wide rules spanning all domains |
How to use it: validate every requested scope against domain and every type against available_types. If the user names something absent from either, ask — don't guess. Use last_saved_memories to infer intent when a query is broad.
Presentation: never dump the Init response to the user unless asked. It is a silent planning input.
4.2 cerebro_load — broad context
cerebro_load — broad contextRetrieves a context package for a scope. Broad by nature. Requires prior Init.
When: the user opens work on a known scope — "working on ProjectX today," "continue the ERP migration," "load context for Customer A."
Don't call: that scope is already in the conversation; the user asked one specific question (→ Search); the requested scope matches no domain from Init (→ ask).
Four modes. Every domain array below carries two or more entries. This is the §9.3 mitigation, not a style choice — the schema's own examples use single-element arrays and would trigger the defect. Copy these shapes.
// Mode 1 — semantic retrieval within a scope (default)
{ "query": "natural language question or topic",
"domain": ["arpia_ciso", "arpia_compliance"] }
// Mode 2 — specific memory by ID
{ "mid": "mid_1df233" }
// Mode 3 — latest activity in a scope, no specific query
{ "domain": ["arpia_docs", "arpia_product"] }
// Mode 4 — inventory: metadata only, no content
{ "mode": "list", "domain": ["arpia_docs", "arpia_product"] }
If the work genuinely concerns one domain, pad the array with a second relevant domain, or omit domain and filter the results. Never pass a one-element array.
mode:"list" is the most underused and most valuable mode. Use it for discovery and onboarding, duplicate detection before any save, and as the safe default when Load-vs-Search is ambiguous. Given that writes are effectively permanent (§3.3), the cost of skipping it is asymmetric.
After the call: do not dump results verbatim. Use them to inform the response. The user wants a working agent, not a memory dump.
4.3 cerebro_search — specific answer
cerebro_search — specific answerReturns a synthesized answer plus structured attribution: the contributing memories and their external references. Narrow by nature.
When: a specific question institutional knowledge could answer — decisions, rules, processes, standards, past resolutions. Also when a prior Load didn't cover it, or the conversation moved outside the loaded scope.
Don't call: the answer is already in the conversation; the question is general knowledge; the user wants a broad scope load (→ Load) or wants to write (→ Save).
// Scoped (two or more domains — see §9.3)
{ "query": "natural language question", "domain": ["arpia_compliance", "arpia_ciso"] }
// Across all accessible domains, global rules included
{ "query": "natural language question" }
Query construction: build it intentionally — include the system names, components, and domain terms the user mentioned.
Presentation (critical): present the synthesized answer and its attribution block as-is. Do not paraphrase, summarize, or strip the contributing memories or external references — that structure is what makes the answer auditable, and it is the only defense against the failure mode in §9.1. This is the one place Cerebro requires verbatim pass-through to the user; Init and Load require the opposite.
If empty: say Cerebro has no knowledge on the topic yet, and offer to save what the conversation uncovers. Do not fill the gap with general knowledge.
4.4 cerebro_save — create only
cerebro_save — create onlyCreates only. Never edits. Every call makes a new memory. A payload containing a mid is rejected, with a pointer to cerebro_update.
Reactive only. Called when the user explicitly says "save," "remember," "store," "persist," or equivalent. Never infer the intent.
| Field | Required | Notes |
|---|---|---|
title | Yes | Infer from context |
knowledge_type_id | Yes | From Init's available_types |
content | Yes | The knowledge body |
domain | Yes | A single domain string — unlike Load/Search, which take arrays |
parent_mid | No | Links the new memory as a child of an existing one |
references | No | Array of { url, title, role } |
expires_at | No | "YYYY-MM-DD HH:MM:SS" |
// Minimum
{ "title": "Memory title", "knowledge_type_id": "Procedure",
"content": "Content...", "domain": "arpia_ciso" }
// With causal antecedent and references
{ "title": "Q2 follow-up on auth refresh design",
"knowledge_type_id": "Tip",
"content": "Continuation of the original auth refresh notes...",
"domain": "arpia_product",
"parent_mid": "mid_a1b2c3d4",
"references": [
{ "url": "https://docs.arpia.ai/...", "title": "Original doc", "role": "context" }
] }
// With expiration
{ "title": "Temporary access note", "knowledge_type_id": "Tip",
"content": "Vendor VPN credentials valid for the Q4 audit only.",
"domain": "arpia_ciso", "expires_at": "2026-12-31 00:00:00" }
API asymmetry — intentional, do not "correct" it.
savetakesdomainas a single string;loadandsearchtake an array. A memory is written to exactly one domain. The two-domain rule in §9.3 applies to retrieval only.
Multi-domain knowledge: if it fits several domains, issue several saves. There is no multi-domain write.
Do not save when:
- The user didn't ask.
- Nothing identifiable was decided or discovered → ask what to persist.
- It's a personal preference — Cerebro is organizational.
- A near-duplicate may exist → run
load mode:"list"first. If a match exists, that's an edit →cerebro_update.
Content fidelity (hard rule): if a draft was shown to the user before saving, content and title must be character-identical to what was displayed. Not a re-summary, paraphrase, or shortened version.
Continuations are new saves. An addition to an existing memory is a new save, optionally linked via parent_mid. Reach for update only to change the existing record itself.
After the call: confirm in natural language. Surface the returned mid only if a follow-up needs it.
Choosing a parent_mid
parent_midLink only the primary, canonical antecedent:
| New memory | Parent |
|---|---|
| DONE / PROGRESS / SHIPPED | the original TODO |
| Bug-fix documentation | the bug or the delivered feature |
| Postmortem | the incident memory |
| Replacement / refinement | the design it supersedes |
| Correction | the corrected memory |
| Next step | the previous step |
Omit when the relationship is loose ("related to," "same topic") or when several parents would apply — reference it textually in the body instead.
The parent must exist, be in the same organization, with no self-references. When in doubt, omit — it can be attached later via update, inside the 15-day window.
4.5 cerebro_update — correct in place
cerebro_update — correct in placeEdits a record in place. Never creates.
Requires a mid. If you don't have one, don't guess — find it via Search or Load, confirm with the user it's the right record, then update.
Reactive only. Called when the user explicitly asks to change, fix, correct, relink, re-domain, expire, or deactivate an existing memory. Use save instead when the user wants to record something new, even if it continues an existing memory.
Editable fields: title, content, domain, knowledge_type_id, references, expires_at, parent_mid, status. At least one must be present or the call is rejected.
Partial updates: send only what changes; omitted fields are untouched.
// Content only — most common
{ "mid": "mid_a1b2c3d4", "content": "Corrected content goes here." }
// Several fields at once
{ "mid": "mid_a1b2c3d4", "title": "Updated title",
"content": "Updated content.", "knowledge_type_id": "Tip" }
// Move domain — needs read_write on the TARGET
{ "mid": "mid_a1b2c3d4", "domain": "arpia_product" }
// Set an expiration
{ "mid": "mid_a1b2c3d4", "expires_at": "2026-12-31 00:00:00" }
// Clear an expiration — empty string, NOT null
{ "mid": "mid_a1b2c3d4", "expires_at": "" }
// Attach or relink a parent
{ "mid": "mid_a1b2c3d4", "parent_mid": "mid_1df233" }
// Detach parent, memory becomes a root — empty string, NOT null
{ "mid": "mid_a1b2c3d4", "parent_mid": "" }
// Replace the references list — full replace; [] is rejected
{ "mid": "mid_a1b2c3d4",
"references": [ { "url": "https://docs.arpia.ai/...",
"title": "Original doc", "role": "context" } ] }
// Retire a memory
{ "mid": "mid_a1b2c3d4", "status": "INACTIVE" }
Semantics that bite
- Clearing vs. changing. For
expires_atandparent_mid,""is an explicit clear; omitting leaves as-is. Use the empty string, not JSONnull. - References are replaced, not merged. Sending
referencesoverwrites the entire list — to add one, resend the existing ones too. An empty array[]is rejected, so references cannot be cleared this way. - Editing window: 15 days from creation, no exceptions. Including
parent_middoes not waive it. Past the window the record is frozen. On that failure, do not retry. - Moving domains requires
read_writeon the target domain, else rejection. statusaccepts onlyAPPROVEDorINACTIVE.
Response fields: updated_fields reports what actually changed. references_saved, embedding_status, and parent_mid report "unchanged" when not part of the edit.
Correcting a memory past the 15-day window
The documented path: mark the record status: "INACTIVE", then carry the correction forward with a new save, parent_mid-linked to the retired record.
Unresolved contradiction — verify before relying on this. The same source states that past 15 days the record is frozen, and that the recovery is an
updatesettingstatus: "INACTIVE"on that frozen record. Both cannot be true unlessstatusis exempt from the window, which is not documented. See Gap 7 in §12. Until a live test resolves it, assume retirement may fail.
Fallback requiring no update call:
- Write a new memory with the correct content.
- Set
parent_midto the memory being corrected — the "Correction" case in §4.4. - State the supersession explicitly in the body: "Supersedes mid_xxxxxx, which stated X incorrectly."
- Log the bad
midfor an administrator to retire out-of-band.
Step 3 is what prevents a future reader from acting on the stale record. It is not optional.
Invocability status
cerebro_update is declared in the MCP tool surface with the full contract above. However, a session verified Aug 14, 2026 exposed only five invocable tools — init, config, load, search, save — with update absent. Either the surface changed since, or that verification was session-specific.
Declared and invocable are not the same thing. Confirm with one live call before designing a workflow that depends on update. Tracked as Gap 1 in §12.
4.6 cerebro_config — domain structure
cerebro_config — domain structureCreates domains, renames them, grants a user access.
When: the user wants to create or rename a domain, grant access, or references a domain that doesn't appear in their Init response.
Don't call: the domain already exists in Init (just use it); the user asks about domain contents (→ Load/Search); the user asks what domains exist (→ Init).
// Create a domain
{ "action": "create_domain", "domain_slug": "elconix", "domain_name": "ELCONIX" }
// Grant a user access — "domain" here is the NAME, not the slug
{ "action": "add_user_domain", "domain": "ELCONIX",
"user": "target_username", "scope": "read_only | read_write" }
The schema's comment for the first action reads "Crate a Domain" — a typo in the source, not a distinct action.
Inference rules
domain_slug: lowercase, underscores, no spaces or special characters. "ELCONIX customer stuff" →elconix; "backend team knowledge" →backend_team.domain_name: infer from context; ask only if genuinely ambiguous.add_user_domainmatches by name, not slug. The most common config error — passing the slug targets nothing. The caller must already holdread_writeon that domain; if not, the call fails. Report the lack of permission; do not retry. Ask forscopeif unspecified.- Rename: confirm the existing domain appears in Init first; if not, ask before calling.
Rename is documented but not specified. The tool description references an existing_domain field, but no rename payload appears in the schema. Do not construct one by analogy — confirm the exact shape first. Tracked as Gap 8 in §12.
Grants only — no revocation. There is no action to revoke access or change an existing scope (read_only → read_write). Direct the user to an administrator; do not attempt a workaround. Treat every grant as permanent when deciding who to add.
CRITICAL — after every successful call, re-call cerebro_init. A new or renamed domain, or a newly granted access, will not appear in Load or Save until the session context refreshes. Config without an Init refresh = stale domain map. This is the only sanctioned second Init in a conversation. Under explicit-invocation-only posture (§4.1), tell the user the re-init is required and get the go-ahead.
After the call: confirm in natural language ("Domain 'elconix' is now available"). Don't expose the raw response. On rename, remind the user that prior saves or external tools referencing the old slug need updating.
5. Choosing the right tool
First relevant message
│
▼
cerebro_init ───────────────────────────── once per conversation
│
├─ "I'm starting on X" / broad scope ──────────► load (Mode 1 or 3)
├─ "what's the rule for Y?" / one question ────► search
├─ "what exists in this domain?" /
│ duplicate check before saving ─────────────► load (mode:"list")
├─ "save / remember this" ─────────────────────► save
│ └─ continues an existing memory? ────────► save + parent_mid
├─ "fix / correct / retire that record" ───────► update (needs mid)
└─ "create a domain" / "give Jane access" ─────► config ──► re-run init
| Signal in the message | Tool |
|---|---|
| "I'm starting on X" / "load the context for Y" | Load (Mode 1 or 3) |
| "what's the rule for X?" / "what did we decide about Y?" | Search |
| "what's in domain Z?" / duplicate check | Load mode:"list" |
| "save / remember this" | Save |
| "save this, it continues the earlier note" | Save + parent_mid |
| "fix / correct / retire that record" | Update (needs mid) |
| "create a domain" / "give Jane access" | Config → then re-run Init |
| It's already in the conversation | Neither — don't re-query |
| It's general, not organizational, knowledge | Neither — Cerebro is not Google |
Load vs. Search, in one line: Load answers "give me everything for this scope"; Search answers "answer this question."
Save vs. Update, in one line: Save creates — including follow-ups and continuations; Update changes an existing record in place.
6. Canonical workflows
A. Scoped work session
cerebro_init- Validate the user's scope against
domain cerebro_load— Mode 1, ≥2 domains + query- Work;
cerebro_searchfor anything outside the loaded scope - On explicit request,
cerebro_save
B. Safe save into an unfamiliar domain
cerebro_initcerebro_loadwithmode:"list"on the target scope (≥2 domains)- Duplicate found? →
cerebro_update. None? →cerebro_save - Confirm in natural language
C. Correcting an existing memory
cerebro_searchorcerebro_loadto obtain themid- Confirm with the user it's the right record
- Within 15 days of creation? →
cerebro_update. Done. - Past 15 days? → the edit is rejected. Do not retry.
- Attempt
cerebro_updatewithstatus: "INACTIVE"— may also be rejected; see Gap 7 cerebro_savea new memory carrying the correction,parent_mid-linked to the old record, supersession stated in the body
D. New domain
cerebro_config→create_domaincerebro_initagain — mandatory refreshcerebro_saveinto the now-visible domain
7. Rules for agents
| Rule | Applies to |
|---|---|
| Call Init once, first; never twice except after a successful Config | Init |
Never guess a domain, type, or mid — ask the user | All |
| Don't re-query knowledge already in the conversation | Load, Search |
| Never dump raw Init or Load output to the user | Init, Load |
| Do present Search's answer and attribution block verbatim | Search |
| Save and Update are reactive only — never infer intent | Save, Update |
| Displayed draft text must be sent character-identical | Save, Update |
| Cerebro is organizational; personal preferences don't belong | Save |
| Never pass exactly one domain | Load, Search |
| Re-run Init after any successful Config | Config |
8. Failure modes
| Condition | Behavior | Correct response |
|---|---|---|
mid present in a save payload | Rejected | Reroute to update |
update without a mid | Rejected | Find the record first via Search/Load |
update with no editable field | Rejected | Include at least one editable field |
| Memory older than 15 days | Edit rejected, record frozen | Don't retry — attempt INACTIVE (Gap 7) + new Save |
status other than APPROVED/INACTIVE | Rejected | Use one of the two |
references: [] on update | Rejected | References can't be cleared this way |
null instead of "" to clear a field | Not the documented clear | Use the empty string |
Domain move without read_write on target | Rejected | Tell the user; don't work around it |
add_user_domain without caller read_write | Fails | Report lack of permission; don't retry |
add_user_domain given a slug instead of a name | Targets nothing | Pass the domain name |
| Revoke access / change existing scope | No such action | Refer to an administrator |
| Search returns nothing | Empty result | Say Cerebro has no knowledge yet; offer to save what emerges |
| Requested domain not in Init | N/A | Ask the user, or config to create it |
| Malformed payload of any kind | Runtime error, not schema rejection | additionalProperties: true — no MCP-layer validation |
9. Known defects
Everything in this section is empirical. None of it appears in the schema documentation.
9.1 The confidence trap
The most consequential finding from the Oria evaluations, August 2026: models learn institutional structure accurately and then fabricate specific details — CVE IDs, metrics, dates — without flagging that they are extrapolating.
The risk isn't that the model doesn't know. It's that it sounds exactly as confident when it knows as when it invents. In an executive decision-making context, that is worse than having no memory at all: unaided, a reader discounts an unsourced claim. Wrapped in institutional structure, the same claim reads as retrieved fact.
Mitigation:
- Verify every numeric value or identifier against the source memory —
loadbymid. - Prefer
searchoverloadwhen attribution matters; onlysearchreturns the contributing-memories block. - Treat any response without an attribution block as unverified.
- This applies to this document. An earlier revision asserted that
midvalues are always 8 hex characters — an inference from a single example, stated as fact, inside the section warning against exactly that. Where a figure here carries no source or verification date, treat it as unverified.
9.2 Empty memories and phantom duplicates
Memories marked [HIDDEN — EMPTY] or [HIDDEN — DRAFT DUPLICATE] remain retrievable by search. Structure present, substance absent — this feeds §9.1 directly. Editing the title does not remove them from the retrieval index.
Mitigation, assuming update is invocable: within 15 days, correct the content or set expires_at. Past 15 days, attempt status: "INACTIVE" — subject to Gap 7.
Open question: it is not documented whether INACTIVE removes a memory from the retrieval index or merely flags it. If it only flags, retirement doesn't solve this and the phantom stays searchable. Tracked as Gap 9.
Prevention beats all of the above: set expires_at at creation for anything provisional, and run load mode:"list" before every save.
9.3 Domain-scoping defect
Confirmed: when exactly one domain is passed, the global domain is silently excluded — meaning the organization-wide immutable rules are not applied, in what is otherwise the most natural way to call the API.
Mitigation until fixed: always pass ≥2 domains, or omit domain entirely. Every example in §4 follows that shape. Note that the schema documentation's own examples use single-element arrays — following them literally reproduces the defect.
9.4 Fact inversion in the synthesis layer
cerebro_search has produced fact inversions — asserting the opposite of what the source memory says — in its synthesis layer. This is why the attribution block matters: it is not decorative, it is the verification mechanism. Trimming it for brevity removes the only signal that would catch an inversion.
10. ARPIA domain map
As of Aug 14, 2026 — not re-verified since. Confirm against cerebro_init before relying on it.
| Domain | Users | Purpose |
|---|---|---|
global | (all) | Organizational immutable rules |
personal | individual | User's own space |
arpia_oria | 6 | Oria agent — evaluations, defects, design |
arpia_core_support | 5 | Core platform support |
arpia_git | 3 | Repository and versioning practices |
arpia_product | 3 | Product knowledge |
guia_de_cerebro | 2 | Cerebro usage documentation |
csf_risk_governance_outcomes | 2 | NIST CSF 2.0, KEV, risk outcomes |
arpia_ciso | 1 | Security governance |
arpia_compliance | 1 | ISO 27001 / 42001, SOC 2 |
arpia_docs | 1 | Platform documentation |
arpia_feature_updates | 1 | Product changes |
test | 2 | Testing |
new_action_test | 2 | Testing |
10.1 Governance observation
Four high-value domains have a single user (arpia_ciso, arpia_compliance, arpia_docs, arpia_feature_updates). Cerebro's stated purpose is institutional memory; a domain with a single reader is personal memory carrying infrastructure overhead.
This is a bus-factor risk in the most sensitive domains. arpia_compliance in particular should have a second reader — certification knowledge is exactly what an auditor asks for when the person who loaded it is unavailable.
Explicit trade-off: widening access to arpia_ciso exposes unremediated findings to more people. The mitigation isn't to close the domain, it's to split it — open findings in a restricted domain, control and process knowledge in a shared one.
Compounding factor: because access can be granted but never revoked (§4.6), the split must happen before the second reader is added. Adding a reader to arpia_ciso as it stands is a one-way door.
11. Global immutable rules in force
Loaded automatically on every Init. They apply across all domains regardless of working scope — provided the call did not pass exactly one domain (§9.3).
11.1 Backup handling — mid_3d734797
mid_3d734797Every backup goes to .backups/ at the project root, chmod 700 on the directory and 600 on files, with .backups/ in .gitignore. Never file.php.bak next to the original. Backup lookups are performed first and only in .backups/.
11.2 npm install prohibition — mid_26ee9c33
npm install prohibition — mid_26ee9c33npm install / npm i / npm update / npm ci without flags are prohibited. Hierarchy of alternatives: pnpm (--ignore-scripts) → yarn berry (enableScripts:false) → bun (--ignore-scripts) → npm ci --ignore-scripts only as a last resort. Context: the TeamPCP campaign / "Mini Shai-Hulud" worm, March–May 2026, including the first documented attack carrying valid SLSA Build L3 attestations.
Implication for agents: if asked to run a prohibited command, reject the command as formulated, cite the rule, and propose the alternative appropriate to the lockfile present.
12. Open gaps
| # | Gap | Impact | Status |
|---|---|---|---|
| 1 | update declared in schema but absent from the Aug 14 verified session | Whether any memory is correctable from the agent is currently unknown | Needs one live call |
| 2 | No domain access revocation | Access can only be granted, never removed; every grant is a one-way door | Open — requires admin |
| 3 | Scoping excludes global with exactly 1 domain | Immutable rules not applied. Schema examples reproduce the defect | Confirmed |
| 4 | Fact inversion in search synthesis | Executive decision risk | Confirmed |
| 5 | Empty memories retrievable | Feeds detail fabrication. Fixable within 15 days if Gap 1 resolves favorably | Open |
| 6 | Bus factor in single-user domains | Compliance/CISO continuity — blocked behind Gap 2, since the domain split must precede any new grant | Not addressed |
| 7 | 15-day freeze vs. INACTIVE recovery contradiction | The documented recovery from an expired edit window is itself an update on a frozen record. If retirement fails, expired-window memories are permanently uncorrectable and permanently searchable | Needs one live test |
| 8 | Rename references an existing_domain field with no documented payload | Renames cannot be performed safely; slug drift accumulates | Open |
| 9 | Unknown whether INACTIVE removes a memory from retrieval | If it only flags, retirement does not remediate Gap 5 | Needs one live test |
| 10 | No MCP-layer schema validation (additionalProperties: true) | All malformed payloads fail at runtime; no early rejection | By design — behave accordingly |
Updated 4 days ago
