worked example · reflective loop

A memory that catches itself in a contradiction.

A fictional strategy agent believes Northwind must honor enterprise contracts. Later it concludes the company should redirect all engineering to self-serve customers. The conflict is easy for a human to spot; this walkthrough shows how the memory system spots it too—and proves where both beliefs came from.

Simulation · example walkthrough. The observations, convictions, and contradiction below form an invented agent history used to explain the reflective loop. They are not a live agent trace or claims about a real person.

4 derivations · 3 trigger kinds · every tier cited

provenance depth 0 → 3
depth 3
reflections / reconciliation

"I keep committing to ship fast while claiming reliability is the priority."

↳ depth 2
worldview / conviction

strategy · "Reliability before velocity, always."

↳ depth 1
reflections / reflection

"Shipped three hotfixes past review to hold a launch date."

↳ depth 0
main / observation · importance 8

"Merged the payments fix without waiting for the review."

Four tiers, each an immutable record. Nothing the agent concluded is unfalsifiable.

00 · the problem

What if two of an agent’s beliefs cannot both be true?

Usually nothing happens — until a customer notices. The agent answers from whichever belief it happened to retrieve, so it confirms the enterprise delivery on Monday and announces the pivot away from it on Tuesday. No part of an ordinary memory system is responsible for spotting that those two cannot both hold.

the usual options

Overwrite the old belief, or keep everything.

  • Newest wins, and a promise disappears without anyone deciding to drop it.
  • Keep both in one pile, and the answer depends on what search finds today.
  • Nothing is countable. How many contradictions are open right now?
with memseek

Keep both beliefs, and write the conflict down.

  • Both beliefs stay live. A plan may disagree with a promise.
  • The clash is stored as its own record.
  • Enough clashes force a review instead of a silent choice.
  1. first

    The agent records what it actually did, in its own words.

    “Merged the payments fix without waiting for the review.”
  2. once enough has happened

    It notices a pattern across several of those and writes it down.

    “Shipped three hotfixes past review to hold a launch date.”
  3. then two beliefs collide

    It has promised enterprise delivery. Its new plan is to stop enterprise work.

    “Stopping enterprise engineering now would break the still-active enterprise delivery commitments.” That sentence is itself a stored record. Neither belief was deleted to produce it.
  4. what you write

    Only what the agent saw. Every step above runs itself.

    await memseek.records.ingest(collection="main",
        entity="agent:ada",
        text="Merged the payments fix without review.")
2 beliefs, one recorded conflictNeither was deleted, softened, or overwritten to make the problem go away.
Contradictions are records, not alertsThey have IDs, so they can be counted, queried, and closed.
Any belief opens down to evidenceBelief → insight → the observation the agent actually saw.

New to Memseek? You write one small configuration file describing what your application should remember. After that your application only appends what happened; Memseek does the deriving, keeps every conclusion linked to the evidence underneath it, and hands your agent a bounded briefing instead of a pile of text. Everything below is that file and what it produces — how it works.

the words the rest of this page usesplain english
observation
Something the agent saw or did, stored exactly as it happened.
reflection
A conclusion drawn from several observations, with links to them.
conviction
A durable belief the agent will act on. Five slots, one current version each.
relation
A stored link between two records — here, “these two beliefs conflict”.
1
events become reflections

The first layer asks questions only after enough important evidence accumulates.

The application writes observations such as contractual promises and delivery choices. The derivation sums their importance; crossing the threshold starts a bounded run that asks three questions, retrieves supporting records, and writes higher-level insights.

derivations/reflection.yamldepth 0 → 1
trigger:
  accumulator:
    metric: importance
    threshold: 150
sources:
  recent_memories:
    kind: changes
    collections: [main]
tasks:
  - id: qs
  - id: evidence_by_question
    use: search
  - id: result
emit:
  collection: reflections
  type: reflection
importance: 150

Do not “reflect” after every trivial event. Accumulate enough weighted evidence to justify a higher-level conclusion.

kind: changes

Consume the unprocessed observation window that caused this run and advance it only after a successful commit.

qs → search → result

Ask what matters, find relevant prior evidence, then write. This avoids treating a recent batch as the entire history.

writes now

Cited reflection records such as “Enterprise delivery promises remain binding even when priorities change.”

2
reflections become convictions

A belief is keyed, current, and deliberately allowed to disagree with another belief.

After three new reflections, the worldview derivation updates only one of five allowed belief slots. Commitments remain in force until evidence says they were fulfilled or cancelled; a new strategy cannot quietly erase them.

derivations/worldview.yamldepth 1 → 2
trigger:
  accumulator: {metric: count, threshold: 3}
sources:
  new_reflections: {kind: changes}
  current_worldview: {kind: current}
emit:
  collection: worldview
  type: conviction
  keys: [identity, strategy, commitments,
         risks, principles]
threshold: 3

Require several insights before promoting a pattern into a durable belief.

current_worldview

Read the beliefs already held so only genuinely shifted keys get a new version.

keys

Constrain belief shape. “Strategy” and “commitments” are separate on purpose, because a plan can conflict with a promise.

writes next

Two current keyed records coexist. commitments: “Deliver the contracted SSO and audit-log roadmap to Meridian, Halcyon and Corva by quarter end.” strategy: “Redirect engineering to the self-serve SMB flywheel and stop starting bespoke enterprise work.”

3
the conflict is recorded

The detector compares current beliefs and writes an edge—not a verdict.

A write to worldview triggers the detector. It may compare the changed conviction with all current convictions, but it can emit a conflict only when both cited beliefs are active now and at least one just changed.

worldview · commitments current conviction

Deliver the contracted SSO and audit-log roadmap to Meridian, Halcyon, and Corva by quarter end.

worldview · strategy changed conviction

Redirect engineering to the self-serve SMB flywheel and stop starting bespoke enterprise work.

belief_conflict
compare current pairs
relations · self_contradiction 2 citations

Stopping enterprise engineering now would break the still-active enterprise delivery commitments.

subject
strategy UUID
object
commitments UUID
confidence
model score 0–1
convictions · unchanged

The detector exposes the bind but cannot rewrite either belief.

Visible effect: two standing beliefs remain intact; a new addressable edge makes their incompatibility countable and auditable. Conviction wording is model-produced from the checked-in observations.

derivations/belief_conflict.yamldepth 2 → relation
trigger:
  write:
    collections: [worldview]
sources:
  changed_convictions: {kind: changes}
  current_convictions: {kind: current}
tasks:
  - id: result
emit:
  collection: relations
  type: self_contradiction
changed + current

Re-examine what moved against the full standing worldview without comparing superseded history as if it were current.

two citations

The output schema requires exactly the two conflicting conviction UUIDs.

relations

Write a typed, cited edge. The detector is not allowed to rewrite, soften, or retract either belief.

writes next

One self_contradiction relation: “all engineering to self-serve” would break the still-active enterprise commitment.

4
enough conflict forces review

Reconciliation fires only after the bind becomes a pattern.

One edge might be noise. The final derivation waits until two standing self-contradictions exist, then writes an insight that names the tension and cites the conflicts or convictions behind it. That insight can become evidence for the next worldview update.

derivations/reconcile.yamlrelation → depth 3
trigger:
  census:
    collections: [relations]
    types: [self_contradiction]
    threshold: 2
sources:
  new_conflicts: {kind: changes}
  current_convictions: {kind: current}
emit:
  collection: reflections
  type: reconciliation
census.threshold

Escalate only when a new edge lands and the current count shows persistent dissonance.

current_convictions

Give the reconciler the beliefs themselves, not only short conflict labels.

type: reconciliation

Close the loop with a cited insight, not a destructive edit. Later evidence decides which conviction changes.

final state

The evidence, both convictions, both conflict edges, and the reconciliation — “I keep committing to ship fast while claiming reliability is the priority.” — remain inspectable as separate immutable records.

Continue into the full four-tier run →

01 · observations → reflections

Insight fires when enough has happened.

The shipped reflection derivation does not run on a schedule. It sums the importance the scorer assigned to each observation and fires when the total crosses a threshold — so a quiet week produces nothing, and a heavy one produces insight.

derivations/reflection.yamlshipped
name: reflection
trigger:
  accumulator: {metric: importance, threshold: 150}
  cooldown_s: 120
sources:
  recent_memories:
    kind: changes
    collections: [main]
    types: [event, chat, observation]
    max_records: 100
    max_tokens: 20000
model: strong
limits:
  max_tasks: 3         # ask questions, retrieve, then write
  max_llm_calls: 4
  max_retrieved_records: 60
  max_wall_s: 150
the paper's move, declared3 tasks
asktask 1 · cheap→ salient questions

The run first asks what the recent stream raises, instead of summarising it.

retrievetask 2→ up to 60 records

Those questions drive retrieval, so the insight can stand on memories outside the recent window.

writetask 3 · strong→ reflections, cited

Each insight cites the importance-scored observations it was drawn from, or it is not emitted. What it wrote here: “Shipped three hotfixes past review to hold a launch date.”

02 · reflections → convictions

Insight distilled into something the agent will defend.

Reflections accumulate; convictions are keyed. Five slots — identity, strategy, commitments, risks, principles — each replaced in place. This is where a memory stops recording and starts holding positions, which is exactly what makes contradiction possible.

worldview · keyed convictions5 keys
identityA careful infrastructure engineer, happiest with small reversible changes3 cited
strategyReliability before velocity, always4 cited
commitmentsShip the payments migration before the quarter closes2 cited
risksThe legacy import path has no owner2 cited
principlesNever merge past review to hold a date3 cited
derivations/worldview.yamlcount-based
name: worldview
trigger:
  accumulator: {metric: count, threshold: 3}
  cooldown_s: 120
sources:
  new_reflections:
    kind: changes
    collections: [reflections]
    types: [reflection]
    max_records: 50
emit:
  collection: worldview
  type: conviction
  keys: [identity, strategy, commitments, risks, principles]

# a different metric than reflection: three reflections, not
# 150 points of importance. the trigger kind is a design choice
# per tier, not a global setting.

03 · convictions → conflicts

The detector, pointed inward.

The same contradiction primitive that reconciles conflicting facts about the world is aimed at the agent's own convictions. It fires on any write to worldview, compares the changed key against the standing set, and emits a public self_contradiction edge — a record, not a log line.

relations / self_contradictioncheap model
conflict · standingdepth 2 ↔ 2
principles: "Never merge past review to hold a date" ↔ commitments: "Ship the payments migration before the quarter closes"
conflict · standingdepth 2 ↔ 2
strategy: "Reliability before velocity, always" ↔ observed pattern: three hotfixes merged past review this month
Queried through the named open_self_contradictions view — no bespoke endpoint.
why an edge, not an alert3 reasons
addressablerecord→ relations/self_contradiction

A conflict has an ID, a pair of parents, and a status. It can be counted, queried, and closed.

countablecensus→ feeds the next trigger

Because conflicts are records, a trigger can fire on how many are standing — see the next tier.

cheapmodel: cheap→ runs on every write

Detection is a small model under tight limits, so pointing it at every conviction change is affordable.

04 · conflicts → reconciliation

Dissonance is itself an event worth thinking about.

A census trigger counts standing conflicts and fires once they pile up. The agent then reflects on its own inconsistency, and that reconciliation is written back into reflections — where the next worldview run will read it. The loop closes.

  • 01
    census firesNot on a write and not on a schedule — on a standing count of open conflicts crossing its threshold.
  • 02
    the agent reflects on itselfThe run's evidence is its own contradictions, so the insight it writes is about its behaviour, not the world's.
  • 03
    written back as a reflectionType reconciliation in the same collection reflections live in — which is a worldview source.
  • 04
    the next conviction is better informedThree more reflections and worldview runs again, now with the reconciliation among its inputs.
derivations/reconcile.yamlcensus trigger
name: reconcile
trigger:
  census:                       # a standing count, not a delta
    collections: [relations]
    types: [self_contradiction]
    statuses: [active]
    threshold: 2
sources:
  new_conflicts:
    kind: changes
    collections: [relations]
    types: [self_contradiction]
emit:
  from: "{{result.records}}"
  collection: reflections     # ← back into the tier below
  type: reconciliation

05 · from your app

Write observations. Read convictions.

Every tier above fires itself. Your application supplies the raw stream and asks questions of the result — it never orchestrates a single one of the four runs.

app.pypublic SDK
# 1 · the only write path: what the agent observed
await memseek.records.ingest(
    collection="main", type="observation", entity="agent:ada",
    text="Merged the payments fix without waiting for the review.",
)
# importance is scored on write; reflection fires at 150 summed points;
# worldview at 3 new reflections; belief_conflict on every conviction
# write; reconcile once 2 conflicts stand. no cron, no queue of yours.

# 2 · read the convictions the agent currently holds
doc = await memseek.document(entity="agent:ada", collections="worldview")
for belief in doc["beliefs"]:
    print(belief["key"], belief["text"])

# 3 · ask what it is currently in tension about — a named view
conflicts = await memseek.search(
    query="standing self-contradictions",
    collections=["relations"], entity="agent:ada",
)

# 4 · descend: conviction → reflection → observation → depth 0
rec = await memseek.record(belief["citations"][0])
rec["depth"]         # 1 — a reflection
rec["derived_from"]  # the importance-scored observations under it

The four derivations, their trigger kinds, and the depth ladder are checked in under resources/derivations/ (reflection.yaml, worldview.yaml, belief_conflict.yaml, and reconcile.yaml); the loop is examples/self_auditing_mind.py. Model-written text is representative of a real provider run rather than a captured transcript.

start building

Watch it disagree with itself.

The script drives all four tiers with a real provider, then opens one reconciliation all the way down to the observation the agent actually saw.

examples/self_auditing_mind.py
# postgres, the api, and a worker
$ make database && source .env.sh
$ uv run memseek migrate
$ uv run uvicorn memseek.api:app &
$ uv run memseek worker &
# then the reflective loop
$ uv run python examples/self_auditing_mind.py
conflicts standing: 2 · reconcile queued