Hindsight · engine/reflect · knowledge pages

How a mental model refreshes


Every knowledge page in Hindsight is a mental model that rewrites itself. This is the whole pipeline — the mode decision, the seven phases, every branch, and each of the four outcomes — traced against the code as it stands today.

A refresh is split in two on purpose. _execute_mental_model_refresh runs the entire pipeline and writes nothing — it returns a _MentalModelRefreshRun recording every decision it made. refresh_mental_model persists that run; dry_run_refresh_mental_model reports it and throws it away. There is deliberately no flag that makes the executor behave differently for a preview, because a preview that could be configured would stop predicting the refresh it exists to predict.

1 · The shape of one refresh

Seven phases, in order. Phases 1–4 are unconditional: even a pure full-mode refresh resolves scope, takes a snapshot, and calls reflect. The branching all happens in 5 and 6.

01 Read the trigger requested_mode = trigger.mode or "full" 02 Mode gate resolves use_delta · loads structured_content 03 Scope + snapshot refresh_cutoff · processed_watermark 04 reflect_async → candidate markdown (reflect_result.text) 05 Delta operations only when use_delta — otherwise skipped whole sets delta_applied, final_content, final_structured 06 Guards empty render · delta-window candidate then parse_markdown(final_content) if needed 07 Persist content + structured_content + watermark full mode: phase 5 skipped content_preserved_no_new_facts document untouched · watermark advances refresh_failed_empty_candidate raises · watermark held refresh_failed_delta_not_applied raises · watermark held content_written
One refresh, end to end. The executor writes nothing; it returns a run object carrying the outcome. Three of the four outcomes leave the stored document exactly as it was — only content_written replaces it. Note the dashed bypass: in full mode phase 5 does not run at all, which is why full-mode pages never exhibit delta-specific damage.

2 · Phase 2 — the mode gate

Asking for delta mode is not the same as getting it. requested_mode comes from the trigger — knowledge pages get KNOWLEDGE_PAGE_DEFAULT_TRIGGER, which sets mode: "delta", so delta is the default for every page. Three gates then decide whether the request survives.

trigger.mode "delta" | "full" Gate 1 — baseline content non-empty (never refreshed = empty) Gate 2 — topic last_refreshed_source_query is NULL or unchanged use_delta = True loads structured_content (may be NULL) no_baseline_content first ever refresh source_query_changed topic shifted → runs as full Two more fallback reasons are set later, inside phase 5, once delta is already running: structured_doc_unreadable · delta_ops_failed · delta_ops_all_skipped
Delta is requested, then earned. Gate 1 is why a brand-new page's first refresh is always full — there is no content to edit yet. Gate 2 catches a changed source query, where surgical edits to a document about the old topic would be wrong. use_delta being true still says nothing about whether delta will succeed; three further fallback reasons wait inside phase 5.
Worth knowing

Gate 2 passes when last_refreshed_source_query is NULL, not just when it matches. That is deliberate: someone who hand-writes a page and then turns on delta mode expects their prose to be the starting point, not to be discarded by a one-time full rebuild.

3 · Phase 5 — inside the delta branch

This is where the interesting failures live. The branch needs a baseline document to edit, and it will try two sources in order before giving up.

Step A — find a baseline structured_content stored StructuredDocument validated with Pydantic parse_markdown(content) fallback — re-derives structure from the stored markdown structured_doc_unreadable no baseline at all → delta abandoned NULL / invalid raises Step B — is there anything new? supporting_facts from this run's reflect only content_preserved_no_new_facts returns immediately · no LLM call empty Step C — the operations call delta LLM call sees current_doc as JSON, never as markdown text-mode, hand-parsed apply_operations typed Block payloads: paragraph · bullet_list · ordered_list · code · table delta_applied = True render_document(doc) → content delta_ops_all_skipped delta_ops_failed any exception Step D — what the caller sees effective_mode = "delta" if delta_applied else "full" A page can request delta, pass both gates, call the LLM — and still be recorded as having run in full mode, because effective_mode reports what happened to the document, not what was attempted. When delta did not apply, final_content is still the reflect candidate — which phase 6 then refuses to write.
The delta branch has four exits, and only one of them edits the document. Note step A's fallback arrow: whenever structured_content is missing or fails validation, the baseline is re-derived from the stored markdown. Since create_mental_model never populates structured_content, that fallback is guaranteed to run on a hand-authored page's first delta refresh.

4 · Phase 6 — the guards

Two guards stand between a candidate and the database, and then one last conversion.

Where the table bug lived

That last step is the lossy one. It runs on every full-mode refresh, and until recently a markdown table it could not recognise became a paragraph joined with spaces — the whole table welded onto one line. Because that join is idempotent, the next refresh re-parsed the wreck as prose and re-emitted it, so the page could never recover.

5 · Phase 7 — what actually gets written

The outcome literal decides everything about persistence. Two of the four raise MentalModelRefreshError after recording the failure for audit.

Outcome content structured_content watermark raises Why
content_written written written advances no The normal path, for both full and delta.
content_preserved_no_new_facts untouched untouched advances no Nothing new in the window. The watermark still moves so the empty window is not re-read.
refresh_failed_empty_candidate untouched untouched held yes Almost always an upstream LLM failure. Holding the watermark means a retry re-reads the same window.
refresh_failed_delta_not_applied untouched untouched held yes The candidate covers only the delta window, so it is not a document.

A fifth failure can occur during persistence itself: when the trigger carries a response_schema, the stored content is projected into structured_output, and if that extraction fails the whole refresh is failed rather than persisting content with a stale machine-readable view beside it.

Edge case

The delta-window guard only fires when created_after is set. A model with no last_refreshed_at would fall through and write the candidate as a full document — legitimate, because a candidate read over full history is a document. In practice the column is NOT NULL and defaults to creation time, so a delta refresh always has a window.

6 · Where markdown becomes structure

This is the diagram that matters for the design decision. The delta LLM never sees markdown — it is handed current_doc.model_dump_json() and answers in typed blocks. Markdown enters the structured world at exactly three points, and two of them are on the refresh hot path.

mental_models row content — markdown, authoritative structured_content — nullable cache ① create_knowledge_page client POSTs markdown structured_content stays NULL ② delta baseline parse_markdown(current_content) when the cache is NULL/invalid ③ baseline seeding parse_markdown(final_content) every full-mode refresh reflect candidate free-form markdown prose reflect_result.text delta LLM — typed blocks only model_dump_json() in · operations out structure in render_document damage written here… …is read back as the next baseline Blue = never leaves the structured representation. Red = a markdown → structure conversion, where anything the schema cannot express is lost.
Two of the three conversions are on the hot path. ② and ③ run during refresh, repeatedly and silently, and they feed each other: a full-mode refresh seeds the cache by parsing its own markdown output, and the next delta refresh edits whatever that parse produced. The structured spine in the middle — where the LLM actually works — is already lossless.

7 · The inversion, drawn

The open question is whether structured_content should become the source of truth, with markdown rendered on read. Here is the difference, and it is narrower than it first appears — because on the delta path Hindsight already works this way.

Today markdown authoritative · structure is a cache content markdown · indexed structured_content nullable parse delta ops reflect candidate re-parsed every full refresh • Parsing runs on the hot path, silently, forever • Schema ceiling applies only sometimes — depends on mode • A bad parse is re-signed on every later refresh Inverted structure authoritative · markdown is a projection structured_content source of truth content rendered · still indexed render delta ops ingest parse create · import only • Parsing happens once, at the edge, where failure is visible • Schema ceiling applies always — consistent, not intermittent • New cost: content must be kept in sync as a read model
The edge that disappears is the re-parse. Under the inversion the parser survives, demoted from the refresh loop to the ingest boundary — page creation, import, transfer — where a failure happens once and someone can see it. The delta spine is unchanged in both columns because it is already structure-native.

What the inversion does not remove

Neither approach repairs pages already damaged: a collapsed table is a single physical line, and recognising a table needs at least two. That backfill is its own piece of work regardless of which direction wins.