DeepSpace
DeepSpace is one package for real-time collaborative apps on Cloudflare
Workers. It provides auth, RBAC, synchronized records, messaging, integrations,
payments, and deploys to <name>.app.space.
This skill is the bootstrap: how to start, how to operate, and how to consult
the documentation. The documentation at https://docs.deep.space is the
authority for everything else — it is written to be opinionated, so when it
recommends an approach, take that as the default rather than one option among
many.
One source authority
Every app has exactly one Git authority — DeepSpace source (packaged,
commit-first) or GitHub source (manual; deploys ship the local working tree,
dirty bytes included). A new app starts unclaimed, then its first
source-producing action latches one authority once, permanently: a deploy
from a checkout with a selected GitHub remote claims that repository, while
the first deepspace push or a deploy without a GitHub remote claims
DeepSpace. There is no manual declaration or later transfer. Never run
deepspace push before the first GitHub deploy unless the user has chosen
DeepSpace source; after a GitHub claim, all DeepSpace source verbs refuse with
source_managed_by_github. deepspace app source is read-only (the old
setter refuses source_inferred). Never maintain two sources of truth, and
read the source-control and workspaces docs before any source, push, pull, or
clone operation — the docs, not this skill, state what each verb does under
each authority.
Sharing and handing over an app
Collaborators and ownership transfer (deepspace app collaborators …,
deepspace app transfer …) change who can deploy, read secrets, and own the
app. Read the app-identity guide before either — it states exactly what each
grant confers and what a transfer takes away — and treat both as decisions to
surface to the user, not steps to run in passing.
How to read the documentation
Consult the docs BEFORE building in an area, not after something breaks. The
reading procedure:
- Fetch the index once per task area: https://docs.deep.space/llms.txt
— generated from the docs on every deploy, so it is never stale — lists
every page with a one-line summary.
- Pick 1–2 pages from it and fetch each as Markdown by appending
.md
to the page URL (https://docs.deep.space/guides/authentication.md).
If the page you picked lacks the answer, do not wander adjacent pages —
go to step 3.
- To prove whether a topic exists at all, fetch
https://docs.deep.space/llms-full.txt once (the whole corpus in one
file; each page ends with
Source: /route.md) and grep it. A confirmed
absence means the docs lag the build — fall back to the installed
.d.ts or the CLI's own --help.
- Use MCP for point lookups when its tools are already available:
https://docs.deep.space/mcp exposes documentation_search and
documentation_read. If those tools are unavailable or fail, use the
Markdown URLs above with the harness's existing fetch tool or a bounded
HTTP request (for example, curl -fsSL --max-time 20 <url>). Building an
app does not require MCP registration: configure a docs server only when
the user asks for that setup, and keep it separate from dependency-install
recovery. Search cannot prove a topic is absent — use step 3 for that.
- For exact type signatures, read the installed package —
ls node_modules/deepspace/dist/*.d.ts (in the SDK monorepo itself:
packages/deepspace/dist/) — authoritative when any doc lags the
installed version.
The docs' shape, so you pick pages fast: /get-started/* is setup and
project layout; /concepts/* is the runtime model — read it before touching
worker.ts, schemas, or sync behavior; /guides/* is one feature or
lifecycle area per page; /design/* is visual design; /sdk-reference/* is
exact exports per module; /cli-reference/* is the CLI, including its exit
codes and machine-executable action contract. The catalogs are live CLI
calls, not pages: npx deepspace integrations list / integrations info <integration>/<endpoint> and npx deepspace add --list.
Operating sequence
Authenticate before running app commands.
npx deepspace auth whoami --json
npx deepspace auth login # only when signed out
Login opens browser OAuth and polls for up to ten minutes. Leave it in the
foreground and let the user finish it; never request, invent, or handle a
password. A container or CI shell has no browser: run auth login --help
and use the operator-supplied env credentials it names — that help is the
authority on which variables they are — and never put a password on a
command line.
Scaffold instead of assembling the runtime by hand.
Use a supported Node version from the installation docs and npm 11.6+
for npm-based scaffolds. Check node --version and npm --version
first: npm 10's peer resolver can crash on the template's Vitest dependency
even when Node is supported. If installation fails, retain the actual error
and runtime versions before diagnosing it or choosing a recovery; keep the
app's selected package manager and lockfile consistent.
npm create deepspace@latest <app-name>
cd <app-name>
npx deepspace dev start
App ids are server-minted at registration, and apps register on first
use: the first id-needing verb (deploy, dev start, test run,
push, a secrets write) mints the id under whichever login and plane
the shell holds, announced on stderr naming the account email. Check
auth whoami BEFORE that first verb so the registration lands on the
intended account; when the shell's login is not the intended owner, log
in as the owner first (or run npx deepspace app init as them) rather
than minting an id you then have to throw away. A scaffold made while
signed out is fine — it registers the same way once someone logs in. Any
app_not_registered or app_not_initialized refusal means exactly that
and nothing else.
Inspect catalogs before hand-building a feature. Names alone are not a
sufficient fit check.
npx deepspace add --list
npx deepspace add --info <feature>
npx deepspace integrations list
npx deepspace integrations info <integration>/<endpoint>
Extend the scaffold. Keep schemas in src/schemas.ts and
src/schemas/, routes in src/pages/, app providers in
src/pages/(app)/_layout.tsx, and Durable Object wiring in worker.ts.
src/constants.ts exposes the display APP_NAME, immutable APP_ID, and
primary SCOPE_ID = app:${APP_ID}.
Test runtime changes, then deploy.
npx deepspace test run # the quick default; it names what it skipped
npx deepspace test run all # every spec, including ones you added
npx deepspace deploy
Multi-user behavior needs a two-user test. Use a distinct port for parallel
apps or worktrees. Never kill a sibling session's server.
Shared agent tools
Keep buildTools in src/ai/tools.ts as the single tool definition and
register it once in worker.ts with
registerAgent(app, { tools: buildTools }); do not duplicate tools or mount
the REST routes yourself. Both the website and local agent are enabled by
default; use local: false or inApp: false for only one surface.
When access depends on app policy, pass a developer-owned
authorize({ userId, claims, request, env }) callback to registerAgent. It
returns a boolean (or promise) for subscription, team, role, or app-data checks,
applies to both surfaces after identity and membership verification, and can
only narrow the app's existing RBAC.
A local agent discovers before invoking:
npx deepspace agent tools <app> --json
npx deepspace agent invoke <app> <tool> --input-file tool-input.json --json
Use only a returned tool and follow its description and input schema. These
stateless commands reuse the selected CLI session—there is no connect or
separate consent step—but a CLI login proves identity, not app membership. Use
the CLI rather than raw REST, and follow refusals as described below.
When a command refuses
Every refusal is a stable code, an exit code, and at most one executable
action. Branch on those, never on the prose:
- Exit 1 means fix the stated cause; retrying unchanged will not help.
Exit 2 means the command did what it could and one local step or
judgment remains.
- Run the
action when one is shipped — as the argv it gives, in the
cwd it gives. When none is shipped, do not guess a remedy. Absence
is deliberate: the refusal names choices (fork or restore, finish or abort,
free a slot or upgrade) that belong to the user, or states a fact to
inspect. Read the message, then surface the choice.
- The refusal itself now names the two states agents used to misdiagnose: a
wrong plane (
not_authenticated says which plane the command selected,
which one holds your session, and which variable to unset — do not "log in
again") and a malformed app id (invalid_app_id for a
DEEPSPACE_APP_ID in wrangler.toml that is not a valid app id — do not
run app init --new-id, which would orphan the app; plain app init
refuses over a malformed id). Via --app the codes
differ: a malformed app_… value answers invalid_app; a non-app_
string is treated as a subdomain NAME — one that is not a legal name
(uppercase, dots, wrong length) answers invalid_app without a lookup,
and a legal name that matches no app answers app_not_found, which
means check the spelling with app list, not that the id was
malformed. (transfer accept is the one exception: it takes a raw
app_… id only and answers invalid_app for any name.)
The CLI overview (/cli-reference/overview) is the contract — exit codes,
the action rules, and a table of codes by command. Do not pre-check
preconditions with separate probes; run the operation and read its refusal.
Operate what you shipped
A deploy that reports serving: confirmed is the start of the app's life,
not the end of the task. Before declaring done, look at it running:
npx deepspace logs --follow --json # what the worker actually did
npx deepspace activity # pushes, workspaces, releases
npx deepspace releases # the ledger, and what is rollback-able
npx deepspace app usage # credits, quota, per-integration spend
Two things the docs explain and you should not infer: a schedule arms on
the app's first request (deploy sends one; the scheduled-jobs guide says
what to check when it did not), and a caught error is a log line, not an
exception — the logs reference says how to read outcome and
eventType before you conclude an action failed or succeeded.
After the first deploy
The sequence above does not end at deploy — the app has a life afterwards,
and each part of it has a documented shape you should read before acting:
- Moving to a newer SDK is
deepspace app update, not a hand-edited
package.json. Run the newest CLI first —
npx deepspace@latest app update --json — then follow its steps; it is a
read-only guide and never rewrites the app for you. The updating guide
(/guides/updating) is the sequence, and the CLI reference says what each
field means. A scaffold older than server-minted ids refuses
app_not_registered at every turn; its one remedy is
npx deepspace@latest app init --new-id, which the guide explains.
- Active apps consume a slot in your tier's quota. A deploy or a fresh
registration can be refused for that reason alone, and the remedy is a
choice (free a slot or upgrade) — which is why that refusal ships no
executable action. Surface it to the user; do not pick for them.
- Connecting a domain you already own is different from buying one. Read
the custom-domains guide before either. Keep an externally registered domain
at its registrar, use the supported external-domain attach/verify flow, and
follow the returned DNS records. Prove ownership before claiming routing;
never substitute an ad hoc reverse proxy or an internal registry write.
Keep certificate-validation records needed for renewal, and verify the app
through HTTPS on the custom hostname before calling the connection complete.
- Taking an app down is
deepspace app undeploy. It is the most
destructive app command and the docs state exactly what it removes and what
survives. Read the app-identity guide before running it, and never run it
to "clean up" without the user asking.
Rules that prevent expensive mistakes
- Treat records as envelopes: fields are under
record.data; put(id, patch)
merges a partial value server-side.
- Disable write controls until
useMutations().ready. Use a confirmed mutation
when navigation, access changes, or a success message depends on acceptance.
- Data and auth hooks require the
(app)/ provider boundary. Top-level pages
are static and must not call them.
- Keep the scaffold's required
users schema. Extend it; do not rename it.
- App secrets belong in
deepspace secrets, never hand-edited .dev.vars,
shell environment prefixes, logs, commits, or screenshots.
- Caller identity comes only from a verified JWT. Never send identity in a
WebSocket URL or client-controlled internal headers.
- A tool or server action that reads the
users collection is not the same
thing as the client roster, and the two do not project the same fields to
the same people. Before exposing one to a model, a client, or a log, check
the permissions docs for what that path returns and to whom — "it reads
users" is not an answer.
- The local
ToastProvider and UI primitives come from src/components/ui,
not from the SDK.
- Treat scaffold themes and the starter home as placeholders. Give shipped
apps their own design.
- Run apps on a supported Node line — the installation guide
(
/get-started/installation) is the authority on which lines those are.
1---2name: deepspace3description: Use when building or maintaining real-time collaborative apps with the DeepSpace SDK on Cloudflare Workers; when code imports `deepspace`, `deepspace/worker`, or uses `RecordRoom`; when running `npx deepspace`; or when the task involves app.space deploys, live sync, presence, collaborative editing, RBAC, messaging, payments, Durable Objects, or DeepSpace source; or when creating or migrating native DeepSpace documentation with `documentation.json`, Markdown/MDX, the documentation feature, or an explicitly attached documentation domain.4---5
6# DeepSpace
7
8DeepSpace is one package for real-time collaborative apps on Cloudflare
9Workers. It provides auth, RBAC, synchronized records, messaging, integrations,
10payments, and deploys to `<name>.app.space`.
11
12This skill is the bootstrap: how to start, how to operate, and how to consult
13the documentation. The documentation at <https://docs.deep.space> is the
14authority for everything else — it is written to be opinionated, so when it
15recommends an approach, take that as the default rather than one option among
16many.
17
18## One source authority
19
20Every app has exactly one Git authority — DeepSpace source (packaged,
21commit-first) or GitHub source (manual; deploys ship the local working tree,
22dirty bytes included). A new app starts unclaimed, then its first
23source-producing action latches one authority **once, permanently**: a deploy
24from a checkout with a selected GitHub remote claims that repository, while
25the first `deepspace push` or a deploy without a GitHub remote claims
26DeepSpace. There is no manual declaration or later transfer. Never run
27`deepspace push` before the first GitHub deploy unless the user has chosen
28DeepSpace source; after a GitHub claim, all DeepSpace source verbs refuse with
29`source_managed_by_github`. `deepspace app source` is read-only (the old
30setter refuses `source_inferred`). Never maintain two sources of truth, and
31read the source-control and workspaces docs before any source, push, pull, or
32clone operation — the docs, not this skill, state what each verb does under
33each authority.
34
35## Sharing and handing over an app
36
37Collaborators and ownership transfer (`deepspace app collaborators …`,
38`deepspace app transfer …`) change who can deploy, read secrets, and own the
39app. Read the app-identity guide before either — it states exactly what each
40grant confers and what a transfer takes away — and treat both as decisions to
41surface to the user, not steps to run in passing.
42
43## How to read the documentation
44
45Consult the docs BEFORE building in an area, not after something breaks. The
46reading procedure:
47
481. **Fetch the index once per task area:** <https://docs.deep.space/llms.txt>
49 — generated from the docs on every deploy, so it is never stale — lists
50 every page with a one-line summary.
512. **Pick 1–2 pages from it and fetch each as Markdown** by appending `.md`
52 to the page URL (`https://docs.deep.space/guides/authentication.md`).
53 If the page you picked lacks the answer, do not wander adjacent pages —
54 go to step 3.
553. **To prove whether a topic exists at all**, fetch
56 <https://docs.deep.space/llms-full.txt> once (the whole corpus in one
57 file; each page ends with `Source: /route.md`) and grep it. A confirmed
58 absence means the docs lag the build — fall back to the installed
59 `.d.ts` or the CLI's own `--help`.
604. **Use MCP for point lookups when its tools are already available:**
61 `https://docs.deep.space/mcp` exposes `documentation_search` and
62 `documentation_read`. If those tools are unavailable or fail, use the
63 Markdown URLs above with the harness's existing fetch tool or a bounded
64 HTTP request (for example, `curl -fsSL --max-time 20 <url>`). Building an
65 app does not require MCP registration: configure a docs server only when
66 the user asks for that setup, and keep it separate from dependency-install
67 recovery. Search cannot prove a topic is absent — use step 3 for that.
685. **For exact type signatures, read the installed package** —
69 `ls node_modules/deepspace/dist/*.d.ts` (in the SDK monorepo itself:
70 `packages/deepspace/dist/`) — authoritative when any doc lags the
71 installed version.
72
73The docs' shape, so you pick pages fast: `/get-started/*` is setup and
74project layout; `/concepts/*` is the runtime model — read it before touching
75`worker.ts`, schemas, or sync behavior; `/guides/*` is one feature or
76lifecycle area per page; `/design/*` is visual design; `/sdk-reference/*` is
77exact exports per module; `/cli-reference/*` is the CLI, including its exit
78codes and machine-executable `action` contract. The catalogs are live CLI
79calls, not pages: `npx deepspace integrations list` / `integrations info
80<integration>/<endpoint>` and `npx deepspace add --list`.
81
82## Operating sequence
83
841. **Authenticate before running app commands.**
85
86 ```bash
87 npx deepspace auth whoami --json
88 npx deepspace auth login # only when signed out
89 ```
90
91 Login opens browser OAuth and polls for up to ten minutes. Leave it in the
92 foreground and let the user finish it; never request, invent, or handle a
93 password. A container or CI shell has no browser: run `auth login --help`
94 and use the operator-supplied env credentials it names — that help is the
95 authority on which variables they are — and never put a password on a
96 command line.
97
982. **Scaffold instead of assembling the runtime by hand.**
99
100 Use a supported Node version from the installation docs and **npm 11.6+
101 for npm-based scaffolds**. Check `node --version` and `npm --version`
102 first: npm 10's peer resolver can crash on the template's Vitest dependency
103 even when Node is supported. If installation fails, retain the actual error
104 and runtime versions before diagnosing it or choosing a recovery; keep the
105 app's selected package manager and lockfile consistent.
106
107 ```bash
108 npm create deepspace@latest <app-name>
109 cd <app-name>
110 npx deepspace dev start
111 ```
112
113 App ids are server-minted at registration, and apps **register on first
114 use**: the first id-needing verb (`deploy`, `dev start`, `test run`,
115 `push`, a `secrets` write) mints the id under whichever login and plane
116 the shell holds, announced on stderr naming the account email. Check
117 `auth whoami` BEFORE that first verb so the registration lands on the
118 intended account; when the shell's login is not the intended owner, log
119 in as the owner first (or run `npx deepspace app init` as them) rather
120 than minting an id you then have to throw away. A scaffold made while
121 signed out is fine — it registers the same way once someone logs in. Any
122 `app_not_registered` or `app_not_initialized` refusal means exactly that
123 and nothing else.
124
1253. **Inspect catalogs before hand-building a feature.** Names alone are not a
126 sufficient fit check.
127
128 ```bash
129 npx deepspace add --list
130 npx deepspace add --info <feature>
131 npx deepspace integrations list
132 npx deepspace integrations info <integration>/<endpoint>
133 ```
134
1354. **Extend the scaffold.** Keep schemas in `src/schemas.ts` and
136 `src/schemas/`, routes in `src/pages/`, app providers in
137 `src/pages/(app)/_layout.tsx`, and Durable Object wiring in `worker.ts`.
138 `src/constants.ts` exposes the display `APP_NAME`, immutable `APP_ID`, and
139 primary `SCOPE_ID = app:${APP_ID}`.
140
1415. **Test runtime changes, then deploy.**
142
143 ```bash
144 npx deepspace test run # the quick default; it names what it skipped
145 npx deepspace test run all # every spec, including ones you added
146 npx deepspace deploy
147 ```
148
149 Multi-user behavior needs a two-user test. Use a distinct port for parallel
150 apps or worktrees. Never kill a sibling session's server.
151
152## Shared agent tools
153
154Keep `buildTools` in `src/ai/tools.ts` as the single tool definition and
155register it once in `worker.ts` with
156`registerAgent(app, { tools: buildTools })`; do not duplicate tools or mount
157the REST routes yourself. Both the website and local agent are enabled by
158default; use `local: false` or `inApp: false` for only one surface.
159
160When access depends on app policy, pass a developer-owned
161`authorize({ userId, claims, request, env })` callback to `registerAgent`. It
162returns a boolean (or promise) for subscription, team, role, or app-data checks,
163applies to both surfaces after identity and membership verification, and can
164only narrow the app's existing RBAC.
165
166A local agent discovers before invoking:
167
168```bash
169npx deepspace agent tools <app> --json
170npx deepspace agent invoke <app> <tool> --input-file tool-input.json --json
171```
172
173Use only a returned tool and follow its description and input schema. These
174stateless commands reuse the selected CLI session—there is no connect or
175separate consent step—but a CLI login proves identity, not app membership. Use
176the CLI rather than raw REST, and follow refusals as described below.
177
178## When a command refuses
179
180Every refusal is a stable `code`, an exit code, and at most one executable
181`action`. Branch on those, never on the prose:
182
183- **Exit 1** means fix the stated cause; retrying unchanged will not help.
184 **Exit 2** means the command did what it could and one local step or
185 judgment remains.
186- **Run the `action` when one is shipped** — as the argv it gives, in the
187 `cwd` it gives. **When none is shipped, do not guess a remedy.** Absence
188 is deliberate: the refusal names choices (fork or restore, finish or abort,
189 free a slot or upgrade) that belong to the user, or states a fact to
190 inspect. Read the message, then surface the choice.
191- The refusal itself now names the two states agents used to misdiagnose: a
192 **wrong plane** (`not_authenticated` says which plane the command selected,
193 which one holds your session, and which variable to unset — do not "log in
194 again") and a **malformed app id** (`invalid_app_id` for a
195 `DEEPSPACE_APP_ID` in wrangler.toml that is not a valid app id — do not
196 run `app init --new-id`, which would orphan the app; plain `app init`
197 refuses over a malformed id). Via `--app` the codes
198 differ: a malformed `app_…` value answers `invalid_app`; a non-`app_`
199 string is treated as a subdomain NAME — one that is not a legal name
200 (uppercase, dots, wrong length) answers `invalid_app` without a lookup,
201 and a legal name that matches no app answers `app_not_found`, which
202 means check the spelling with `app list`, not that the id was
203 malformed. (`transfer accept` is the one exception: it takes a raw
204 `app_…` id only and answers `invalid_app` for any name.)
205
206The CLI overview (`/cli-reference/overview`) is the contract — exit codes,
207the `action` rules, and a table of codes by command. Do not pre-check
208preconditions with separate probes; run the operation and read its refusal.
209
210## Operate what you shipped
211
212A deploy that reports `serving: confirmed` is the start of the app's life,
213not the end of the task. Before declaring done, look at it running:
214
215```bash
216npx deepspace logs --follow --json # what the worker actually did
217npx deepspace activity # pushes, workspaces, releases
218npx deepspace releases # the ledger, and what is rollback-able
219npx deepspace app usage # credits, quota, per-integration spend
220```
221
222Two things the docs explain and you should not infer: a **schedule arms on
223the app's first request** (deploy sends one; the scheduled-jobs guide says
224what to check when it did not), and a **caught error is a `log` line, not an
225`exception`** — the `logs` reference says how to read `outcome` and
226`eventType` before you conclude an action failed or succeeded.
227
228## After the first deploy
229
230The sequence above does not end at `deploy` — the app has a life afterwards,
231and each part of it has a documented shape you should read before acting:
232
233- **Moving to a newer SDK** is `deepspace app update`, not a hand-edited
234 `package.json`. Run the **newest** CLI first —
235 `npx deepspace@latest app update --json` — then follow its `steps`; it is a
236 read-only guide and never rewrites the app for you. The updating guide
237 (`/guides/updating`) is the sequence, and the CLI reference says what each
238 field means. A scaffold older than server-minted ids refuses
239 `app_not_registered` at every turn; its one remedy is
240 `npx deepspace@latest app init --new-id`, which the guide explains.
241- **Active apps consume a slot in your tier's quota.** A deploy or a fresh
242 registration can be refused for that reason alone, and the remedy is a
243 choice (free a slot or upgrade) — which is why that refusal ships no
244 executable action. Surface it to the user; do not pick for them.
245- **Connecting a domain you already own** is different from buying one. Read
246 the custom-domains guide before either. Keep an externally registered domain
247 at its registrar, use the supported external-domain attach/verify flow, and
248 follow the returned DNS records. Prove ownership before claiming routing;
249 never substitute an ad hoc reverse proxy or an internal registry write.
250 Keep certificate-validation records needed for renewal, and verify the app
251 through HTTPS on the custom hostname before calling the connection complete.
252- **Taking an app down** is `deepspace app undeploy`. It is the most
253 destructive app command and the docs state exactly what it removes and what
254 survives. Read the app-identity guide before running it, and never run it
255 to "clean up" without the user asking.
256
257## Rules that prevent expensive mistakes
258
259- Treat records as envelopes: fields are under `record.data`; `put(id, patch)`
260 merges a partial value server-side.
261- Disable write controls until `useMutations().ready`. Use a confirmed mutation
262 when navigation, access changes, or a success message depends on acceptance.
263- Data and auth hooks require the `(app)/` provider boundary. Top-level pages
264 are static and must not call them.
265- Keep the scaffold's required `users` schema. Extend it; do not rename it.
266- App secrets belong in `deepspace secrets`, never hand-edited `.dev.vars`,
267 shell environment prefixes, logs, commits, or screenshots.
268- Caller identity comes only from a verified JWT. Never send identity in a
269 WebSocket URL or client-controlled internal headers.
270- A tool or server action that reads the `users` collection is not the same
271 thing as the client roster, and the two do not project the same fields to
272 the same people. Before exposing one to a model, a client, or a log, check
273 the permissions docs for what that path returns and to whom — "it reads
274 `users`" is not an answer.
275- The local `ToastProvider` and UI primitives come from `src/components/ui`,
276 not from the SDK.
277- Treat scaffold themes and the starter home as placeholders. Give shipped
278 apps their own design.
279- Run apps on a supported Node line — the installation guide
280 (`/get-started/installation`) is the authority on which lines those are.