@event4u/agent-config 3.1.1 → 3.2.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.
Files changed (52) hide show
  1. package/.agent-src/commands/analytics/prune.md +78 -0
  2. package/.agent-src/commands/analytics/show.md +107 -0
  3. package/.agent-src/commands/analytics.md +64 -0
  4. package/.agent-src/commands/knowledge/forget.md +104 -0
  5. package/.agent-src/commands/knowledge/ingest.md +122 -0
  6. package/.agent-src/commands/knowledge/list.md +102 -0
  7. package/.agent-src/commands/knowledge.md +75 -0
  8. package/.agent-src/scripts/update_roadmap_progress.py +1 -1
  9. package/.agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  10. package/.claude-plugin/marketplace.json +8 -1
  11. package/CHANGELOG.md +38 -230
  12. package/README.md +12 -2
  13. package/dist/discovery/deprecation-report.md +1 -1
  14. package/dist/discovery/discovery-manifest.json +162 -8
  15. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  16. package/dist/discovery/discovery-manifest.summary.md +3 -3
  17. package/dist/discovery/orphan-report.md +1 -1
  18. package/dist/discovery/packs.json +12 -5
  19. package/dist/discovery/trust-report.md +2 -2
  20. package/dist/discovery/workspaces.json +11 -4
  21. package/dist/mcp/mcp-cloudflare-catalogue.json +2 -0
  22. package/dist/mcp/registry-manifest.json +5 -3
  23. package/docs/architecture.md +1 -1
  24. package/docs/archive/CHANGELOG-pre-3.2.0.md +268 -0
  25. package/docs/catalog.md +9 -2
  26. package/docs/contracts/CHANGELOG-conventions.md +19 -0
  27. package/docs/contracts/at-rest-encryption.md +146 -0
  28. package/docs/contracts/daily-workspace.md +137 -0
  29. package/docs/contracts/explain-modes.md +146 -0
  30. package/docs/contracts/host-agent-protocol.md +88 -0
  31. package/docs/contracts/local-analytics.md +148 -0
  32. package/docs/contracts/local-knowledge-ingestion.md +96 -0
  33. package/docs/contracts/role-experience.md +121 -0
  34. package/docs/contracts/workspace-documents.md +140 -0
  35. package/docs/decisions/ADR-022-daily-workspace-decomposition.md +140 -0
  36. package/docs/decisions/ADR-023-host-agent-protocol.md +129 -0
  37. package/docs/decisions/ADR-024-workspace-v0-feature-floor.md +126 -0
  38. package/docs/decisions/ADR-025-workspace-chrome.md +119 -0
  39. package/docs/decisions/ADR-026-explain-mode-translation.md +117 -0
  40. package/docs/deploy/small-team-recipe.md +148 -0
  41. package/docs/deploy/team-deployment-posture.md +91 -0
  42. package/docs/getting-started-by-role.md +27 -0
  43. package/docs/getting-started.md +1 -1
  44. package/docs/guides/local-analytics.md +125 -0
  45. package/docs/guides/local-knowledge.md +127 -0
  46. package/package.json +4 -2
  47. package/scripts/__pycache__/validate_frontmatter.cpython-312.pyc +0 -0
  48. package/scripts/_lib/__pycache__/__init__.cpython-312.pyc +0 -0
  49. package/scripts/_lib/__pycache__/agent_src.cpython-312.pyc +0 -0
  50. package/scripts/_lib/changelog_eras.py +330 -0
  51. package/scripts/memory_lookup.py +78 -1
  52. package/scripts/release.py +93 -3
@@ -0,0 +1,137 @@
1
+ # Daily Workspace Surface Contract
2
+
3
+ > **Status** · v0 / design · 2026-05-24. Surface contract for the daily
4
+ > workspace introduced as Phase 4 of the employee-product workstream.
5
+ > Governed by ADRs [`022`](../decisions/ADR-022-daily-workspace-decomposition.md) ·
6
+ > [`023`](../decisions/ADR-023-host-agent-protocol.md) ·
7
+ > [`024`](../decisions/ADR-024-workspace-v0-feature-floor.md) ·
8
+ > [`025`](../decisions/ADR-025-workspace-chrome.md).
9
+
10
+ ## Shape (v0)
11
+
12
+ Browser tab at `http://127.0.0.1:<gui-port>/workspace`, served by the
13
+ existing installer GUI (`packages/core/installer/src/gui/server.ts`).
14
+ Same CSRF token, same loopback bind, same kill-switch as
15
+ [`gui-wizard`](gui-wizard.md). Launched via
16
+ `npx @event4u/agent-config workspace` (alias for
17
+ `init --gui --route=/workspace` once wired).
18
+
19
+ ```
20
+ ┌─ /workspace ─────────────────────────────────────────────────┐
21
+ │ [identity strip — shared with installer GUI shell] │
22
+ ├────────────────────┬─────────────────────────────────────────┤
23
+ │ Role + Task │ Active session log │
24
+ │ launcher │ (latest JSONL entries, append-only) │
25
+ │ │ │
26
+ │ - galabau │ ▸ 12:04 launch · role=galabau │
27
+ │ - content-creator │ ▸ 12:05 host · claude / tier-1 │
28
+ │ - consultant │ ▸ 12:08 host · turn.completed │
29
+ │ │ │
30
+ │ (Phase 3 roles) │ Knowledge pane │
31
+ │ │ - source: handbuch.pdf │
32
+ │ │ - source: angebot-template.md │
33
+ │ │ (Phase 2 namespace; "no sources yet" │
34
+ │ │ when empty) │
35
+ └────────────────────┴─────────────────────────────────────────┘
36
+ ```
37
+
38
+ No left / centre / right three-rail layout in v0 (deferred per
39
+ ADR-024). One launcher, one log, one stub pane.
40
+
41
+ ## Endpoints (additions to the GUI server)
42
+
43
+ All endpoints CSRF-gated, loopback-bound. Existing wizard endpoints
44
+ in [`gui-wizard`](gui-wizard.md) are untouched.
45
+
46
+ | Method · Path | Purpose |
47
+ |---|---|
48
+ | `GET /workspace` | HTML shell + initial state (role list, recent sessions). |
49
+ | `GET /api/v1/workspace/roles` | List available roles from `agents/roles/<role>/`. |
50
+ | `GET /api/v1/workspace/roles/:role/tasks` | Per-role task list from `skills.yml` + `prompts/`. |
51
+ | `POST /api/v1/workspace/launch` | Body: `{ role, task, host? }`. Resolves host via ADR-023 tier; runs the launch; appends to JSONL log. |
52
+ | `GET /api/v1/workspace/sessions` | List of recent sessions (≤ 20, ordered by mtime). |
53
+ | `GET /api/v1/workspace/sessions/:id` | Streams the JSONL log for one session. |
54
+ | `GET /api/v1/workspace/knowledge` | Snapshot of the current `knowledge:` memory namespace (read-only). |
55
+
56
+ ## Session JSONL schema
57
+
58
+ Path: `~/.event4u/agent-config/workspace/sessions/<yyyy-mm-dd>/<session-id>.jsonl`
59
+ (one file per session; append-only; UTF-8). Session id = `YYYYMMDDTHHMMSSZ-<8-hex>`.
60
+
61
+ Each line is one JSON record with the shared envelope:
62
+
63
+ ```json
64
+ { "ts": "<iso-8601-utc>", "kind": "<event-kind>", "data": { … } }
65
+ ```
66
+
67
+ Event kinds:
68
+
69
+ - `launcher.input` — `{ role, task, rendered_prompt, host_tier, host_id }`
70
+ - `host.turn` — `{ host_id, turn_id, model, input_tokens, output_tokens, latency_ms }`
71
+ - `host.output` — `{ host_id, turn_id, role: "assistant", text }` *(verbatim host envelope text — Tier 1 only)*
72
+ - `host.tool` — `{ host_id, turn_id, tool_name, input, output_excerpt }` *(when the host envelope surfaces it)*
73
+ - `host.error` — `{ host_id, message, exit_code }`
74
+ - `inbox.handoff` — `{ inbox_path, copied_to_clipboard: bool }` *(Tier 3 only)*
75
+
76
+ No PII in filenames. No remote sync. Encryption-at-rest deferred to a
77
+ future ADR.
78
+
79
+ ## Inbox handoff (Tier 3)
80
+
81
+ Path: `~/.event4u/agent-config/workspace/inbox/<yyyy-mm-dd>/<id>.md`.
82
+
83
+ ```markdown
84
+ ---
85
+ created_at: 2026-05-24T12:08:00Z
86
+ role: galabau
87
+ task: angebot-erstellen
88
+ host_tier: 3
89
+ host_id: cursor
90
+ ---
91
+
92
+ [rendered prompt body — skill context inlined per ADR-023]
93
+ ```
94
+
95
+ The UI surfaces a one-line banner: "Workspace wrote
96
+ `~/.event4u/.../<id>.md`. Open it in Cursor and paste." Clicking
97
+ the banner copies the path to clipboard.
98
+
99
+ ## Skill resolution
100
+
101
+ Tier 1 with skill surface (Claude Code only) — workspace passes the
102
+ slash command as part of the prompt body (`/work "<task>"` style)
103
+ and lets the host resolve it from `.claude/commands/`.
104
+
105
+ Tier 1 without skill surface (Codex, Gemini) and Tier 3 — workspace
106
+ **inlines** the skill body into the rendered prompt. The host gets
107
+ the prompt with skill context as a self-contained block.
108
+
109
+ ## State scope
110
+
111
+ - Per-user. Local-only. One workspace per OS user.
112
+ - No multi-tenant view in v0. Multi-user deployment (the topology
113
+ from [`ADR-021`](../decisions/ADR-021-deployment-shape.md)) is
114
+ out of scope for v0.
115
+ - Closing the browser tab does not kill running host subprocesses.
116
+ Reopening shows the live JSONL log.
117
+
118
+ ## Failure modes & telemetry
119
+
120
+ - Host CLI not installed → workspace renders "Host `<id>` not
121
+ available" banner with install link. No silent fallback.
122
+ - JSON envelope shape change → demote host to Tier 3 per ADR-023.
123
+ - Inbox write failure (disk full, permissions) → red banner; no
124
+ silent loss.
125
+
126
+ Telemetry stays off by default (project inertia). When the user
127
+ opts in via `.agent-settings.yml`, the workspace emits
128
+ `workspace.launch`, `workspace.host_turn`, `workspace.inbox_handoff`
129
+ counters only. No prompt bodies, no response bodies.
130
+
131
+ ## Cross-references
132
+
133
+ - ADRs: [`022`](../decisions/ADR-022-daily-workspace-decomposition.md) · [`023`](../decisions/ADR-023-host-agent-protocol.md) · [`024`](../decisions/ADR-024-workspace-v0-feature-floor.md) · [`025`](../decisions/ADR-025-workspace-chrome.md).
134
+ - Host-agent protocol: [`host-agent-protocol`](host-agent-protocol.md).
135
+ - GUI substrate: [`gui-wizard`](gui-wizard.md).
136
+ - Knowledge ingestion: [`local-knowledge-ingestion`](local-knowledge-ingestion.md).
137
+ - Role experience: [`role-experience`](role-experience.md).
@@ -0,0 +1,146 @@
1
+ # Explain Modes Contract
2
+
3
+ > **Status** · v0 / design · 2026-05-24. Phase 6 of the
4
+ > employee-product workstream.
5
+ > Governed by [`ADR-026`](../decisions/ADR-026-explain-mode-translation.md).
6
+ > Translates the existing engineer-shaped `explain-v1` envelope into a
7
+ > role-aware plain surface, without changing the underlying data.
8
+
9
+ ## Two modes over one envelope
10
+
11
+ The agent-memory MCP already returns an `explain-v1` envelope per
12
+ `memory_explain`. It speaks engineer: `trust_score`, `score_breakdown`,
13
+ `promotion_history`, `contradictions`, `decay`. Phase 6 keeps that
14
+ envelope as the single source of truth and renders **two views**:
15
+
16
+ | Mode | Default for | Vocabulary |
17
+ |---|---|---|
18
+ | `technical` | engineering-lead, platform-engineer, default for `--debug` flag | trust_score, decay rate, promotion path, contradictions count |
19
+ | `plain` | every other role (galabau, content-creator, consultant, …) | "where this came from", "how confident", "when last reviewed", "what's contested" |
20
+
21
+ No new MCP call. No new data fetch. The plain renderer is a **pure
22
+ function** over the existing envelope.
23
+
24
+ ## Field mapping
25
+
26
+ | envelope field | technical label | plain label (default) |
27
+ |---|---|---|
28
+ | `trust_score` (0.0–1.0) | "Trust score" | "Confidence" with 4-band label (Very High ≥ 0.85 · High ≥ 0.65 · Medium ≥ 0.40 · Low < 0.40) |
29
+ | `score_breakdown.validation` | "Validation contribution" | "How well it's been checked" |
30
+ | `score_breakdown.usage` | "Usage contribution" | "How often it's been used" |
31
+ | `score_breakdown.recency` | "Recency contribution" | "How recently it was confirmed" |
32
+ | `score_breakdown.contradictions` | "Contradiction penalty" | "Disagreements found" |
33
+ | `promotion_history[]` | "Promotion timeline" | "When this was confirmed" (most recent first, ≤ 3 entries) |
34
+ | `contradictions[]` | "Unresolved contradictions" | "What disagrees with this" |
35
+ | `decay.applied_factor` | "Decay factor" | "Freshness" with 3-band label (Fresh ≥ 0.80 · Aging ≥ 0.50 · Stale < 0.50) |
36
+ | `evidence.sources[]` | "Sources" | "Where this came from" |
37
+ | `last_reviewed_at` | "Last reviewed" | "When last reviewed" + human-relative ("3 days ago") |
38
+
39
+ The technical view renders one section per envelope field, terse,
40
+ tabular. The plain view renders four labelled paragraphs:
41
+
42
+ ```
43
+ Where this came from
44
+ 3 sources — handbuch.pdf · offer-template.md · 1 council vote.
45
+
46
+ How confident
47
+ High (0.74). Last confirmed 3 days ago.
48
+
49
+ When last reviewed
50
+ 2026-05-21 — by the maintenance pass.
51
+
52
+ What's contested
53
+ No open disagreements.
54
+ ```
55
+
56
+ ## Per-role glossary override
57
+
58
+ Each role may ship an `agents/roles/<role>/explain-glossary.yml`
59
+ that overrides default plain-mode labels and the 4-band threshold
60
+ points. The file is optional; missing → defaults are used.
61
+
62
+ ```yaml
63
+ # agents/roles/galabau/explain-glossary.yml
64
+ schema: explain-glossary/v0
65
+ labels:
66
+ confidence: "Sicherheit"
67
+ sources: "Woher das stammt"
68
+ last_reviewed: "Zuletzt geprüft"
69
+ contradictions: "Was widerspricht"
70
+ bands:
71
+ confidence:
72
+ very_high: 0.85
73
+ high: 0.65
74
+ medium: 0.40
75
+ freshness:
76
+ fresh: 0.80
77
+ aging: 0.50
78
+ ```
79
+
80
+ Labels stay in `.md` source English (per `language-and-tone`);
81
+ **glossary YAMLs are the exception** — they hold the localized
82
+ runtime strings for the rendered surface and may be in the role's
83
+ native language. Loader validates `schema:` matches `explain-glossary/v0`.
84
+
85
+ ## `/why` quick command
86
+
87
+ Any role may invoke `/why` on the last agent reply. Resolution:
88
+
89
+ 1. Look up the last `host.turn` in the active session JSONL.
90
+ 2. Extract memory entry IDs referenced in the reply (regex on
91
+ `mem://<id>` markers the host envelope already emits).
92
+ 3. Call `memory_explain` for each id; merge envelopes.
93
+ 4. Render in the active mode (plain by default, technical if the
94
+ role's `explain_default` is `technical`).
95
+ 5. Append the rendered output to the session JSONL as
96
+ `{ kind: "explain.rendered", data: { mode, ids: [...] } }`.
97
+
98
+ `/why` never makes a network call beyond the existing MCP transport.
99
+
100
+ ## Renderer surface (pure function)
101
+
102
+ ```ts
103
+ function renderExplain(
104
+ envelope: ExplainV1,
105
+ options: {
106
+ mode: "technical" | "plain",
107
+ glossary?: ExplainGlossaryV0,
108
+ locale?: string, // affects relative-date rendering only
109
+ }
110
+ ): { markdown: string, mode: string, ids: string[] }
111
+ ```
112
+
113
+ Implementation lives in `packages/core/src/workspace/explain/`. No I/O,
114
+ no clock dependency beyond the `now` injected for relative-date
115
+ formatting; testable with fixtures.
116
+
117
+ ## Coverage (Phase 6 Step 5)
118
+
119
+ Fixture-driven golden tests against `tests/golden/explain/` for ≥ 5
120
+ envelope shapes:
121
+
122
+ 1. High-trust validated entry — fresh, no contradictions.
123
+ 2. Low-trust quarantined entry — never promoted.
124
+ 3. Contradicted entry — 2 open contradictions, one resolved.
125
+ 4. Recently promoted entry — last `promotion_history[0]` < 24h old.
126
+ 5. Deprecated entry — superseded-by chain, decay factor 0.20.
127
+
128
+ Each fixture exercised in both `technical` and `plain` modes plus
129
+ one with a glossary override. ≥ 90 % branch on the renderer module.
130
+
131
+ ## Failure modes
132
+
133
+ - Missing envelope field → render placeholder "(unavailable)" in
134
+ plain mode; renderer never throws. Technical mode shows the raw
135
+ null with a warning marker.
136
+ - Unknown `schema:` in glossary → loader logs a warning and falls
137
+ back to defaults; never blocks rendering.
138
+ - `/why` finds no `mem://` markers → renders "This reply did not
139
+ cite any stored memory entries." No error.
140
+
141
+ ## Cross-references
142
+
143
+ - ADR: [`ADR-026`](../decisions/ADR-026-explain-mode-translation.md).
144
+ - Envelope contract: [`agent-memory-contract`](agent-memory-contract.md) (`explain-v1`).
145
+ - Workspace integration: [`daily-workspace`](daily-workspace.md) (right rail).
146
+ - Roles: [`role-experience`](role-experience.md) (`explain_default` field).
@@ -0,0 +1,88 @@
1
+ # Host-Agent Protocol Contract
2
+
3
+ > **Status** · v0 / inventory · 2026-05-24. The daily workspace shells out to
4
+ > a host agent for every model interaction; it never re-implements one. This
5
+ > contract names which surfaces each host agent exposes today, where the
6
+ > workspace can rely on them, and what the fallback is when a surface is
7
+ > missing. Governs ADR-023 / ADR-024 / ADR-025 — see
8
+ > [`ADR-022`](../decisions/ADR-022-daily-workspace-decomposition.md).
9
+
10
+ ## Required capabilities
11
+
12
+ The workspace v0 requires exactly two surfaces from a host agent:
13
+
14
+ 1. **`launch(prompt, skill?, cwd)`** — start a new conversation in the host
15
+ agent with `prompt` pre-filled and (optionally) `skill` pre-selected, in
16
+ the named working directory. Must be invocable from a non-interactive
17
+ shell. Return shape: success / failure; the conversation runs inside the
18
+ host's own UI from there.
19
+ 2. **`emit_trace(session) → ndjson`** — append-only, structured event stream
20
+ for the running conversation: model id, tool calls, citations,
21
+ explain-trace envelope (per
22
+ [`memory-explain-v1`](memory-explain-v1.md) when memory is involved).
23
+ Must be readable by tail-style consumers without polling the host's UI.
24
+
25
+ Both surfaces must be **stable** — documented by the vendor, covered by
26
+ their semver, not derived from unstable stdout parsing.
27
+
28
+ ## Today's inventory (2026-05-24)
29
+
30
+ | Host agent | `launch` surface | `emit_trace` surface | Effective tier |
31
+ |---|---|---|---|
32
+ | **Claude Code (CLI)** | `claude -p "<prompt>" --output-format json` (subprocess; documented). Slash commands resolved against `.claude/commands/`. | JSON envelope on stdout per turn; session id preserved; no live append stream. | **Tier 1** — only host with both surfaces today. |
33
+ | **OpenAI Codex CLI** | `codex exec --json` consumes stdin; documented. No slash-command surface (skills not first-class). | NDJSON event stream on stdout — `turn.completed`, `item.completed`, tool envelopes. | **Tier 1**, no skill surface — workspace must pre-render the prompt with skill context inlined. |
34
+ | **Gemini CLI** | `gemini --output-format json` consumes stdin; documented. | JSON envelope on stdout per turn. OAuth grant required once. | **Tier 1**, no skill surface (same as Codex). |
35
+ | **Augment (IDE)** | None documented. Hook trampolines exist (`scripts/hooks/augment-dispatcher.sh`) — post-event only, cannot initiate a conversation. | None — hook payloads cover events, not model output. | **Tier 3** — observe-only. |
36
+ | **Cursor (IDE)** | `cursor://` deep links open files / chats but cannot pre-fill a prompt with skill context from a non-Cursor process. Hooks (`.cursor/hooks.json`) are post-event. | None at the protocol layer. | **Tier 3** — observe-only. |
37
+ | **Cline (VS Code ext)** | None. Hooks (`~/Documents/Cline/Hooks/`) are post-event. | None at the protocol layer. | **Tier 3** — observe-only. |
38
+ | **Windsurf (Cascade)** | None. Hooks (`.windsurf/hooks.json`) are post-event. | None at the protocol layer. | **Tier 3** — observe-only. |
39
+
40
+ ## Tier definitions
41
+
42
+ - **Tier 1 — first-class.** Both `launch` and `emit_trace` are stable.
43
+ Workspace can build full features against the host.
44
+ - **Tier 2 — degraded.** One of the two surfaces exists; workspace can
45
+ partially drive but degrades a named feature (e.g. no inline citations).
46
+ *(No host agent occupies this tier today.)*
47
+ - **Tier 3 — observe-only.** Neither surface exists at the agent boundary.
48
+ The workspace falls back to (a) user-paste of a generated prompt, or (b)
49
+ inbox-file handoff (writes `~/.event4u/agent-config/workspace/inbox/<id>.md`,
50
+ user opens the host themselves). Hook trampolines remain available for
51
+ passive event recording but do not initiate conversations.
52
+
53
+ ## v0 scope
54
+
55
+ - The workspace v0 ships against **Claude Code** as the single Tier-1 host.
56
+ Codex and Gemini are wired but secondary (no skill surface — see ADR-024).
57
+ - Tier-3 hosts get the **inbox handoff** fallback only: workspace writes the
58
+ rendered prompt + skill body into the inbox file and surfaces a one-line
59
+ copy-to-clipboard banner. No tighter integration is attempted in v0.
60
+ - The CLI shell-out is the **only** mechanism. No HTTP RPC, no MCP-driven
61
+ agent control, no shared SQLite — those are deferred to v1+ when at least
62
+ one Tier-3 host moves up.
63
+
64
+ ## Stability & change policy
65
+
66
+ - The vendor-published JSON envelope shapes are the contract. Workspace
67
+ parses by named keys, never by positional fields.
68
+ - A new host-agent CLI release that breaks the envelope **fails closed** —
69
+ the workspace surfaces a banner and degrades to Tier 3 (inbox handoff)
70
+ until this contract is updated.
71
+ - This file is the source of truth for host-agent tier. Adding a host or
72
+ promoting a tier requires (a) a vendor-link in the inventory row,
73
+ (b) at least one integration test under
74
+ `tests/integration/host-agent-protocol/`.
75
+
76
+ ## Cross-references
77
+
78
+ - ADR: [`ADR-022`](../decisions/ADR-022-daily-workspace-decomposition.md) ·
79
+ [`ADR-023`](../decisions/ADR-023-host-agent-protocol.md) ·
80
+ [`ADR-024`](../decisions/ADR-024-workspace-v0-feature-floor.md) ·
81
+ [`ADR-025`](../decisions/ADR-025-workspace-chrome.md).
82
+ - Skill: [`ai-council`](../../.agent-src/skills/ai-council/SKILL.md) — uses
83
+ the same CLI subprocess shape (claude / codex / gemini) for council
84
+ members; the workspace inherits the proven invocation paths.
85
+ - Hooks: [`hook-architecture-v1`](hook-architecture-v1.md) — covers the
86
+ post-event surface for all hosts including Tier-3.
87
+ - Daily workspace surface: [`daily-workspace`](daily-workspace.md) — UI
88
+ contract that consumes this protocol.
@@ -0,0 +1,148 @@
1
+ # Local Analytics Contract
2
+
3
+ > **Status** · v0 / design · 2026-05-24. Phase 7 of the
4
+ > employee-product workstream.
5
+ > **Local-only.** Does NOT lift the Hard-Floor item from 3.1.0 — no
6
+ > network egress, no remote Worker, no POST. Inertia of the prior
7
+ > telemetry roadmap is preserved.
8
+
9
+ ## Position vs the 3.1.0 telemetry SDK
10
+
11
+ 3.1.0 shipped the telemetry SDK + Cloudflare Worker as **source-only**;
12
+ the kill-switch defaults off and nothing is deployed. Phase 7 builds
13
+ a **separate local-only** analytics path:
14
+
15
+ | Surface | Lives | Egress | Default |
16
+ |---|---|---|---|
17
+ | 3.1.0 remote telemetry | Worker (undeployed) | ✗ inert | off, Hard-Floor |
18
+ | **Phase 7 local analytics** | `~/.event4u/agent-config/workspace/analytics/` | ✗ never | **on** for local-only |
19
+
20
+ The two surfaces share **event vocabulary** where it overlaps; they
21
+ never share a transport. Local analytics writes to disk; remote
22
+ telemetry remains undeployed.
23
+
24
+ ## Event vocabulary
25
+
26
+ Re-uses the `install_stage` schema (3.1.0) where applicable, and
27
+ adds the `workspace_event` schema for launcher / document / explain
28
+ interactions:
29
+
30
+ | schema | source | example fields |
31
+ |---|---|---|
32
+ | `install_stage/v1` | installer (3.1.0) | `stage`, `outcome`, `duration_ms`, `package_version` |
33
+ | `workspace_event/v0` | Phase 4–6 workspace | `event`, `role`, `task`, `host_tier`, `duration_ms` |
34
+
35
+ `workspace_event/v0` event names (closed set):
36
+
37
+ - `launcher.opened` · `launcher.task_picked` · `launcher.task_launched`
38
+ - `session.started` · `session.host_turn` · `session.completed`
39
+ - `document.created` · `document.edited` · `document.exported`
40
+ - `explain.opened` · `explain.mode_toggled` · `why.invoked`
41
+ - `knowledge.queried` · `knowledge.source_clicked`
42
+
43
+ No prompt bodies. No response bodies. No PII. Only counters, role
44
+ labels, task slugs (already public), and durations.
45
+
46
+ ## Storage
47
+
48
+ ```
49
+ ~/.event4u/agent-config/workspace/analytics/
50
+ ├── events.jsonl ← append-only event log
51
+ └── retention.lock ← prune-pass mutex
52
+ ```
53
+
54
+ One JSON record per line:
55
+
56
+ ```json
57
+ {
58
+ "ts": "2026-05-24T12:08:00Z",
59
+ "schema": "workspace_event/v0",
60
+ "event": "launcher.task_launched",
61
+ "data": { "role": "galabau", "task": "angebot-erstellen",
62
+ "host_tier": 1, "duration_ms": 420 }
63
+ }
64
+ ```
65
+
66
+ Rolling retention: **90 days local**. A prune pass on workspace
67
+ launch trims records older than 90 days; the lockfile prevents
68
+ concurrent prune (cheap fs lock, not a real mutex).
69
+
70
+ ## Opt-out
71
+
72
+ Single env var, single config flag, both checked:
73
+
74
+ | Surface | Default | Override |
75
+ |---|---|---|
76
+ | Env | `AGENT_CONFIG_NO_LOCAL_ANALYTICS` unset | set to any non-empty value → no writes |
77
+ | Config | `.agent-settings.yml` → `analytics.local: on` | set to `off` → no writes |
78
+
79
+ Either set to off → emitter short-circuits before opening the file.
80
+ No retention pruning either; the existing log stays until the user
81
+ removes it.
82
+
83
+ ## Emitter API
84
+
85
+ ```python
86
+ # packages/core/src/workspace/analytics/emitter.py
87
+ class LocalAnalytics:
88
+ def emit(self, event: str, data: dict) -> None: ...
89
+ def query(self, since: datetime, event: str | None = None) -> list[Event]: ...
90
+ def prune(self) -> int: ... # returns number of records dropped
91
+ ```
92
+
93
+ The emitter is a synchronous append-line write. Never blocks the UI
94
+ thread above 5 ms (90th percentile); no async / queue / batch
95
+ machinery in v0.
96
+
97
+ ## `/analytics:show` command
98
+
99
+ Local-only query. Renders to ASCII / Markdown table; never POSTs.
100
+
101
+ ```
102
+ $ npx @event4u/agent-config analytics:show --window 30d
103
+
104
+ Top prompts (last 30 days)
105
+ galabau · angebot-erstellen 47
106
+ content-creator · video-from-script 31
107
+ consultant · meeting-memo 24
108
+
109
+ Launcher → completion rate per role
110
+ galabau 87% (47 launched · 41 completed)
111
+ content-creator 71% (31 launched · 22 completed)
112
+ consultant 92% (24 launched · 22 completed)
113
+
114
+ Average session length: 4m 12s
115
+ Knowledge sources clicked: 18 (handbuch.pdf · offer-template.md · …)
116
+ ```
117
+
118
+ Flags: `--window <30d|7d|24h>` · `--event <name>` · `--role <slug>` ·
119
+ `--format <markdown|csv|json>`. No `--upload`, no `--share`; the
120
+ command can only read and render.
121
+
122
+ ## Coverage (Phase 7 Step 4)
123
+
124
+ - pytest against fixture JSONL stores (`tests/fixtures/local-analytics/`):
125
+ emitter writes, query filters by window + event + role, prune
126
+ drops correctly at the 90-day boundary.
127
+ - Env-flag short-circuit: emitter is a no-op when
128
+ `AGENT_CONFIG_NO_LOCAL_ANALYTICS=1`; no file is created.
129
+ - Concurrency: two emitters writing the same file produce
130
+ well-formed lines (POSIX `O_APPEND` semantics — test on Linux,
131
+ document Windows caveat).
132
+
133
+ ## Failure modes
134
+
135
+ - Disk full → emitter logs warning to stderr, drops the event, never
136
+ raises. UI thread is unaffected.
137
+ - Malformed line in `events.jsonl` → query skips the line, increments
138
+ a `malformed_lines` counter exposed via `/analytics:show --health`.
139
+ - Schema bump (`workspace_event/v0` → `v1`) → emitter writes the new
140
+ schema; query reads both. Migration is forward-compatible.
141
+
142
+ ## Cross-references
143
+
144
+ - Phase 4 shell that produces the events: [`daily-workspace`](daily-workspace.md).
145
+ - Phase 5 document events: [`workspace-documents`](workspace-documents.md).
146
+ - Phase 6 explain events: [`explain-modes`](explain-modes.md).
147
+ - 3.1.0 telemetry inertia: archived `road-to-product-adoption.md` Phase 4.
148
+ - Walkthrough doc (Phase 7 Step 5): `docs/guides/local-analytics.md` (deferred).
@@ -0,0 +1,96 @@
1
+ ---
2
+ stability: beta
3
+ keep-beta-until: 2026-08-24
4
+ ---
5
+
6
+ # Local knowledge ingestion contract
7
+
8
+ **Purpose.** Freeze the input shape, bounds, storage target, and redaction defaults for the single-user, local-only knowledge surface (`/knowledge:ingest`, `/knowledge:list`, `/knowledge:forget`) **before** any implementation lands. Closes the "Copy/Paste AI" complaint from feedback A without touching OAuth, multi-tenancy, or Hard-Floor connector territory.
9
+
10
+ Last refreshed: 2026-05-24. Phase 2 of the employee-product workstream.
11
+
12
+ ## What this doc is **not**
13
+
14
+ - Not the connector contract for GitHub / Jira / Confluence — those sit behind OAuth and stay cancelled in `road-to-internal-ai-os-deployment.md` Phase 5.
15
+ - Not a remote-fetch surface — every input must resolve to a local path on the same machine the agent runs on.
16
+ - Not a memory replacement — ingested content lives **inside** the existing memory layer under a dedicated namespace, never as a parallel store.
17
+
18
+ ## Input shapes
19
+
20
+ The ingestion command accepts exactly these input shapes; anything else is rejected at the input validator with a structured error and no partial write.
21
+
22
+ | Input | Resolution rule | Example |
23
+ |---|---|---|
24
+ | `file://<absolute-path>` | Single file. Path must be absolute and inside the user's home or the project root (no `/etc`, `/var`, `~root/`, `..` escapes). | `file:///Users/maintainer/clients/acme/brief.pdf` |
25
+ | Local folder path | Recursive walk; symlinks not followed; hidden directories skipped (`.git`, `.venv`, `node_modules`). | `/Users/maintainer/clients/acme/` |
26
+ | `.zip` archive | Unpacked to a temp dir inside `$TMPDIR/agent-knowledge-<uuid>`, walked, then the temp dir is removed before the command returns. | `/Users/maintainer/clients/acme.zip` |
27
+
28
+ **Remote URLs are rejected** — `http://`, `https://`, `s3://`, `gs://`, `azure://`. The error message names `/knowledge:ingest` as local-only by design.
29
+
30
+ ## Supported MIME types
31
+
32
+ The ingestion module routes each file through the existing `markitdown` adapter when the MIME type is not native markdown.
33
+
34
+ | MIME | Adapter | Notes |
35
+ |---|---|---|
36
+ | `text/markdown` | passthrough | UTF-8 only; other encodings rejected |
37
+ | `text/plain` | passthrough | UTF-8 only |
38
+ | `application/pdf` | `markitdown` | OCR if scanned; OCR confidence < 0.7 surfaces as `low_confidence` tag |
39
+ | `application/vnd.openxmlformats-officedocument.wordprocessingml.document` (`.docx`) | `markitdown` | |
40
+ | `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` (`.xlsx`) | `markitdown` | One sheet per chunk |
41
+ | `application/epub+zip` (`.epub`) | `markitdown` | Chapter per chunk |
42
+ | `image/png`, `image/jpeg` | `markitdown` OCR | OCR confidence stored same as PDF |
43
+
44
+ Unsupported MIME → file skipped with a counted `skipped: <mime>` entry in the command summary. No partial-content writes.
45
+
46
+ ## Bounds (non-negotiable, enforced at command entry)
47
+
48
+ | Bound | Limit | Rationale |
49
+ |---|---|---|
50
+ | Total ingest size | ≤ 100 MB per `/knowledge:ingest` call | Keeps a single ingestion bounded; multi-ingest is fine |
51
+ | Document count | ≤ 1000 per call | Avoids unbounded walks on a misconfigured folder |
52
+ | Per-file size | ≤ 20 MB | One outlier file cannot blow the budget |
53
+ | Total memory footprint | ≤ 500 MB across all ingests | LRU eviction at the namespace level when crossed |
54
+ | Path traversal depth | ≤ 10 directories | Cheap guard against pathological folder trees |
55
+
56
+ Crossing any bound is a **hard reject** at command entry — not a warning. The command returns a structured error with the bound name and the observed value.
57
+
58
+ ## Storage target
59
+
60
+ Ingested content lives inside the existing memory namespace as a dedicated prefix:
61
+
62
+ ```
63
+ memory/
64
+ └── knowledge/
65
+ ├── <ingest-id>/ # uuid7 per ingest call
66
+ │ ├── manifest.json # source path, count, timestamps, redactions
67
+ │ └── chunks/<n>.md # one markdown chunk per logical unit
68
+ ```
69
+
70
+ - `<ingest-id>` is a uuid7 so timestamps are recoverable from the id; never user-controlled.
71
+ - `manifest.json` is the audit row — used by `/knowledge:list` and by the LRU eviction loop.
72
+ - Chunks are markdown only after the adapter has run; the original binary is never stored.
73
+
74
+ The MCP tool `memory_retrieve` (existing surface in `agent-memory`) **must** tag retrieved entries from this namespace with `source: knowledge`. The host model decides what to do with user-supplied vs maintainer-curated entries; this contract only requires the tag.
75
+
76
+ ## Redaction defaults
77
+
78
+ Redaction runs **before** the chunk write, never after.
79
+
80
+ - **PII allowlist** — only the following identifier classes survive the ingest: project names, document titles, headings, technical terminology. Everything else that pattern-matches a PII regex set (e-mail addresses, phone numbers, IBAN, credit-card-shaped strings, SSN-shaped strings) is replaced with class placeholders (`[EMAIL]`, `[PHONE]`, `[IBAN]`, `[CC]`, `[SSN]`).
81
+ - **Secrets never stored** — anything matching the existing `gitleaks` ruleset (or equivalent) is replaced with `[SECRET]` and the manifest's `secrets_redacted` counter is incremented. A chunk with ≥ 1 secret redaction is tagged `contains_redactions: true`.
82
+ - **The user can opt out per-call** with `--no-redact`, but the manifest captures the flag so the audit trail names exactly which ingests bypassed redaction. The default is always redact.
83
+
84
+ ## Eviction policy
85
+
86
+ LRU at the namespace level. When the 500 MB cap is crossed, oldest ingests (by `manifest.last_touched`) are dropped whole — never per-chunk. `last_touched` updates on every `memory_retrieve` hit against an entry in the namespace. The user can pin an ingest with `/knowledge:list --pin <ingest-id>`; pinned ingests are never evicted.
87
+
88
+ ## Command surface (deferred to impl PR)
89
+
90
+ `/knowledge:ingest <path>`, `/knowledge:list`, `/knowledge:forget <prefix>` are defined elsewhere — this contract pins their **inputs, bounds, storage, and redaction**. The Python module that implements the file walk + MIME routing + chunk writing lives at `packages/core/installer/python/knowledge_ingest.py`, ≤ 600 LOC (bumped from the pre-impl ≤ 400 budget once five PII classes, five secret patterns, LRU eviction, manifest persistence, pin/unpin and the multi-verb CLI were all in one file — splitting would have added an import seam without changing the surface), per Phase 2 Step 2.
91
+
92
+ ## Open questions (Phase 2 council pass, optional)
93
+
94
+ - Chunk size — fixed (e.g. 2 KB markdown) vs adaptive per document? Default: 2 KB until the recruit sessions or eval surface a reason to change.
95
+ - OCR confidence threshold — 0.7 is a guess; first three sessions inform the right number.
96
+ - Pinning UX — `--pin` flag vs a separate `/knowledge:pin` command. Default in this contract: a flag on `/knowledge:list`.