diff --git a/.claude/ux-guidelines.md b/.claude/ux-guidelines.md
index c1dc2cf..43fdbc8 100644
--- a/.claude/ux-guidelines.md
+++ b/.claude/ux-guidelines.md
@@ -434,3 +434,45 @@ For end users, present the same data as:
- **Quiet reasoning summary** — raw counts (nodes, edges, etc.) visually secondary
Internal graph concepts should remain available for developers (Developer Details) but should not dominate the primary view. The panel should feel like a facilitator's notebook: someone looking at it should immediately understand where the investigation stands, what has been learned, and what remains uncertain — without needing to understand graph theory.
+
+## Graph Projection
+
+The reasoning engine produces a rich graph with structured concepts (observations, unknowns, assumptions, relationships, metrics, states). The UI increasingly becomes a translation layer over this graph rather than maintaining separate duplicated summaries.
+
+This section records principles for projecting graph data into human-meaningful views.
+
+### Translation over exposure
+
+- The graph is internal structure; the UI communicates human meaning.
+- User-facing panels should translate graph state rather than expose graph terminology.
+- Display only the amount of graph information useful for the current task.
+
+### Epistemic clarity
+
+- Known information, uncertainty and assumptions must remain visibly distinct.
+- Assumptions must never look like facts.
+- Use explicit structural labels (e.g., "Possible explanation", "Not yet established") rather than relying on colour or implicit cues.
+
+### Curation as explanation
+
+- Prioritisation and omission are part of good explanation.
+- Repeated scenario text should not dominate derived summaries.
+- Complete technical detail remains available through Developer Details.
+
+### Robustness constraints
+
+- Meaning must remain understandable without relying on colour.
+- Displayed content must be grounded in existing graph fields — never invent facts absent from the graph.
+- When nothing useful is established, show calm fallback language rather than an empty panel or a fabricated summary.
+
+### Label hygiene
+
+- Prefer labels over descriptions when labels are clearer.
+- Normalise text for deduplication (lowercase, trim, collapse whitespace).
+- Omit items that are too verbose to scan; do not synthesise rewritten claims that change meaning.
+- Avoid displaying graph identifiers, confidence values without context, or raw enum categories in user-facing views.
+
+### State-aware framing
+
+- The same panel must remain useful during early, active and terminal investigation states.
+- Terminal state content should change its framing (e.g., "What the evidence supports" rather than "Still investigating") but not invent certainty.
diff --git a/components/investigation-summary-panel-v3.jsx b/components/investigation-summary-panel-v3.jsx
new file mode 100644
index 0000000..7836f68
--- /dev/null
+++ b/components/investigation-summary-panel-v3.jsx
@@ -0,0 +1,172 @@
+/**
+ * InvestigationSummaryPanelV3 — Phase 4, Experiment 12
+ * A user-facing facilitator view that translates the reasoning graph into
+ * a concise, human-meaningful presentation.
+ *
+ * Design principles:
+ * - The panel shows up to four sections: what we know, still investigating,
+ * possible explanations, and a quiet summary.
+ * - All content is grounded in existing graph fields. No invented facts.
+ * - Epistemic labels are explicit (structural), not colour-dependent.
+ * - The same panel remains useful during early, active and terminal states.
+ */
+
+import { buildFacilitatorViewModel } from "@/lib/presentation/facilitator-view-adapter";
+
+/* ── Item rendering ─────────────────────────────────────────────── */
+
+/**
+ * Render a single item with its structural label where applicable.
+ */
+function renderItem(item, isExplanation) {
+ if (isExplanation && typeof item === "object") {
+ return (
+
diff --git a/docs/design-evolution-log.md b/docs/design-evolution-log.md
index a5e908c..982b314 100644
--- a/docs/design-evolution-log.md
+++ b/docs/design-evolution-log.md
@@ -502,6 +502,25 @@ The current "Investigation in progress" panel exposes developer-oriented statist
---
+## Emerging Direction — Graph as Source of Truth
+
+The reasoning graph is becoming the shared source of truth for multiple UI views.
+
+Different interfaces may project the same graph for different audiences:
+
+- Version A — compact technical progress;
+- Version B — detailed graph inspection;
+- Version C — user-facing facilitator view;
+- Developer Details — complete diagnostics;
+- Investigation Map — future spatial projection;
+- Current Question — active uncertainty projection.
+
+The UI should not maintain separate invented summaries where the graph already contains the underlying information.
+
+This is an emerging direction, not a final architecture decision.
+
+---
+
## Emerging Direction — Facilitator Translation Layer
> The UI should progressively become a translation layer over the reasoning graph rather than maintaining separate duplicated summaries. Internal graph concepts should remain available for developers, while end users see a facilitator-style explanation of what is currently understood and what remains uncertain.
@@ -531,14 +550,59 @@ A facilitator-style panel should communicate:
- Is a quiet reasoning summary sufficient, or does it need more context?
- Does the translation-layer principle hold — presenting the graph as a notebook rather than raw data?
+#### Result
+
+Partially confirmed.
+
+#### What did we learn?
+
+- Version B proved that the reasoning graph contains substantially more useful information than Version A exposes.
+- The graph already contains observations, unknowns, assumptions, metrics, relationships and state.
+- The graph is rich enough to support multiple UI projections.
+- Exposing the graph almost verbatim overwhelms the user.
+- Technical categories are useful for development but do not directly communicate investigation progress.
+- The user needs a translation of the graph rather than a graph browser.
+- Developer Details should remain the place for complete technical inspection.
+- A user-facing view needs filtering, prioritisation, deduplication and clear epistemic labels.
+
+#### Decision
+
+Keep Version A and Version B available for comparison.
+
+Proceed with a Version C facilitator view built from the same graph.
+
+---
+
+### Experiment 12 — Facilitator View (Version C)
+
+#### Hypothesis
+
+The existing reasoning graph can be deterministically translated into a concise facilitator view that helps the user understand:
+
+- what is currently known;
+- what remains uncertain;
+- what may explain the situation;
+- why the investigation is continuing.
+
+#### Questions
+
+- Can the graph produce a useful human-facing summary without another LLM call?
+- Can observations, unknowns and assumptions be clearly distinguished?
+- Can duplicate or low-value graph content be filtered reliably?
+- Does a concise projection improve understanding without exposing implementation detail?
+- Does the panel remain useful across mocks and live Ollama output?
+- Can the same view work during early, middle and terminal investigation states?
+
#### Evaluation
-Pending visual review.
+Pending visual and live-data review.
#### Status
Experimental.
+Do not record a conclusion yet.
+
---
## Emerging Direction
diff --git a/docs/reasoning-contract-backlog.md b/docs/reasoning-contract-backlog.md
index e4468d1..ca72614 100644
--- a/docs/reasoning-contract-backlog.md
+++ b/docs/reasoning-contract-backlog.md
@@ -143,3 +143,38 @@ The current adapter (`lib/map/investigation-map-adapter.js`) uses generic placeh
7. **Investigation duration tracking**: The summary panel computes elapsed time from `Date.now() - updatedAt`. If the engine emits proper timestamps, the UI can show accurate elapsed duration and investigate stalls (>5 min between turns).
8. **Layout independence (v0.7 workspace layout phase)**: Reasoning outputs must remain entirely independent of presentation layout. The UI's responsive workspace layout — which progressively reveals simultaneous context on wide screens — is a pure presentation concern. No reasoning contract field should be added, removed, or modified to accommodate layout changes. Future reasoning outputs should carry data semantically; how that data arranges itself visually is the responsibility of the presentation layer alone.
+
+---
+
+## Facilitator View Projection (Experiment 12)
+
+Version C derives its content from existing graph fields without requiring new backend data. The following fields are used as inputs:
+
+| Input | Source |
+|-------|--------|
+| node type / kind | `node.kind` (observation, unknown, assumption, state, metric, conclusion) |
+| node label or description | `node.label`, `node.description` |
+| support / status | `node.status`, `resolvedNodeIds` |
+| confidence where available | `node.confidence` |
+| active unknown identity | `graph.activeUnknownNodeId` |
+| resolution state | `node.status === "resolved"` or `resolvedNodeIds.includes(id)` |
+| evidence references where available | `node.evidenceIds` (currently empty in mocks) |
+| relationship relevance where available | `edge.relevance`, `node.relationships` |
+
+### Current limitations (observations, not requests)
+
+The following are observed constraints of the current graph output. They are documented here because they affect the adapter's filtering and ranking logic. They should NOT be treated as backend change requests during this experiment.
+
+- Graph text may repeat the full original scenario verbatim in node labels or descriptions.
+- Labels may be verbose relative to what a user can scan quickly.
+- The selected question rationale and the selected question itself may diverge slightly in wording from the underlying unknown node.
+- Ranking signals (relevance, priority) may not be sufficient for ideal user-facing ordering; the adapter uses deterministic fallbacks.
+- Some assumptions may be too generic to be useful without context.
+- Duplicate semantic content may occur across node types (e.g., an observation and an unknown restating the same scenario fragment).
+
+The adapter handles these limitations through:
+
+1. Length-based filtering of overly verbose items;
+2. Normalised text deduplication across node kinds;
+3. Deprioritisation of items matching known boilerplate patterns;
+4. Deterministic ranking with explicit fallback ordering documented in code comments.
diff --git a/lib/presentation/facilitator-view-adapter.js b/lib/presentation/facilitator-view-adapter.js
new file mode 100644
index 0000000..6ed80d4
--- /dev/null
+++ b/lib/presentation/facilitator-view-adapter.js
@@ -0,0 +1,441 @@
+/**
+ * FacilitatorViewAdapter — deterministic projection of the reasoning graph
+ * into a concise, human-facing facilitator view (Version C).
+ *
+ * This adapter is pure and testable. It receives a prepared view model from
+ * ReasoningWorkspace and returns a structured display model with up to four
+ * primary sections:
+ *
+ * 1. What we know — supported observations, resolved state nodes
+ * 2. Still investigating — unresolved unknowns, active unknown context
+ * 3. Possible explanations — assumptions and tentative causal claims
+ * 4. Quiet reasoning summary — secondary counts from the same graph
+ *
+ * All filtering, deduplication and ranking is deterministic and uses only
+ * existing graph fields. No new backend data or API contracts are required.
+ */
+
+/* ── Normalisation helpers ─────────────────────────────────────── */
+
+/**
+ * Normalise a string for deduplication comparison.
+ * Lowercase, trim, remove punctuation, collapse whitespace.
+ */
+function normaliseText(text) {
+ if (!text || typeof text !== "string") return "";
+ return text
+ .toLowerCase()
+ .replace(/[^\w\s]/g, "")
+ .replace(/\s+/g, " ")
+ .trim();
+}
+
+/**
+ * Remove repeated boilerplate prefixes that add no meaning.
+ */
+function stripBoilerplate(text) {
+ if (!text || typeof text !== "string") return text;
+ const result = text.replace(/^need evidence about\s*/i, "").trim();
+ return result || null;
+}
+
+/**
+ * Determine whether text is too long to scan usefully.
+ */
+function isTooLong(text, maxChars) {
+ if (!text) return false;
+ if (maxChars === undefined) maxChars = 280;
+ return text.length > maxChars;
+}
+
+/* ── Filtering helpers ─────────────────────────────────────────── */
+
+// Patterns that flag content as likely technical or boilerplate summary text.
+const TECHNICAL_SUMMARY_PATTERNS = [
+ /\bnodes?\s*[:\d]/i,
+ /\bedges?\s*[:\d]/i,
+ /\bsorted\s*/i,
+ /by_kind/i,
+ /\b(?:node|edge|unknown|state)\s+count/i,
+];
+
+/**
+ * Decide whether a raw graph text item should be included in the panel.
+ * Returns { included, displayText, reason } where reason is null when accepted.
+ */
+function filterItem(raw) {
+ const label = raw.label;
+ const description = raw.description;
+ const kind = raw.kind;
+
+ // Extract display text — prefer description if it adds beyond label
+ let text = description || label;
+ if (!text || typeof text !== "string") return { included: false, reason: "empty" };
+
+ const trimmed = text.trim();
+ if (!trimmed) return { included: false, reason: "empty" };
+
+ // Depriorise items that are purely technical summaries
+ for (var i = 0; i < TECHNICAL_SUMMARY_PATTERNS.length; i++) {
+ if (TECHNICAL_SUMMARY_PATTERNS[i].test(trimmed)) return { included: false, reason: "technical" };
+ }
+
+ // Skip internal IDs — items whose text is just an ID or contains only one
+ if (/^[a-z0-9-]{1,40}$/i.test(trimmed) && trimmed.length < 60) {
+ return { included: false, reason: "internal-id" };
+ }
+
+ // Depriorise items that are too long to scan usefully.
+ // The adapter does not synthesise rewritten claims from verbose text.
+ if (isTooLong(trimmed)) return { included: false, reason: "too-long" };
+
+ return { included: true, displayText: trimmed, sourceKind: kind };
+}
+
+/* ── Node collection helpers ───────────────────────────────────── */
+
+/**
+ * Determine whether a node is resolved.
+ */
+function isResolved(node, resolvedIds) {
+ return resolvedIds.has(node.id) || node.status === "resolved";
+}
+
+/**
+ * Determine whether this node is the active unknown target.
+ */
+function isActiveUnknown(node, activeUnknownNodeId) {
+ return node.id === activeUnknownNodeId;
+}
+
+/* ── Core adapter function ─────────────────────────────────────── */
+
+/**
+ * Build a Version C facilitator view model from graph data.
+ *
+ * @param {Object} params
+ * @param {Array