Da jeg startede pks brain 14. maj havde jeg ikke en graf i hovedet. Jeg havde fire JSONL-filer. Der gik et par uger før jeg så at det var én og samme ting.
Det her er det 3. indlæg i serien om pks brain. I det første gennemgik jeg hvorfor AI-hjernen eksisterer. I det andet viste jeg hvad den producerer.
Erkendelsen kom fra en prompt
Jeg sad i en session og troede stadig vi byggede én ting. Så skrev jeg den her prompt:
Den prompt opdelte det i to: ingest-laget (deterministisk firehose-dump) og graf-laget (det der binder noderne sammen).
Fire nodes, tre edges
DAG'en pks brain producerer består af præcis fire node-typer:
- Session — én Claude Code-session, identificeret ved UUID. Lever som en JSONL-fil i
~/.claude/projects/<projekt>/<uuid>.jsonl. - Prompt — én brugerprompt inde i en session. Tekst, timestamp, eventuel slash-command.
- ToolCall — én tool-invokation drevet af en prompt. Værktøjsnavn, varighed, fejl-flag, parent assistant-uuid.
- File — én fil rørt af et ToolCall. Filsti, op (
read/write/edit/multi-edit), success-flag.
Mellem dem er der tre edges — alle peger samme vej:
Session ──contains──→ PromptPrompt ──drove──→ ToolCallToolCall ──wrote/edited/read──→ File
Visuelt er det den her struktur fra det første indlæg — værd at have foran sig:

Det er en DAG — alle edges peger nedad, ingen cykler. Det er det der gør queries forudsigelige. Et File-opslag kan aldrig ende tilbage hos sig selv.
Firehose-rækker er edges forklædt som tabelrækker
Ingest-fasen skriver fire JSONL-filer i ~/.pks-cli/brain/. Kigger man kun på filerne, ligner hver række en almindelig tabelrække. Men hver række er også en edge i grafen.
prompts.jsonl — én række pr. brugerprompt. I grafen bliver rækken til to ting: Prompt-noden og Session → Prompt-edgen:
{
"sessionId": "1a475348-b3f7-4038-8256-2f364859c3d2",
"projectSlug": "-workspaces-agentic-live-www--...",
"timestampUtc": "2026-04-06T15:28:44.458Z",
"promptId": "0c2762b1-7e6f-45bd-8372-7b499b8a6edd",
"uuid": "85993c63-296b-4d22-a029-9ddacb9a9db5",
"text": "Project: pks-agent-inbox\nan email inbox for agents\n\nScope: scarfold\n\n...",
"textHash": "d39524e8cbdf3c4c",
"cwd": "/workspaces/agentic-live-www/.agentics/_work/...",
"gitBranch": "task/mnmzxlux-85f5wh",
"length": 1901,
"isSlash": false
}
tools.jsonl — én række pr. tool-invokation. Rækken bliver til ToolCall-noden og edgen tilbage til Prompt via parentAssistantUuid:
{
"sessionId": "agent-a6e61f715b03161e3",
"projectSlug": "-tmp-pks-runner-jobs-...",
"timestampUtc": "2026-04-12T15:40:17.614Z",
"toolName": "Glob",
"toolUseId": "toolu_014X92w1RkizWovUjbToPuTf",
"inputDigest": "f7361c8f7acae6c4",
"inputPreview": "{\"pattern\":\"/tmp/pks-runner-jobs/.../**/*\"}",
"parentAssistantUuid": "c0c06cbd-5884-4885-85bb-5d27f47348f0",
"durationMs": 16,
"isError": false,
"resultSize": 1217,
"isMcp": false,
"isSubagent": false
}
files.jsonl — én række pr. fil-operation. Repræsenterer både File-noden og wrote/edited/read-edgen fra ToolCall:
{
"sessionId": "agent-a6e61f715b03161e3",
"projectSlug": "-tmp-pks-runner-jobs-...",
"timestampUtc": "2026-04-12T15:45:18.075Z",
"op": "write",
"filePath": "/tmp/pks-runner-jobs/.../design-system/components.md",
"success": true
}
errors.jsonl — her markerer jeg de ToolCalls der fejlede. Fejlen er metadata på ToolCall, ikke en node i sig selv.
Joinet er gratis fordi der er en timestamp
De fire firehoses har ingen foreign keys mellem sig — kun (sessionId, timestampUtc) til fælles. Det er nok. Har jeg en fil og vil finde prompten bag ændringen, er opskriften: filtrér files.jsonl på filePath, og tag seneste prompt i samme session før fileTs.
I det 4. indlæg bruger pks brain commit-plan præcis den lookup. Den bygger ikke grafen i memory først.
Deterministisk vs. AI-syntetiseret
Ingest kører på 1–2 sekunder fordi der ikke er en model i loopet — 100% deterministisk. Det er bevidst: hele firehose'en skal kunne genberegnes gratis.
Oven på den deterministiske graf ligger to AI-lag:
extract — modellen laver et markdown-resumé pr. session ud fra prompts.jsonl, tools.jsonl og files.jsonl filtreret til den session. synth + wiki — finder klynger af sessions med samme tema (extract-tekst, filstier, tool-mønstre) og skriver én wiki-side pr. klynge.
AI-laget koster tokens, og det er fortolkning — ikke sandhed. Derfor holder jeg dem adskilt: jeg kan ændre extract-skillet og genkøre extracts uden at røre ingest. Og hvis ingest er ødelagt, ser jeg det med det samme i stedet for at opdage det via en wiki der lyver troværdigt.
Hvorfor det er en DAG og ikke "bare" en tabel
Jeg overvejede faktisk at slå alle fire firehoses sammen til én bredtabel: (session_id, prompt_id, tool_call_id, file_path, timestamp). Det ville fungere for de fleste queries.
Som tabel bliver det bare tilstand. Som DAG har hver edge sit eget timestamp, og jeg kan se præcis hvilken prompt der førte til hvilken ændring. Det er den sekvens der gør commit-plan-queryen i det 4. indlæg mulig — staged filer ind, reverse query over grafen, commit-messages ud.
Denne post er blevet revideret 4 gange — se hele historikken
Førstegangs ai-draft. Brugte `knude`/`kanter` (akademisk graf-teori-calque) i headings, prosa og JSON-blok-introduktioner. Definitions-sektioner åbnede med termer i stedet for jeg-iagttagelser. Pks writing score 60 (Tone 2, Terminology 2).
Erstattet `knude`/`kant` (akademisk graf-teori-calque) med `node`/`edge` overalt — calques.txt-driven cleanup. Tilføjet jeg-stemme i definitions-sektioner (deterministisk-vs-AI, hvorfor-en-DAG-og-ikke-tabel) som åbnede med termer i stedet for iagttagelser. Reverse-lookup-sætning normaliseret fra maskinoversat-shape til naturlig dansk-mix prosa.
Naturalness-pass (28.-29. maj) der forkortede sætninger ud fra NATURALNESS-PICKS. Passen fjernede undervejs firehose-sektionens lead-ins (prompts.jsonl/tools.jsonl-overskrifterne) og efterlod 'Det er bevidst.' uden antecedent — rettet i v3.
Naturalness-passen i v2 havde fjernet firehose-sektionens lead-ins (prompts.jsonl/tools.jsonl-overskrifterne) og efterladt 'Det er bevidst.' uden antecedent — begge genoprettet i kompakt form. Genus rettet ('et ToolCall', 'et File-opslag'), heading 'Erkendelsen kom fra dig' → 'fra en prompt' (passede ikke længere til brødteksten), og 'Næste indlæg'-sektionen droppet til fordel for series_footer.
Humanprompten 'there are two things here' erstattet med sessionstory fra session c848c842 (27. maj 08:44): firehose-arkæologien, 'grafen findes allerede som files.jsonl'-fundet og 10ms-vs-200s-tabellen, verbatim.
