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.
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_ordersandexample_archive…"
contains three distinct things:
example_orders/example_archive".Each must be handled differently, and who is speaking changes what may be learned.
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.
Two independent layers, deliberately separated:
isHumanAdmin,
derived from the pairing role). Hard, non-bypassable.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.
| 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.
"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.
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.
allow_from or an open policy) is stored
with no user ID. Such lines are nobody's: never the speaker of the running
turn, never trusted, and with no personal memory to go to, so they are not
sent to the extractor at all.isHumanAdmin). For anyone else in the thread it is the role their messages
were stored with: their operational items are learned only when every one of
their messages in the slice was stored with the admin role.admin trust only when everyone in the slice may
author operational knowledge, so a relearn of a shared thread quarantines what
its regular users said.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.
These carry a system principal (cron-system / sop-system / webhook-system)
and are not human admins (isHumanAdmin excludes them). Consequently:
memory_write to agent_notes from such a run is allowed (a
webhook-triggered run included): the run carries out a procedure an admin
wrote, and the procedure may tell it to record a result. They cannot write
global or agent_config, whatever memory.write_access lists: those shape
every conversation, and such a run works on outside input.personal, and no user's personal memory is put in their prompt.review_learned admin tool and the
unverified exclusion remain available for other/manual uses. An
unverified record retires nothing; confirming it queues belief revision.
The memory.quarantine_learned_facts setting that switched quarantine on
is retired: nothing read it after this policy, and a config that still sets
it gets a startup warning saying so.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.
Every retirement — by a subject rule, by the judge, or by a consolidation
merge — is a row in memory_revisions. The row records:
via: subject_rule, belief_revision or consolidation);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.
| 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 |