bikky 0.4.2 → 0.4.4

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 (62) hide show
  1. package/README.md +64 -37
  2. package/dist/config.d.ts +15 -1
  3. package/dist/config.js +116 -20
  4. package/dist/daemon/capture-policy.d.ts +0 -1
  5. package/dist/daemon/capture-policy.js +0 -2
  6. package/dist/daemon/consolidation.d.ts +2 -1
  7. package/dist/daemon/consolidation.js +32 -15
  8. package/dist/daemon/entity-typing.js +10 -0
  9. package/dist/daemon/episode-summary.d.ts +4 -0
  10. package/dist/daemon/episode-summary.js +39 -8
  11. package/dist/daemon/extraction.d.ts +2 -2
  12. package/dist/daemon/extraction.js +65 -22
  13. package/dist/daemon/loop.js +8 -0
  14. package/dist/daemon/maintenance-state.d.ts +1 -1
  15. package/dist/daemon/maintenance-state.js +2 -0
  16. package/dist/daemon/qdrant.d.ts +32 -10
  17. package/dist/daemon/qdrant.js +199 -60
  18. package/dist/daemon/quality-rollups.d.ts +51 -0
  19. package/dist/daemon/quality-rollups.js +378 -0
  20. package/dist/daemon/relations.d.ts +3 -3
  21. package/dist/daemon/relations.js +28 -16
  22. package/dist/daemon/session-index.d.ts +5 -0
  23. package/dist/daemon/session-index.js +36 -9
  24. package/dist/daemon/session-summary.d.ts +3 -0
  25. package/dist/daemon/session-summary.js +48 -15
  26. package/dist/daemon/staleness.js +3 -3
  27. package/dist/daemon/transcript-sources.js +3 -2
  28. package/dist/daemon/watcher.js +2 -0
  29. package/dist/daemon/workstream-summary.d.ts +4 -0
  30. package/dist/daemon/workstream-summary.js +58 -16
  31. package/dist/install.d.ts +11 -0
  32. package/dist/install.js +38 -0
  33. package/dist/lifecycle.js +7 -5
  34. package/dist/llm/embedding/index.js +2 -1
  35. package/dist/llm/embedding/providers/openai.js +8 -2
  36. package/dist/llm/embedding/providers/portkey.js +9 -2
  37. package/dist/llm/inference/index.js +2 -1
  38. package/dist/llm/util.d.ts +12 -0
  39. package/dist/llm/util.js +18 -0
  40. package/dist/mcp/helpers.d.ts +8 -0
  41. package/dist/mcp/helpers.js +36 -3
  42. package/dist/mcp/taxonomy.d.ts +9 -13
  43. package/dist/mcp/taxonomy.js +59 -42
  44. package/dist/mcp/tools.js +351 -83
  45. package/dist/mcp/types.d.ts +35 -0
  46. package/dist/package-verifier.d.ts +19 -0
  47. package/dist/package-verifier.js +83 -0
  48. package/dist/prompts/brief.d.ts +2 -2
  49. package/dist/prompts/brief.js +0 -1
  50. package/dist/prompts/extraction.js +9 -11
  51. package/dist/provenance/origin.d.ts +57 -0
  52. package/dist/provenance/origin.js +254 -0
  53. package/dist/routing-context.d.ts +16 -0
  54. package/dist/routing-context.js +55 -0
  55. package/dist/status.d.ts +1 -0
  56. package/dist/status.js +7 -1
  57. package/docs/config/fully-hosted.md +33 -13
  58. package/docs/config/hosted-models.md +33 -13
  59. package/docs/config/hosted-qdrant-local-models.md +1 -0
  60. package/docs/config/local.md +1 -0
  61. package/docs/configuration.md +42 -17
  62. package/package.json +2 -2
package/README.md CHANGED
@@ -2,48 +2,69 @@
2
2
 
3
3
  <p align="center"><b>Persistent memory for AI coding agents — built for teams and multi-agent engineering workflows.</b></p>
4
4
 
5
- bikky gives AI coding agents (GitHub Copilot, Claude Code, Cursor, and other MCP clients) long-term memory that persists across sessions, across tools, and across your whole team. When multiple engineers, agents, or repos need to build on the same knowledge base, bikky captures what's learned *during* sessions so future sessions start smarter.
6
-
7
- ### Who it's for
8
-
9
- - 👥 **Teams & software factories** — What one engineer's agent learns today, every agent on the team can recall tomorrow. Shared memory turns institutional knowledge into something queryable instead of tribal — onboarding accelerates, conventions stop drifting, and the same lesson never gets re-learned twice.
10
- - 🤖 **Multi-agent engineering workflows** — Multiple Cursor / Claude Code / Copilot sessions can share codebase context, conventions, and recent decisions instead of re-learning them from scratch.
11
-
12
5
  <p align="center">
13
6
  <img src="https://raw.githubusercontent.com/bikky-dev/bikky/main/docs/diagrams/team-memory.svg" alt="Memory — facts flow from individual sessions into a self-curating knowledge store shared across your team" width="720" />
14
7
  </p>
15
8
 
16
- <p align="center"><i>Knowledge flows from every session into a store that curates itself over time — deduplicating, distilling, and decaying stale facts — so every future session starts smarter across the team.</i></p>
9
+ <p align="center"><i>Selected knowledge flows from supported sessions into a store that curates itself over time — deduplicating, distilling, and decaying stale facts — so future sessions can start with more context.</i></p>
17
10
 
18
- ---
11
+ bikky provides long-term memory for MCP-capable AI coding agents. It exposes memory tools over the Model Context Protocol (MCP), stores facts in Qdrant, and can run a local daemon that extracts durable facts from supported transcript sources. Teams can share memory across tools, repos, and engineers without treating chat history or closed PRs as the source of truth.
19
12
 
20
- ### The problem
13
+ ### Who it's for
21
14
 
22
- The most valuable things you and your agents learn why a config value exists, which deploy step matters, what broke last quarter, the convention you settled on yesterday — happen *during* sessions. And then they vanish when the session closes. Across teams, repos, and tools, knowledge still lives in heads, chat threads, and closed PRs, and every new agent session has to learn it from scratch. Hand-written docs drift the moment they're published.
15
+ - 👥 **Teams & software factories**What one engineer's agent learns today can be recalled by other agents on the team tomorrow. Shared memory makes institutional knowledge queryable, helps onboarding, and reduces convention drift and repeated rediscovery.
16
+ - 🤖 **Multi-agent engineering workflows** — Multiple MCP-capable agent sessions can share codebase context, conventions, and recent decisions instead of re-learning them from scratch.
23
17
 
24
- ### How bikky solves it
18
+ ---
19
+
20
+ ### How bikky works
25
21
 
26
- bikky gives your agent memory tools and runs a small background service after `bikky setup`. You keep working normally; bikky captures useful facts, organizes them, recalls them in future sessions, and keeps the store tidy over time.
22
+ bikky gives your agent memory tools and runs a small background service after `bikky setup`. You keep working normally; bikky captures useful facts from supported transcript sources, organizes them, recalls them in future sessions, and keeps the store tidy over time.
27
23
 
28
- - **Capture** — Facts are extracted automatically from session transcripts; no manual docs to write.
29
- - **Classify** — Memories are grouped as **engineering**, **product**, **human**, or **system** so they stay easy to browse and filter.
30
- - **Recall** — Every new session, yours or a teammate's, recalls from the same store via semantic search.
24
+ - **Capture** — Facts are extracted automatically from supported session transcripts without requiring manual notes for every fact.
25
+ - **Classify** — Memories are grouped as **engineering**, **product**, or **system** so they stay easy to browse and filter.
26
+ - **Recall** — New sessions can recall from the same store via semantic search.
31
27
  - **Curate** — bikky merges duplicates, fades stale facts, resolves contradictions, distills recurring patterns, and builds an entity graph over time.
32
- - **Compound** — Session 50 is dramatically better than session 1 because memory accumulates.
28
+ - **Compound** — Later sessions can start with more context because memory accumulates.
33
29
  - **Route** — Optionally keep team, client, or environment-specific memory in separate Qdrant destinations from one install. See [separate memory stores](#optional-separate-memory-stores).
34
30
 
35
31
  Subtypes keep recall precise without making setup harder:
36
32
 
37
- - **Engineering** — codebase maps, architecture decisions, infra topology, access patterns, operational procedures, troubleshooting gotchas, and conventions.
33
+ - **Engineering** — codebase maps, architecture decisions, infra topology, access patterns, operational procedures, troubleshooting gotchas, conventions, preferences, person/ownership context, working agreements, and durable activity events.
38
34
  - **Product** — domain rules, product decisions, requirements, user workflows, roadmap items, success metrics, and market insights.
39
- - **Human** — preferences, person profiles, ownership notes, working agreements, and activity events.
40
35
  - **System** — session indexes, episodes, workstreams, and feedback signals.
41
36
 
42
37
  ---
43
38
 
39
+ ## Supported integrations
40
+
41
+ bikky has two integration surfaces: MCP tool access for agents and optional background transcript capture. Tool access is broader than transcript capture.
42
+
43
+ ### Coding agents and MCP clients
44
+
45
+ | Client or agent | MCP tool access | `bikky setup` registration | Background transcript capture |
46
+ | --- | --- | --- | --- |
47
+ | GitHub Copilot | Supported | Supported via `~/.copilot/mcp-config.json` | Supported from `~/.copilot/session-state` |
48
+ | Claude Code | Supported | Supported via the `claude` CLI or `~/.claude.json` fallback | Supported from `~/.claude/projects` |
49
+ | Cursor and other stdio MCP clients | Standard MCP server is available via `npx -y bikky mcp` | Not auto-configured today | No built-in watcher today |
50
+
51
+ If your client can launch a stdio MCP server, it can use bikky's memory tools after manual configuration. bikky does not currently ship Cursor-specific setup or transcript parsing. Automatic transcript ingestion is implemented for GitHub Copilot and Claude Code.
52
+
53
+ ### Storage and model providers
54
+
55
+ | Component | Supported today | Notes |
56
+ | --- | --- | --- |
57
+ | Vector store | Qdrant | Local Docker, Qdrant Cloud, or self-hosted Qdrant. Qdrant is required. |
58
+ | `embedding.provider` | `ollama`, `openai`, `bedrock`, `portkey` | Used to embed memories for semantic search. |
59
+ | `llm.provider` | `ollama`, `openai`, `bedrock`, `portkey` | Used for extraction, curation, distillation, and relation inference. |
60
+
61
+ Portkey support means bikky talks to Portkey as the configured gateway; upstream model availability, routing, and fallbacks are controlled by your Portkey configuration. Providers not listed above are not built in today, but the provider registry is designed to make additions small and reviewable.
62
+
63
+ ---
64
+
44
65
  ## Quick start
45
66
 
46
- This is the fastest path to a working memory store: Qdrant runs locally, while hosted embeddings and LLM calls provide strong extraction and recall quality without running local models.
67
+ This is the fastest path to a working memory store: Qdrant runs locally, while hosted embeddings and LLM calls handle extraction and recall without running local models.
47
68
 
48
69
  ```bash
49
70
  # 1. Pull and run Qdrant (vector store)
@@ -60,7 +81,7 @@ cat > ~/.bikky/config.json <<'JSON'
60
81
  "embedding": {
61
82
  "provider": "openai",
62
83
  "model": "text-embedding-3-small",
63
- "dimensions": 1536,
84
+ "dimensions": 1024,
64
85
  "api_key": "sk-..."
65
86
  },
66
87
  "llm": {
@@ -73,19 +94,19 @@ JSON
73
94
  # qdrant_api_key is optional; leave it empty or omit it for local Qdrant.
74
95
  # Prefer env vars? Omit api_key above and set OPENAI_API_KEY instead.
75
96
 
76
- # 3. Register bikky with your editor and start the background service
77
- bikky setup # writes MCP config for Copilot + Claude Code, then starts the daemon
97
+ # 3. Register bikky with supported clients and start the background service
98
+ bikky setup # writes MCP config for GitHub Copilot + Claude Code, then starts the daemon
78
99
  ```
79
100
 
80
101
  `npm install -g bikky` runs a best-effort postinstall setup hook for convenience. It never fails the install, and you should still run `bikky setup` after writing your config to make setup explicit and repeatable.
81
102
 
82
- Restart your editor. The memory tools appear automatically in supported MCP clients.
103
+ Restart your editor. The memory tools appear automatically in GitHub Copilot and Claude Code; configure other stdio MCP clients manually with `npx -y bikky mcp`.
83
104
 
84
105
  ```bash
85
106
  bikky status # confirms Qdrant, embeddings, daemon, and UI health
86
107
  ```
87
108
 
88
- That's it. You can keep Qdrant local forever, or move the vector store to Qdrant Cloud later for a shared team setup.
109
+ At this point, you can continue with local Qdrant or move the vector store to Qdrant Cloud later for a shared team setup.
89
110
 
90
111
  For other deployment shapes — fully hosted, 100% local, or hosted Qdrant with local models — see [Setup options](#setup-options).
91
112
 
@@ -101,22 +122,22 @@ bikky supports four common setup shapes. Pick based on where you want Qdrant to
101
122
  | ----------------------- | ------------------------------ | ---------------------------------------------------------------------------------------- |
102
123
  | **Node.js** | ≥ 20 | `nvm install 20` or your package manager |
103
124
  | **Vector store** | Qdrant | Local Docker · [Qdrant Cloud](https://cloud.qdrant.io) · Self-hosted |
104
- | **Embeddings** | One provider | OpenAI · Ollama · Bedrock · Portkey |
105
- | **LLM** | One provider | OpenAI · Ollama · Bedrock · Portkey |
125
+ | **Embeddings** | One provider | Portkey · OpenAI · Ollama · Bedrock |
126
+ | **LLM** | One provider | Portkey · OpenAI · Ollama · Bedrock |
106
127
  | **Docker** *(optional)* | Only if you run Qdrant locally | Docker Desktop, OrbStack, colima, etc. |
107
128
 
108
- Both `embedding.provider` and `llm.provider` accept the same values: `ollama`, `openai`, `bedrock`, or `portkey`.
129
+ Both `embedding.provider` and `llm.provider` accept the same values: `ollama`, `openai`, `bedrock`, or `portkey`. Portkey can be used as a hosted gateway when you want one configured provider in bikky and upstream routing/fallbacks managed outside bikky. The documented examples use **1024-dimensional embeddings** because that size works across the built-in provider examples. Some providers expose larger native dimensions (for example OpenAI `text-embedding-3-small` can return 1536), but using 1024 keeps the documented setup portable without rebuilding every collection.
109
130
 
110
131
  > ⚠️ **Qdrant Cloud free tier does not include automatic backups.** Deleted collections cannot be recovered. If your memory data is valuable, use a paid Qdrant Cloud plan (which includes daily backups), run Qdrant locally with your own backup strategy, or periodically export snapshots via the [Qdrant snapshots API](https://qdrant.tech/documentation/concepts/snapshots/).
111
132
 
112
133
  ### Choose a setup
113
134
 
114
- | Setup | Best for | Config |
115
- | -------------------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------- |
116
- | **Fully hosted** | Best performance and teams; managed vector storage and models | [Fully hosted config][fully-hosted-config] |
117
- | **Local Qdrant + hosted models** | Local vector storage with hosted extraction and embedding | [Hosted models config][hosted-models-config] |
118
- | **Local and free** | Local evaluation; quality depends on local models | [Local config guide][local-config] |
119
- | **Hosted Qdrant + local Ollama** | Shared vector storage while keeping model calls local | [Hosted Qdrant + local models][hosted-qdrant-local-models-config] |
135
+ | Setup | Use when | Config |
136
+ | -------------------------------- | -------------------------------------------------------------- | ----------------------------------------------------------------------- |
137
+ | **Fully hosted** | Teams want managed vector storage and hosted models | [Fully hosted config][fully-hosted-config] |
138
+ | **Local Qdrant + hosted models** | You want local vector storage with hosted extraction/embedding | [Hosted models config][hosted-models-config] |
139
+ | **Local and free** | You are evaluating locally and can accept local-model quality | [Local config guide][local-config] |
140
+ | **Hosted Qdrant + local Ollama** | You want shared vectors while keeping model calls local | [Hosted Qdrant + local models][hosted-qdrant-local-models-config] |
120
141
 
121
142
  ### Configuration basics
122
143
 
@@ -128,6 +149,8 @@ Pick the setup guide above for the copy-paste config. All setup shapes use the s
128
149
 
129
150
  Config lives at `~/.bikky/config.json`, or at `BIKKY_HOME/config.json` when `BIKKY_HOME` is set. You can keep credentials out of the file with environment variables such as `QDRANT_URL`, `QDRANT_API_KEY`, and provider API keys.
130
151
 
152
+ `bikky setup` also provisions `identity.user_id` / `identity.user_name` when they are missing. New memory writes store canonical `origin` metadata with the configured human user, the acting agent or daemon/UI surface, the interface, and the operation. MCP clients cannot supply or spoof `origin.user`; if config, env, Git, and shell identity detection all fail, bikky falls back to the local hostname.
153
+
131
154
  For hosted models, custom providers, multiple profiles, or advanced tuning, use the full configuration guide.
132
155
 
133
156
  > 📖 **Full configuration guide:** [docs/configuration.md][configuration-guide]
@@ -194,9 +217,9 @@ bikky-ui # opens http://localhost:1422
194
217
  <p align="center"><i>Dashboard — memory stats, category breakdown, and recent facts at a glance</i></p>
195
218
 
196
219
  <p align="center">
197
- <img src="https://raw.githubusercontent.com/bikky-dev/bikky/main/docs/screenshots/memory.png" alt="Memory browser — search, filter, and browse all stored facts" width="720" />
220
+ <img src="https://raw.githubusercontent.com/bikky-dev/bikky/main/docs/screenshots/memory.png" alt="Memory browser — search, filter, and browse current user-facing memories" width="720" />
198
221
  </p>
199
- <p align="center"><i>Memory browser — search, filter by category/kind/source, and browse all stored facts</i></p>
222
+ <p align="center"><i>Memory browser — search, filter by category, subtype, entity, usefulness, date, and sort order</i></p>
200
223
 
201
224
  <p align="center">
202
225
  <img src="https://raw.githubusercontent.com/bikky-dev/bikky/main/docs/screenshots/graph.png" alt="Entity graph — interactive visualization of entity relationships" width="720" />
@@ -205,11 +228,15 @@ bikky-ui # opens http://localhost:1422
205
228
 
206
229
  The UI reads from your existing `~/.bikky/config.json` (or `BIKKY_HOME/config.json`) — no extra configuration required.
207
230
 
231
+ By default, the dashboard, memory list, and search results show current user-facing memories only. Internal telemetry, system lifecycle summaries (`session_index`, `episode`, `workstream`), entity sidecars, and superseded archive records are hidden from the main views so counts match what you normally mean by "memories." Diagnostic API queries can still request those records explicitly, including superseded records with `include_superseded=true`.
232
+
233
+ Memory cards and detail pages also surface provenance from canonical `origin` metadata: the configured user, origin surface/operation, agent, last operation, repo, branch, workstream, task, session, and episode when present. Older records that only have legacy `source`, `actor_id`, or `metadata.actor_label` still display useful fallback labels.
234
+
208
235
  ## CLI
209
236
 
210
237
  ```bash
211
238
  bikky mcp # start MCP server (stdio) — used by editors
212
- bikky setup # install MCP configs for Copilot + Claude Code, then start the daemon
239
+ bikky setup # install MCP configs for GitHub Copilot + Claude Code, then start the daemon
213
240
  bikky start # alias for setup
214
241
  bikky stop # stop the background daemon
215
242
  bikky daemon # run the daemon in the foreground
package/dist/config.d.ts CHANGED
@@ -4,6 +4,12 @@
4
4
  * Resolution order: defaults → ~/.bikky/config.json → env vars.
5
5
  * Config directory: ~/.bikky/
6
6
  */
7
+ export declare function getBikkyDir(): string;
8
+ export declare function getConfigPath(): string;
9
+ export declare function getLogDir(): string;
10
+ export declare function getStateDir(): string;
11
+ export declare function getPidPath(): string;
12
+ export declare function getExtractionHealthPath(): string;
7
13
  export declare const BIKKY_DIR: string;
8
14
  export declare const CONFIG_PATH: string;
9
15
  export declare const LOG_DIR: string;
@@ -55,6 +61,10 @@ export interface DaemonConfig {
55
61
  entity_typing_enabled: boolean;
56
62
  entity_typing_interval_sec: number;
57
63
  entity_typing_max_entities_per_run: number;
64
+ memory_quality_rollups_enabled: boolean;
65
+ memory_quality_rollups_interval_sec: number;
66
+ memory_quality_rollups_low_confidence_threshold: number;
67
+ memory_quality_rollups_max_scopes_per_run: number;
58
68
  staleness_threshold_days: number;
59
69
  }
60
70
  export interface QdrantClientConfig {
@@ -63,7 +73,11 @@ export interface QdrantClientConfig {
63
73
  retry_base_delay_ms: number;
64
74
  }
65
75
  export interface IdentityConfig {
76
+ user_id: string | null;
77
+ user_name: string | null;
78
+ /** @deprecated Use origin.user.id instead. */
66
79
  actor_id: string | null;
80
+ /** @deprecated Use origin.user.name / origin.agent.name instead. */
67
81
  actor_label: string | null;
68
82
  }
69
83
  export interface WatcherConfig {
@@ -158,7 +172,7 @@ export interface ConfigFileDiagnostics {
158
172
  issues: ConfigIssue[];
159
173
  }
160
174
  declare const DEFAULTS: BikkyConfig;
161
- export declare const CONFIG_ENV_KEYS: readonly ["QDRANT_URL", "QDRANT_API_KEY", "BIKKY_COLLECTION", "EMBEDDING_PROVIDER", "EMBEDDING_MODEL", "EMBEDDING_BASE_URL", "EMBEDDING_DIMENSIONS", "OPENAI_API_KEY", "LLM_PROVIDER", "LLM_MODEL", "LLM_BASE_URL", "LLM_FALLBACK_PROVIDER", "AWS_PROFILE", "AWS_BEDROCK_REGION", "AWS_REGION", "QDRANT_TIMEOUT_MS", "QDRANT_RETRIES", "QDRANT_RETRY_BASE_DELAY_MS", "BIKKY_EMBEDDING_TIMEOUT_MS", "BIKKY_EMBEDDING_RETRIES", "BIKKY_EMBEDDING_RETRY_BASE_DELAY_MS", "BIKKY_LLM_TIMEOUT_MS", "BIKKY_LLM_RETRIES", "BIKKY_LLM_RETRY_BASE_DELAY_MS", "BIKKY_DAEMON_RELATION_INFERENCE_ENABLED", "BIKKY_DAEMON_RELATION_INFERENCE_INTERVAL_SEC", "BIKKY_DAEMON_RELATION_INFERENCE_MAX_PAIRS_PER_RUN", "BIKKY_DAEMON_ENTITY_TYPING_ENABLED", "BIKKY_DAEMON_ENTITY_TYPING_INTERVAL_SEC", "BIKKY_DAEMON_ENTITY_TYPING_MAX_ENTITIES_PER_RUN", "BIKKY_ACTOR_ID", "BIKKY_ACTOR_LABEL"];
175
+ export declare const CONFIG_ENV_KEYS: readonly ["QDRANT_URL", "QDRANT_API_KEY", "BIKKY_COLLECTION", "EMBEDDING_PROVIDER", "EMBEDDING_MODEL", "EMBEDDING_BASE_URL", "EMBEDDING_DIMENSIONS", "OPENAI_API_KEY", "PORTKEY_API_KEY", "LLM_PROVIDER", "LLM_MODEL", "LLM_BASE_URL", "LLM_FALLBACK_PROVIDER", "AWS_PROFILE", "AWS_BEDROCK_REGION", "AWS_REGION", "QDRANT_TIMEOUT_MS", "QDRANT_RETRIES", "QDRANT_RETRY_BASE_DELAY_MS", "BIKKY_EMBEDDING_TIMEOUT_MS", "BIKKY_EMBEDDING_RETRIES", "BIKKY_EMBEDDING_RETRY_BASE_DELAY_MS", "BIKKY_LLM_TIMEOUT_MS", "BIKKY_LLM_RETRIES", "BIKKY_LLM_RETRY_BASE_DELAY_MS", "BIKKY_DAEMON_RELATION_INFERENCE_ENABLED", "BIKKY_DAEMON_RELATION_INFERENCE_INTERVAL_SEC", "BIKKY_DAEMON_RELATION_INFERENCE_MAX_PAIRS_PER_RUN", "BIKKY_DAEMON_ENTITY_TYPING_ENABLED", "BIKKY_DAEMON_ENTITY_TYPING_INTERVAL_SEC", "BIKKY_DAEMON_ENTITY_TYPING_MAX_ENTITIES_PER_RUN", "BIKKY_DAEMON_MEMORY_QUALITY_ROLLUPS_ENABLED", "BIKKY_DAEMON_MEMORY_QUALITY_ROLLUPS_INTERVAL_SEC", "BIKKY_DAEMON_MEMORY_QUALITY_ROLLUPS_LOW_CONFIDENCE_THRESHOLD", "BIKKY_DAEMON_MEMORY_QUALITY_ROLLUPS_MAX_SCOPES_PER_RUN", "BIKKY_USER_ID", "BIKKY_USER_NAME", "BIKKY_AGENT_ID", "BIKKY_AGENT_NAME", "BIKKY_ACTOR_ID", "BIKKY_ACTOR_LABEL"];
162
176
  export declare function validateConfigObject(raw: unknown): ConfigIssue[];
163
177
  export declare function inspectConfigFile(configPath?: string): ConfigFileDiagnostics;
164
178
  export declare function getActiveConfigEnvOverrides(env?: NodeJS.ProcessEnv): string[];
package/dist/config.js CHANGED
@@ -12,15 +12,41 @@ import { z } from "zod";
12
12
  // Paths
13
13
  // ---------------------------------------------------------------------------
14
14
  // BIKKY_HOME env var lets tests (and advanced users) override the config dir
15
- // without touching the real ~/.bikky/. Tests MUST set this to an isolated
16
- // tempdir before importing this module otherwise saveConfig() will write to
17
- // the user's real config file.
18
- export const BIKKY_DIR = process.env.BIKKY_HOME ?? path.join(os.homedir(), ".bikky");
19
- export const CONFIG_PATH = path.join(BIKKY_DIR, "config.json");
20
- export const LOG_DIR = path.join(BIKKY_DIR, "logs");
21
- export const STATE_DIR = path.join(BIKKY_DIR, "state");
22
- export const PID_PATH = path.join(STATE_DIR, "daemon.pid");
23
- export const EXTRACTION_HEALTH_PATH = path.join(STATE_DIR, "extraction-health.json");
15
+ // without touching the real ~/.bikky/. The getter functions below re-read the
16
+ // env var on every call, so changing BIKKY_HOME at runtime (e.g. in a test
17
+ // setup hook) takes effect for all subsequent saveConfig()/loadConfig() calls.
18
+ //
19
+ // The legacy `BIKKY_DIR` / `CONFIG_PATH` / etc. exports are kept for
20
+ // backward-compatibility, but they capture the env state at module load
21
+ // time and should NOT be relied on for safe writes. Internal callers — and
22
+ // any test that wants sandboxing — should call the getter functions instead.
23
+ export function getBikkyDir() {
24
+ return process.env.BIKKY_HOME ?? path.join(os.homedir(), ".bikky");
25
+ }
26
+ export function getConfigPath() {
27
+ return path.join(getBikkyDir(), "config.json");
28
+ }
29
+ export function getLogDir() {
30
+ return path.join(getBikkyDir(), "logs");
31
+ }
32
+ export function getStateDir() {
33
+ return path.join(getBikkyDir(), "state");
34
+ }
35
+ export function getPidPath() {
36
+ return path.join(getStateDir(), "daemon.pid");
37
+ }
38
+ export function getExtractionHealthPath() {
39
+ return path.join(getStateDir(), "extraction-health.json");
40
+ }
41
+ // Legacy constant exports — captured at module load. Prefer the getter
42
+ // functions above when you need fresh values (e.g. inside tests, or after
43
+ // mutating BIKKY_HOME at runtime).
44
+ export const BIKKY_DIR = getBikkyDir();
45
+ export const CONFIG_PATH = getConfigPath();
46
+ export const LOG_DIR = getLogDir();
47
+ export const STATE_DIR = getStateDir();
48
+ export const PID_PATH = getPidPath();
49
+ export const EXTRACTION_HEALTH_PATH = getExtractionHealthPath();
24
50
  // ---------------------------------------------------------------------------
25
51
  // Defaults
26
52
  // ---------------------------------------------------------------------------
@@ -66,9 +92,15 @@ const DEFAULTS = {
66
92
  entity_typing_enabled: true,
67
93
  entity_typing_interval_sec: 900,
68
94
  entity_typing_max_entities_per_run: 5,
95
+ memory_quality_rollups_enabled: true,
96
+ memory_quality_rollups_interval_sec: 3600,
97
+ memory_quality_rollups_low_confidence_threshold: 0.6,
98
+ memory_quality_rollups_max_scopes_per_run: 100,
69
99
  staleness_threshold_days: 30,
70
100
  },
71
101
  identity: {
102
+ user_id: null,
103
+ user_name: null,
72
104
  actor_id: null,
73
105
  actor_label: null,
74
106
  },
@@ -91,6 +123,7 @@ export const CONFIG_ENV_KEYS = [
91
123
  "EMBEDDING_BASE_URL",
92
124
  "EMBEDDING_DIMENSIONS",
93
125
  "OPENAI_API_KEY",
126
+ "PORTKEY_API_KEY",
94
127
  "LLM_PROVIDER",
95
128
  "LLM_MODEL",
96
129
  "LLM_BASE_URL",
@@ -113,6 +146,14 @@ export const CONFIG_ENV_KEYS = [
113
146
  "BIKKY_DAEMON_ENTITY_TYPING_ENABLED",
114
147
  "BIKKY_DAEMON_ENTITY_TYPING_INTERVAL_SEC",
115
148
  "BIKKY_DAEMON_ENTITY_TYPING_MAX_ENTITIES_PER_RUN",
149
+ "BIKKY_DAEMON_MEMORY_QUALITY_ROLLUPS_ENABLED",
150
+ "BIKKY_DAEMON_MEMORY_QUALITY_ROLLUPS_INTERVAL_SEC",
151
+ "BIKKY_DAEMON_MEMORY_QUALITY_ROLLUPS_LOW_CONFIDENCE_THRESHOLD",
152
+ "BIKKY_DAEMON_MEMORY_QUALITY_ROLLUPS_MAX_SCOPES_PER_RUN",
153
+ "BIKKY_USER_ID",
154
+ "BIKKY_USER_NAME",
155
+ "BIKKY_AGENT_ID",
156
+ "BIKKY_AGENT_NAME",
116
157
  "BIKKY_ACTOR_ID",
117
158
  "BIKKY_ACTOR_LABEL",
118
159
  ];
@@ -122,7 +163,7 @@ const CONFIG_ENV_PREFIXES = [
122
163
  ];
123
164
  const nonNegativeInt = z.number().int().nonnegative();
124
165
  const positiveInt = z.number().int().positive();
125
- const stringRecord = z.record(z.string());
166
+ const stringRecord = z.record(z.string(), z.string());
126
167
  const embeddingConfigFileSchema = z.object({
127
168
  provider: z.string().optional(),
128
169
  model: z.string().optional(),
@@ -157,6 +198,10 @@ const daemonConfigFileSchema = z.object({
157
198
  entity_typing_enabled: z.boolean().optional(),
158
199
  entity_typing_interval_sec: nonNegativeInt.optional(),
159
200
  entity_typing_max_entities_per_run: nonNegativeInt.optional(),
201
+ memory_quality_rollups_enabled: z.boolean().optional(),
202
+ memory_quality_rollups_interval_sec: nonNegativeInt.optional(),
203
+ memory_quality_rollups_low_confidence_threshold: z.number().min(0).max(1).optional(),
204
+ memory_quality_rollups_max_scopes_per_run: positiveInt.optional(),
160
205
  staleness_threshold_days: nonNegativeInt.optional(),
161
206
  }).passthrough();
162
207
  const watcherConfigFileSchema = z.object({
@@ -175,6 +220,8 @@ const qdrantClientConfigFileSchema = z.object({
175
220
  retry_base_delay_ms: nonNegativeInt.optional(),
176
221
  }).passthrough();
177
222
  const identityConfigFileSchema = z.object({
223
+ user_id: z.string().nullable().optional(),
224
+ user_name: z.string().nullable().optional(),
178
225
  actor_id: z.string().nullable().optional(),
179
226
  actor_label: z.string().nullable().optional(),
180
227
  }).passthrough();
@@ -183,7 +230,7 @@ const destinationMatchSchema = z.object({
183
230
  cwd: regexArrayField,
184
231
  entity: regexArrayField,
185
232
  content: regexArrayField,
186
- metadata: z.record(z.array(z.string())).optional(),
233
+ metadata: z.record(z.string(), z.array(z.string())).optional(),
187
234
  }).passthrough();
188
235
  const destinationFileSchema = z.object({
189
236
  name: z.string().min(1),
@@ -442,7 +489,7 @@ export function validateConfigObject(raw) {
442
489
  validateUrlLike(llm.base_url, "llm.base_url", issues);
443
490
  return issues;
444
491
  }
445
- export function inspectConfigFile(configPath = CONFIG_PATH) {
492
+ export function inspectConfigFile(configPath = getConfigPath()) {
446
493
  if (!fs.existsSync(configPath)) {
447
494
  return { path: configPath, exists: false, parse_error: null, issues: [] };
448
495
  }
@@ -490,21 +537,41 @@ export function loadConfig() {
490
537
  if (_config)
491
538
  return _config;
492
539
  // Ensure dirs exist
493
- fs.mkdirSync(BIKKY_DIR, { recursive: true });
494
- fs.mkdirSync(LOG_DIR, { recursive: true });
495
- fs.mkdirSync(STATE_DIR, { recursive: true });
540
+ fs.mkdirSync(getBikkyDir(), { recursive: true });
541
+ fs.mkdirSync(getLogDir(), { recursive: true });
542
+ fs.mkdirSync(getStateDir(), { recursive: true });
496
543
  // Start from defaults
497
544
  let config = structuredClone(DEFAULTS);
498
545
  // Merge config file
499
- if (fs.existsSync(CONFIG_PATH)) {
546
+ const configPath = getConfigPath();
547
+ let fileConfig = {};
548
+ if (fs.existsSync(configPath)) {
500
549
  try {
501
- const fileConfig = JSON.parse(fs.readFileSync(CONFIG_PATH, "utf-8"));
550
+ fileConfig = JSON.parse(fs.readFileSync(configPath, "utf-8"));
502
551
  config = deepMerge(config, fileConfig);
503
552
  }
504
553
  catch (e) {
505
- console.error(`bikky: failed to parse ${CONFIG_PATH}: ${e instanceof Error ? e.message : String(e)}`);
554
+ console.error(`bikky: failed to parse ${configPath}: ${e instanceof Error ? e.message : String(e)}`);
506
555
  }
507
556
  }
557
+ // Provider/base_url consistency (issue #131): the DEFAULTS.embedding.base_url
558
+ // is the ollama localhost URL, baked in for the default ollama provider. If
559
+ // the user picks a different provider (portkey/openai/bedrock) but doesn't
560
+ // set an explicit base_url, drop the inherited ollama URL so initEmbedding()
561
+ // can apply the provider's own default — otherwise we'd POST every embedding
562
+ // request to localhost:11434 and Ollama would reject the foreign model name.
563
+ const fileEmbedding = (fileConfig.embedding ?? {});
564
+ if (config.embedding.provider !== DEFAULTS.embedding.provider
565
+ && typeof fileEmbedding.base_url !== "string"
566
+ && !process.env.EMBEDDING_BASE_URL) {
567
+ config.embedding.base_url = "";
568
+ }
569
+ const fileLlm = (fileConfig.llm ?? {});
570
+ if (config.llm.provider !== DEFAULTS.llm.provider
571
+ && typeof fileLlm.base_url !== "string"
572
+ && !process.env.LLM_BASE_URL) {
573
+ config.llm.base_url = "";
574
+ }
508
575
  // Env var overrides (highest priority)
509
576
  if (process.env.QDRANT_URL)
510
577
  config.qdrant_url = process.env.QDRANT_URL;
@@ -526,6 +593,12 @@ export function loadConfig() {
526
593
  }
527
594
  if (process.env.OPENAI_API_KEY)
528
595
  config.embedding.api_key = process.env.OPENAI_API_KEY;
596
+ // Portkey users can supply their gateway key via PORTKEY_API_KEY without
597
+ // needing to repurpose OPENAI_API_KEY. Only applied when the embedding
598
+ // provider is Portkey, so non-Portkey setups remain untouched.
599
+ if (process.env.PORTKEY_API_KEY && config.embedding.provider === "portkey") {
600
+ config.embedding.api_key = process.env.PORTKEY_API_KEY;
601
+ }
529
602
  // Generic provider-extras: BIKKY_EMBEDDING_EXTRA_<KEY>=value
530
603
  config.embedding.extra = config.embedding.extra ?? {};
531
604
  for (const [k, v] of Object.entries(process.env)) {
@@ -542,6 +615,9 @@ export function loadConfig() {
542
615
  config.llm.base_url = process.env.LLM_BASE_URL;
543
616
  if (process.env.OPENAI_API_KEY && !config.llm.api_key)
544
617
  config.llm.api_key = process.env.OPENAI_API_KEY;
618
+ if (process.env.PORTKEY_API_KEY && config.llm.provider === "portkey" && !config.llm.api_key) {
619
+ config.llm.api_key = process.env.PORTKEY_API_KEY;
620
+ }
545
621
  if (process.env.LLM_FALLBACK_PROVIDER)
546
622
  config.llm.fallback_provider = process.env.LLM_FALLBACK_PROVIDER;
547
623
  if (process.env.AWS_PROFILE)
@@ -632,6 +708,26 @@ export function loadConfig() {
632
708
  const entityTypingMax = positiveInt(process.env.BIKKY_DAEMON_ENTITY_TYPING_MAX_ENTITIES_PER_RUN);
633
709
  if (entityTypingMax !== null)
634
710
  config.daemon.entity_typing_max_entities_per_run = entityTypingMax;
711
+ const qualityRollupsEnabled = booleanEnv(process.env.BIKKY_DAEMON_MEMORY_QUALITY_ROLLUPS_ENABLED);
712
+ if (qualityRollupsEnabled !== null)
713
+ config.daemon.memory_quality_rollups_enabled = qualityRollupsEnabled;
714
+ const qualityRollupsInterval = positiveInt(process.env.BIKKY_DAEMON_MEMORY_QUALITY_ROLLUPS_INTERVAL_SEC);
715
+ if (qualityRollupsInterval !== null)
716
+ config.daemon.memory_quality_rollups_interval_sec = qualityRollupsInterval;
717
+ const qualityRollupsThresholdRaw = process.env.BIKKY_DAEMON_MEMORY_QUALITY_ROLLUPS_LOW_CONFIDENCE_THRESHOLD;
718
+ if (qualityRollupsThresholdRaw) {
719
+ const threshold = Number.parseFloat(qualityRollupsThresholdRaw);
720
+ if (Number.isFinite(threshold) && threshold >= 0 && threshold <= 1) {
721
+ config.daemon.memory_quality_rollups_low_confidence_threshold = threshold;
722
+ }
723
+ }
724
+ const qualityRollupsMaxScopes = positiveInt(process.env.BIKKY_DAEMON_MEMORY_QUALITY_ROLLUPS_MAX_SCOPES_PER_RUN);
725
+ if (qualityRollupsMaxScopes !== null)
726
+ config.daemon.memory_quality_rollups_max_scopes_per_run = qualityRollupsMaxScopes;
727
+ if (process.env.BIKKY_USER_ID)
728
+ config.identity.user_id = process.env.BIKKY_USER_ID;
729
+ if (process.env.BIKKY_USER_NAME)
730
+ config.identity.user_name = process.env.BIKKY_USER_NAME;
635
731
  if (process.env.BIKKY_ACTOR_ID)
636
732
  config.identity.actor_id = process.env.BIKKY_ACTOR_ID;
637
733
  if (process.env.BIKKY_ACTOR_LABEL)
@@ -679,8 +775,8 @@ export function getEffectiveDestinations(config = loadConfig()) {
679
775
  }
680
776
  /** Save config to disk (used by setup command). */
681
777
  export function saveConfig(config) {
682
- fs.mkdirSync(BIKKY_DIR, { recursive: true });
683
- fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + "\n");
778
+ fs.mkdirSync(getBikkyDir(), { recursive: true });
779
+ fs.writeFileSync(getConfigPath(), JSON.stringify(config, null, 2) + "\n");
684
780
  _config = config;
685
781
  }
686
782
  /** Reset cached config (for testing). */
@@ -86,7 +86,6 @@ export declare const CAPTURE_KIND_SUBTYPES: {
86
86
  export declare const FACT_CATEGORY_TO_SUBTYPE: Record<Category, MemorySubtype>;
87
87
  export declare const DEFAULT_CAPTURE_CONTEXT: {
88
88
  readonly domain: "software_engineering" | "product_strategy" | "business_operations" | "research" | "personal_productivity";
89
- readonly source: "system";
90
89
  readonly reviewStatus: "candidate";
91
90
  readonly volatility: "evolving";
92
91
  };
@@ -105,12 +105,10 @@ export const CAPTURE_KIND_SUBTYPES = {
105
105
  export const FACT_CATEGORY_TO_SUBTYPE = {
106
106
  engineering: "codebase_map",
107
107
  product: "domain_rule",
108
- human: "preference",
109
108
  system: "codebase_map",
110
109
  };
111
110
  export const DEFAULT_CAPTURE_CONTEXT = {
112
111
  domain: DEFAULT_DOMAIN,
113
- source: "system",
114
112
  reviewStatus: "candidate",
115
113
  // Default fallback volatility when the LLM does not self-judge. Storage path
116
114
  // overrides this with the LLM's value (or the volatility verifier's
@@ -51,6 +51,7 @@ declare const detectContradiction: (fact: {
51
51
  }, _config: BikkyConfig, telemetry?: {
52
52
  sessionId?: string;
53
53
  workstreamKey?: string;
54
+ destination?: string;
54
55
  }) => Promise<ContradictionResult>;
55
56
  /**
56
57
  * Main consolidation tick — called from daemon tick loop.
@@ -58,6 +59,6 @@ declare const detectContradiction: (fact: {
58
59
  */
59
60
  declare const tick: (config: BikkyConfig, opts?: ConsolidationTickOptions) => Promise<void>;
60
61
  /** Reset state (for testing). */
61
- declare const _reset: () => void;
62
+ declare const _reset: (tickCount?: number) => void;
62
63
  export { detectContradiction, tick, setLogger, _reset, };
63
64
  //# sourceMappingURL=consolidation.d.ts.map