Agent persona memory¶
Each bot of the org module gets a memory of its own: written after every finished wake from the bot's point of view, bounded, inspectable on /org/agents/<name>, and the one path by which a bot may ask for a change to itself. Nothing here lets a bot widen what it is: a self-change is a card a person applies, or closes.
This page is the engine side. The vault side (the memory: block in org.yaml, the compiler that writes it into org-agents.json, the dispatcher that passes a run's turns and cost to wake-done, and the ledger that measures a shipped proposal) lives with the vault and the dispatcher, not here.
Three tiers, not one file¶
| Tier | Holds | Where | Written by | Read |
|---|---|---|---|---|
| Episodic | One row per finished wake: what woke it, the card, how it ended (done, blocked, request_changes, no_report, error), whether a reviewer sent it back, turns, cost, duration, a one-line first-person summary, the log path |
org_agent_episodes |
cli-org wake-done (or cli-org reflect), mechanically, no model call |
cli-org memory episodes, the agent page, the reflect step |
| Semantic | First-person lessons, anti-patterns, preferences and frictions, one claim each, with how often it recurred and which episodes produced it. Closed (superseded or retired), never deleted | org_agent_memory |
The reflect step (one cheap-model call), corrected by the owner or the bot's lead with cli-org memory edit\|retire |
cli-org memory <agent>, cli-org recall, the agent page |
| Core | The active semantic rows rendered under a token budget: the block the wake prompt can inject without a search | .agents/org/memory/<name>/core.md in the vault, generated |
cli-org reflect after every write, memory edit\|retire |
The dispatcher (at wake), cli-org recall, the agent page |
The hand-written .agents/org/memory/<name>.md keeps working next to the generated block: recall searches both, and the agent page shows both.
The write pipeline¶
cli-org wake-done <ids> --run R --exit N --result "…" [--turns N] [--cost USD] [--duration S] [--log path] [--transcript path]
├─ as before: closes the wakeups, posts a fallback status if the run left none
└─ then, never before and never fatal to it:
1. one org_agent_episodes row (mechanical; a second wake-done for the same run is a no-op)
2. if the agent's config allows and the run had at least min_turns turns and the day's cap is not spent:
one cheap-model call with the episode, an excerpt of the run's tool calls and results, and the bot's
active memory → a list of AUDN operations (add / update / delete / none)
3. the operations applied in one transaction: a repeat bumps the row's count, new wording supersedes
it, a delete closes it with the reason; most wakes produce none
4. core.md re-rendered: rows by times seen, then recency, until the budget is spent
5. a friction or anti-pattern seen repeat_threshold times becomes one self-improvement card (below)
The model never sees another bot's run: the input is this agent's episode and this agent's rows, and a line that names another agent other than to describe a handoff is dropped. A failed or slow model call is logged and skipped; the episode stays, and the next cli-org reflect <agent> --run R can retry it.
cli-org reflect is the same step without a wakeup to close: the dispatcher's chat end, a Hermes session, or a backfill call it directly with --reason chat|task|….
Config: the memory: block¶
Per agent, in the vault's compiled .agents/org/org-agents.json under agents.<name>.memory (the vault's compiler writes it from org.yaml, where defaults.memory is inherited and overridden per agent). Every key has a default that works when the block is missing.
{
"tiers": ["core", "episodic", "semantic"],
"core_budget_tokens": 1500,
"reflect": { "enabled": true, "trigger": ["wake_done", "chat_end"], "model": "cheap", "min_turns": 2 },
"self_improvement": { "enabled": true, "max_proposals_per_week": 1, "repeat_threshold": 2, "targets": ["own_prompt", "own_skills"], "requires_ship_gate": true }
}
The engine enforces what no block may set: core_budget_tokens above 4000 is refused, repeat_threshold below 2 is refused, requires_ship_gate is true or the block is invalid, and targets may name only the agent's own prompt and its own skill files. An agent's knowledge list (same file) says which files own_skills means.
reflect.model is cheap, off, or <provider>/<model>. cheap resolves through ORG_REFLECT_MODEL: the local Ollama first, then the default chat provider that has a key here; ORG_REFLECT_MODEL=off turns the model step off everywhere (episodes are still recorded). ORG_REFLECT_MAX_PER_DAY caps the model calls across every bot.
Self-improvement: propose, never apply¶
When a row of kind friction or anti_pattern has been seen repeat_threshold times, the reflect step (or cli-org propose-improvement <agent> --memory-id <id>) asks the cheap model for the smallest change to one of the bot's own files that would stop it, against the exact current text of that file, and validates the answer:
- the target is the agent's own block in
.agents/org/org.yamlor a markdown file under itsknowledgepaths; the harness and the owner's files (AGENTS.md,CLAUDE.md,.claude/, the guard, hooks,CURRENT/,.env) are refused whatever the config says; - in the org.yaml block only prose may change (
personality,goal,perspective,style,voice,principles,description); a diff that touchestools,permission_mode,model,wakes_per_day,capabilitiesor any other key is refused and the refusal names the key; - at most 40 changed lines: a rewrite is refused.
What passes becomes one card of kind improve on the owner's backlog, authored by the bot and assigned to nobody, with the diff, the first-person rationale and the evidence episodes. A person applies it the way any org.yaml or skill edit ships today, or closes it. There is no ship-now path, and requires_ship_gate is not a setting a bot can change. The memory row records the card (promoted_to_proposal, proposal_task_id), so a denied friction re-proposes only after fresh evidence; max_proposals_per_week caps the rest.
Measuring it¶
cli-org memory stats <agent> [--days N] prints the two numbers a shipped proposal is judged by, computed from the episodes: corrections per completed card (episodes marked corrected over episodes that finished done; cli-org task redo marks the builder's latest episode on the reviewed card) and cost per completed card. The ledger and the two-week revert check that use them follow the ratchet pattern in self-improvement.md and live with the vault.
Commands¶
| Command | Who | What |
|---|---|---|
cli-org memory <agent> [--all] |
anyone on the roster (a bot sees only its own rows) | The active lines, most seen first; --all includes closed ones |
cli-org memory episodes <agent> |
same | The finished wakes |
cli-org memory stats <agent> [--days N] |
same | Corrections and cost per completed card, memory counts, proposals filed |
cli-org memory edit <id> "<text>" |
the owner, or the bot's lead | Closes the row and inserts the correction as a new one that supersedes it; re-renders core.md |
cli-org memory retire <id> "<why>" |
the owner, or the bot's lead | Closes the row with the reason |
cli-org reflect <agent> --run R … |
the dispatcher, the owner | The write pipeline for one run (--dry-run prints the operations without writing) |
cli-org propose-improvement <agent> --memory-id <id> [--dry-run] |
the dispatcher, the owner | One card from one recurring row; exit 3 with the reason when refused |
Security¶
org_agent_episodesandorg_agent_memoryhave row-level security: thesb_orgrole reads only rows whereagent_nameis the connection's agent and cannot insert or update either table. Writes come from the owner's connection: the dispatcher'swake-done, the app, the owner's CLI.- The reflect prompt is scoped to one agent's episode;
CURRENT/,Areas/Finance/and.envstay outside everything here as they are outsiderecall. - A generated
core.mdis bounded bycore_budget_tokens; a superseded line stays in the table for audit but leaves the block. - No proposal is applied by code. The validator is in
app/server/src/org-memory.ts(validateProposal), not in configuration.
Settings¶
| Variable | Default | Meaning |
|---|---|---|
ORG_REFLECT_MODEL |
cheap |
What the reflect step calls: cheap (local Ollama, then the default keyed provider), off, or <provider>/<model> |
ORG_REFLECT_MAX_PER_DAY |
200 |
Model calls allowed per day across every bot; past it, wakes still record their episode |