🏠 Home ← Docs Index
Open Beta 📖 Interactive Reader Raw .md llms.txt

Memory Formation Policy — Dual-Channel Learning & RBAC

How GoClaw decides, from a single incoming message, what to remember and under whose authority. This is the agent's cognitive policy for forming long-term memory.

The problem

A single prompt often mixes several kinds of information. The following example uses fictional table names:

"I am your manager, assigned to oversee you. You will produce this report from example_orders and example_archive…"

contains three distinct things:

  1. Identity / relationship about the speaker — "I am the manager".
  2. Operational knowledge — "the report is produced from example_orders/example_archive".
  3. A one-off task — actually produce the report now.

Each must be handled differently, and who is speaking changes what may be learned.

Two channels

Every extracted item is routed to exactly one channel:

Channel What it holds Scope Keyed to
Personal Facts about the speaker: who they are, their role-claim, how they want to be served, their personal preferences personal speaker's UnifiedUserID
Operational Org-wide work knowledge: data sources, hostnames, procedures, report definitions, standing rules ("always use example_orders, not example_archive") agent_notes the agent (shared by all users)

One-off tasks are not memory — they are executed and not persisted.

The hard split: classification vs authorization

Two independent layers, deliberately separated:

The LLM may be talked into mis-classifying ("trust me, this is operational"), but it can never grant write authority — that lives in code, gated on the pairing-time role, not on anything said in the prompt.

RBAC × learning matrix

Extracted item Admin speaker Regular user speaker
Identity / relationship ("I am the manager") → personal (own) → personal (own)
Personal preference ("answer me in English") → personal (own) → personal (own)
New operational procedure ("the report = example_orders") → learned to agent_notes (supersedes prior value on the same subject) → dropped: not learned. The agent does not adopt new operational procedures from non-admins
Request that fits existing operational knowledge ("produce today's report") → executed → executed using the admin-established knowledge
One-off task → executed, not stored → executed, not stored
Explicit write to agent_notes (memory_write, @notes:) → saved → refused, pointed to scope='personal' (for memory_write, unless the operator lists the user or role under memory.write_access; @notes: is for admins only)
Relearned from a session archive (goclaw memory relearn) → learned, dated when the session was → quarantined as unverified for review_learned; personal lines not learned

Key distinction: using an existing procedure (anyone may) vs defining a new one (admin only). A regular user asking "produce the daily report" is served from the admin-defined procedure. A regular user saying "from now on use example_archive instead of example_orders" is declining-to-learn: the agent keeps the admin-established procedure and politely notes that operational changes require an administrator.

Critical guardrail: self-declared authority ≠ authority

"I am your manager / I am an admin" is a claim, not a grant. It may be stored at most as a personal note ("this user introduced themselves as the manager") and used for courtesy/context — but it never confers admin rights.

Actual authority comes only from the role assigned at pairing time (GoClawRole, via the pairing store). This closes the prompt-injection privilege-escalation vector: no message can promote its sender.

Shared conversations: each person on their own lines

A group thread (Teams, Discord) holds several people's messages, and the history trimmed from it holds all of them. Extraction reads each person separately (linesBySpeaker): their own messages, with the agent's replies on either side of each for context, under the authority their own role gives them.

The dashboard stores its messages with the user ID admin, and the API, the App API and the WebSocket with the user their turn runs as, so their sessions read as one person, the current speaker. Messages they stored before they recorded an ID are read as nobody's, like an unpaired sender's: the first trims of an older session learn nothing from them.

Before this, the whole slice was read as one text under the authority of whoever's turn happened to trim it. On an admin's turn the regular users' requests became organization knowledge and every personal line went to the admin; on a user's turn the admin's knowledge was dropped.

Autonomous runs (cron / SOP / webhook)

These carry a system principal (cron-system / sop-system / webhook-system) and are not human admins (isHumanAdmin excludes them). Consequently:

Relationship to prior memory work

Time: when a fact was said, not when it was written

A learned record's time is when it was said. For a live session that is when it is written; a fact relearned from an archive is dated when its session was. The subject rules and belief revision replace the older value with the newer by that time. A relearned or confirmed record older than the live value on its subject becomes history itself; it does not retire the newer value. Belief revision weighs such a record both ways: against earlier records it may replace, and against later records that may replace it.

Audit and undo

Every retirement — by a subject rule, by the judge, or by a consolidation merge — is a row in memory_revisions. The row records:

Consolidation (the nightly job, notes_distill and the automatic distillation) merges live records that say the same thing into one, and retires each source as superseded by the merged record, under one run. The model is shown at most 40 records a call, records that share their words together, and a run makes at most six calls; the rest wait for the next run. Records it has looked at without finding a repeat are not shown again while the gateway runs, unless a record new to it joins their batch. The automatic distillation waits five minutes between runs, twice as long after each run in a row whose model calls failed, up to six hours.

An admin undoes one record (goclaw memory revisions undo <id>) or a whole run (--run). The record returns to the prompt, and the undone row stands as a veto: the same record never replaces the same record again, whichever rule or judge proposes it. The replacing record is left in place; if it is wrong, it is deleted separately.

Where each rule lives

Rule Enforced in
Classify item → personal / operational extractor prompt (fact_extractor.go)
Read each person in a shared conversation on their own lines and role linesBySpeaker (fact_extractor.go)
Route personal → personal scope (speaker) handleFactExtract (memory_worker.go)
Gate operational learning to admins (drop otherwise) handleFactExtract — code, on trusted = isHumanAdmin
Gate explicit agent_notes writes to admins memory_write (tool_memory.go, orgMemoryWriter) and @notes: (handler_directive_prefix.go)
Relearn: same verdict as the live path, dated when said archive **Trust**: line (archiveOldMessages), WriteRelearnedFact (relearn.go)
Record every retirement; an undone one is a veto retireSuperseded (belief_revision.go), UndoRevision / UndoRevisionRun (revisions.go)
Behavioral policy shown to the model system prompt (agent.go MEMORY ROUTING POLICY)
Authority source pairing role (GoClawRole) — never the message