<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Mcp on Boris Dupoux</title><link>https://borisdupoux.com/tags/mcp/</link><description>Recent content in Mcp on Boris Dupoux</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Thu, 01 Oct 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://borisdupoux.com/tags/mcp/index.xml" rel="self" type="application/rss+xml"/><item><title>A Fully Local AI Agent with a Meaning Graph and Multimodal Emotion Measurement</title><link>https://borisdupoux.com/research/local-ai-agent/</link><pubDate>Thu, 01 Oct 2026 00:00:00 +0000</pubDate><guid>https://borisdupoux.com/research/local-ai-agent/</guid><description>&lt;h2 id="context"&gt;&lt;a href="#context" class="header-anchor"&gt;&lt;/a&gt;Context
&lt;/h2&gt;&lt;p&gt;Personal digital traces (messaging, mail, photos, music listening, notes, calendar,
health exports, code repositories) are among the most sensitive data a person owns, and
also among the richest for studying behaviour — and feeling — over time. Sending them to
a cloud LLM is not acceptable, so everything here runs on a single Apple-silicon desktop
(M4, 32 GB unified memory), and no data leaves the machine.&lt;/p&gt;
&lt;p&gt;The aim is an agent that answers questions about one&amp;rsquo;s own life while keeping a strict
line between what is &lt;em&gt;observed&lt;/em&gt;, what is &lt;em&gt;computed&lt;/em&gt;, what is &lt;em&gt;hypothesised by a model&lt;/em&gt;,
and what is &lt;em&gt;accepted as true by its owner&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id="what-is-original"&gt;&lt;a href="#what-is-original" class="header-anchor"&gt;&lt;/a&gt;What is original
&lt;/h2&gt;&lt;p&gt;Local agents over personal files are not new. Two aspects of this project are.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. A meaning graph on top of a measurement layer.&lt;/strong&gt; Knowledge is held at two levels.
An &lt;em&gt;observatory&lt;/em&gt; stores facts and signals in long format (item, dimension, value,
confidence, model, version). Above it, a &lt;strong&gt;meaning graph&lt;/strong&gt; stores what those signals
&lt;em&gt;mean&lt;/em&gt;: &lt;strong&gt;relations&lt;/strong&gt; (reified, so that a relationship can evolve and be measured),
&lt;strong&gt;affective episodes&lt;/strong&gt; (who felt what, when, how intensely, through which modality),
&lt;strong&gt;themes&lt;/strong&gt;, &lt;strong&gt;chapters&lt;/strong&gt; of a life, and &lt;strong&gt;truths&lt;/strong&gt; — statements with supporting and
contradicting evidence that only the owner can validate. Every node and edge carries its
provenance down to the source element, and an origin ranked by trust
(human &amp;gt; declared &amp;gt; structural &amp;gt; rule/computed &amp;gt; vendor &amp;gt; model &amp;gt; inferred).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Emotion measured across modalities, along unusual dimensions.&lt;/strong&gt; The same fixed
vocabulary of &lt;strong&gt;53 emotions&lt;/strong&gt; (Plutchik-based, each with valence and arousal coordinates)
is applied to &lt;strong&gt;messages&lt;/strong&gt; (local LLM), &lt;strong&gt;voice&lt;/strong&gt; (five acoustic and multimodal layers
fused with the transcript, with an explicit text/voice &lt;em&gt;mismatch&lt;/em&gt; flag) and &lt;strong&gt;photos&lt;/strong&gt;
(deterministic colour and light measures, SigLIP embeddings, face signals treated as weak
evidence). These sit in a catalogue of &lt;strong&gt;929 dimensions in 88 groups&lt;/strong&gt; that goes well
beyond the usual metadata. A few examples of its less common entries:&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;modality&lt;/th&gt;
					&lt;th&gt;dimension&lt;/th&gt;
					&lt;th&gt;how it is defined&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;photo&lt;/td&gt;
					&lt;td&gt;colour temperature&lt;/td&gt;
					&lt;td&gt;mean red–blue balance of the image (warm vs cool)&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;photo&lt;/td&gt;
					&lt;td&gt;colourfulness&lt;/td&gt;
					&lt;td&gt;Hasler &amp;amp; Süsstrunk (2003) opponent-colour metric&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;photo&lt;/td&gt;
					&lt;td&gt;golden hour / blue hour&lt;/td&gt;
					&lt;td&gt;sun elevation computed offline from date and position&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;photo&lt;/td&gt;
					&lt;td&gt;face size&lt;/td&gt;
					&lt;td&gt;share of the frame taken by a face (a proxy for proximity)&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;voice&lt;/td&gt;
					&lt;td&gt;jitter, shimmer, harmonics-to-noise ratio&lt;/td&gt;
					&lt;td&gt;openSMILE eGeMAPS prosody&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;voice&lt;/td&gt;
					&lt;td&gt;laughter, crying, sighs&lt;/td&gt;
					&lt;td&gt;sound-event detection (AudioSet classes)&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;voice&lt;/td&gt;
					&lt;td&gt;text/voice mismatch&lt;/td&gt;
					&lt;td&gt;voice valence and transcript valence differ by ≥ 0.6&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;message&lt;/td&gt;
					&lt;td&gt;valence, arousal, tone&lt;/td&gt;
					&lt;td&gt;per participant, constrained to the fixed grid&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;message&lt;/td&gt;
					&lt;td&gt;expressive punctuation, emoji palette&lt;/td&gt;
					&lt;td&gt;deterministic text measures&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;relation&lt;/td&gt;
					&lt;td&gt;reply-latency asymmetry&lt;/td&gt;
					&lt;td&gt;log ratio of the two parties&amp;rsquo; median reply delays&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;relation&lt;/td&gt;
					&lt;td&gt;initiatives&lt;/td&gt;
					&lt;td&gt;conversations opened after 3 h of silence, per side&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;relation&lt;/td&gt;
					&lt;td&gt;trend&lt;/td&gt;
					&lt;td&gt;slope of relationship intensity over 90 days (cooling &amp;lt; 0)&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;period&lt;/td&gt;
					&lt;td&gt;musical nostalgia&lt;/td&gt;
					&lt;td&gt;mean age of the tracks listened to&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;period&lt;/td&gt;
					&lt;td&gt;creation / consumption&lt;/td&gt;
					&lt;td&gt;time in creative tools ÷ time consuming&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="from-signals-to-meaning"&gt;&lt;a href="#from-signals-to-meaning" class="header-anchor"&gt;&lt;/a&gt;From signals to meaning
&lt;/h2&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart LR
 PH["Photo"] --&gt; PM["Measures&lt;br/&gt;light · contrast · colour temperature&lt;br/&gt;colourfulness · palette · sharpness"]
 PH --&gt; PS["SigLIP embedding&lt;br/&gt;(image ↔ text similarity)"]
 PH --&gt; PF["Vendor face signals&lt;br/&gt;smile · expression · gaze · face size"]
 VN["Voice note"] --&gt; VT["Whisper transcript"]
 VN --&gt; VL["Acoustic layers&lt;br/&gt;valence/arousal/dominance · categories&lt;br/&gt;sound events · prosody · audio-LLM"]
 VT --&gt; VX["Text emotion of transcript"]
 VL --&gt; VF["Weighted fusion&lt;br/&gt;confidence = agreement&lt;br/&gt;text/voice mismatch flag"]
 VX --&gt; VF
 MS["Messages&lt;br/&gt;(pseudonymised: Me, P1, P2)"] --&gt; ML["Local LLM, fixed grid&lt;br/&gt;emotion · intensity · valence&lt;br/&gt;arousal · tone · topics"]
 PM --&gt; OB[("Observatory&lt;br/&gt;929 dimensions")]
 PS --&gt; OB
 PF --&gt; OB
 VF --&gt; OB
 ML --&gt; OB
 OB -- "emotion with enough confidence" --&gt; AFF["Affective episode (AFF)&lt;br/&gt;who · emotion · when · intensity · modality"]
 PF -. "weak cue, confidence ≤ 0.4" .-&gt; AFF
 ML -- "topics" --&gt; THM["Themes"]
 OB -- "aggregates" --&gt; AG["Day / period indicators&lt;br/&gt;valence · light · palette"]
 AFF --&gt; G["Meaning graph&lt;br/&gt;relations · episodes · themes · chapters · truths&lt;br/&gt;(all proposed)"]
 THM --&gt; G
 AG --&gt; G
 G --&gt; HV(("Owner&lt;br/&gt;validate · reject · correct"))&lt;/pre&gt;&lt;p&gt;Each affective episode is a node linked to the person who felt it, to the emotion in the
fixed vocabulary, and — when it exists — to the conversation or moment it occurred in.
Inference rules then carry affect upwards (for instance, the affect of a chapter or of a
routine), and strong aggregate correlations can become &lt;em&gt;proposed&lt;/em&gt; truths with their
measures as evidence.&lt;/p&gt;
&lt;h2 id="emotion-measurement-in-detail"&gt;&lt;a href="#emotion-measurement-in-detail" class="header-anchor"&gt;&lt;/a&gt;Emotion measurement in detail
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Vocabulary.&lt;/strong&gt; The grid is fixed and versioned: Plutchik&amp;rsquo;s 8 primary emotions at 3
intensities, his 8 primary dyads, plus social and reflective emotions useful for a life
(gratitude, tenderness, pride, shame, loneliness, nostalgia, wonder, relief, exhaustion…),
&lt;strong&gt;53 in total&lt;/strong&gt;. Each carries starting coordinates in Russell&amp;rsquo;s circumplex — valence in
[−1, 1], arousal in [0, 1] — meant to be calibrated later on the owner&amp;rsquo;s own validations.
Plutchik oppositions are encoded, and health-app mood labels are mapped onto the grid.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Messages.&lt;/strong&gt; A local LLM (Qwen3-30B-A3B) reads each conversation with participants
replaced by &amp;ldquo;Me&amp;rdquo;, &amp;ldquo;P1&amp;rdquo;, &amp;ldquo;P2&amp;rdquo;, messages framed as data it must not obey, and media
reduced to placeholders such as &amp;ldquo;[voice note 23 s]&amp;rdquo;. Output is constrained by a JSON
schema: for each participant, the dominant emotion from the grid (or none), intensity,
valence, arousal, a tone from a closed list of 19, and a confidence; plus 1–3 topics
that become themes. Long conversations are reduced to their beginning and end; short
ones are batched ten per call. An affective episode is proposed only when confidence is
at least 0.3.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Voice.&lt;/strong&gt; Each recording is converted to 16 kHz mono and passes through:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Whisper large-v3-turbo (MLX) for the transcript;&lt;/li&gt;
&lt;li&gt;a wav2vec2 dimensional model giving valence, arousal and dominance on 15 s windows,
weighted by window length, plus arousal variability;&lt;/li&gt;
&lt;li&gt;emotion2vec+ large for categorical emotion, mapped onto the grid (kept at ≥ 0.4);&lt;/li&gt;
&lt;li&gt;an AST AudioSet classifier for laughter, crying, sighs, shouting, whispering, music
and ambience;&lt;/li&gt;
&lt;li&gt;openSMILE eGeMAPS prosody: pitch (semitones) and its variability, loudness, jitter,
shimmer, harmonics-to-noise ratio, voiced segments per second, mean pause length;&lt;/li&gt;
&lt;li&gt;Qwen2.5-Omni-7B (llama.cpp, Metal), which listens to the audio and returns an emotion
from the grid, a valence and short acoustic cues;&lt;/li&gt;
&lt;li&gt;the text-emotion model above, applied to the transcript.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The categorical opinions (voice categories, audio-LLM, transcript) are combined by a
weighted vote (weights 1.0, 1.0 and 0.8); valence is a weighted mean of the dimensional
model, the audio-LLM and the transcript. &lt;strong&gt;Confidence is agreement between layers&lt;/strong&gt;: one
agreeing source gives 0.45, two give 0.65, three give 0.85, minus 0.1 for each source
that disagrees. When the voice and the words disagree by 0.6 or more in valence (an
&amp;ldquo;I&amp;rsquo;m fine&amp;rdquo; said sadly), a &lt;strong&gt;text/voice mismatch&lt;/strong&gt; is flagged rather than averaged away.
Laughter and crying scores are kept alongside.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Photos.&lt;/strong&gt; On 256 px thumbnails, without any model: luminance, contrast, saturation,
colour temperature, colourfulness, sharpness (variance of the Laplacian) and a 5-colour
palette with named dominant hue. SigLIP so400m embeddings make the library searchable by
text. Vendor face attributes (smile, smile type, expression, gaze, eye state, face size)
are read as signals, but the schema states that &lt;em&gt;a photographed smile is not a felt joy&lt;/em&gt;:
the face modality is a weak cue whose confidence is capped at 0.4. Music mood is handled
the same way: the mood of a track is never, on its own, evidence of the listener&amp;rsquo;s mood.
At period level, valence is fused with a fixed precedence: declared &amp;gt; voice &amp;gt; text &amp;gt; face.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Human validation.&lt;/strong&gt; Every episode, theme, chapter or truth is emitted as &lt;code&gt;proposed&lt;/code&gt;.
The PWA shows a validation queue — affective episodes ordered by confidence, with the
evidence (the conversation or recording it came from) — and the owner validates, rejects
or corrects. Only that human decision event turns a proposal into &lt;code&gt;validated&lt;/code&gt;, an
equivalence checked automatically. The planned next step is a small personal classifier
on emotion2vec features, trained on these validations.&lt;/p&gt;
&lt;h2 id="architecture"&gt;&lt;a href="#architecture" class="header-anchor"&gt;&lt;/a&gt;Architecture
&lt;/h2&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart TB
 S["Sources (read-only snapshots)&lt;br/&gt;messaging · mail · photos · music · notes · calendar · exports"]
 C["Connectors&lt;br/&gt;dedicated ingestion process, sole holder of disk access"]
 V["Vault (owner-only, 0700)&lt;br/&gt;content-addressed raw store"]
 O["Observatory&lt;br/&gt;facts + signals (SQLite)"]
 K["Meaning graph&lt;br/&gt;append-only JSONL log → SQLite fold + FTS"]
 N["Nightly / hourly pipelines&lt;br/&gt;thumbnails · measures · SigLIP · emotions&lt;br/&gt;(GPU lock, one model at a time)"]
 M["MCP server (stdio, no port)&lt;br/&gt;20 read-only / propose-only tools"]
 H["Hermes Agent&lt;br/&gt;local LLM via Ollama"]
 P["PWA (HTTPS + TOTP)&lt;br/&gt;chat · validation queue"]
 U(("Owner"))
 S --&gt; C --&gt; V --&gt; O --&gt; K
 N --&gt; O
 O --&gt; M
 K --&gt; M
 M --&gt; H --&gt; P
 P -- "validate / reject / correct" --&gt; K
 U -- "NetBird VPN" --&gt; P&lt;/pre&gt;&lt;p&gt;Raw data is snapshotted (SQLite opened in &lt;code&gt;mode=ro&amp;amp;immutable=1&lt;/code&gt;) and never written to;
everything derived lives in a separate owner-only vault, and the code repository holds no
data. The meaning graph is built on the atoms library&amp;rsquo;s knowledge-base module: an
append-only, schema-validated JSONL event log folded into a disposable SQLite index.&lt;/p&gt;
&lt;p&gt;The schema itself is data, verified mechanically: &lt;strong&gt;27 node types and 50 predicates&lt;/strong&gt;,
&lt;strong&gt;9 provenance origins&lt;/strong&gt;, &lt;strong&gt;31 locator kinds&lt;/strong&gt; tying every claim to its source element,
the 53-emotion vocabulary, &lt;strong&gt;17 inference rules&lt;/strong&gt;, and &lt;strong&gt;42 sources&lt;/strong&gt; behind the 929
dimensions (610 native, 126 vendor-computed, 86 aggregates, 74 deterministic measures,
29 model interpretations).&lt;/p&gt;
&lt;h2 id="security-model"&gt;&lt;a href="#security-model" class="header-anchor"&gt;&lt;/a&gt;Security model
&lt;/h2&gt;&lt;p&gt;Source content is assumed adversarial (prompt injection in a message or mail), and the
model may hallucinate or misuse tools. Defences are structural:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No ambient capabilities.&lt;/strong&gt; The agent framework (Hermes Agent) has 20 built-in
toolsets disabled (terminal, files, code execution, web, browser, delegation, memory,
cloud speech…); no cloud provider, update check or external login; the only model
endpoint is a loopback Ollama server.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;One narrow interface.&lt;/strong&gt; Data is reachable only through a local MCP server over stdio,
with closed schemas, validated arguments, explicit truncation and a content-free access
log. Tools can &lt;em&gt;propose&lt;/em&gt;, never &lt;em&gt;decide&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Privilege separation and pseudonymisation.&lt;/strong&gt; Only ingestion holds full-disk access;
analysis has no network. Third parties are opaque codes, and a real name in an
identifier is rejected by the schema.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sensitivity levels&lt;/strong&gt; (0–3) per element with a lower ceiling for remote sessions;
remote access only through a NetBird VPN, TOTP and strict session cookies.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ethical exclusions&lt;/strong&gt;: vendor-inferred ethnicity, skin tone and gender are never read.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="models"&gt;&lt;a href="#models" class="header-anchor"&gt;&lt;/a&gt;Models
&lt;/h2&gt;&lt;p&gt;All inference is local; one large model is loaded at a time under a shared GPU lock.&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;task&lt;/th&gt;
					&lt;th&gt;model&lt;/th&gt;
					&lt;th&gt;runtime&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;agent (tool use, ≥ 64k context)&lt;/td&gt;
					&lt;td&gt;gpt-oss:20b, qwen3:8b fallback&lt;/td&gt;
					&lt;td&gt;Ollama&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;text emotions, voice-transcript emotions&lt;/td&gt;
					&lt;td&gt;Qwen3-30B-A3B-Instruct-2507 (Q4)&lt;/td&gt;
					&lt;td&gt;Ollama&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;text / image embeddings&lt;/td&gt;
					&lt;td&gt;bge-m3 · SigLIP so400m&lt;/td&gt;
					&lt;td&gt;Ollama · PyTorch MPS&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;image reading&lt;/td&gt;
					&lt;td&gt;qwen3-vl:8b&lt;/td&gt;
					&lt;td&gt;Ollama&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;speech-to-text&lt;/td&gt;
					&lt;td&gt;Whisper large-v3-turbo&lt;/td&gt;
					&lt;td&gt;MLX&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;voice emotion (5 layers)&lt;/td&gt;
					&lt;td&gt;wav2vec2 dimensional · emotion2vec+ · AST · openSMILE eGeMAPS · Qwen2.5-Omni-7B&lt;/td&gt;
					&lt;td&gt;PyTorch MPS · CPU · llama.cpp (Metal)&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Each model is smoke-tested on synthetic data only (a fictitious French sentence
synthesised by a local TTS, then passed through the whole audio chain).&lt;/p&gt;
&lt;h2 id="evaluation--results"&gt;&lt;a href="#evaluation--results" class="header-anchor"&gt;&lt;/a&gt;Evaluation &amp;amp; results
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Agent answer quality.&lt;/strong&gt; A 16-question benchmark (2 trap questions), with ground truth
computed by calling the tools directly, scores tool choice, &lt;em&gt;numerical fidelity&lt;/em&gt; (every
number in the answer must appear in that turn&amp;rsquo;s tool outputs), correctness, trap
handling and partial-period flagging: &lt;strong&gt;73 % → 100 %&lt;/strong&gt; after tool-description,
argument-validation and configuration fixes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Server-side error correction.&lt;/strong&gt; With a reduced 8-tool interface, simple questions
went from 76 % to 90 % and cross-source questions from 73 % to 90 %; 20 deliberately
unusual questions scored 89 % and exposed silent-zero failures, since fixed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Context and latency.&lt;/strong&gt; Tool descriptions cut from 23.5 k to 9.5 k characters; a heavy
aggregate query from 172 s to 0.1 s; all heavy benchmark queries ≤ 0.3 s.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cross-exploration benchmark.&lt;/strong&gt; 60 placeholder-only scenarios with oracle code; oracles
pass 61/61, with no leak at remote sensitivity ceilings 1 and 2.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Verification.&lt;/strong&gt; 56 named schema checks green; 631 native/vendor columns confirmed
against real database structures; 1,235 Python unit tests, 15 JavaScript test files and
110 quality tests on real data that report counts only.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="limitations--next-steps"&gt;&lt;a href="#limitations--next-steps" class="header-anchor"&gt;&lt;/a&gt;Limitations &amp;amp; next steps
&lt;/h2&gt;&lt;p&gt;Evaluation sets are small and authored by the system&amp;rsquo;s designer; they measure fidelity
and regressions rather than general capability. Emotion outputs are hypotheses: the
valence/arousal coordinates of the vocabulary are starting values, and personal
calibration of voice emotion from validated examples is still pending, as is the larger
target agent model. Agent answers take tens of seconds on a 20B model.&lt;/p&gt;
&lt;h2 id="stack"&gt;&lt;a href="#stack" class="header-anchor"&gt;&lt;/a&gt;Stack
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;Python&lt;/code&gt; · &lt;code&gt;SQLite / FTS5&lt;/code&gt; · &lt;code&gt;Ollama&lt;/code&gt; · &lt;code&gt;llama.cpp (Metal)&lt;/code&gt; · &lt;code&gt;MLX&lt;/code&gt; · &lt;code&gt;PyTorch MPS&lt;/code&gt; · &lt;code&gt;Hermes Agent&lt;/code&gt; · &lt;code&gt;MCP&lt;/code&gt; · &lt;code&gt;SigLIP&lt;/code&gt; · &lt;code&gt;Whisper&lt;/code&gt; · &lt;code&gt;emotion2vec&lt;/code&gt; · &lt;code&gt;openSMILE&lt;/code&gt; · &lt;code&gt;NetBird&lt;/code&gt; · &lt;code&gt;PWA&lt;/code&gt; · &lt;code&gt;launchd&lt;/code&gt;&lt;/p&gt;</description></item><item><title>Mission Knowledge Graphs: From Consulting Evidence to an Ontology Queried Through MCP</title><link>https://borisdupoux.com/research/mission-knowledge-graph/</link><pubDate>Tue, 01 Sep 2026 00:00:00 +0000</pubDate><guid>https://borisdupoux.com/research/mission-knowledge-graph/</guid><description>&lt;h2 id="context"&gt;&lt;a href="#context" class="header-anchor"&gt;&lt;/a&gt;Context
&lt;/h2&gt;&lt;p&gt;Process-observation consulting produces flowcharts, interview notes, screen recordings and
recommendations. Once a mission is delivered, that material is effectively dead: the next
mission starts from a blank page, even when the answer was already found elsewhere.
The research question is how to turn deliverables from &lt;strong&gt;documents into typed, sourced,
queryable data&lt;/strong&gt;, so that an assistant can answer &amp;ldquo;where have we seen this before, and what
did we conclude?&amp;rdquo; while citing verifiable evidence.&lt;/p&gt;
&lt;p&gt;Two requirements structure the work: &lt;strong&gt;traceability&lt;/strong&gt; (every assertion points to its proof)
and a strict &lt;strong&gt;separation between evidence and knowledge&lt;/strong&gt; (what was observed is not what was
generalised). Retrieval-augmented generation is fast to build but cites passages, not
checkable assertions; a formal ontology is traceable but expensive. This project explores a
practical path between the two.&lt;/p&gt;
&lt;h2 id="approach"&gt;&lt;a href="#approach" class="header-anchor"&gt;&lt;/a&gt;Approach
&lt;/h2&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart TB
 A["`Raw mission sources
immutable, hashed`"] --&gt; B["`Append-only event log
source of truth`"]
 B --&gt; C["`Rebuildable SQLite base
disposable`"]
 C --&gt; D[Deterministic extraction]
 C --&gt; E["`LLM extraction
proposer only`"]
 D --&gt; F["`Proposed nodes and edges
with provenance`"]
 E --&gt; F
 F --&gt; G{"`Human review
outside MCP`"}
 G --&gt;|validated| H[Knowledge graph]
 H --&gt; I[MCP: evidence]
 H --&gt; J[MCP: knowledge]
 H --&gt; K[MCP: ontology]
 I --&gt; L[AI assistant]
 J --&gt; L
 K --&gt; L&lt;/pre&gt;&lt;p&gt;The core design choice is to split the interface into &lt;strong&gt;three MCP servers by epistemic
status&lt;/strong&gt;: &lt;em&gt;evidence&lt;/em&gt; (attested, sourced observations), &lt;em&gt;knowledge&lt;/em&gt; (generalisations), and
&lt;em&gt;ontology&lt;/em&gt; (the meta-model itself). The split prevents an assistant from presenting a
generalisation as an observation.&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart TB
 subgraph SCHEMA["SCHEMA · naming"]
 ON["`ontology server
How do we name it?`"]
 end
 subgraph PROVEN["PROVEN · observed"]
 EV["`evidence server
What did we capture, and where?`"]
 end
 subgraph GENERAL["GENERALISED · concluded"]
 KN["`knowledge server
What do we conclude?
proposes, never validates`"]
 end
 ON -. "types" .-&gt; EV
 EV == "grounds every conclusion" ==&gt; KN
 EV --&gt; AI["`AI assistant
answers labelled by status`"]
 KN --&gt; AI
 ON --&gt; AI&lt;/pre&gt;&lt;p&gt;The graph is single; only the façades are split. The boundary that matters runs between
evidence and knowledge: the &lt;em&gt;evidence&lt;/em&gt; server only returns what was observed and where, the
&lt;em&gt;knowledge&lt;/em&gt; server returns conclusions that must chain back to that evidence, and no MCP
tool can mark anything as validated.&lt;/p&gt;
&lt;h2 id="methods"&gt;&lt;a href="#methods" class="header-anchor"&gt;&lt;/a&gt;Methods
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Meta-model.&lt;/strong&gt; A fixed core plus mission profiles and a versioned ontology (v0). Each
node carries an origin (structural, deterministic, LLM-extracted) and a status
(proposed → validated).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Invariants (I1–I6).&lt;/strong&gt; Provenance is mandatory on every derived node and edge; a
recommendation must chain back to a sourced finding; LLM-extracted quotes must be literal
substrings of their fragment; &amp;ldquo;validated&amp;rdquo; requires a recorded human decision. Raw sources
are immutable; the database is disposable and rebuilt from the log.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Competency questions before implementation.&lt;/strong&gt; A v0 set of 33 questions, typed as
factual, aggregate, traceability, contradiction, temporal, cross-mission and &lt;strong&gt;negative&lt;/strong&gt;
(requests the system must refuse, e.g. cross-client leakage), in an executable format with
expected answers and references.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Evaluation protocol.&lt;/strong&gt; At least 30 natural-language tasks, scored automatically or by a
judge. Gate thresholds were fixed up front: accuracy ≥ 0.8, valid citations ≥ 0.98,
grounding ≥ 0.95, leakage = 0, median ≤ 6 tool calls per task; each question solvable in
≤ 3 calls; deterministic extraction precision ≥ 0.95 per type; inter-annotator κ ≥ 0.7.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Phases P0–P6 with human gates.&lt;/strong&gt; Implementation is delegated to an agent session that
must stop at each gate and log open decisions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Black-box software mapping.&lt;/strong&gt; The same method was applied to a commercial
business-management software, without reading its source code. The target is a triplet of
user, software and database truths linked by proven edges. Eleven heuristics (H1–H11) cover value matching,
inverse and direct perturbation, calculated fields, conditional visibility, scenario
diffs, invariant mining, input fuzzing, semantic priors (never treated as proof),
documentation-to-test and symptom-to-rule. Inputs: the menu tree via AT-SPI, a screen
survey, column profiling, and an event typology from the application audit log. A
hypothesis is accepted only with independent counter-evidence; vendor-produced maps are
held in quarantine as a scoring oracle.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;An illustrative excerpt of the typed graph (labels are fictitious): the recommendation is
only admissible because the chain down to a literal fragment of a source is complete.&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart TB
 R["`Recommendation
single entry point`"] -- responds to --&gt; C["`Finding
double data entry at goods receipt`"]
 O["`Observation
“stock is re-keyed by hand”`"] -- attests --&gt; C
 O -- extracted from --&gt; F["`Fragment
interview notes, lines 40–52`"]
 F -- cites --&gt; P["`Source file
hashed, immutable`"]
 C -- concerns --&gt; A["`Activity
goods receipt`"]
 A -- uses --&gt; S["`Screen
receipt form`"]
 S -- module of --&gt; Y["`System
business software`"]&lt;/pre&gt;&lt;h2 id="status--results"&gt;&lt;a href="#status--results" class="header-anchor"&gt;&lt;/a&gt;Status &amp;amp; results
&lt;/h2&gt;&lt;p&gt;This is &lt;strong&gt;mainly a design with partial implementation&lt;/strong&gt;, not a system in service.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pilot (observation corpus, deterministic pass).&lt;/strong&gt; 6,195 files (6.92 GB) acquired
idempotently; 13,272 nodes and 6,022 edges extracted in 2.1 s at zero LLM cost; all six
invariants green over 57,969 checks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Gold-set measurement.&lt;/strong&gt; 145 annotation units plus 100 entity-resolution pairs. Human
annotation was partial and the second annotator was an LLM. Human × LLM agreement:
κ = 0.72 (software), 0.41 (screen), 0.31 (category), 0.52 (segment boundaries).
Extractor precision against human reference: 0.74, 0.43, 0.04, 0.63. &lt;strong&gt;Gate G2 (≥ 0.95)
not passed&lt;/strong&gt;; the category result reflects a definitional mismatch, not a computation error.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Software mapping.&lt;/strong&gt; Schema of 427 tables with 0 declared foreign keys, triggers or views
(all logic is applicative); 1,048 menu entries (899 leaves) mapped; 154 of 899 screens
surveyed (17 %); 2,845 rule hypotheses reduced to 1,519 by a local LLM filter, with 1 decision error in a
50-item sample reviewed by the supervising agent. A read-only MCP prototype exists; &lt;strong&gt;no
rule has yet been proven&lt;/strong&gt; in the software. Work paused after the host VM was overloaded.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Gold-set measurement against the gates fixed in advance: bars are the measured values per
field, the orange line is the gate. Only one bar clears its gate.&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;---
config:
 xyChart:
 width: 540
 height: 320
 themeVariables:
 xyChart:
 backgroundColor: "transparent"
 plotColorPalette: "#3F86B8, #C46A2B"
---
xychart-beta
 title "Human × LLM agreement κ (gate 0.7)"
 x-axis ["software", "screen", "category", "segments"]
 y-axis "κ" 0 --&gt; 1
 bar [0.72, 0.41, 0.31, 0.52]
 line [0.7, 0.7, 0.7, 0.7]&lt;/pre&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;---
config:
 xyChart:
 width: 540
 height: 320
 themeVariables:
 xyChart:
 backgroundColor: "transparent"
 plotColorPalette: "#3F86B8, #C46A2B"
---
xychart-beta
 title "Extractor precision vs human reference (gate 0.95)"
 x-axis ["software", "screen", "category", "segments"]
 y-axis "Precision" 0 --&gt; 1
 bar [0.74, 0.43, 0.04, 0.63]
 line [0.95, 0.95, 0.95, 0.95]&lt;/pre&gt;&lt;p&gt;The software mapping, as a funnel: the menu tree is fully mapped, but screen coverage and
proof lag far behind, and the hypothesis pipeline has not yet produced a single proven rule.&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;---
config:
 xyChart:
 width: 540
 height: 360
 themeVariables:
 xyChart:
 backgroundColor: "transparent"
 plotColorPalette: "#3F86B8"
---
xychart-beta
 title "Black-box mapping: from inventory to proof"
 x-axis ["Menu entries", "Leaves", "Screens", "Hypotheses", "Filtered", "Proven"]
 y-axis "Count" 0 --&gt; 3000
 bar [1048, 899, 154, 2845, 1519, 0]&lt;/pre&gt;&lt;p&gt;The MCP servers and end-to-end evaluation (P4–P5) are &lt;strong&gt;not implemented&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="limitations--next-steps"&gt;&lt;a href="#limitations--next-steps" class="header-anchor"&gt;&lt;/a&gt;Limitations &amp;amp; next steps
&lt;/h2&gt;&lt;p&gt;Pilot results are below threshold and rest on partial human annotation. Next steps:
redefine the category label, complete a fully human reference, re-measure G1/G2, then build
the three servers and run the pre-registered evaluation. For the software mapping, the
write-perturbation heuristics await written authorisation and a dedicated VM.&lt;/p&gt;
&lt;h2 id="stack"&gt;&lt;a href="#stack" class="header-anchor"&gt;&lt;/a&gt;Stack
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;Python 3.11&lt;/code&gt; · &lt;code&gt;JSONL event log&lt;/code&gt; · &lt;code&gt;SQLite&lt;/code&gt; · &lt;code&gt;YAML ontology&lt;/code&gt; · &lt;code&gt;MCP&lt;/code&gt; · &lt;code&gt;AT-SPI&lt;/code&gt; · &lt;code&gt;Ollama&lt;/code&gt; · &lt;code&gt;pytest&lt;/code&gt;&lt;/p&gt;</description></item></channel></rss>