Skip to content

Memory and decision-skill bindings

Two subsystems that look like one, and one flag that used to mean two things. Both caused silent failures — a curated fact that reached no agent, and a binding that was accepted and never evaluated. This is what each actually does and how to turn it on.

There are two memories

They are separate stores with different shapes, different write paths, and different purposes. swarmkit memory addresses the first; the memory-reader skill historically read only the second.

governed memory workspace memory
shape {subject, attribute, value, type, confidence} {topic, context, key_points, tags}
written by governed-memory skill, swarmkit memory, the curation flow memory-writer skill, automatically after a run
reviewed reconcile-on-write, quarantine on contradiction, human gate no
decays yes (confidence by recency) no
stored in the configured store (storage.runtime) the same configured store (workspace_memory table; was .swarmkit/memory.json before 1.227.0), or GBrain
CLI swarmkit memory search \| get \| quarantine \| resolve
UI /memory

Governed memory is for facts you want an agent to treat as established — a correction a human made, a decision that should not be re-litigated. It is curated: a write is reconciled against what is already there, a contradiction is quarantined rather than applied, and resolution is a human action. That machinery is what makes a fact trustworthy enough to act on.

Workspace memory is what a run remembered by itself. Useful, unreviewed, and rewritten freely.

Since 1.168.0 the memory-reader binding reads both, and curated facts are rendered first — they went through review; workspace memory is whatever a previous run happened to record.

Turning governed memory on

Since runtime 1.233.0 it is on unless the workspace says otherwise (design/details/memory-by-default.md): the governed-memory and memory-reconcile skills are bundled and loaded when the workspace defines no skill with that id, and memory-reader / memory-writer are bound automatically, advisory, on every agent. What used to be three easy-to- miss steps is now one optional block:

# workspace.yaml — optional
memory:
  enabled: true                # false switches off everything automatic
  reader: {max_results: 5, similarity_threshold: 0.15, search_scope: all}
  writer: {min_output_length: 100}

The two steps below are what the defaults do for you; you still write them when you want something narrower (a reader scoped to one agent, a reconciler of your own).

1. (Default) The governed-memory skill is present

A copy in skills/ overrides the bundled one — that is how you change its prompt or effects.

2. (Default) memory-reader is bound at pre_input

An explicit binding is used exactly as written and the automatic one is skipped:

# workspace.yaml
governance:
  decision_skills:
    - id: memory-reader
      trigger: pre_input
      scope: "analyst"         # only this agent reads memory
      required: false          # advisory — see below, and read that section
      config:
        governed_limit: 5      # how many curated facts to inject (default 5)

required: false is correct here. A memory read that can fail a run is worse than no memory. Note that before 1.169.0 this silently discarded the binding — if you are on an earlier version, the reader never runs, and swarmkit memory search showing your fact tells you nothing about whether an agent can see it.

3. Grant governed-memory to an agent that should WRITE

Reading needs no grant beyond the binding. Writing does, and the skill carries kb:write:

# archetypes/curator.yaml
defaults:
  skills:
    - governed-memory        # write access — grant deliberately

Do not grant this to make reading work. Reading comes from the memory-reader binding; granting the write skill to get reads is how a curated store stops being curated.

Confirming it works

The reader logs when it injects:

Memory context injected for agent=triage (user=None, query=enumerate the cartons...)

and the agent's input gains a delimited block:

<curated-memory>
Established facts for this workspace:
- sn8 · carton-count-source: Carton count comes from the TASK LIST, not Shipment/Containers.
</curated-memory>

If swarmkit memory search finds the fact and that line never appears, the binding is not reaching the compiler — check your version and the enabled/required spelling below.

Writing a curated fact

swarmkit memory search "carton count"          # what is already known
swarmkit memory get sn8 carton-count-source    # one fact, with its history

A write goes through reconciliation: a new value for an existing (subject, attribute) is compared against the current one and classified — reinforce, refine, update, or contradict. A contradiction is quarantined rather than applied, and swarmkit memory resolve is a human decision. That is the point of the store; an agent cannot overwrite a reviewed fact by asserting louder.

Decision-skill bindings: enabled and required

Two questions, two flags. They were one flag until 1.169.0, and collapsing them is what made an advisory binding disappear.

governance:
  decision_skills:
    - id: memory-reader
      trigger: pre_input
      enabled: true          # does it run at all?      (default true)
      required: false        # can a `fail` stop the run? (default true)
enabled required
asks does this binding run? can its verdict stop the run?
false means the skill is not bound; nothing happens the skill runs; a fail is logged, not fatal
set it in a topology to switch off something inherited from the workspace make an inherited gate advisory

The triggers

trigger fires a fail from a required binding
pre_input before any LLM work rejects the input; no tokens spent
post_output after the agent answers sends it back for revision, bounded by retries
checkpoint between task batches logged only
pre_synthesis before a leader synthesises logged only

Migrating from the old spelling

Before 1.169.0, a topology disabled an inherited binding with required: false, and a workspace binding with required: false was discarded outright. If you have either:

# was: disable an inherited binding
- id: grounding-verifier
  trigger: post_output
  required: false      # now means ADVISORY — it will run

# write instead:
- id: grounding-verifier
  trigger: post_output
  enabled: false       # off

The runtime warns when it sees the old shape on a topology override, naming the binding. It does not reinterpret silently, because a gate that starts running when it used to be off is a real change.

Why both of these failed quietly

Each is the same shape, and it is worth recognising:

  • Governed memory had a full curation flow, a CLI, a UI page and a store — and no agent read path. The reader searched a different store and reported finding nothing, which is exactly what finding nothing looks like.
  • An advisory binding was accepted by swarmkit validate, appeared in the resolved workspace, and was dropped at compile time with no message.

Neither produced an error. If you are configuring either and it appears to do nothing, the useful question is not "is my YAML right" — it is "what does the runtime say it loaded". swarmkit validate answers the first; the injection log line above answers the second.

  • Getting an image to a model — the same "it exists but nothing surfaces it" shape, one subsystem over
  • design/details/governed-memory.md in the repo — the store, reconciliation and decay
  • docs/notes/reported-bugs.md in the repo — bugs 21 and 22, the two failures above