wendkeep 0.71.1 → 0.72.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,34 @@ All notable changes to **wendkeep** are documented here. Format based on
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this project follows
5
5
  [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.72.0] — 2026-08-17
8
+
9
+ ### Added
10
+
11
+ - **Observer SQL authority.** O volume Docker agora usa `/data/observer.sqlite` como
12
+ autoridade única para documentos, sessões, agentes, uso, chamadas LLM e transcripts
13
+ comprimidos; as migrações SQL são versionadas e idempotentes.
14
+ - **Ingestão resiliente.** Hooks de sessão e subagentes enviam eventos idempotentes ao
15
+ Observer, preservam custos registrados e usam outbox local quando o container está
16
+ indisponível.
17
+ - **Dashboard de Consumo.** Cada projeto ganhou resumo de tokens/custos, filtros,
18
+ hierarquia agente/subagente/modelo, tendência diária, chamadas e leitura de transcript.
19
+ - **Migração sem perda.** Conteúdo legado do volume, `MEMORY_EVENTS.jsonl`, frontmatter de
20
+ custo e históricos sem transcript são importados sem apagar as fontes existentes;
21
+ históricos incompletos são marcados como `summary_only`; divergências entre frontmatter e
22
+ ledger ficam em linhas explícitas de reconciliação e `session_id` duplicado é desambiguado
23
+ por arquivo.
24
+
25
+ ### Changed
26
+
27
+ - Markdown deixou de ser autoridade operacional no container. Ele permanece armazenado como
28
+ conteúdo documental no SQLite e só é materializado por exportação explícita.
29
+ - O transporte divide lotes por quantidade e tamanho, reconhece retries do hash legado e
30
+ preserva transcripts grandes dentro do limite HTTP do Observer.
31
+ - Lotes SQL agora usam gzip no transporte e são expandidos com limite controlado no Observer,
32
+ permitindo importar transcripts históricos que excedem 64 MB em JSON puro sem aumentar
33
+ indiscriminadamente o limite de requisição.
34
+
7
35
  ## [0.71.1] — 2026-08-17
8
36
 
9
37
  ### Added
package/README.en.md CHANGED
@@ -88,7 +88,11 @@ Decisions, dead ends, the reason you chose X over Y — gone next session. The p
88
88
  | **Cost** — what it all cost | Per‑model, cache‑aware token pricing per session — plus `cost --trend` with a run‑rate projection across the whole vault; research previews without a final rate remain unestimated. |
89
89
  | **Multi‑agent** — one vault, both agents | `init` wires the session hooks into `.claude/settings.json` *and* `.codex/hooks.json`, and every note is tagged with the agent that wrote it: Claude Code is detected from its environment, anything else is recorded as Codex. One shared graph, whichever agent you are in. |
90
90
  | **Local‑first** — no cloud, no account | Everything is plain Markdown on your disk. An optional MCP server (`@bitbonsai/mcpvault`) lets the agent read/write the vault. |
91
- | **Local Observer** — many projects, one view | `wendkeep observer` keeps snapshots plus a complete copy of sessions, decisions, bugs, learnings, specs, and changes in the Docker volume; the read-only dashboard opens directly at `http://127.0.0.1:8787/`, local mode has no token, and unavailable hooks use an outbox without blocking the session. |
91
+ | **Local Observer** — many projects, one view | `wendkeep observer` keeps documents, sessions, agents, tokens, costs, calls, and transcripts in the Docker volume SQLite database; every project gets a **Consumption** tab in the read-only dashboard at `http://127.0.0.1:8787/`, loopback has no token, and unavailable hooks use a gzip outbox without blocking the session, including large transcripts. |
92
+
93
+ During historical migration, the Observer preserves differences between frontmatter totals and the
94
+ ledger as explicit reconciliation rows, and disambiguates duplicate `session_id` values per file
95
+ without inventing calls.
92
96
 
93
97
  ## Requirements
94
98
 
@@ -111,7 +115,7 @@ npx wendkeep init
111
115
 
112
116
  1. Create the vault folder taxonomy and a templated `README.md` (default vault: `<project>/.<project-name>-vault`, e.g. `.MyApp-vault`; override with `--vault`).
113
117
  2. Write a provider-neutral **`.wendkeep.json`** binding at the project root and a matching `.brain/PROJECT.json` marker in the vault, then merge the session hooks into **`.claude/settings.json`**. The binding is provider-neutral by design: any agent resolves the same vault from its session `cwd`, with no machine-global environment variable. Older registrations already in `.claude/settings.json` are adopted automatically.
114
- 3. Wire the Codex hooks in **`.codex/hooks.json`** — ten compatible entries: `brain-inject` + `session-start` + `observer-publish` on `SessionStart`, `session-ensure` + `change-context` on `UserPromptSubmit`, `session-stop` + `observer-publish` + `change-nag` on `Stop`, `subagent-stop` on `SubagentStop`, and `change-guard` on `PreToolUse` for `Bash`, `exec_command`, `apply_patch`, and mutable MCP tools, always in the `npx wendkeep hook <name>` form. `observer-publish` only publishes a sanitized, fail-open projection; it does not replace the local lifecycle. The guard accepts object, raw-string, and argv Codex payloads; before a mutation it compares the session with the project, Git root, remote, branch, and worktree, denying missing or divergent targets. The other four stay out because Codex offers no equivalent payload, tool, or event: `change-warn` (no reliable `tool_input.file_path`), `plan-capture` (no `ExitPlanMode`), `decision-capture` (`AskUserQuestion` is Claude-only), and `task-log` (`TaskCompleted` is not in Codex's event enum). Codex scope blocks use `permissionDecision: "deny"`; `ask` is never emitted in `PreToolUse`. The merge remains non-destructive, preserves third-party hooks, and migrates legacy `timeout` to `timeoutSec`. **Codex enumerates every hook as untrusted and runs none until you approve the “Hooks need review” prompt at startup — `init` cannot pre-approve them**.
118
+ 3. Wire the Codex hooks in **`.codex/hooks.json`** — eleven compatible entries: `brain-inject` + `session-start` + `observer-publish` on `SessionStart`, `session-ensure` + `change-context` on `UserPromptSubmit`, `session-stop` + `observer-publish` + `change-nag` on `Stop`, `subagent-stop` + `observer-publish` on `SubagentStop`, and `change-guard` on `PreToolUse` for `Bash`, `exec_command`, `apply_patch`, and mutable MCP tools, always in the `npx wendkeep hook <name>` form. `observer-publish` keeps the sanitized index projection and also sends the local SQL authority with documents, consumption, and transcripts; it does not replace the local lifecycle. The guard accepts object, raw-string, and argv Codex payloads; before a mutation it compares the session with the project, Git root, remote, branch, and worktree, denying missing or divergent targets. The other four stay out because Codex offers no equivalent payload, tool, or event: `change-warn` (no reliable `tool_input.file_path`), `plan-capture` (no `ExitPlanMode`), `decision-capture` (`AskUserQuestion` is Claude-only), and `task-log` (`TaskCompleted` is not in Codex's event enum). Codex scope blocks use `permissionDecision: "deny"`; `ask` is never emitted in `PreToolUse`. The merge remains non-destructive, preserves third-party hooks, and migrates legacy `timeout` to `timeoutSec`. **Codex enumerates every hook as untrusted and runs none until you approve the “Hooks need review” prompt at startup — `init` cannot pre-approve them**.
115
119
  4. Add the **`wendkeep-vault`** MCP server to `.mcp.json` so the agent can read/write the vault. Skip with `--no-mcp` — e.g. when the agent already has a vault MCP. (`--no-mcp` skips *only wendkeep's own* MCP; companion MCPs still follow `--companions`.)
116
120
  5. Offer to pin **companion** plugins/MCP (multi-choice; **none** pre-checked — wendkeep is a neutral harness and presumes no third-party plugin). Each is wired the most agent-agnostic way it supports:
117
121
  - **`context-mode`** — context optimizer + FTS5 memory, wired as a Claude Code plugin. It ships its own MCP server, so wendkeep deliberately adds no `.mcp.json` entry (registering both cold-started two servers at once). On non-Claude agents, add the MCP by hand: `npx -y context-mode`.
package/README.md CHANGED
@@ -88,7 +88,11 @@ Decisions, dead ends, the reason you chose X over Y — gone next session. The p
88
88
  | **Cost** — what it all cost | Per‑model, cache‑aware token pricing per session — plus `cost --trend` with a run‑rate projection across the whole vault; research previews without a final rate remain unestimated. |
89
89
  | **Multi‑agent** — one vault, both agents | `init` wires the session hooks into `.claude/settings.json` *and* `.codex/hooks.json`, and every note is tagged with the agent that wrote it: Claude Code is detected from its environment, anything else is recorded as Codex. One shared graph, whichever agent you are in. |
90
90
  | **Local‑first** — no cloud, no account | Everything is plain Markdown on your disk. An optional MCP server (`@bitbonsai/mcpvault`) lets the agent read/write the vault. |
91
- | **Local Observer** — many projects, one view | `wendkeep observer` keeps snapshots plus a complete copy of sessions, decisions, bugs, learnings, specs, and changes in the Docker volume; the read-only dashboard opens directly at `http://127.0.0.1:8787/`, local mode has no token, and unavailable hooks use an outbox without blocking the session. |
91
+ | **Local Observer** — many projects, one view | `wendkeep observer` keeps documents, sessions, agents, tokens, costs, calls, and transcripts in the Docker volume SQLite database; every project gets a **Consumption** tab in the read-only dashboard at `http://127.0.0.1:8787/`, loopback has no token, and unavailable hooks use a gzip outbox without blocking the session, including large transcripts. |
92
+
93
+ During historical migration, the Observer preserves differences between frontmatter totals and the
94
+ ledger as explicit reconciliation rows, and disambiguates duplicate `session_id` values per file
95
+ without inventing calls.
92
96
 
93
97
  ## Requirements
94
98
 
@@ -111,7 +115,7 @@ npx wendkeep init
111
115
 
112
116
  1. Create the vault folder taxonomy and a templated `README.md` (default vault: `<project>/.<project-name>-vault`, e.g. `.MyApp-vault`; override with `--vault`).
113
117
  2. Write a provider-neutral **`.wendkeep.json`** binding at the project root and a matching `.brain/PROJECT.json` marker in the vault, then merge the session hooks into **`.claude/settings.json`**. The binding is provider-neutral by design: any agent resolves the same vault from its session `cwd`, with no machine-global environment variable. Older registrations already in `.claude/settings.json` are adopted automatically.
114
- 3. Wire the Codex hooks in **`.codex/hooks.json`** — ten compatible entries: `brain-inject` + `session-start` + `observer-publish` on `SessionStart`, `session-ensure` + `change-context` on `UserPromptSubmit`, `session-stop` + `observer-publish` + `change-nag` on `Stop`, `subagent-stop` on `SubagentStop`, and `change-guard` on `PreToolUse` for `Bash`, `exec_command`, `apply_patch`, and mutable MCP tools, always in the `npx wendkeep hook <name>` form. `observer-publish` only publishes a sanitized, fail-open projection; it does not replace the local lifecycle. The guard accepts object, raw-string, and argv Codex payloads; before a mutation it compares the session with the project, Git root, remote, branch, and worktree, denying missing or divergent targets. The other four stay out because Codex offers no equivalent payload, tool, or event: `change-warn` (no reliable `tool_input.file_path`), `plan-capture` (no `ExitPlanMode`), `decision-capture` (`AskUserQuestion` is Claude-only), and `task-log` (`TaskCompleted` is not in Codex's event enum). Codex scope blocks use `permissionDecision: "deny"`; `ask` is never emitted in `PreToolUse`. The merge remains non-destructive, preserves third-party hooks, and migrates legacy `timeout` to `timeoutSec`. **Codex enumerates every hook as untrusted and runs none until you approve the “Hooks need review” prompt at startup — `init` cannot pre-approve them**.
118
+ 3. Wire the Codex hooks in **`.codex/hooks.json`** — eleven compatible entries: `brain-inject` + `session-start` + `observer-publish` on `SessionStart`, `session-ensure` + `change-context` on `UserPromptSubmit`, `session-stop` + `observer-publish` + `change-nag` on `Stop`, `subagent-stop` + `observer-publish` on `SubagentStop`, and `change-guard` on `PreToolUse` for `Bash`, `exec_command`, `apply_patch`, and mutable MCP tools, always in the `npx wendkeep hook <name>` form. `observer-publish` keeps the sanitized index projection and also sends the local SQL authority with documents, consumption, and transcripts; it does not replace the local lifecycle. The guard accepts object, raw-string, and argv Codex payloads; before a mutation it compares the session with the project, Git root, remote, branch, and worktree, denying missing or divergent targets. The other four stay out because Codex offers no equivalent payload, tool, or event: `change-warn` (no reliable `tool_input.file_path`), `plan-capture` (no `ExitPlanMode`), `decision-capture` (`AskUserQuestion` is Claude-only), and `task-log` (`TaskCompleted` is not in Codex's event enum). Codex scope blocks use `permissionDecision: "deny"`; `ask` is never emitted in `PreToolUse`. The merge remains non-destructive, preserves third-party hooks, and migrates legacy `timeout` to `timeoutSec`. **Codex enumerates every hook as untrusted and runs none until you approve the “Hooks need review” prompt at startup — `init` cannot pre-approve them**.
115
119
  4. Add the **`wendkeep-vault`** MCP server to `.mcp.json` so the agent can read/write the vault. Skip with `--no-mcp` — e.g. when the agent already has a vault MCP. (`--no-mcp` skips *only wendkeep's own* MCP; companion MCPs still follow `--companions`.)
116
120
  5. Offer to pin **companion** plugins/MCP (multi-choice; **none** pre-checked — wendkeep is a neutral harness and presumes no third-party plugin). Each is wired the most agent-agnostic way it supports:
117
121
  - **`context-mode`** — context optimizer + FTS5 memory, wired as a Claude Code plugin. It ships its own MCP server, so wendkeep deliberately adds no `.mcp.json` entry (registering both cold-started two servers at once). On non-Claude agents, add the MCP by hand: `npx -y context-mode`.
@@ -4,20 +4,23 @@
4
4
 
5
5
  ## Purpose
6
6
 
7
- The Observer consolidates observability for multiple WendKeep projects in a local service and
8
- stores a complete copy of the memory published by hooks in the Docker volume. The content can be
9
- browsed and searched in the container without depending on Obsidian for queries.
7
+ The Observer consolidates observability for multiple WendKeep projects in a local service. The
8
+ Docker volume keeps `/data/observer.sqlite` as the single authority for documents, sessions,
9
+ agents, usage, calls, and complete transcripts. The content can be browsed and searched in the
10
+ container without depending on Obsidian for queries.
10
11
 
11
12
  ## When to use
12
13
 
13
- Use it to query changes, sessions, decisions, bugs, learnings, specs, brain documents, and health
14
- across projects through one local memory. During the transition, the vault is preserved as a
15
- recovery copy; the Observer is authoritative for queries made through its container.
14
+ Use it to query changes, sessions, decisions, bugs, learnings, specs, brain documents, per-agent
15
+ and per-model consumption, and health across projects through one local memory. During the
16
+ transition, the vault and legacy Markdown files remain a recovery copy; the Observer is
17
+ authoritative for queries made through its container.
16
18
 
17
19
  ## When not to use
18
20
 
19
- Do not use the Observer to edit, complete, or archive changes, curate memory, store transcripts,
20
- or expose the service to the network. Edits still go through local WendKeep hooks.
21
+ Do not use the Observer to edit, complete, or archive changes, curate memory, automatically export
22
+ the authority back to Markdown, or expose the service to the network. Edits still go through local
23
+ WendKeep hooks.
21
24
 
22
25
  ## Prerequisites
23
26
 
@@ -67,10 +70,11 @@ dashboard is served by the same process and opens directly, without a form or to
67
70
  bound to the computer loopback; do not expose this address on a network interface.
68
71
 
69
72
  The dashboard shows the multi-project list, version, health, latest session, active change, change
70
- count, and last capture time. Opening a project exposes Overview, Sessions, Memory, Changes, and
71
- Sync screens. Each list opens the complete Markdown document in a read-only reader, with local
72
- filtering, body search, and a source toggle. Loading, empty, unavailable-server, conflict, and
73
- stale-data states are visible, with manual refresh and an automatic 15-second refresh.
73
+ count, and last capture time. Opening a project exposes Overview, Consumption, Sessions, Memory,
74
+ Changes, and Sync screens. Consumption shows total cost, token categories, primary agents,
75
+ subagents, providers, models, daily trend, historical coverage, and calls with prompt, response,
76
+ and complete transcript. Loading, empty, unavailable-server, conflict, no-pricing, and stale-data
77
+ states are visible, with manual refresh and an automatic 15-second refresh.
74
78
 
75
79
  If the browser shows the shell but the list fails, check the service health at
76
80
  `http://127.0.0.1:8787/healthz` and confirm that the container is running.
@@ -78,25 +82,31 @@ If the browser shows the shell but the list fails, check the service health at
78
82
  ## Expected result
79
83
 
80
84
  `register` stores `project_id`, name, version, and registration time. `publish` reads the local
81
- vault, produces the snapshot, and also sends idempotent events containing the complete content of
82
- sessions, decisions, bugs, learnings, specs, changes, CORE, DIGEST, SHARED_MEMORY, and brain
83
- state. The container stores Markdown under `/data/memory` plus `MEMORY_EVENTS.jsonl` and
84
- `MEMORY_INDEX.json` in the `observer-data` volume; it does not mount `C:\GitHub` or any
85
- `.WendKeep-vault`. `memory import` performs the initial load and returns file/hash parity.
86
-
87
- `init` projects `observer-publish` into `SessionStart` and `Stop` after the primary hooks. Without
88
- `WENDKEEP_OBSERVER_URL`, the hook is a no-op. When the server is stopped, it writes snapshots to
89
- `.brain/observer-outbox/` and complete memory to `.brain/observer-memory-outbox/` without blocking
90
- the session; a later run retries both event types.
85
+ vault, produces the snapshot, and sends idempotent events to SQLite containing the complete content
86
+ of sessions, decisions, bugs, learnings, specs, changes, CORE, DIGEST, SHARED_MEMORY, brain state,
87
+ agent sessions, cost rollups, calls, and transcripts. The container stores everything in
88
+ `/data/observer.sqlite`; it does not mount `C:\GitHub` or any `.WendKeep-vault`. Markdown is only
89
+ the text held in SQL and is recreated as files only by an explicit read-only export.
90
+ `memory import` performs the initial load and returns file/hash parity. During migration, the
91
+ cost/token total recorded in frontmatter is preserved through an explicit reconciliation row when
92
+ the detailed ledger does not add up; that row does not invent calls. Historical sessions sharing
93
+ one `session_id` receive a canonical per-file identity so one rollup cannot overwrite the other.
94
+
95
+ `init` projects `observer-publish` into `SessionStart`, `Stop`, and `SubagentStop` after the primary
96
+ hooks. When the server is unavailable, it writes snapshots to `.brain/observer-outbox/` and SQL
97
+ events to `.brain/observer-sql-outbox/` without blocking the session; a later run retries the
98
+ batches. SQL batches use gzip so complete transcripts larger than 64 MB as plain JSON remain within
99
+ the transport limit; the Observer decompresses and validates the body before ingesting it. The
100
+ outbox is temporary transport, not authority.
91
101
 
92
102
  ## Common errors and diagnosis
93
103
 
94
104
  - `project_not_registered`: run `observer register` before publishing.
95
105
  - `host loopback`: replace `0.0.0.0` or a LAN address with `127.0.0.1`.
96
- - Pending outbox: the service was unavailable; preserve `.brain/observer-outbox/` and rerun the
97
- publisher. Do not delete events manually.
98
- - If memory is incomplete, check the Sync screen, preserve the outbox, and run
99
- `observer memory import` to rebuild the copy from the vault.
106
+ - Pending outbox: the service was unavailable; preserve `.brain/observer-outbox/` and
107
+ `.brain/observer-sql-outbox/`, then rerun the publisher. Do not delete events manually.
108
+ - If memory or usage is incomplete, check the Sync screen, preserve the outbox, and run
109
+ `observer memory import` to rebuild the load from the vault.
100
110
 
101
111
  ## Next steps
102
112
 
@@ -106,25 +116,34 @@ projection.
106
116
 
107
117
  ## Data authority
108
118
 
109
- The container is canonical for Observer queries and stores the complete published content. The
110
- vault remains preserved locally during migration as a transition and recovery copy; Observer
111
- screens do not complete, archive, repair, or promote state.
119
+ The container SQLite database is canonical for Observer queries and stores the complete published
120
+ content. The vault and any legacy `/data/memory` remain preserved during migration as a transition
121
+ and recovery copy; hooks do not update container Markdown after cutover. Observer screens do not
122
+ complete, archive, repair, or promote state.
112
123
 
113
124
  ## Minimal API
114
125
 
115
- - `GET /healthz` — availability without project data.
116
- - `GET /v1/projects` — projects with an accepted snapshot.
126
+ - `GET /healthz` — availability, SQLite migration version, and legacy migration state.
127
+ - `GET /v1/projects` — projects registered in SQLite, with a snapshot when available.
117
128
  - `GET /v1/projects/:project_id` — the latest project snapshot.
118
129
  - `GET /v1/projects/:project_id/changes` — change summary from the snapshot.
119
130
  - `PUT /v1/projects/:project_id` — explicit local registration.
120
131
  - `POST /v1/projects/:project_id/snapshot` — idempotent local ingestion.
132
+ - `POST /v1/projects/:project_id/ingest` — idempotent batches of documents, sessions, agents,
133
+ rollups, calls, and transcripts.
121
134
  - `GET /v1/projects/:project_id/memory/tree` — document tree and metadata.
122
135
  - `GET /v1/projects/:project_id/memory/document?path=...` — complete Markdown content.
123
136
  - `GET /v1/projects/:project_id/memory/search?q=...` — path and body search.
124
137
  - `GET /v1/projects/:project_id/sync` — mode, counts, conflicts, and latest event.
125
- - `PUT /v1/projects/:project_id/sync` — explicitly changes the local mode.
138
+ - `PUT /v1/projects/:project_id/sync` — compatibility configuration; SQL remains authoritative.
126
139
  - `GET /v1/projects/:project_id/memory/export` — read-only export with complete content.
127
140
  - `POST /v1/projects/:project_id/memory/events` — idempotent batch ingestion.
128
-
129
- The `/v1` routes reject oversized bodies and validate project, path, revision, hash, and
130
- isolation before writing content to the volume.
141
+ - `GET /v1/projects/:project_id/usage/summary` — filterable totals by period, change, session,
142
+ agent, provider, model, and role.
143
+ - `GET /v1/projects/:project_id/usage/breakdown` agent, subagent, and model hierarchy.
144
+ - `GET /v1/projects/:project_id/usage/calls` — individual calls with prompt and response.
145
+ - `GET /v1/projects/:project_id/transcripts/:transcript_id` — compressed transcript validated by hash.
146
+
147
+ The `/v1` routes reject transported or expanded bodies above their limits and validate project,
148
+ path, revision, hash, idempotency, and isolation before writing to SQLite. Use `memory/export` for
149
+ a Markdown copy; it does not alter SQL authority.
@@ -4,21 +4,23 @@
4
4
 
5
5
  ## Objetivo
6
6
 
7
- O Observer consolida a observabilidade de vários projetos WendKeep em um serviço local e grava no
8
- volume Docker uma cópia integral da memória publicada pelos hooks. O conteúdo fica disponível
9
- para navegação e busca no próprio container, sem depender do Obsidian para consulta.
7
+ O Observer consolida a observabilidade de vários projetos WendKeep em um serviço local. O volume
8
+ Docker mantém o SQLite `/data/observer.sqlite` como autoridade única para documentos, sessões,
9
+ agentes, uso, chamadas e transcripts completos. O conteúdo fica disponível para navegação e busca
10
+ no próprio container, sem depender do Obsidian para consulta.
10
11
 
11
12
  ## Quando usar
12
13
 
13
- Use para consultar changes, sessões, decisões, bugs, aprendizados, specs, documentos do brain e
14
- saúde de vários projetos em uma única memória local. Durante a transição, o vault continua sendo
15
- preservado como cópia de origem para recuperação; o Observer é a autoridade de consulta do seu
16
- container.
14
+ Use para consultar changes, sessões, decisões, bugs, aprendizados, specs, documentos do brain,
15
+ consumo por agente/modelo e saúde de vários projetos em uma única memória local. Durante a
16
+ transição, o vault e os arquivos Markdown legados continuam preservados como cópia de origem para
17
+ recuperação; o Observer é a autoridade de consulta do seu container.
17
18
 
18
19
  ## Quando não usar
19
20
 
20
- Não use o Observer para editar, concluir ou arquivar changes, curar memória, armazenar transcripts
21
- ou expor o serviço na rede. As edições continuam passando pelos hooks e pelo WendKeep local.
21
+ Não use o Observer para editar, concluir ou arquivar changes, curar memória, exportar a autoridade
22
+ de volta para Markdown automaticamente ou expor o serviço na rede. As edições continuam passando
23
+ pelos hooks e pelo WendKeep local.
22
24
 
23
25
  ## Pré-requisitos
24
26
 
@@ -68,11 +70,12 @@ O painel é servido pelo mesmo processo e abre diretamente, sem formulário ou t
68
70
  presa ao loopback do computador; não coloque o endereço em uma interface de rede.
69
71
 
70
72
  O painel mostra a lista multi-projeto, versão, saúde, sessão mais recente, change ativa, contagem
71
- de changes e data da última captura. Ao abrir um projeto, o workspace oferece as telas Overview,
72
- Sessões, Memória, Changes e Sincronização. Cada lista abre o documento Markdown completo em um
73
- leitor read-only, com filtro local, busca no corpo e alternância para a fonte. Os estados de
74
- carregamento, vazio, servidor indisponível, conflito e dados desatualizados ficam visíveis, e a
75
- atualização pode ser manual ou automática a cada 15 segundos.
73
+ de changes e data da última captura. Ao abrir um projeto, o workspace oferece Overview, Consumo,
74
+ Sessões, Memória, Changes e Sincronização. A aba Consumo mostra custo total, tokens por categoria,
75
+ agentes principais, subagentes, provedores, modelos, tendência diária, cobertura histórica e
76
+ chamadas com prompt, resposta e transcript completo. Os estados de carregamento, vazio, servidor
77
+ indisponível, conflito, modelo sem tarifa e dados desatualizados ficam visíveis, e a atualização
78
+ pode ser manual ou automática a cada 15 segundos.
76
79
 
77
80
  Se o navegador mostrar a tela mas a lista falhar, confirme a saúde em
78
81
  `http://127.0.0.1:8787/healthz` e verifique se o container está em execução.
@@ -80,25 +83,32 @@ Se o navegador mostrar a tela mas a lista falhar, confirme a saúde em
80
83
  ## Resultado esperado
81
84
 
82
85
  `register` grava `project_id`, nome, versão e data de registro. `publish` lê o vault local,
83
- produz o snapshot e também envia eventos idempotentes com o conteúdo integral das sessões,
84
- decisões, bugs, aprendizados, specs, changes, CORE, DIGEST, SHARED_MEMORY e estado do brain. O
85
- container grava os Markdown em `/data/memory` e mantém `MEMORY_EVENTS.jsonl` e
86
- `MEMORY_INDEX.json` no volume `observer-data`; não monta `C:\GitHub` nem qualquer
87
- `.WendKeep-vault`. `memory import` faz a carga inicial e retorna a paridade por arquivo e hash.
88
-
89
- O `init` projeta `observer-publish` para `SessionStart` e `Stop` depois dos hooks principais. Sem
90
- `WENDKEEP_OBSERVER_URL`, o hook é no-op. Com o servidor parado, ele grava os snapshots em
91
- `.brain/observer-outbox/` e a memória integral em `.brain/observer-memory-outbox/`, sem bloquear a
92
- sessão; uma execução posterior tenta reenviar os dois tipos de evento.
86
+ produz o snapshot e envia eventos idempotentes para o SQLite com o conteúdo integral das sessões,
87
+ decisões, bugs, aprendizados, specs, changes, CORE, DIGEST, SHARED_MEMORY, estado do brain,
88
+ sessões de agentes, rollups de custo, chamadas e transcripts. O container grava tudo em
89
+ `/data/observer.sqlite`; não monta `C:\GitHub` nem qualquer `.WendKeep-vault`. Markdown é aceito
90
+ somente como conteúdo de uma coluna SQL e volta a existir como arquivo apenas pela exportação
91
+ read-only sob demanda. `memory import` faz a carga inicial e retorna a paridade por arquivo e hash.
92
+ Na migração, o total de custo/token registrado no frontmatter é preservado por uma linha de
93
+ reconciliação quando o ledger detalhado não fecha com ele; essa linha não inventa chamadas.
94
+ Sessões históricas com o mesmo `session_id` recebem uma identidade canônica por arquivo para
95
+ evitar que um rollup sobrescreva o outro.
96
+
97
+ O `init` projeta `observer-publish` para `SessionStart`, `Stop` e `SubagentStop` depois dos hooks
98
+ principais. Sem servidor disponível, ele grava snapshots em `.brain/observer-outbox/` e eventos
99
+ SQL em `.brain/observer-sql-outbox/`, sem bloquear a sessão; uma execução posterior tenta
100
+ reenviar os lotes. Os lotes SQL são enviados com gzip para que transcripts completos maiores que
101
+ 64 MB em JSON puro continuem dentro do limite do transporte; o Observer descomprime e valida o
102
+ corpo antes de ingerir. O outbox é transporte temporário, não autoridade.
93
103
 
94
104
  ## Erros comuns e diagnóstico
95
105
 
96
106
  - `project_not_registered`: rode `observer register` antes de publicar.
97
107
  - `host loopback`: troque `0.0.0.0` ou endereço LAN por `127.0.0.1`.
98
- - Outbox pendente: o serviço estava indisponível; preserve `.brain/observer-outbox/` e repita o
99
- publisher. Não apague eventos manualmente.
100
- - Se a memória ficar incompleta, verifique a tela Sincronização, preserve o outbox e rode
101
- `observer memory import` para reconstruir a cópia a partir do vault.
108
+ - Outbox pendente: o serviço estava indisponível; preserve `.brain/observer-outbox/` e
109
+ `.brain/observer-sql-outbox/` e repita o publisher. Não apague eventos manualmente.
110
+ - Se a memória ou o consumo ficarem incompletos, verifique a tela Sincronização, preserve o
111
+ outbox e rode `observer memory import` para reconstruir a carga a partir do vault.
102
112
 
103
113
  ## Próximos passos
104
114
 
@@ -107,25 +117,34 @@ removido com `docker compose down -v` durante a operação normal, pois isso apa
107
117
 
108
118
  ## Autoridade dos dados
109
119
 
110
- O container é a memória canônica para consultas do Observer e guarda o conteúdo integral
111
- publicado. O vault continua preservado localmente durante a migração como cópia de transição e
112
- fonte de recuperação; as telas do Observer não concluem, arquivam, reparam ou promovem estado.
120
+ O SQLite do container é a memória canônica para consultas do Observer e guarda o conteúdo integral
121
+ publicado. O vault e qualquer `/data/memory` legado continuam preservados durante a migração como
122
+ cópia de transição e fonte de recuperação; os hooks não atualizam Markdown no container depois do
123
+ corte. As telas do Observer não concluem, arquivam, reparam ou promovem estado.
113
124
 
114
125
  ## API mínima
115
126
 
116
- - `GET /healthz` — disponibilidade sem dados de projeto.
117
- - `GET /v1/projects` — projetos com snapshot aceito.
127
+ - `GET /healthz` — disponibilidade, versão das migrações SQLite e estado da migração legada.
128
+ - `GET /v1/projects` — projetos registrados no SQLite, com snapshot quando houver.
118
129
  - `GET /v1/projects/:project_id` — último snapshot do projeto.
119
130
  - `GET /v1/projects/:project_id/changes` — resumo das changes do snapshot.
120
131
  - `PUT /v1/projects/:project_id` — registro explícito local.
121
132
  - `POST /v1/projects/:project_id/snapshot` — ingestão local idempotente.
133
+ - `POST /v1/projects/:project_id/ingest` — lote idempotente de documentos, sessões, agentes, rollups,
134
+ chamadas e transcripts.
122
135
  - `GET /v1/projects/:project_id/memory/tree` — árvore e metadados dos documentos.
123
136
  - `GET /v1/projects/:project_id/memory/document?path=...` — conteúdo Markdown integral.
124
137
  - `GET /v1/projects/:project_id/memory/search?q=...` — busca no caminho e no corpo.
125
138
  - `GET /v1/projects/:project_id/sync` — modo, contagem, conflitos e último evento.
126
- - `PUT /v1/projects/:project_id/sync` — altera explicitamente o modo local.
139
+ - `PUT /v1/projects/:project_id/sync` — compatibilidade de configuração; a autoridade continua SQL.
127
140
  - `GET /v1/projects/:project_id/memory/export` — exportação read-only com conteúdo completo.
128
141
  - `POST /v1/projects/:project_id/memory/events` — ingestão idempotente em lote.
129
-
130
- As rotas `/v1` rejeitam corpo acima do limite e validam projeto, caminho, revisão, hash e
131
- isolamento antes de gravar o conteúdo no volume.
142
+ - `GET /v1/projects/:project_id/usage/summary` — totais filtráveis por período, change, sessão,
143
+ agente, provedor, modelo e papel.
144
+ - `GET /v1/projects/:project_id/usage/breakdown` — hierarquia de agentes, subagentes e modelos.
145
+ - `GET /v1/projects/:project_id/usage/calls` — chamadas individuais com prompt e resposta.
146
+ - `GET /v1/projects/:project_id/transcripts/:transcript_id` — transcript comprimido, validado por hash.
147
+
148
+ As rotas `/v1` rejeitam corpo transportado ou expandido acima do limite e validam projeto, caminho,
149
+ revisão, hash, idempotência e isolamento antes de gravar o conteúdo no SQLite. Para preservar uma
150
+ cópia Markdown, use a rota `memory/export`; ela não altera a autoridade SQL.
@@ -9,6 +9,7 @@ async function main() {
9
9
  const result = await publishObserverSnapshot({
10
10
  vaultBase: resolved.base,
11
11
  projectRoot: resolved.projectRoot,
12
+ input,
12
13
  });
13
14
  if (!result.ok && result.error) debugLog('Observer publish fail-open:', result.error);
14
15
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wendkeep",
3
- "version": "0.71.1",
3
+ "version": "0.72.0",
4
4
  "description": "Vault-first persistent memory for AI coding agents, with an optional profile-aware governance runtime: OFF, FLOW, GUIDE, GOVERN, or ASSURE. Local-first and agent-agnostic (Claude Code, Codex, Cursor…).",
5
5
  "type": "module",
6
6
  "workspaces": [
@@ -41,7 +41,7 @@
41
41
  "node": ">=18"
42
42
  },
43
43
  "scripts": {
44
- "check": "node --check scripts/release.mjs && node --check scripts/release-plan.mjs && node --check bin/wendkeep.mjs && node --check packages/cli/src/index.mjs && node --check src/init.mjs && node --check src/doctor.mjs && node --check src/project-vault.mjs && node --check src/observer-snapshot.mjs && node --check src/observer-store.mjs && node --check src/observer-memory.mjs && node --check src/observer-memory-publish.mjs && node --check src/observer-server.mjs && node --check src/observer.mjs && node --check src/observer-publish.mjs && node --check src/operating-profile.mjs && node --check src/profile.mjs && node --check src/flow.mjs && node --check web/observer/app.mjs && node --check hooks/observer-publish.mjs && node --check hooks/operating-profile-runtime.mjs && node --check hooks/operating-profile-task-store.mjs && node --check hooks/flow-core.mjs && node --check hooks/flow-protected-policy.mjs && node --check hooks/git-snapshot.mjs && node --check hooks/vault-path-safety.mjs && node --check hooks/vault-runtime-store.mjs && node --check packages/harness/src/index.mjs && node --check packages/harness/src/flow-store.mjs && node --check packages/harness/src/operating-profile.mjs && node --check packages/harness/src/sensors-core.mjs && node --check packages/integrations/src/host-hooks.mjs && node --check packages/integrations/src/hook-envelope.mjs && node --check packages/integrations/src/prompt-content.mjs && node --check packages/integrations/src/transcript-usage.mjs && node --check packages/integrations/src/transcripts.mjs && node --check packages/integrations/src/session-identity.mjs && node --check packages/integrations/src/index.mjs && node --check packages/mcp/src/config.mjs && node --check packages/mcp/src/index.mjs && node --check packages/vault/src/index.mjs && node --check packages/vault/src/project-vault.mjs && node --check packages/vault/src/vault-path-safety.mjs && node --check packages/vault/src/locale.mjs && node --check packages/vault/src/memory-schema.mjs && node --check packages/vault/src/memory-mode.mjs && node --check packages/vault/src/memory-handoff.mjs && node --check packages/vault/src/memory-store.mjs && node --check packages/vault/src/validate-core.mjs && node --check packages/vault/src/validate-memory.mjs",
44
+ "check": "node --check scripts/release.mjs && node --check scripts/release-plan.mjs && node --check bin/wendkeep.mjs && node --check packages/cli/src/index.mjs && node --check src/init.mjs && node --check src/doctor.mjs && node --check src/project-vault.mjs && node --check src/observer-snapshot.mjs && node --check src/observer-store.mjs && node --check src/observer-memory.mjs && node --check src/observer-memory-publish.mjs && node --check src/observer-sql-store.mjs && node --check src/observer-sql-migrate.mjs && node --check src/observer-sql-publish.mjs && node --check src/observer-transcript-store.mjs && node --check src/observer-server.mjs && node --check src/observer.mjs && node --check src/observer-publish.mjs && node --check src/operating-profile.mjs && node --check src/profile.mjs && node --check src/flow.mjs && node --check web/observer/app.mjs && node --check hooks/observer-publish.mjs && node --check hooks/operating-profile-runtime.mjs && node --check hooks/operating-profile-task-store.mjs && node --check hooks/flow-core.mjs && node --check hooks/flow-protected-policy.mjs && node --check hooks/git-snapshot.mjs && node --check hooks/vault-path-safety.mjs && node --check hooks/vault-runtime-store.mjs && node --check packages/harness/src/index.mjs && node --check packages/harness/src/flow-store.mjs && node --check packages/harness/src/operating-profile.mjs && node --check packages/harness/src/sensors-core.mjs && node --check packages/integrations/src/host-hooks.mjs && node --check packages/integrations/src/hook-envelope.mjs && node --check packages/integrations/src/prompt-content.mjs && node --check packages/integrations/src/transcript-usage.mjs && node --check packages/integrations/src/transcripts.mjs && node --check packages/integrations/src/session-identity.mjs && node --check packages/integrations/src/index.mjs && node --check packages/mcp/src/config.mjs && node --check packages/mcp/src/index.mjs && node --check packages/vault/src/index.mjs && node --check packages/vault/src/project-vault.mjs && node --check packages/vault/src/vault-path-safety.mjs && node --check packages/vault/src/locale.mjs && node --check packages/vault/src/memory-schema.mjs && node --check packages/vault/src/memory-mode.mjs && node --check packages/vault/src/memory-handoff.mjs && node --check packages/vault/src/memory-store.mjs && node --check packages/vault/src/validate-core.mjs && node --check packages/vault/src/validate-memory.mjs",
45
45
  "test": "node --test --test-concurrency=2",
46
46
  "release": "node scripts/release.mjs",
47
47
  "release:dry": "node scripts/release.mjs --dry-run",
@@ -24,6 +24,8 @@ export const SESSION_HOOKS = [
24
24
  { event: 'PostToolUse', matcher: 'AskUserQuestion', name: 'decision-capture', timeout: 15, statusMessage: 'wendkeep: recording decision' },
25
25
  // Refresh subagent/workflow telemetry as each subagent finishes (resilient to a missed Stop).
26
26
  { event: 'SubagentStop', matcher: null, name: 'subagent-stop', timeout: 20, codex: true, statusMessage: 'wendkeep: subagent telemetry' },
27
+ // Publish the SQL observer projection after the subagent telemetry is settled.
28
+ { event: 'SubagentStop', matcher: null, name: 'observer-publish', timeout: 5, order: 20, codex: true, statusMessage: 'wendkeep: publishing local observer usage' },
27
29
  // Log plan/task progress into the active session note when a task is marked complete.
28
30
  // codex: TaskCompleted is not in Codex's hook event enum.
29
31
  { event: 'TaskCompleted', matcher: null, name: 'task-log', timeout: 10, statusMessage: 'wendkeep: plan progress' },
@@ -0,0 +1,107 @@
1
+ PRAGMA foreign_keys = ON;
2
+
3
+ CREATE TABLE IF NOT EXISTS schema_migrations (
4
+ version INTEGER PRIMARY KEY,
5
+ name TEXT NOT NULL,
6
+ applied_at TEXT NOT NULL
7
+ );
8
+
9
+ CREATE TABLE IF NOT EXISTS projects (
10
+ project_id TEXT PRIMARY KEY,
11
+ project_name TEXT NOT NULL,
12
+ wendkeep_version TEXT NOT NULL DEFAULT '',
13
+ registered_at TEXT NOT NULL,
14
+ updated_at TEXT NOT NULL
15
+ );
16
+
17
+ CREATE TABLE IF NOT EXISTS ingest_events (
18
+ event_id TEXT PRIMARY KEY,
19
+ project_id TEXT NOT NULL REFERENCES projects(project_id) ON DELETE CASCADE,
20
+ kind TEXT NOT NULL,
21
+ payload_hash TEXT NOT NULL,
22
+ payload_json TEXT NOT NULL,
23
+ occurred_at TEXT NOT NULL,
24
+ ingested_at TEXT NOT NULL,
25
+ status TEXT NOT NULL DEFAULT 'accepted'
26
+ );
27
+
28
+ CREATE INDEX IF NOT EXISTS idx_ingest_events_project_time
29
+ ON ingest_events(project_id, occurred_at);
30
+
31
+ CREATE TABLE IF NOT EXISTS memory_events (
32
+ event_id TEXT PRIMARY KEY REFERENCES ingest_events(event_id) ON DELETE CASCADE,
33
+ project_id TEXT NOT NULL REFERENCES projects(project_id) ON DELETE CASCADE,
34
+ entity_type TEXT NOT NULL,
35
+ logical_path TEXT NOT NULL,
36
+ operation TEXT NOT NULL,
37
+ revision INTEGER NOT NULL DEFAULT 1,
38
+ content_hash TEXT NOT NULL DEFAULT '',
39
+ source_session_id TEXT NOT NULL DEFAULT '',
40
+ source_turn_id TEXT NOT NULL DEFAULT '',
41
+ occurred_at TEXT NOT NULL,
42
+ payload_json TEXT NOT NULL,
43
+ UNIQUE(project_id, logical_path, revision)
44
+ );
45
+
46
+ CREATE INDEX IF NOT EXISTS idx_memory_events_project_path
47
+ ON memory_events(project_id, logical_path, revision);
48
+
49
+ CREATE TABLE IF NOT EXISTS documents (
50
+ document_id TEXT PRIMARY KEY,
51
+ project_id TEXT NOT NULL REFERENCES projects(project_id) ON DELETE CASCADE,
52
+ logical_path TEXT NOT NULL,
53
+ entity_type TEXT NOT NULL,
54
+ title TEXT NOT NULL DEFAULT '',
55
+ content TEXT NOT NULL DEFAULT '',
56
+ metadata_json TEXT NOT NULL DEFAULT '{}',
57
+ content_hash TEXT NOT NULL DEFAULT '',
58
+ revision INTEGER NOT NULL DEFAULT 1,
59
+ source_session_id TEXT NOT NULL DEFAULT '',
60
+ source_turn_id TEXT NOT NULL DEFAULT '',
61
+ captured_at TEXT NOT NULL,
62
+ deleted_at TEXT,
63
+ UNIQUE(project_id, logical_path)
64
+ );
65
+
66
+ CREATE INDEX IF NOT EXISTS idx_documents_project_type
67
+ ON documents(project_id, entity_type, deleted_at);
68
+
69
+ CREATE INDEX IF NOT EXISTS idx_documents_project_revision
70
+ ON documents(project_id, revision);
71
+
72
+ CREATE TABLE IF NOT EXISTS sessions (
73
+ session_id TEXT PRIMARY KEY,
74
+ project_id TEXT NOT NULL REFERENCES projects(project_id) ON DELETE CASCADE,
75
+ provider TEXT NOT NULL DEFAULT '',
76
+ status TEXT NOT NULL DEFAULT 'unknown',
77
+ summary TEXT NOT NULL DEFAULT '',
78
+ change_slug TEXT NOT NULL DEFAULT '',
79
+ started_at TEXT,
80
+ ended_at TEXT,
81
+ updated_at TEXT NOT NULL,
82
+ metadata_json TEXT NOT NULL DEFAULT '{}'
83
+ );
84
+
85
+ CREATE INDEX IF NOT EXISTS idx_sessions_project_time
86
+ ON sessions(project_id, started_at, ended_at);
87
+
88
+ CREATE TABLE IF NOT EXISTS agent_runs (
89
+ agent_id TEXT PRIMARY KEY,
90
+ project_id TEXT NOT NULL REFERENCES projects(project_id) ON DELETE CASCADE,
91
+ session_id TEXT NOT NULL REFERENCES sessions(session_id) ON DELETE CASCADE,
92
+ parent_agent_id TEXT,
93
+ role TEXT NOT NULL DEFAULT 'main',
94
+ agent_name TEXT NOT NULL DEFAULT '',
95
+ agent_type TEXT NOT NULL DEFAULT '',
96
+ workflow TEXT NOT NULL DEFAULT '',
97
+ status TEXT NOT NULL DEFAULT 'unknown',
98
+ model TEXT NOT NULL DEFAULT '',
99
+ effort TEXT NOT NULL DEFAULT '',
100
+ started_at TEXT,
101
+ ended_at TEXT,
102
+ metadata_json TEXT NOT NULL DEFAULT '{}',
103
+ FOREIGN KEY(parent_agent_id) REFERENCES agent_runs(agent_id) ON DELETE SET NULL
104
+ );
105
+
106
+ CREATE INDEX IF NOT EXISTS idx_agent_runs_project_session
107
+ ON agent_runs(project_id, session_id, role);
@@ -0,0 +1,72 @@
1
+ CREATE TABLE IF NOT EXISTS usage_rollups (
2
+ rollup_key TEXT PRIMARY KEY,
3
+ project_id TEXT NOT NULL REFERENCES projects(project_id) ON DELETE CASCADE,
4
+ session_id TEXT NOT NULL REFERENCES sessions(session_id) ON DELETE CASCADE,
5
+ agent_id TEXT NOT NULL REFERENCES agent_runs(agent_id) ON DELETE CASCADE,
6
+ role TEXT NOT NULL DEFAULT 'main',
7
+ provider TEXT NOT NULL DEFAULT '',
8
+ model_provider TEXT NOT NULL DEFAULT '',
9
+ model TEXT NOT NULL DEFAULT '',
10
+ effort TEXT NOT NULL DEFAULT '',
11
+ calls INTEGER NOT NULL DEFAULT 0,
12
+ tokens_input INTEGER NOT NULL DEFAULT 0,
13
+ tokens_cache_write INTEGER NOT NULL DEFAULT 0,
14
+ tokens_cache_read INTEGER NOT NULL DEFAULT 0,
15
+ tokens_output INTEGER NOT NULL DEFAULT 0,
16
+ tokens_reasoning INTEGER NOT NULL DEFAULT 0,
17
+ tokens_total INTEGER NOT NULL DEFAULT 0,
18
+ cost_usd REAL NOT NULL DEFAULT 0,
19
+ cost_status TEXT NOT NULL DEFAULT 'unknown',
20
+ pricing_source TEXT NOT NULL DEFAULT '',
21
+ pricing_version TEXT NOT NULL DEFAULT '',
22
+ wasted_usd REAL NOT NULL DEFAULT 0,
23
+ revision INTEGER NOT NULL DEFAULT 1,
24
+ occurred_at TEXT NOT NULL,
25
+ source_event_id TEXT NOT NULL REFERENCES ingest_events(event_id) ON DELETE CASCADE,
26
+ metadata_json TEXT NOT NULL DEFAULT '{}'
27
+ );
28
+
29
+ CREATE INDEX IF NOT EXISTS idx_usage_rollups_project_time
30
+ ON usage_rollups(project_id, occurred_at);
31
+
32
+ CREATE INDEX IF NOT EXISTS idx_usage_rollups_project_agent
33
+ ON usage_rollups(project_id, agent_id, role);
34
+
35
+ CREATE INDEX IF NOT EXISTS idx_usage_rollups_project_model
36
+ ON usage_rollups(project_id, model_provider, model);
37
+
38
+ CREATE TABLE IF NOT EXISTS llm_calls (
39
+ call_id TEXT PRIMARY KEY,
40
+ project_id TEXT NOT NULL REFERENCES projects(project_id) ON DELETE CASCADE,
41
+ session_id TEXT NOT NULL REFERENCES sessions(session_id) ON DELETE CASCADE,
42
+ agent_id TEXT NOT NULL REFERENCES agent_runs(agent_id) ON DELETE CASCADE,
43
+ role TEXT NOT NULL DEFAULT 'main',
44
+ provider TEXT NOT NULL DEFAULT '',
45
+ model_provider TEXT NOT NULL DEFAULT '',
46
+ model TEXT NOT NULL DEFAULT '',
47
+ effort TEXT NOT NULL DEFAULT '',
48
+ sequence INTEGER NOT NULL DEFAULT 0,
49
+ occurred_at TEXT NOT NULL,
50
+ tokens_input INTEGER NOT NULL DEFAULT 0,
51
+ tokens_cache_write INTEGER NOT NULL DEFAULT 0,
52
+ tokens_cache_read INTEGER NOT NULL DEFAULT 0,
53
+ tokens_output INTEGER NOT NULL DEFAULT 0,
54
+ tokens_reasoning INTEGER NOT NULL DEFAULT 0,
55
+ tokens_total INTEGER NOT NULL DEFAULT 0,
56
+ cost_usd REAL NOT NULL DEFAULT 0,
57
+ cost_status TEXT NOT NULL DEFAULT 'unknown',
58
+ transcript_id TEXT,
59
+ prompt_text TEXT NOT NULL DEFAULT '',
60
+ response_text TEXT NOT NULL DEFAULT '',
61
+ status TEXT NOT NULL DEFAULT 'complete',
62
+ metadata_json TEXT NOT NULL DEFAULT '{}'
63
+ );
64
+
65
+ CREATE INDEX IF NOT EXISTS idx_llm_calls_project_time
66
+ ON llm_calls(project_id, occurred_at);
67
+
68
+ CREATE INDEX IF NOT EXISTS idx_llm_calls_project_agent
69
+ ON llm_calls(project_id, agent_id, sequence);
70
+
71
+ CREATE INDEX IF NOT EXISTS idx_llm_calls_project_model
72
+ ON llm_calls(project_id, model_provider, model);