@desplega.ai/agent-swarm 1.78.1 → 1.79.1
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/README.md +1 -0
- package/openapi.json +1335 -236
- package/package.json +4 -4
- package/plugin/skills/artifacts/SKILL.md +151 -0
- package/plugin/skills/artifacts/examples/static-report.sh +1 -1
- package/plugin/skills/kv-storage/SKILL.md +168 -0
- package/plugin/skills/pages/SKILL.md +423 -0
- package/src/artifact-sdk/browser-sdk.ts +396 -19
- package/src/be/db.ts +548 -0
- package/src/be/migrations/059_pages.sql +34 -0
- package/src/be/migrations/060_page_versions.sql +19 -0
- package/src/be/migrations/061_kv_store.sql +34 -0
- package/src/be/migrations/062_pages_view_count.sql +9 -0
- package/src/commands/artifact.ts +17 -11
- package/src/commands/provider-credentials.ts +1 -1
- package/src/http/index.ts +9 -1
- package/src/http/kv.ts +658 -0
- package/src/http/page-proxy.ts +213 -0
- package/src/http/pages-public.ts +507 -0
- package/src/http/pages.ts +608 -0
- package/src/http/status.ts +1 -1
- package/src/http/utils.ts +68 -5
- package/src/pages/version.ts +44 -0
- package/src/prompts/session-templates.ts +51 -0
- package/src/providers/pi-mono-adapter.ts +3 -3
- package/src/providers/pi-mono-extension.ts +1 -1
- package/src/server.ts +29 -1
- package/src/tasks/context-key.ts +28 -0
- package/src/telemetry.ts +65 -1
- package/src/tests/artifact-commands.test.ts +92 -0
- package/src/tests/artifact-sdk.test.ts +80 -74
- package/src/tests/context-key.test.ts +17 -0
- package/src/tests/create-page-tool.test.ts +197 -0
- package/src/tests/fixtures/sample-json-page.json +52 -0
- package/src/tests/kv-http.test.ts +331 -0
- package/src/tests/kv-namespace-resolution.test.ts +172 -0
- package/src/tests/kv-page-proxy.test.ts +212 -0
- package/src/tests/kv-storage.test.ts +227 -0
- package/src/tests/kv-tool.test.ts +217 -0
- package/src/tests/launch-password-rejection.test.ts +139 -0
- package/src/tests/page-proxy-authed.test.ts +146 -0
- package/src/tests/page-proxy.test.ts +270 -0
- package/src/tests/page-session.test.ts +169 -0
- package/src/tests/pages-actions-endpoint.test.ts +102 -0
- package/src/tests/pages-authed-mode.test.ts +211 -0
- package/src/tests/pages-http.test.ts +193 -0
- package/src/tests/pages-list-endpoint.test.ts +149 -0
- package/src/tests/pages-password-hash.test.ts +57 -0
- package/src/tests/pages-password-mode.test.ts +265 -0
- package/src/tests/pages-public-authed-401.test.ts +102 -0
- package/src/tests/pages-public-html.test.ts +151 -0
- package/src/tests/pages-public-json-redirect.test.ts +86 -0
- package/src/tests/pages-storage.test.ts +196 -0
- package/src/tests/pages-versioning.test.ts +231 -0
- package/src/tests/pages-view-count.test.ts +220 -0
- package/src/tests/prompt-template-session.test.ts +3 -2
- package/src/tests/skill-update-scope.test.ts +165 -0
- package/src/tests/swarm-diff.test.ts +303 -0
- package/src/tests/telemetry-init.test.ts +149 -0
- package/src/tests/workflow-wait-event.test.ts +4 -7
- package/src/tools/create-page.ts +263 -0
- package/src/tools/kv/index.ts +5 -0
- package/src/tools/kv/kv-delete.ts +89 -0
- package/src/tools/kv/kv-get.ts +64 -0
- package/src/tools/kv/kv-incr.ts +116 -0
- package/src/tools/kv/kv-list.ts +81 -0
- package/src/tools/kv/kv-set.ts +194 -0
- package/src/tools/kv/resolve-namespace.ts +58 -0
- package/src/tools/skills/skill-update.ts +26 -0
- package/src/tools/tool-config.ts +10 -0
- package/src/types.ts +107 -0
- package/src/utils/internal-ai/complete-structured.ts +2 -2
- package/src/utils/internal-ai/credentials.ts +3 -3
- package/src/utils/page-session.ts +254 -0
- package/plugin/skills/artifacts/skill.md +0 -70
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@desplega.ai/agent-swarm",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.79.1",
|
|
4
4
|
"description": "Multi-agent orchestration for Claude Code, Codex, Gemini CLI, and other AI coding assistants",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "desplega.sh <contact@desplega.sh>",
|
|
@@ -104,9 +104,9 @@
|
|
|
104
104
|
"@desplega.ai/localtunnel": "^2.2.0",
|
|
105
105
|
"@inkjs/ui": "^2.0.0",
|
|
106
106
|
"@linear/sdk": "^77.0.0",
|
|
107
|
-
"@
|
|
108
|
-
"@
|
|
109
|
-
"@
|
|
107
|
+
"@earendil-works/pi-agent-core": "^0.74.0",
|
|
108
|
+
"@earendil-works/pi-ai": "^0.74.0",
|
|
109
|
+
"@earendil-works/pi-coding-agent": "^0.74.0",
|
|
110
110
|
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
111
111
|
"@openai/codex-sdk": "^0.128.0",
|
|
112
112
|
"@opencode-ai/sdk": "^1.14.30",
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: artifacts
|
|
3
|
+
description: Serve interactive web content (HTML pages, dashboards, approval flows, static reports, custom Hono apps) to a public URL via localtunnel. Use when the user asks to "create an artifact for X", "host this for me", "make me a tunneled URL", "spin up a web server for X", "publish this report so I can see it", "share this file/page publicly", "expose this dashboard", "give me a live link", or anything that needs a browser-reachable URL pointing at agent-generated content. Wraps the `agent-swarm artifact` CLI plus the `createArtifactServer` SDK; covers static directories, custom Hono apps, daemonization (nohup / PM2), HTTP Basic auth, and the in-page swarm Browser SDK.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Artifacts — Serving Interactive Web Content
|
|
7
|
+
|
|
8
|
+
Serve a directory or a Hono app to a public, auth-protected URL via localtunnel. Useful for sharing reports, dashboards, approval flows, or anything else a human needs to look at in a browser.
|
|
9
|
+
|
|
10
|
+
The CLI is a subcommand of `agent-swarm`. Always invoke as **`agent-swarm artifact <subcommand>`** — there is no top-level `artifact` binary.
|
|
11
|
+
|
|
12
|
+
## Quick Start
|
|
13
|
+
|
|
14
|
+
### Static content
|
|
15
|
+
```bash
|
|
16
|
+
# Create your content in a persisted directory
|
|
17
|
+
mkdir -p /workspace/personal/artifacts/my-report
|
|
18
|
+
echo '<h1>My Report</h1>' > /workspace/personal/artifacts/my-report/index.html
|
|
19
|
+
|
|
20
|
+
# Serve it (auto-assigns a free port, creates tunnel, registers in service registry)
|
|
21
|
+
agent-swarm artifact serve /workspace/personal/artifacts/my-report --name my-report
|
|
22
|
+
# -> Artifact "my-report" live at https://<agentId>-my-report.lt.desplega.ai (port <auto>)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Programmatic (custom Hono server)
|
|
26
|
+
```typescript
|
|
27
|
+
import { createArtifactServer } from '../artifact-sdk';
|
|
28
|
+
import { Hono } from 'hono';
|
|
29
|
+
|
|
30
|
+
const app = new Hono();
|
|
31
|
+
app.get('/', (c) => c.html('<h1>Dashboard</h1>'));
|
|
32
|
+
|
|
33
|
+
const server = createArtifactServer({ name: 'dashboard', app });
|
|
34
|
+
await server.start();
|
|
35
|
+
console.log(`Live at: ${server.url}`);
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
You can also `agent-swarm artifact serve ./server.ts --name dashboard` if `server.ts` exports a Hono instance as its default export.
|
|
39
|
+
|
|
40
|
+
## CLI Commands
|
|
41
|
+
|
|
42
|
+
| Command | Description |
|
|
43
|
+
|---|---|
|
|
44
|
+
| `agent-swarm artifact serve <path> --name <name> [--port <port>] [--no-auth] [--subdomain <sub>]` | Start serving content. `<path>` is a directory (static) or a `.ts`/`.js` file exporting a default Hono app. |
|
|
45
|
+
| `agent-swarm artifact list` | List active artifacts (name, agent, port, URL, status) from the service registry. |
|
|
46
|
+
| `agent-swarm artifact stop <name>` | Stop an artifact: deletes the matching PM2 process and unregisters it from the service registry. See "Known limitation" below for non-PM2 processes. |
|
|
47
|
+
|
|
48
|
+
Flags accepted by `serve`:
|
|
49
|
+
- `--name <name>` — defaults to the basename of `<path>`. Used for the subdomain and PM2 process name.
|
|
50
|
+
- `--port <port>` — pin to a specific port. Default: auto-assigned ephemeral port.
|
|
51
|
+
- `--no-auth` — disable HTTP Basic auth on the tunnel (DANGEROUS — anyone with the URL can access).
|
|
52
|
+
- `--subdomain <sub>` — override the default `${agentId}-${name}` subdomain.
|
|
53
|
+
|
|
54
|
+
## Auth & URL Pattern
|
|
55
|
+
|
|
56
|
+
Tunnels are protected by **HTTP Basic auth** by default:
|
|
57
|
+
- **Username:** `hi` (hardcoded MVP default in `src/artifact-sdk/tunnel.ts`)
|
|
58
|
+
- **Password:** the agent's `API_KEY`
|
|
59
|
+
|
|
60
|
+
Two equivalent URL forms:
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
# Plain (browser will prompt for credentials)
|
|
64
|
+
https://<agentId>-<name>.lt.desplega.ai
|
|
65
|
+
|
|
66
|
+
# Auth-prefilled (works in curl, scripts, and most browsers without a prompt)
|
|
67
|
+
https://hi:<API_KEY>@<agentId>-<name>.lt.desplega.ai
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Use `--no-auth` only for genuinely public content. Anyone who learns the subdomain can read it.
|
|
71
|
+
|
|
72
|
+
## Running it as a daemon
|
|
73
|
+
|
|
74
|
+
`agent-swarm artifact serve` blocks on a never-resolving promise to stay alive — you cannot inline it in a script that needs to do other work. Pick one of these:
|
|
75
|
+
|
|
76
|
+
### Option A — `nohup` (quick, throwaway)
|
|
77
|
+
|
|
78
|
+
Easiest for one-off "host this for the next 10 minutes" cases:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
mkdir -p /workspace/personal/logs
|
|
82
|
+
nohup agent-swarm artifact serve /workspace/personal/artifacts/my-report \
|
|
83
|
+
--name my-report \
|
|
84
|
+
> /workspace/personal/logs/my-report.out 2>&1 &
|
|
85
|
+
echo $! > /workspace/personal/logs/my-report.pid
|
|
86
|
+
|
|
87
|
+
# Later, kill it manually:
|
|
88
|
+
kill "$(cat /workspace/personal/logs/my-report.pid)"
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Option B — PM2 (recommended for anything you'll come back to)
|
|
92
|
+
|
|
93
|
+
PM2 gives you auto-restart on crash, a process name, log management, and — crucially — **lets `agent-swarm artifact stop <name>` actually kill it** (see Known limitation below).
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
pm2 start agent-swarm \
|
|
97
|
+
--name artifact-my-report \
|
|
98
|
+
-- artifact serve /workspace/personal/artifacts/my-report --name my-report
|
|
99
|
+
|
|
100
|
+
# Stop it cleanly later:
|
|
101
|
+
agent-swarm artifact stop my-report
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
The PM2 process name **must** be `artifact-<name>` (matching `--name`) — that's exactly what `artifact stop` looks for.
|
|
105
|
+
|
|
106
|
+
### Known limitation — `artifact stop` only kills PM2-started processes
|
|
107
|
+
|
|
108
|
+
Today, `agent-swarm artifact stop <name>` runs `pm2 delete artifact-<name>` and then unregisters the entry from the service registry. If you started the artifact with `nohup` (or `&`, or any non-PM2 launcher), `pm2 delete` silently fails and the actual server keeps running and serving — even though the command prints `Artifact '<name>' stopped.` Tracked as a follow-up bug filed alongside PR #469; until it's fixed:
|
|
109
|
+
|
|
110
|
+
- Use **PM2** if you want `artifact stop` to actually do its job.
|
|
111
|
+
- For `nohup`/foreground processes, kill the PID yourself (`kill <pid>` or `pkill -f 'artifact serve.*<name>'`) **and then** run `agent-swarm artifact stop <name>` to clear the registry row.
|
|
112
|
+
|
|
113
|
+
## Multiple Artifacts
|
|
114
|
+
|
|
115
|
+
Each artifact gets its own port (auto-assigned) and subdomain (`<agentId>-<name>`). You can run several simultaneously — see `examples/multi-artifact.ts`.
|
|
116
|
+
|
|
117
|
+
## Browser SDK
|
|
118
|
+
|
|
119
|
+
HTML artifacts can call back into the swarm API via a server-side proxy that injects auth, so browser code never sees the API key:
|
|
120
|
+
|
|
121
|
+
```html
|
|
122
|
+
<script src="/@swarm/sdk.js"></script>
|
|
123
|
+
<script>
|
|
124
|
+
const swarm = new SwarmSDK();
|
|
125
|
+
await swarm.createTask({ task: 'Do something' });
|
|
126
|
+
const agents = await swarm.getSwarm();
|
|
127
|
+
</script>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Available SDK Methods
|
|
131
|
+
- `createTask(opts)` — Create a new task
|
|
132
|
+
- `getTasks(filters)` — List tasks with optional filters
|
|
133
|
+
- `getTaskDetails(id)` — Get details for a specific task
|
|
134
|
+
- `storeProgress(taskId, data)` — Update task progress
|
|
135
|
+
- `postMessage(opts)` — Post a message to a channel
|
|
136
|
+
- `readMessages(opts)` — Read messages from a channel
|
|
137
|
+
- `getSwarm()` — Get list of agents
|
|
138
|
+
- `listServices()` — List registered services
|
|
139
|
+
- `slackReply(opts)` — Reply to a Slack thread
|
|
140
|
+
|
|
141
|
+
## API Proxy
|
|
142
|
+
|
|
143
|
+
The `/@swarm/api/*` proxy forwards requests to the MCP server with proper authentication headers. This allows browser-side JavaScript to call swarm APIs without exposing credentials.
|
|
144
|
+
|
|
145
|
+
## Storage
|
|
146
|
+
|
|
147
|
+
Always store artifact content in persisted directories — the working dir is wiped between sessions:
|
|
148
|
+
- `/workspace/personal/artifacts/` — per-agent, persists across sessions (default)
|
|
149
|
+
- `/workspace/shared/artifacts/` — shared across the swarm
|
|
150
|
+
|
|
151
|
+
See the `examples/` directory for complete working examples.
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: kv-storage
|
|
3
|
+
description: Use the swarm KV store (Redis-like, namespaced) for cross-task / cross-session / per-page state. Auto-scoped to your context (Slack thread / PR / Linear issue / agent / page). Use for counters, cursors, page state. Do NOT use for secrets (`swarm_config`), embedded knowledge (`memory`), or files (`agent-fs`).
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# KV Storage
|
|
7
|
+
|
|
8
|
+
Namespaced key/value store inside the swarm SQLite DB. Auto-scoped to your
|
|
9
|
+
calling context — same string used by `agent_tasks.contextKey`.
|
|
10
|
+
|
|
11
|
+
> **Capability gate**: the `kv-*` MCP tools are only available when your
|
|
12
|
+
> `CAPABILITIES` includes `kv` (default-on; check `my-agent-info`). The REST
|
|
13
|
+
> endpoints under `/api/kv/*` are always present on the API server.
|
|
14
|
+
|
|
15
|
+
## When to use KV
|
|
16
|
+
|
|
17
|
+
| You need… | Use this | Not this |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| Count something in this Slack thread / PR / Linear issue | **KV** (auto-scoped) | memory / agent-fs |
|
|
20
|
+
| Save a cursor / last-seen state for a recurring schedule | **KV** | swarm_config |
|
|
21
|
+
| Page-internal counter / vote / state across reloads | **KV** via `swarmSdk.kv` | memory |
|
|
22
|
+
| Cross-task state in the same conversation | **KV** (auto-scoped to `task:slack:...`) | parentTaskId only |
|
|
23
|
+
| Secrets, API tokens, OAuth creds | `swarm_config` (encrypted + masked) | **NOT KV** |
|
|
24
|
+
| Cross-session knowledge for this agent ("how do I…") | `memory_search` / `memory-get` | **NOT KV** |
|
|
25
|
+
| Files, binaries, long documents | `agent-fs` | **NOT KV** |
|
|
26
|
+
| Workflow run state | workflow vars (own KV) | **NOT KV** |
|
|
27
|
+
|
|
28
|
+
Rule of thumb:
|
|
29
|
+
- If a future invocation should *find this without knowing the key* → memory.
|
|
30
|
+
- If a future invocation will *know exactly which key to read* → KV.
|
|
31
|
+
- If it has secrets in it → `swarm_config`.
|
|
32
|
+
- If it's bytes (image, pdf, large doc) → agent-fs.
|
|
33
|
+
|
|
34
|
+
## Namespacing
|
|
35
|
+
|
|
36
|
+
Namespace is just a string. It mirrors the `contextKey` schema
|
|
37
|
+
(`src/tasks/context-key.ts`). When you don't pass one, the server resolves it
|
|
38
|
+
from request headers in this order:
|
|
39
|
+
|
|
40
|
+
1. `X-Page-Id` (only the page-proxy sets this) → `task:page:<id>`
|
|
41
|
+
2. `X-Source-Task-Id` → that task's `contextKey` (e.g. `task:slack:C123:1776...`)
|
|
42
|
+
3. `X-Agent-ID` → `task:agent:<id>` (per-agent scratchpad)
|
|
43
|
+
|
|
44
|
+
So **inside a session triggered by a Slack thread, KV is automatically scoped
|
|
45
|
+
to that thread** — your sibling tasks (re-runs, retries, follow-ups in the same
|
|
46
|
+
thread) read the same store with no setup. Same for PRs (`task:trackers:github:owner:repo:pr:N`),
|
|
47
|
+
Linear issues (`task:trackers:linear:DES-42`), schedules, workflows.
|
|
48
|
+
|
|
49
|
+
You can override the namespace explicitly when you need to — see "Explicit
|
|
50
|
+
override" below.
|
|
51
|
+
|
|
52
|
+
## Quick recipes
|
|
53
|
+
|
|
54
|
+
### MCP — inside any agent session
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
kv-set key="vote-count" value=0 valueType="integer" # → namespace = task:slack:...
|
|
58
|
+
kv-incr key="vote-count" # → 1
|
|
59
|
+
kv-incr key="vote-count" by=5 # → 6
|
|
60
|
+
kv-get key="vote-count" # → entry with value=6
|
|
61
|
+
kv-list prefix="vote-" # → all matching entries
|
|
62
|
+
kv-delete key="vote-count" # → done
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`kv-set` defaults to `valueType: 'json'` and JSON-encodes whatever you pass.
|
|
66
|
+
Use `'string'` to skip encoding (good for short tokens, URLs) and
|
|
67
|
+
`'integer'` for counters (required by `kv-incr`).
|
|
68
|
+
|
|
69
|
+
### REST — humans, scripts, external clients
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# Header-resolved namespace (recommended for in-session calls)
|
|
73
|
+
curl -H "Authorization: Bearer $API_KEY" \
|
|
74
|
+
-H "X-Agent-ID: $AGENT_ID" \
|
|
75
|
+
"$MCP_BASE_URL/api/kv/last-cursor"
|
|
76
|
+
|
|
77
|
+
# Explicit namespace
|
|
78
|
+
curl -H "Authorization: Bearer $API_KEY" \
|
|
79
|
+
"$MCP_BASE_URL/api/kv/_/task:trackers:linear:DES-42/last-comment-id"
|
|
80
|
+
|
|
81
|
+
# PUT a JSON value with a 10-minute TTL
|
|
82
|
+
curl -X PUT -H "Authorization: Bearer $API_KEY" -H "X-Agent-ID: $AGENT_ID" \
|
|
83
|
+
-H "Content-Type: application/json" \
|
|
84
|
+
-d '{"value":{"n":42},"valueType":"json","expiresInSec":600}' \
|
|
85
|
+
"$MCP_BASE_URL/api/kv/snapshot"
|
|
86
|
+
|
|
87
|
+
# List with a prefix
|
|
88
|
+
curl -H "Authorization: Bearer $API_KEY" -H "X-Agent-ID: $AGENT_ID" \
|
|
89
|
+
"$MCP_BASE_URL/api/kv?prefix=daily-&limit=50"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Pages browser SDK — inside an authed page
|
|
93
|
+
|
|
94
|
+
Page proxy forces the namespace to `task:page:<id>` — no namespace argument is
|
|
95
|
+
exposed. Use it for page-local counters, vote tallies, multi-step form state,
|
|
96
|
+
"remember this number from last refresh" UX:
|
|
97
|
+
|
|
98
|
+
```js
|
|
99
|
+
// Inside a page's <script> tag
|
|
100
|
+
const count = await swarmSdk.kv.incr('clicks'); // → number-valued entry
|
|
101
|
+
await swarmSdk.kv.set('lastSeen', Date.now()); // → 'json' by default
|
|
102
|
+
const entry = await swarmSdk.kv.get('clicks'); // → { value, valueType, ... } or null
|
|
103
|
+
await swarmSdk.kv.del('clicks');
|
|
104
|
+
const all = await swarmSdk.kv.list({ prefix: 'click', limit: 50 });
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Public pages (`authMode: 'public'`) cannot reach `/@swarm/api/*` and so cannot
|
|
108
|
+
use KV. Promote to `authed` or `password` mode if the page needs state.
|
|
109
|
+
|
|
110
|
+
## Explicit override
|
|
111
|
+
|
|
112
|
+
Pass `namespace` to read/write somewhere other than your auto-context:
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
kv-get key="seed" namespace="swarm:experiments" # ad-hoc namespace
|
|
116
|
+
kv-set key="note" value="hi" namespace="task:agent:OTHER-AGENT-ID"
|
|
117
|
+
# → 403 unless caller is lead
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Rules:
|
|
121
|
+
- **Reads:** any authenticated caller can read any namespace.
|
|
122
|
+
- **Writes to `task:agent:<X>`** where X ≠ caller agentId: **403** unless lead.
|
|
123
|
+
- **Writes to `task:page:<X>`** from anywhere except a page-proxy request: **403**.
|
|
124
|
+
- Everything else: writable by any authenticated caller.
|
|
125
|
+
|
|
126
|
+
## TTL & expiry
|
|
127
|
+
|
|
128
|
+
Default = **no expiry**. Opt in by passing `expiresInSec`:
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
kv-set key="lock-token" value="xyz" valueType="string" expiresInSec=60
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Expiry is *lazy*: reads on an expired key return null and delete the row;
|
|
135
|
+
`kv-list` filters expired rows out of the SELECT but doesn't delete them
|
|
136
|
+
(keeps cursor pagination stable). No background sweeper — expired rows that
|
|
137
|
+
never get touched stay on disk harmlessly.
|
|
138
|
+
|
|
139
|
+
## Body cap
|
|
140
|
+
|
|
141
|
+
2 MiB per value. Over the cap returns 413. If you want to store something
|
|
142
|
+
larger, write it to `agent-fs` and stash the path in KV.
|
|
143
|
+
|
|
144
|
+
## Gotchas
|
|
145
|
+
|
|
146
|
+
- **Namespaces ARE contextKey strings.** The same string that lets the swarm
|
|
147
|
+
find sibling tasks for a PR also indexes KV for that PR.
|
|
148
|
+
- **Reads return `null` for missing AND expired keys** — you can't tell the
|
|
149
|
+
difference from one call. (If you need to know, list the key.)
|
|
150
|
+
- **INCR collides** if the existing row has `valueType` `'json'` or `'string'`
|
|
151
|
+
(409 / `KvTypeCollisionError`). Delete and re-create as `'integer'` first,
|
|
152
|
+
or use a different key.
|
|
153
|
+
- **JSON values round-trip** through `JSON.parse` on read. If you wrote
|
|
154
|
+
`{a:1}`, you'll get back the object — not the raw string. Use
|
|
155
|
+
`valueType: 'string'` if you want byte-exact storage.
|
|
156
|
+
- **No CAS / SETNX yet.** Use `kv-incr` for atomic counters; for
|
|
157
|
+
"claim a token" patterns, set with a short TTL and re-check.
|
|
158
|
+
- **Page SDK has no `namespace` argument.** Pages are always scoped to
|
|
159
|
+
`task:page:<id>`. Don't try to encode another namespace in the key path —
|
|
160
|
+
the URL gets rewritten anyway.
|
|
161
|
+
|
|
162
|
+
## See also
|
|
163
|
+
|
|
164
|
+
- `src/be/migrations/061_kv_store.sql` — schema (`kv_entries`)
|
|
165
|
+
- `src/http/kv.ts` — REST handler + namespace resolution
|
|
166
|
+
- `src/tools/kv/*` — MCP tool registrars
|
|
167
|
+
- `src/artifact-sdk/browser-sdk.ts` — `swarmSdk.kv` for pages
|
|
168
|
+
- `plugin/skills/pages/SKILL.md` — companion skill for authed pages
|