@mercury-fw/cli 0.71.0 → 0.73.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
@@ -1,5 +1,79 @@
1
1
  # @mercury-fw/cli
2
2
 
3
+ ## 0.73.0
4
+
5
+ ### Minor Changes
6
+
7
+ - a8e2fe9: - the work nobody waits on (titles, memory captures, corrections, the archive's index, the nightly review) waits for the model: it runs only in the requests the turns leave free of the model's `capacity`, one model call at a time, titles, corrections and the archive's index first
8
+ - `ollamaModel(name, { backgroundSlots })`: how many of the model's requests that work may take at once; 1 when left out, below `capacity` when it's above 1
9
+ - a turn's corrections, its threshold capture and its message's indexing no longer hold the conversation's next turn
10
+ - a model of a plugin's own (`ctx.modelFor`) goes through the same queue
11
+ - `background.job` in the traces: each piece of that work that called a model, its models, model calls, time waited, time taken, and whether it was done, failed or dropped at stop
12
+ - `mfw create`: `backgroundSlots` read from `OLLAMA_BACKGROUND_SLOTS`, in the env example
13
+ - **Migration:** to set it from the environment, add `backgroundSlots: envNumber("OLLAMA_BACKGROUND_SLOTS"),` to the turn model's `ollamaModel(...)` in `mercury.config.ts`
14
+ - fb1f30a: - conversation-store contract: `ConversationStorePlugin`, `ConversationStore`, `StoredMessage`, `StoredTitle`, `CONVERSATION_STORE_API_VERSION`
15
+ - `conversationStore` in `mercury.config.ts`: where every conversation's messages and title are kept
16
+ - **Breaking:** without `conversationStore` nothing is archived: no reopened or listed conversations, no titles, no `recall_verbatim`
17
+ - `qdrantConversations()` in `@mercury-fw/store-qdrant`: messages in `conversation_messages` (`QDRANT_MESSAGES_COLLECTION`), titles in `conversation_titles` (`QDRANT_TITLES_COLLECTION`); on start, copies the messages of `verbatim_archive` into `conversation_messages` once, marking the copy done only when all are in
18
+ - `@mercury-fw/core` no longer depends on `@qdrant/js-client-rest` and no longer reads `QDRANT_URL`
19
+ - **Breaking:** `GET /health` (HTTP): `conversationStoreReachable` and `vectorIndexReachable` (`null` when not declared) instead of `qdrantReachable`
20
+ - **Breaking:** channel contract 7: `ChannelHostReads.conversation`, `conversations` and `memoryScroll` optional, given only with a conversation store / a vector index; channels written for contract 6 are refused
21
+ - `channel-http`, `channel-google-chat`: channel contract 7, framework 0.73 or later
22
+ - `channel-http`: `/conversation`, `/conversations` (`GET`, `PATCH`) only with a conversation store, `/memory/scroll` only with a vector index; `404` otherwise
23
+ - `system.start` trace event: `conversationStore` (`declared`, `active`)
24
+ - `mfw create`: new apps declare `conversationStore: qdrantConversations()`
25
+ - **Migration:** in `mercury.config.ts`, import `qdrantConversations` from `@mercury-fw/store-qdrant` and add `conversationStore: qdrantConversations(),`; upgrade `@mercury-fw/channel-http` and `@mercury-fw/channel-google-chat` with the framework; a UI reading `qdrantReachable` from `/health` reads the two new fields
26
+ - 4a00b0c: - **Breaking:** new required `MERCURY_DATA_PATH`, the folder for Mercury's own state (account links, profiles, plugins' `dataDir`, traces, `trace-secret`), no longer in the vault's `.mercury/`; the start stops without it
27
+ - first start with an empty data folder copies the vault's `.mercury/` into it, leaving the original in place; a failed copy is undone and stops the start
28
+ - `system.migrated` trace event when that copy happens
29
+ - `mfw identity` reads the links from `MERCURY_DATA_PATH`
30
+ - `mfw reset wiki` no longer deletes links, profiles and traces
31
+ - `mfw reset data` deletes the `mercury-data` volume
32
+ - `mfw create`: new apps get a `mercury-data` volume mounted at `/app/data` and `MERCURY_DATA_PATH=/app/data`
33
+ - **Migration:** in `Dockerfile`, add `/app/data` to the `mkdir -p` and the `chown mercury:mercury` of the volumes' mount points; in `docker-compose.yml`, add `- mercury-data:/app/data` to the `mercury` service's `volumes` and `mercury-data:` with `name: <app>_mercury-data` under the top-level `volumes`; in `.env`, add `MERCURY_DATA_PATH=/app/data`; then `mfw start`
34
+ - 40fde6b: - `ollamaModel(name, settings?)` and `ollamaEmbedding(name, { capacity }?)` declare a model; `capacity`, how many requests its server answers at once, 1 when left out
35
+ - `models` in `mercury.config.ts`: `turn` (required), `background` (titles, memory captures, corrections, nightly review; the turn's when left out), `embedding`
36
+ - a `vectorIndex` without `models.embedding` stops the start
37
+ - `ctx.modelFor(handle)` in a plugin's `build()`: the model of a handle the plugin was given in its configuration
38
+ - `system.start` carries `models` (each role's name and capacity) instead of `model` and `modelSettings`
39
+ - the startup log names each role's model
40
+ - `mfw create`: the config declares `main` from `OLLAMA_MODEL` and its settings, and `models` with `turn` and `embedding`; `OLLAMA_CAPACITY` in the env example
41
+ - **Breaking:** `model` is gone from `mercury.config.ts`, and the core no longer reads `OLLAMA_MODEL` or `OLLAMA_EMBEDDING_MODEL`: declare the models in `models`
42
+ - **Migration:** in `mercury.config.ts`, import `ollamaEmbedding` and `ollamaModel` from `@mercury-fw/core`, add above the config `const main = ollamaModel(process.env.OLLAMA_MODEL, { think: envBoolean("OLLAMA_THINK", true), temperature: envNumber("OLLAMA_TEMPERATURE"), topP: envNumber("OLLAMA_TOP_P"), topK: envNumber("OLLAMA_TOP_K"), capacity: envNumber("OLLAMA_CAPACITY") });`, and replace the `model: { ... }` section with `models: { turn: main, embedding: ollamaEmbedding(process.env.OLLAMA_EMBEDDING_MODEL ?? "nomic-embed-text") },`; `OLLAMA_CAPACITY` is optional
43
+ - b634bd1: - vector-index contract: `VectorIndexPlugin`, `VectorIndex`, `VectorPoint`, `VectorSpaceSpec`, `VectorWhere`, `VECTOR_INDEX_API_VERSION`
44
+ - `vectorIndex` in `mercury.config.ts`: where episodes, semantic facts, procedural corrections and `recall_verbatim`'s search are kept
45
+ - **Breaking:** without `vectorIndex` there is no long-term memory: no conversation summaries, no corrections, no `recall_verbatim` (tool and prompt), idle conversations closed without a summary
46
+ - new package `@mercury-fw/store-qdrant`: `qdrantIndex()`, Qdrant at `QDRANT_URL`, each space the collection of the same name or the one its `QDRANT_*_COLLECTION` names
47
+ - the memory's vector length comes from the embedding model at startup; `QDRANT_*_VECTOR_SIZE` no longer read
48
+ - `GET /memory/scroll` (HTTP) takes the index's space name (`episodic_memory`, `semantic_facts`, `verbatim_archive`), not the `QDRANT_*_COLLECTION` name
49
+ - `system.start` trace event: `vectorIndex` (`declared`, `active`)
50
+ - `mfw memory` runs the memory CLI of `@mercury-fw/store-qdrant`
51
+ - `mfw create`: new apps declare `vectorIndex: qdrantIndex()` and depend on `@mercury-fw/store-qdrant`
52
+ - **Migration:** add `@mercury-fw/store-qdrant` to the app's dependencies, at the framework's version; in `mercury.config.ts`, `import { qdrantIndex } from "@mercury-fw/store-qdrant";` and `vectorIndex: qdrantIndex(),`; `QDRANT_*_VECTOR_SIZE` can go from `.env`
53
+
54
+ ### Patch Changes
55
+
56
+ - Updated dependencies [a8e2fe9]
57
+ - Updated dependencies [fb1f30a]
58
+ - Updated dependencies [4a00b0c]
59
+ - Updated dependencies [40fde6b]
60
+ - Updated dependencies [b634bd1]
61
+ - @mercury-fw/core@0.73.0
62
+ - @mercury-fw/utils@0.73.0
63
+
64
+ ## 0.72.0
65
+
66
+ ### Minor Changes
67
+
68
+ - 468cb12: - `mfw create`: new apps declare `traces: { sink: jsonlTraces(), level: "meta" }` and depend on `@mercury-fw/trace-jsonl`
69
+ - **Migration:** to record traces, `bun add @mercury-fw/trace-jsonl` at the framework's version, add `import { jsonlTraces } from "@mercury-fw/trace-jsonl";` and `traces: { sink: jsonlTraces(), level: "meta" },` to `mercury.config.ts`, then in `src/index.ts` pass `tracer: app.tracer` to `loadChannels` and `await app.tracer.flush();` before `process.exit(0)` in `shutdown`
70
+
71
+ ### Patch Changes
72
+
73
+ - Updated dependencies [468cb12]
74
+ - @mercury-fw/core@0.72.0
75
+ - @mercury-fw/utils@0.72.0
76
+
3
77
  ## 0.71.0
4
78
 
5
79
  ### Patch Changes
package/README.md CHANGED
@@ -19,7 +19,7 @@ On Mercury's documentation site: [the `mfw` reference](https://lucabro81.github.
19
19
  - [`mfw memory list`](#mfw-memory-list)
20
20
  - [`mfw memory read <collection> [--limit N]`](#mfw-memory-read-collection---limit-n)
21
21
  - [`mfw identity links`, `link`, `unlink`](#mfw-identity-links-link-unlink)
22
- - [`mfw reset <memory|wiki>`](#mfw-reset-memorywiki)
22
+ - [`mfw reset <memory|wiki|data>`](#mfw-reset-memorywikidata)
23
23
  - [`mfw credentials setup <plugin>`](#mfw-credentials-setup-plugin)
24
24
  - [`mfw credentials check <plugin>`](#mfw-credentials-check-plugin)
25
25
  - [`mfw credentials reset <plugin> [--user <key>]`](#mfw-credentials-reset-plugin---user-key)
@@ -53,7 +53,7 @@ Writes a new Mercury app into `<folder>`, which has to be missing or empty; the
53
53
  - which channels and which tool plugins to include (none is fine: the REPL always works);
54
54
  - the git remote for `origin`, which you can leave empty (most of the time it doesn't exist yet when you scaffold).
55
55
 
56
- It writes the `mercury.config.ts` for that selection, the persona, a `locales/` folder with an `en.json` for the texts people see (empty, or the starting formatter rules' own texts: `app.jira.no-issues` with Jira), the service and REPL entrypoints, a Dockerfile, a compose file with Qdrant, and an env example listing every variable the chosen pieces read. Its `package.json` declares `plugins/*` as workspaces, the place for the app's own plugins (each a package in `plugins/<name>`, which the image copies before installing), and a `test` script (`bun test`, the plugins' tests included); the workspaces make Bun install with its isolated linker, and the Dockerfile's PATH step follows the links it makes. A plugin that hands over lists comes wrapped in the formatter with a starting rule, yours to change.
56
+ It writes the `mercury.config.ts` for that selection, the persona, the traces (`@mercury-fw/trace-jsonl`, at level `meta`, in `traces/` in the data folder), a `locales/` folder with an `en.json` for the texts people see (empty, or the starting formatter rules' own texts: `app.jira.no-issues` with Jira), the service and REPL entrypoints, a Dockerfile, a compose file with Qdrant (the app's vector index and conversation store, `vectorIndex: qdrantIndex()` and `conversationStore: qdrantConversations()`) and a volume for the wiki and one for Mercury's own state, and an env example listing every variable the chosen pieces read. Its `package.json` declares `plugins/*` as workspaces, the place for the app's own plugins (each a package in `plugins/<name>`, which the image copies before installing), and a `test` script (`bun test`, the plugins' tests included); the workspaces make Bun install with its isolated linker, and the Dockerfile's PATH step follows the links it makes. A plugin that hands over lists comes wrapped in the formatter with a starting rule, yours to change.
57
57
 
58
58
  Then it runs `bun install` in the app and creates a git repository on `main` with a first commit (`bun.lock` included), adding `origin` when you gave one; nothing is pushed. Each step is best effort and never undoes the ones before it: an install that fails still leaves the app committed, without the lockfile, and the closing message says what to run. The repository is skipped when the folder is already inside one (an app created in a monorepo belongs to it). Whether git can commit is git's call: when it refuses (no identity, a hook) or isn't installed, the message reports why and lists the commands to finish by hand. `bun install` prints its own output as it runs.
59
59
 
@@ -92,7 +92,7 @@ mfw start --no-cache
92
92
 
93
93
  ### `mfw stop`
94
94
 
95
- Stops the app and Qdrant and removes their containers (`docker compose down`). The volumes stay, so memory, the wiki and the CLI credentials are all there on the next start.
95
+ Stops the app and Qdrant and removes their containers (`docker compose down`). The volumes stay, so memory, the wiki, Mercury's own state (the data folder) and the CLI credentials are all there on the next start.
96
96
 
97
97
  ```bash
98
98
  mfw stop
@@ -155,23 +155,24 @@ There's no command writing inferred notes on purpose: those are the agent's own,
155
155
 
156
156
  ### `mfw memory list`
157
157
 
158
- Lists the collections of the memory on Qdrant with how many points each holds, in a one-off container that reaches Qdrant the way the app does. Read-only.
158
+ Lists the collections of the memory on Qdrant with how many points each holds, in a one-off container that reaches Qdrant the way the app does. Read-only. It runs the memory CLI of `@mercury-fw/store-qdrant`, which an app made with `mfw create` has: one without it has no `mfw memory`.
159
159
 
160
160
  ```bash
161
161
  mfw memory list
162
162
  ```
163
163
 
164
164
  ```
165
+ conversation_messages 113 points
165
166
  conversation_titles 4 points
166
167
  episodic_memory 25 points
167
168
  semantic_facts 20 points
168
169
  tool_corrections 37 points
169
- verbatim_archive 0 points
170
+ verbatim_archive 112 points
170
171
  ```
171
172
 
172
173
  ### `mfw memory read <collection> [--limit N]`
173
174
 
174
- Prints a collection's points, each as its id followed by one line per payload field. Newest first where the collection has a timestamp index (episodic memory and the verbatim archive do); in Qdrant's own order otherwise, and it says so. `--limit` defaults to 20. Read-only.
175
+ Prints a collection's points, each as its id followed by one line per payload field. Newest first where the collection has a timestamp index (the episodic memory, the conversation messages and the verbatim archive do; `conversation_messages` also holds one point with no person, marking the copy of an older archive done); in Qdrant's own order otherwise, and it says so. `--limit` defaults to 20. Read-only.
175
176
 
176
177
  ```bash
177
178
  mfw memory read episodic_memory
@@ -188,18 +189,19 @@ mfw identity link google-chat:users/123 oidc:3123
188
189
  mfw identity unlink google-chat:users/123
189
190
  ```
190
191
 
191
- ### `mfw reset <memory|wiki>`
192
+ ### `mfw reset <memory|wiki|data>`
192
193
 
193
- Deletes for good what the assistant remembers: `memory` is every collection on Qdrant, `wiki` the whole vault. It reads the volume's real name from the compose file, tells you which one is about to go, and asks you to type the app's name (the `name` in `package.json`); anything else, an empty answer, a `y` or closing the input included, deletes nothing and exits 1. Once confirmed it stops the service using the volume, removes its container and the volume, and starts the service again on an empty one (`docker compose stop`, `rm -f`, `docker volume rm`, `up -d`). After `memory`, a running app is restarted too (`docker compose restart mercury`), since it sets up its collections only when it starts.
194
+ Deletes for good what the assistant remembers: `memory` is every collection on Qdrant, `wiki` the whole vault, `data` Mercury's own state (the linked accounts, the profiles, the plugins' data, the traces), each on a volume of its own. An app whose compose file has no `mercury-data` volume yet has nothing for `data` to delete: the command says so before asking anything. It reads the volume's real name from the compose file, tells you which one is about to go, and asks you to type the app's name (the `name` in `package.json`); anything else, an empty answer, a `y` or closing the input included, deletes nothing and exits 1. Once confirmed it stops the service using the volume, removes its container and the volume, and starts the service again on an empty one (`docker compose stop`, `rm -f`, `docker volume rm`, `up -d`). After `memory`, a running app is restarted too (`docker compose restart mercury`), since it sets up its collections only when it starts.
194
195
 
195
196
  If a step fails once the service is stopped (the volume still in use by a one-off container, say), it stops there and says the service is down: `mfw start` brings it back.
196
197
 
197
198
  ```bash
198
199
  mfw reset memory
199
200
  mfw reset wiki
201
+ mfw reset data
200
202
  ```
201
203
 
202
- Useful for clearing out test data; the other layer isn't touched.
204
+ Useful for clearing out test data; the other volumes aren't touched.
203
205
 
204
206
  ### `mfw credentials setup <plugin>`
205
207
 
@@ -15,12 +15,13 @@ export type AppDeps = {
15
15
  /** The user's home folder, where a CLI keeps its config (`~/.config/<cli>`). */
16
16
  home: string;
17
17
  };
18
- /** The core's maintenance CLIs, from the container's working directory (the
19
- * app's folder, where node_modules/@mercury-fw/core is). A path and not a bin:
20
- * a monorepo image installs before copying the sources, and Bun doesn't link a
21
- * bin whose file isn't there yet. The core moves in lockstep with this CLI. */
18
+ /** The framework's maintenance CLIs, from the container's working directory
19
+ * (the app's folder, where node_modules/@mercury-fw/* is): the core's, and the
20
+ * memory's in its Qdrant package. A path and not a bin: a monorepo image
21
+ * installs before copying the sources, and Bun doesn't link a bin whose file
22
+ * isn't there yet. Both packages move in lockstep with this CLI. */
22
23
  export declare const VAULT_CLI = "node_modules/@mercury-fw/core/src/wiki/vault-cli.ts";
23
- export declare const MEMORY_CLI = "node_modules/@mercury-fw/core/src/memory/memory-cli.ts";
24
+ export declare const MEMORY_CLI = "node_modules/@mercury-fw/store-qdrant/memory-cli.ts";
24
25
  export declare const IDENTITY_CLI = "node_modules/@mercury-fw/core/src/identity/links-cli.ts";
25
26
  /** What `mfw reset` can wipe: the compose service using the volume, the
26
27
  * volume's key in the compose file, and how to say what's lost. */
@@ -35,6 +36,11 @@ export declare const RESET_TARGETS: {
35
36
  readonly volume: "wiki-vault";
36
37
  readonly what: "the wiki vault (every note)";
37
38
  };
39
+ readonly data: {
40
+ readonly service: "mercury";
41
+ readonly volume: "mercury-data";
42
+ readonly what: "Mercury's own state (account links, profiles, plugins' data, traces)";
43
+ };
38
44
  };
39
45
  export type ResetTarget = keyof typeof RESET_TARGETS;
40
46
  /** The commands bound to `app`, each returning its exit code. */
@@ -13,6 +13,10 @@ export type RenderInput = {
13
13
  directory?: string;
14
14
  versions: Record<string, string>;
15
15
  };
16
+ /** The trace sink every new app declares, writing the traces to files in Mercury's data folder. */
17
+ export declare const TRACE_SINK_PACKAGE = "@mercury-fw/trace-jsonl";
18
+ /** The vector index and the conversation store every new app declares: Qdrant, the compose service. */
19
+ export declare const VECTOR_INDEX_PACKAGE = "@mercury-fw/store-qdrant";
16
20
  /** Builds every file of the new app. Throws on an invalid app name, an empty
17
21
  * assistant name or role, an id the catalog doesn't have, or a package with no
18
22
  * version in `input.versions`. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mercury-fw/cli",
3
- "version": "0.71.0",
3
+ "version": "0.73.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -41,23 +41,24 @@
41
41
  "comment:shape": "The Mercury CLI (`mfw`). `mfw create <dir>` writes a new Mercury app from the template. The framework packages move in lockstep with it, so a new app gets them at the CLI's own version; plugins and channels at the registry's latest. The plugin and channel packages are devDependencies only for the catalog test. `main(argv)` is exported for create-mercury-agent.",
42
42
  "dependencies": {
43
43
  "@clack/prompts": "^1.8.1",
44
- "@mercury-fw/core": "0.71.0",
45
- "@mercury-fw/utils": "0.71.0",
44
+ "@mercury-fw/core": "0.73.0",
45
+ "@mercury-fw/utils": "0.73.0",
46
46
  "commander": "^15.0.0"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@mercury-fw/auth-oidc": "0.2.1",
50
50
  "@mercury-fw/auth-static": "0.2.2",
51
- "@mercury-fw/channel-google-chat": "0.12.0",
52
- "@mercury-fw/channel-http": "0.16.0",
51
+ "@mercury-fw/channel-google-chat": "0.13.0",
52
+ "@mercury-fw/channel-http": "0.17.0",
53
53
  "@mercury-fw/directory-static": "0.1.2",
54
- "@mercury-fw/formatter": "0.71.0",
54
+ "@mercury-fw/formatter": "0.73.0",
55
55
  "@mercury-fw/plugin-atlassian-admin": "0.5.1",
56
56
  "@mercury-fw/plugin-bitbucket": "0.6.1",
57
57
  "@mercury-fw/plugin-confluence": "0.1.0",
58
58
  "@mercury-fw/plugin-jira": "0.8.1",
59
- "@mercury-fw/plugin-web-search": "0.4.1",
59
+ "@mercury-fw/plugin-web-search": "0.4.2",
60
60
  "@mercury-fw/plugin-zitadel": "0.5.1",
61
+ "@mercury-fw/store-qdrant": "0.73.0",
61
62
  "@mercury-fw/typescript-config": "*",
62
63
  "@mercury-fw/web-search-searxng": "0.1.1",
63
64
  "@types/bun": "^1.4.2",
@@ -38,12 +38,13 @@ const GOOGLE_CHAT_PACKAGE = "@mercury-fw/channel-google-chat";
38
38
  /** The app's service, the one the image builds. */
39
39
  const SERVICE = "mercury";
40
40
 
41
- /** The core's maintenance CLIs, from the container's working directory (the
42
- * app's folder, where node_modules/@mercury-fw/core is). A path and not a bin:
43
- * a monorepo image installs before copying the sources, and Bun doesn't link a
44
- * bin whose file isn't there yet. The core moves in lockstep with this CLI. */
41
+ /** The framework's maintenance CLIs, from the container's working directory
42
+ * (the app's folder, where node_modules/@mercury-fw/* is): the core's, and the
43
+ * memory's in its Qdrant package. A path and not a bin: a monorepo image
44
+ * installs before copying the sources, and Bun doesn't link a bin whose file
45
+ * isn't there yet. Both packages move in lockstep with this CLI. */
45
46
  export const VAULT_CLI = "node_modules/@mercury-fw/core/src/wiki/vault-cli.ts";
46
- export const MEMORY_CLI = "node_modules/@mercury-fw/core/src/memory/memory-cli.ts";
47
+ export const MEMORY_CLI = "node_modules/@mercury-fw/store-qdrant/memory-cli.ts";
47
48
  export const IDENTITY_CLI = "node_modules/@mercury-fw/core/src/identity/links-cli.ts";
48
49
 
49
50
  /** What `mfw reset` can wipe: the compose service using the volume, the
@@ -51,6 +52,7 @@ export const IDENTITY_CLI = "node_modules/@mercury-fw/core/src/identity/links-cl
51
52
  export const RESET_TARGETS = {
52
53
  memory: { service: "qdrant", volume: "qdrant-data", what: "Layer-3 memory (every Qdrant collection)" },
53
54
  wiki: { service: SERVICE, volume: "wiki-vault", what: "the wiki vault (every note)" },
55
+ data: { service: SERVICE, volume: "mercury-data", what: "Mercury's own state (account links, profiles, plugins' data, traces)" },
54
56
  } as const;
55
57
  export type ResetTarget = keyof typeof RESET_TARGETS;
56
58
 
package/src/program.ts CHANGED
@@ -119,7 +119,7 @@ Examples:
119
119
  program
120
120
  .command("stop")
121
121
  .summary("stops the app")
122
- .description("Stops the app and Qdrant and removes their containers. The volumes (memory, wiki, CLI credentials) stay.")
122
+ .description("Stops the app and Qdrant and removes their containers. The volumes (memory, wiki, Mercury's own state, CLI credentials) stay.")
123
123
  .addHelpText("after", INSIDE_AN_APP)
124
124
  .action(inApp((app) => app.stop()));
125
125
 
@@ -249,9 +249,9 @@ Examples:
249
249
 
250
250
  program
251
251
  .command("reset")
252
- .summary("deletes memory or the wiki, after confirmation")
252
+ .summary("deletes memory, the wiki or Mercury's state, after confirmation")
253
253
  .description(
254
- "Deletes for good what the assistant remembers: memory is every Qdrant collection, wiki the whole vault. It asks you to type the app's name first (anything else deletes nothing), then brings the service back up empty.",
254
+ "Deletes for good what the assistant remembers: memory is every Qdrant collection, wiki the whole vault, data Mercury's own state (account links, profiles, plugins' data, traces). It asks you to type the app's name first (anything else deletes nothing), then brings the service back up empty.",
255
255
  )
256
256
  .addArgument(new Argument("<target>", "what to delete").choices(Object.keys(RESET_TARGETS)))
257
257
  .addHelpText("after", INSIDE_AN_APP)
package/src/render.ts CHANGED
@@ -49,9 +49,19 @@ const CORE_ENV: EnvVar[] = [
49
49
  { name: "OLLAMA_TEMPERATURE", comment: "The chat model's temperature, from 0 up (empty: the model's own)" },
50
50
  { name: "OLLAMA_TOP_P", comment: "The chat model's top_p, above 0 and at most 1 (empty: the model's own)" },
51
51
  { name: "OLLAMA_TOP_K", comment: "The chat model's top_k, a whole number from 1 up (empty: the model's own)" },
52
+ { name: "OLLAMA_CAPACITY", comment: "How many requests the chat model's runtime serves at once (empty: 1)" },
53
+ {
54
+ name: "OLLAMA_BACKGROUND_SLOTS",
55
+ comment: "How many of those the work nobody waits on may take, below the capacity when it's above 1 (empty: 1)",
56
+ },
52
57
  { name: "OLLAMA_EMBEDDING_MODEL", comment: "Embedding model for the episodic memory", value: "nomic-embed-text" },
53
58
  { name: "QDRANT_URL", comment: "Qdrant, the compose service", value: "http://qdrant:6333" },
54
59
  { name: "WIKI_VAULT_PATH", comment: "Wiki vault, the named volume's mount point", value: "/app/wiki-vault" },
60
+ {
61
+ name: "MERCURY_DATA_PATH",
62
+ comment: "Mercury's own state (account links, profiles, plugins' data, traces), the named volume's mount point",
63
+ value: "/app/data",
64
+ },
55
65
  {
56
66
  name: "TURN_STALL_TIMEOUT_MS",
57
67
  comment: "How long a streaming turn waits for the model's next token before it's stopped with an error (0 turns it off)",
@@ -59,15 +69,32 @@ const CORE_ENV: EnvVar[] = [
59
69
  },
60
70
  ];
61
71
 
62
- /** The config's `model` section: the main model's settings, each from its env variable (#355). */
63
- const MODEL_SECTION = [
64
- " // How the main model generates, each value from .env: left empty, the",
65
- " // model's own applies (thinking stays on).",
66
- " model: {",
67
- ' think: envBoolean("OLLAMA_THINK", true),',
68
- ' temperature: envNumber("OLLAMA_TEMPERATURE"),',
69
- ' topP: envNumber("OLLAMA_TOP_P"),',
70
- ' topK: envNumber("OLLAMA_TOP_K"),',
72
+ /** Above the config: the turn model, its name, settings and capacity each from its env variable (#355, #383). */
73
+ const MAIN_MODEL = [
74
+ "/**",
75
+ " * The model the assistant runs on, from .env: a setting left empty is the",
76
+ " * model's own (thinking stays on); capacity, how many requests its runtime",
77
+ " * serves at once, is 1, and so is backgroundSlots, how many of them the work",
78
+ " * nobody waits on may take.",
79
+ " */",
80
+ "const main = ollamaModel(process.env.OLLAMA_MODEL, {",
81
+ ' think: envBoolean("OLLAMA_THINK", true),',
82
+ ' temperature: envNumber("OLLAMA_TEMPERATURE"),',
83
+ ' topP: envNumber("OLLAMA_TOP_P"),',
84
+ ' topK: envNumber("OLLAMA_TOP_K"),',
85
+ ' capacity: envNumber("OLLAMA_CAPACITY"),',
86
+ ' backgroundSlots: envNumber("OLLAMA_BACKGROUND_SLOTS"),',
87
+ "});",
88
+ ].join("\n");
89
+
90
+ /** The config's `models` section: the model of each of the core's roles (#383). */
91
+ const MODELS_SECTION = [
92
+ " // The models it runs on, by role: the turns on `main`, and the work no one",
93
+ " // waits on too unless a `background` model is added here; the memory",
94
+ " // found by meaning is embedded with `embedding`.",
95
+ " models: {",
96
+ " turn: main,",
97
+ ' embedding: ollamaEmbedding(process.env.OLLAMA_EMBEDDING_MODEL ?? "nomic-embed-text"),',
71
98
  " },",
72
99
  ].join("\n");
73
100
 
@@ -83,12 +110,39 @@ const CONFIG_HEADER = `/**
83
110
  * provider when a channel needs one, how the lists its plugins hand over read,
84
111
  * the assistant's persona and the languages it speaks (\`i18n\`: a file per
85
112
  * language in \`locales/\`, \`en.json\` overriding the English texts) and its version
86
- * (\`version\`, its \`package.json\`'s, shown on the HTTP channel's \`/version\`). The entrypoints
113
+ * (\`version\`, its \`package.json\`'s, shown on the HTTP channel's \`/version\`) and where
114
+ * the traces of what it does go (\`traces\`). The entrypoints
87
115
  * (\`src/index.ts\`, \`src/repl.ts\`) hand this config to \`composeMercury\`.
88
116
  *
89
117
  * Every tool plugin and channel declared here is active.
90
118
  */`;
91
119
 
120
+ /** The trace sink every new app declares, writing the traces to files in Mercury's data folder. */
121
+ export const TRACE_SINK_PACKAGE = "@mercury-fw/trace-jsonl";
122
+
123
+ /** The vector index and the conversation store every new app declares: Qdrant, the compose service. */
124
+ export const VECTOR_INDEX_PACKAGE = "@mercury-fw/store-qdrant";
125
+
126
+ /** The config's `vectorIndex` and `conversationStore` entries, with what each holds. */
127
+ const VECTOR_INDEX_SECTION = [
128
+ " // Where the memory is found by meaning (what past conversations were about,",
129
+ " // what the agent learned about each person, the corrections to how a tool",
130
+ " // is used, and searching past messages): Qdrant, the compose service.",
131
+ " // Remove to keep no memory.",
132
+ " vectorIndex: qdrantIndex(),",
133
+ " // Where what was said is kept: every conversation, to reopen and list it,",
134
+ " // and its title. Qdrant, the compose service. Remove to archive nothing.",
135
+ " conversationStore: qdrantConversations(),",
136
+ ].join("\n");
137
+
138
+ /** The config's `traces` entry, with what it records and where. */
139
+ const TRACES_SECTION = [
140
+ " // What the agent does, as events in traces/ in Mercury's data folder:",
141
+ " // a file per day, the last 7 days with activity kept. \"meta\" records names,",
142
+ " // outcomes and times; \"args\" also what the tools were given. Remove to record nothing.",
143
+ ' traces: { sink: jsonlTraces(), level: "meta" },',
144
+ ].join("\n");
145
+
92
146
  /** Builds every file of the new app. Throws on an invalid app name, an empty
93
147
  * assistant name or role, an id the catalog doesn't have, or a package with no
94
148
  * version in `input.versions`. */
@@ -239,7 +293,7 @@ function renderConfig(
239
293
  ): string {
240
294
  const withRules = tools.filter((t) => t.formatter);
241
295
  const imports = [
242
- 'import { defineMercuryConfig, envBoolean, envNumber } from "@mercury-fw/core";',
296
+ 'import { defineMercuryConfig, envBoolean, envNumber, ollamaEmbedding, ollamaModel } from "@mercury-fw/core";',
243
297
  'import en from "./locales/en.json";',
244
298
  'import pkg from "./package.json";',
245
299
  ];
@@ -254,6 +308,8 @@ function renderConfig(
254
308
  for (const c of [...channels, ...(auth ? [auth] : []), ...(directory ? [directory] : [])]) {
255
309
  imports.push(`import { ${c.exportName} } from "${c.package}";`);
256
310
  }
311
+ imports.push(`import { jsonlTraces } from "${TRACE_SINK_PACKAGE}";`);
312
+ imports.push(`import { qdrantConversations, qdrantIndex } from "${VECTOR_INDEX_PACKAGE}";`);
257
313
 
258
314
  const helpers = withRules.map((t) => `${t.formatter?.helpers}\n\n`).join("");
259
315
 
@@ -270,14 +326,18 @@ function renderConfig(
270
326
  "/** The assistant's name: what the persona calls it, and every text the model reads. */",
271
327
  `const name = ${JSON.stringify(assistantName)};`,
272
328
  "",
329
+ MAIN_MODEL,
330
+ "",
273
331
  `${helpers}export default defineMercuryConfig({`,
274
332
  ...renderPersona(role),
275
333
  plugins,
276
334
  channelList,
277
335
  ...(auth ? [` auth: ${auth.exportName},`] : []),
278
336
  ...(directory ? [` directory: ${directory.exportName},`] : []),
279
- MODEL_SECTION,
337
+ VECTOR_INDEX_SECTION,
338
+ MODELS_SECTION,
280
339
  " i18n: { locales: { en } },",
340
+ TRACES_SECTION,
281
341
  VERSION_COMMENT,
282
342
  " version: pkg.version,",
283
343
  "});",
@@ -318,7 +378,7 @@ function renderPackageJson(
318
378
  directory: CatalogEntry | undefined,
319
379
  ): string {
320
380
  const chosen = [...channels, ...(auth ? [auth] : []), ...tools, ...(directory ? [directory] : [])];
321
- const packages = [...new Set(["@mercury-fw/core", ...chosen.flatMap(entryPackages)])];
381
+ const packages = [...new Set(["@mercury-fw/core", TRACE_SINK_PACKAGE, VECTOR_INDEX_PACKAGE, ...chosen.flatMap(entryPackages)])];
322
382
  if (tools.some((t) => t.formatter)) {
323
383
  packages.push("@mercury-fw/formatter");
324
384
  }
@@ -396,6 +456,7 @@ function renderCompose(name: string, hasHttp: boolean, services: Array<{ name: s
396
456
  " required: false",
397
457
  " volumes:",
398
458
  " - wiki-vault:/app/wiki-vault",
459
+ " - mercury-data:/app/data",
399
460
  " # The login of a plugin's CLI, on a volume so what the CLI writes back",
400
461
  " # (refreshed tokens) survives a redeploy: see README.md.",
401
462
  " - cli-credentials:/home/mercury/.config",
@@ -415,6 +476,8 @@ function renderCompose(name: string, hasHttp: boolean, services: Array<{ name: s
415
476
  "volumes:",
416
477
  " wiki-vault:",
417
478
  ` name: ${name}_wiki-vault`,
479
+ " mercury-data:",
480
+ ` name: ${name}_mercury-data`,
418
481
  " qdrant-data:",
419
482
  ` name: ${name}_qdrant-data`,
420
483
  " cli-credentials:",
package/src/versions.ts CHANGED
@@ -7,7 +7,13 @@
7
7
  import pkg from "../package.json";
8
8
 
9
9
  /** The framework packages a new app can depend on: always at the CLI's version. */
10
- export const FRAMEWORK_PACKAGES = ["@mercury-fw/cli", "@mercury-fw/core", "@mercury-fw/formatter"];
10
+ export const FRAMEWORK_PACKAGES = [
11
+ "@mercury-fw/cli",
12
+ "@mercury-fw/core",
13
+ "@mercury-fw/formatter",
14
+ "@mercury-fw/store-qdrant",
15
+ "@mercury-fw/trace-jsonl",
16
+ ];
11
17
 
12
18
  /** The registry asked when `MFW_REGISTRY` doesn't name another. */
13
19
  export const DEFAULT_REGISTRY = "https://registry.npmjs.org";
@@ -32,8 +32,8 @@ COPY --chown=mercury:mercury src ./src
32
32
 
33
33
  # Mount points of the named volumes, owned by the runtime user before a fresh
34
34
  # volume attaches (a new volume takes the ownership it finds here).
35
- RUN mkdir -p /app/wiki-vault /home/mercury/.config \
36
- && chown mercury:mercury /app/wiki-vault \
35
+ RUN mkdir -p /app/wiki-vault /app/data /home/mercury/.config \
36
+ && chown mercury:mercury /app/wiki-vault /app/data \
37
37
  && chown -R mercury:mercury /home/mercury
38
38
 
39
39
  USER mercury
@@ -12,7 +12,7 @@ import mercuryConfig from "../mercury.config.ts";
12
12
 
13
13
  const app = await composeMercury(mercuryConfig);
14
14
 
15
- const loadedChannels: LoadedChannel[] = loadChannels(app.channels, { runtime: app.channelRuntime });
15
+ const loadedChannels: LoadedChannel[] = loadChannels(app.channels, { runtime: app.channelRuntime, tracer: app.tracer });
16
16
  for (const { name, provider } of loadedChannels) {
17
17
  await provider.start(app.handleTurn);
18
18
  console.error(`[channel] ${name} started`);
@@ -34,6 +34,8 @@ async function shutdown(signal: string): Promise<void> {
34
34
  await provider.stop?.();
35
35
  console.error(`[shutdown] channel ${name} stopped`);
36
36
  }
37
+ // The last events written before the signal, stored before exiting.
38
+ await app.tracer.flush();
37
39
  process.exit(0);
38
40
  }
39
41
  process.on("SIGINT", () => void shutdown("SIGINT"));