diff --git a/.changeset/connect-npx-launcher.md b/.changeset/connect-npx-launcher.md new file mode 100644 index 000000000..5d4f56863 --- /dev/null +++ b/.changeset/connect-npx-launcher.md @@ -0,0 +1,5 @@ +--- +'@xnetjs/cli': patch +--- + +`xnet connect` now registers an `npx -y @xnetjs/cli` MCP server launcher when the `xnet` bin is not on PATH, so the zero-install `npx @xnetjs/cli connect claude-code` on-ramp produces a registration that still works after the npx cache is gone. diff --git a/.claude/launch.json b/.claude/launch.json index fba7dd390..a2d595ffe 100644 --- a/.claude/launch.json +++ b/.claude/launch.json @@ -188,10 +188,7 @@ { "name": "site-0432 (astro 4396)", "runtimeExecutable": "/bin/sh", - "runtimeArgs": [ - "-c", - "cd site && exec node_modules/.bin/astro dev --port 4396 --strictPort" - ], + "runtimeArgs": ["-c", "cd site && exec node_modules/.bin/astro dev --port 4396 --strictPort"], "port": 4396 }, { diff --git a/README.md b/README.md index 35234c8b9..3dee5d931 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,16 @@ peer-to-peer or through a hub you control, and signed with your own keys. ## Try it +- **[Connect your coding agent](https://xnet.fyi/agents)** — one command, + no install: + + ```bash + npx @xnetjs/cli connect claude-code # or: connect codex + ``` + + Claude Code or Codex can then read, search, query, and edit your + workspace — read-only until you say otherwise. + - **[Open the demo](https://xnet.fyi/app)** — no signup; sign in with your device's passkey (Touch ID, Face ID, Windows Hello). Demo data lives in your browser, with encrypted backups on our demo hub (10MB, expires after 24 @@ -34,6 +44,23 @@ peer-to-peer or through a hub you control, and signed with your own keys. > not a maturity signal; what is and isn't stable is written down in > [STABILITY.md](./STABILITY.md). +## Your agent, your workspace + +Coding agents are first-class users of an xNet workspace — through the +filesystem they already know, not a wall of tool definitions. `xnet connect` +installs a ~500-token skill and gives the agent three lanes, cheapest first: +the `xnet` CLI (plain stdout), a scoped **vault checkout** (Markdown/JSONL +files whose edits become schema-validated mutation plans), and a slim MCP +server as the no-shell fallback. On a 15-task benchmark the files+CLI surface +uses [~0.11x the tokens of a traditional MCP +toolset](https://xnet.fyi/docs/guides/agent-interfaces/#benchmark-methodology) +at equal success. + +Safety is structural: the server registers **read-only by default**, writes +require an enrolled agent passport or an explicit key, and every change an +agent makes lands in the workspace's signed, hash-chained change log — so you +can verify what your agent did without trusting it. + ## Build with it Everything in xNet is a **node**, and a **schema** describes what a node is: diff --git a/docs/explorations/0455_[_]_CORDIS_LESSONS_FOR_XNET_PLUGIN_COMPOSITION.md b/docs/explorations/0455_[_]_CORDIS_LESSONS_FOR_XNET_PLUGIN_COMPOSITION.md new file mode 100644 index 000000000..166960016 --- /dev/null +++ b/docs/explorations/0455_[_]_CORDIS_LESSONS_FOR_XNET_PLUGIN_COMPOSITION.md @@ -0,0 +1,580 @@ +--- +title: Cordis lessons for xNet plugin composition +status: draft +last_updated: 2026-08-21 +review: 2026-12-16 # re-decide alongside 0452's registry review — the two docs share a fate: 0452 builds the doors, this doc builds the runtime behind them +decider: Chris Smothers +door: two-way # everything proposed is internal runtime mechanics behind existing public seams; nothing touches the wire or a public API +tags: [plugins, architecture, composability, prior-art, agent-tools] +--- + +# Cordis lessons for xNet plugin composition + +> [!TIP] +> **TL;DR** — Do **not** adopt Cordis as a dependency (bus factor 1, unstable +> API, in-process good-faith trust model that ADR-17 exists to refuse). Do +> import its three load-bearing ideas, which are exactly what xNet's plugin +> system is missing: effect scopes (nested, reverse-order, +> awaited disposal instead of today's flat `ctx.subscriptions` array), +> a service layer with inject semantics (plugins *provide* and +> *consume* named services; the container re-resolves on swap — the unwired +> `extraTools` merge point is the one-line proof we need this), and +> reactive reload (the already-built, already-tested, +> zero-caller `createWorkspacePluginHotReloader` is Cordis's HMR sitting on +> our shelf). Cordis answers "how do plugins compose"; ADR-17 answers "how +> much do we trust them." The two are orthogonal, and xNet only has the +> second. + +## Problem Statement + +[Cordis](https://github.com/cordiverse/cordis) — the "Meta-Framework of +Spatiotemporal Composability" (its README's phrase) extracted from the Koishi +chatbot framework — just became the most-watched plugin architecture in the +industry: DeepSeek Harness ("Everything is a Plugin.", ~181k stars, open-sourced +2026-08-13) vendors it as its plugin kernel, and a companion preprint +formalises its model. Koishi has grown **4,551 community plugins** on it +(registry.koishi.chat, measured 2026-08-21) with essentially one maintainer. + +xNet's stated ambition is the same shape: "everything is a plugin" governed by +a trust fabric (ADR-17), a lift-out ladder for first-party features +([0452](./0452_[_]_HOW_FAR_TO_PLUGINIZE_THE_KERNEL_THE_SHELL_AND_THE_LIFT_OUT_LADDER.md)), +and an agent that builds plugins from inside the workspace +([0331](./0331_[x]_DEVELOPING_XNET_FROM_INSIDE_XNET_SPEC_TO_PLUGIN_LOOP.md)). +Yet `registry/community.json` is `[]`, exactly one first-party feature ships +through the plugin door end-to-end, and every plugin-contributed agent tool is +stranded behind a merge point no host passes. What does Cordis know about +plugin composition that we don't — and which parts of it are poison for a +local-first, sandboxed, CRDT-backed system? + +## Executive Summary + +| Question | Answer | +| --- | --- | +| What is Cordis, in one line? | A context tree where plugins are `(ctx, config)` functions whose every side effect is collected on a disposable scope, and where services are reactively injected — unload on disappear, reload on swap. | +| Should xNet depend on it? | **No.** MIT-licensed but bus factor ≈ 1 (sole npm maintainer), README warns the API "may change without notice", v3→v4 renamed the entire scope layer, and its trust model is in-process good faith — the opposite of ADR-17. | +| What do we take? | Three mechanisms: (1) effect **scopes** replacing the flat `Disposable[]`; (2) a **service registry** with `provide`/`inject` and availability semantics; (3) **reactive reload** — wire the existing workspace-plugin hot reloader and give config edits partial-reload semantics. | +| What do we already have that Cordis doesn't? | The entire trust half: provenance→tier→sandbox mapping, capability guards (`guardStore`, `guardedFetch`), fail-closed paid licensing, consent dialogs, a registry pipeline with CI. Cordis plugins run with full process privileges on good faith. | +| Sharpest evidence we need the service idea? | `AiSurfaceService` has one `extraTools` merge point; all three hosts (`agent-mcp-server.ts`, `cli mcp.ts`, `AiChatPanel.tsx`) construct it without passing the argument, stranding `plugin_*`, `lab_*`, and every plugin-contributed agent tool. With resolution instead of hand-threading, all three sites are correct by construction. | +| Relationship to 0452 | Complementary, not competing. 0452 opens the missing contribution **doors** (node types, surfaces). This doc fixes the **runtime** behind all doors: scoped disposal, service edges between plugins, reload. Both walk through `packages/plugins`. | + +--- + +## Current State In The Repository + +The full survey is long; this section keeps only what the comparison needs. + +### What exists and is real + +- **Manifest + 21 contribution kinds** — `packages/plugins/src/manifest.ts` + (`XNetExtension`, `PluginContributions`); `ContributionRegistry` in + `packages/plugins/src/contributions.ts` holds 22 `TypedRegistry` fields + (`statusBar` and `frameRenderers` are runtime-only, no manifest path). +- **Lifecycle with trust gates** — `PluginRegistry` + (`packages/plugins/src/registry.ts`): `install()` runs 9 ordered gates + (validation → platform → duplicate → host-compat → dependencies → consent → + fail-closed license → persist as node → activate). This half is genuinely + ahead of Cordis, which has none of it. +- **Per-plugin context** — `createExtensionContext` + (`packages/plugins/src/context.ts`): 21 `register*` methods, each returning + a `Disposable`, all collected in a flat `ctx.subscriptions` array walked at + `deactivate()`. +- **One end-to-end dogfood** — `charts-extra-plugin.ts` registering donut and + horizontal-bar chart types into `chartTypeRegistry` and disposing cleanly. +- **The 0331 workspace-plugin runtime** — + `packages/plugins/src/workspace-plugins/` (~1,700 lines, 7 test files): + opaque-origin iframe host, per-file SWC build, typed postMessage protocol, + denylist-wins store RPC, **and a hot reloader** + (`watcher.ts: createWorkspacePluginHotReloader` — 250 ms debounce, rebuild, + hot-swap, crash → auto-disable with last-good hash pinned). **Zero non-test + callers.** + +### What is missing, and where it bites + +| Gap | Where | Consequence | +| --- | --- | --- | +| No scope tree | `context.ts` — flat `Disposable[]`, disposed in registration order, unawaited | A plugin cannot open a sub-scope for a feature it toggles; teardown order is accidental; async `deactivate` races the next mount (`packages/react/src/context.ts:427-457` fires deactivations without awaiting) | +| Three disposal conventions | `Disposable` in `plugins/src/types.ts`, a second copy in `views/src/types.ts`, bare `() => void` in `slot-registry.tsx` / `TypedRegistry.onChange` | Every consumer handles cleanup differently; composition helpers can't exist | +| No plugin→plugin service edge | `ecosystem/dependencies.ts` resolves **versions**, never objects | `dependencies` gates install order but grants no API access; a plugin cannot consume what another provides | +| No inject semantics | — | A plugin needing the AI surface, a connector, or another plugin's API has no way to say so, wait for it, or be unloaded when it disappears | +| `extraTools` never passed | `packages/plugins/src/ai-surface/service.ts:210` merge point; omitted by `apps/electron/src/main/agent-mcp-server.ts`, `packages/cli/src/commands/mcp.ts`, `packages/workbench/src/views/AiChatPanel.tsx:215` | `plugin_*` (9 tools, 0331), `lab_*`, all connector `agentTools`, and the auto-installed `WorkspaceAgentModule`'s tools reach **no model**. `ContributionRegistry.agentTools` is written by three files and read by nobody | +| Hot reload unwired | `workspace-plugins/watcher.ts` | The only code-as-data plugin path (source stored as `PluginSourceSchema` nodes) has no host mounting it | +| `registerSchema` stub | `context.ts:225` — empty `dispose()`, `// schemaRegistry.unregister would go here` | `contributes.schemas` is a no-op (0452 tracks the registry-side fix) | +| Config is static | `first-party-catalog.ts` config forms → `PluginConfigDialog` | A config edit has no partial-reload path; nothing like `scope.accept(keys)` exists | + +> [!NOTE] +> The gaps are all in one layer. Trust, gating, marketplace, contribution +> *collection* — solid. What happens *between* activation and deactivation — +> scopes, services, reload — is where xNet is a flat, static approximation of +> what Cordis makes dynamic. + +--- + +## External Research + +### The Cordis model, precisely + +Facts verified against the repo (`cordiverse/cordis`, MIT, 6,953★, created +2022-05-17), npm (latest `4.0.0-rc.8`, 2026-08-10, ~20k downloads/wk), the +v3-era README, and koishi.chat docs. + +**Context tree.** `new Context()` is the root; `ctx.extend()` creates children +via the JS prototype chain plus per-context metadata. Koishi builds filtered +contexts on top (`ctx.platform('discord').user('112233')`, plus +`intersect`/`union`/`exclude`); anything registered through a filtered context +— plugins, commands, listeners — is scoped to the filter. + +**Plugins as scopes.** A plugin is a function/class/`{ apply }` taking +`(ctx, config)`. `ctx.plugin(p, config)` returns a fork scope; +`fork.dispose()` reverts **every** collected effect. v4 renames the scope +machinery `Fiber` and makes effects explicit: `ctx.effect(runner)` registers a +revertible effect; disposers replay in **reverse order**; child fibers are +themselves effects on the parent, so disposing a context tears down its whole +subtree. A `dispose` event covers effects the framework can't auto-track +(the README's example: close the port you opened in `ready`). Plugins can be +**forked** — applied multiple times with per-fork config and per-fork +disposal (`export const reusable = true`). + +**Services with inject semantics.** A plugin declares +`export const inject = ['database']`. The contract (v3 README, verbatim +semantics): the plugin *"will not be loaded until the service becomes +truthy"*, is *"unloaded as soon as the service changes"*, and reloaded if the +new value is truthy. Services are provided by other plugins (v4: +`ctx.provide(name, value)` returns a disposer; a `Reflect` service throws +typed errors on undeclared access — `cannot get property "X" without +inject`). `ctx.isolate(name)` splits a service per subtree, so two instances +of the same service can coexist. Swapping a service implementation +automatically bounces every dependent plugin. + +**Reactive config.** `schemastery` (~89k downloads/wk — Cordis's most-adopted +piece) is a chainable schema that both validates config and auto-generates +config UIs. The loader calls `fork.update(config)`; a plugin can +`scope.accept(keys, cb)` to patch accepted keys in place instead of +restarting. Result: a config edit reloads only the plugins whose changed keys +demand it. + +**HMR.** `@cordisjs/plugin-hmr` watches files with chokidar, walks the module +dependency graph, disposes the affected plugins' fibers, and re-applies them +with fresh exports — the process never restarts for plugin code changes. +(It needs Node internals via `--expose-internals`; Koishi ships the same idea +as its "watcher".) + +
+The paper's framing: temporal and spatial composability + +The companion preprint (`cordiverse/paper`, 2,610★, draft 2026-08-13, PDF +only, **no named authors** — the "DeepSeek wrote it" framing in press coverage +is unverified) names the two halves: + +- **Temporal composability** — a removed component's side effects can be + fully reverted ("revertible effects"). This is the fiber/scope machinery. +- **Spatial composability** — dependencies between components are declared + and reactively managed ("reactive coeffects"). This is `inject`/`provide`. + +The mapping to xNet: we have a weak form of the first (flat disposables) and +none of the second. The vocabulary is useful even if the paper's provenance +is murky. + +
+ +### Ecosystem reality check + +| Signal | Value | Read | +| --- | --- | --- | +| Koishi community plugins | **4,551** (registry.koishi.chat, 2026-08-21) | The model scales to real ecosystems | +| `cordis` npm downloads | ~20k/wk | Small direct adoption outside Koishi/dsh | +| `schemastery` downloads | ~89k/wk | The config-schema piece travels furthest | +| DeepSeek Harness | vendors Cordis (219 `package.json` matches: `ui-cordis`, `tool-cordis`, `cordis-host-runner`…) | The star spike is dsh's, not organic Cordis growth | +| Maintainer | `shigma`, sole npm publisher across cordis/koishi/schemastery | Bus factor ≈ 1 | +| API stability | README: API "may change without notice"; v3→v4 renamed EffectScope→Fiber, changed `isolate` signature | Real churn, mid-rc | +| Docs | Standalone docs site dead; deep material zh-CN; best English API guide lives in a historical README commit | High adoption friction | + +### Criticisms that matter for us + +- **Trust model**: plugins run in-process with full reach — "good faith + rather than sandboxing" (Justin3go's dsh review, 2026-08-15). For a chatbot + framework that's tolerable; for a workspace holding a user's life it is + disqualifying. This is precisely the gap ADR-17 closes, and why "adopt + Cordis" and "keep our sandbox" cannot both be true for untrusted tiers. +- **Magic**: prototype-chain contexts, Proxy interception, + `this[Context.current]` caller tracking, TS declaration merging for typing. + Costs readability; pre-v4, an unavailable service silently read as + `undefined` (v4's typed reflect errors are the admission). +- **Over-engineering critique** (from dsh beta feedback): hot-reload + composability "benefits only edge cases"; agents that couldn't drive a + plugin correctly "just edit their own code instead". A useful caution for + 0331's agent-builds-plugins loop: the plugin API has to be *easier* than + forking the app, or agents will route around it. + +--- + +## Key Findings + +### 1. Contributions vs services — the two halves of a plugin system + +xNet's model is VS Code's, and says so +(`packages/workbench/src/contributions.tsx`: "Containers vs items, the VS +Code model"): plugins **declare contributions into fixed host registries**. +Cordis's model is a service container: plugins **provide and consume named +capabilities**, and the container re-resolves when providers change. These +are not rivals — VS Code itself has both (contribution points *and* an +exported-API/service layer). xNet has only the first. There is no way for +plugin B to use what plugin A provides; `dependencies` in the manifest +resolves version constraints, never objects. + +### 2. Disposables without scopes + +xNet has the leaf of Cordis's temporal model (everything returns a +`Disposable`; `ctx.subscriptions` auto-disposes on deactivate) and none of +the tree: no nested scopes, no reverse-order guarantee, no awaited teardown, +no fork (a plugin instantiated twice with different config), and three +inconsistent disposal conventions across packages. This is the smallest +change with the largest payoff, and it is invisible until you need it — hot +reload, per-feature toggles, and service bouncing all *require* scoped +disposal to be correct. + +### 3. The `extraTools` omission is the DI argument in one line + +```text + ┌──────────────────────────────┐ + plugin_* (9, built)──▶│ │ + lab_* (built)────────▶│ AiSurfaceService.extraTools │──▶ tools/list, dispatch + connector agentTools─▶│ (one merge point, service.ts│ + WorkspaceAgentModule─▶│ line 210) │ + └──────────────▲───────────────┘ + │ never passed by: + agent-mcp-server.ts ─┤ (Electron bridge) + cli mcp.ts ──────────┤ (xnet mcp serve) + AiChatPanel.tsx ─────┘ (in-app assistant) +``` + +One merge point, three construction sites, three independent omissions, and +every downstream tool family stranded — including the auto-installed +`WorkspaceAgentModule`, whose entire design is tools driving the shell. With +hand-threading, every new host must remember every provider. With a service +registry, `AiSurfaceService` *resolves* tool providers at construction and +re-resolves when a plugin activates or deactivates; all three sites become +correct by construction, and a newly activated plugin's tools appear in a +running session without restart — which is Cordis's `inject` reload semantics, +needed here for a concrete shipped feature. + +### 4. Hot reload exists here and is disconnected + +`createWorkspacePluginHotReloader` already does what Cordis HMR does — +rebuild on change, hot-swap the frame, crash → auto-disable with the +last-good hash pinned. The reason it's unwired is structural, not accidental: +Model A plugins (in-bundle, host realm) can't reload because their code isn't +data, and Model C (source-as-`PluginSourceSchema`-node, which can) has no UI +host. Wiring it is a 0452-ladder item (rung 4 prerequisite: "wire the +workspace-plugin tools before rung 4") — this doc adds the *why now*: it is +the temporal-composability half we already paid for. + +### 5. What Cordis validates about paths we already chose + +- **Registry-as-repo scales.** Koishi's 4,551 plugins ride an npm-scan + registry with marketplace metadata in `package.json` — structurally our + `registry/` + CI pipeline (0201, 0374) at larger scale. The pipeline shape + is right; our zero community plugins is a demand/capability problem, not an + infrastructure one. +- **Schema-driven config UIs.** schemastery's config forms are our + `first-party-catalog.ts` config specs + `PluginConfigDialog`. Same idea; + ours lacks the reload wire (a config save should `update(config)` the + plugin, not require toggle-off-on). +- **Everything-is-a-plugin needs a non-plugin referee.** Cordis's kernel + (Context/Fiber/Registry) is not itself a plugin. 0452's four exemptions + (kernel, shell, plugin system, protocol schemas) are the same line drawn + for the same reason. + +```mermaid +flowchart LR + subgraph Cordis["Cordis has"] + A[Effect scopes / fibers
reverse-order revert] + B[Service provide/inject
reactive rebind] + C[HMR without restart] + D[Reactive config
schemastery] + end + subgraph xNet["xNet has"] + E[Trust tiers + sandbox kinds
ADR-17] + F[Capability guards
guardStore / guardedFetch] + G[Fail-closed licensing,
consent, provenance] + H[Registry pipeline + CI
marketplace UI] + end + A -. missing in xNet .-> xNet + B -. missing in xNet .-> xNet + C -. built, unwired .-> xNet + D -. forms only, no reload .-> xNet + E -. absent in Cordis .-> Cordis + F -. absent in Cordis .-> Cordis + G -. absent in Cordis .-> Cordis +``` + +--- + +## Options And Tradeoffs + +### Option A — Adopt Cordis as a dependency + +Replace `PluginRegistry`/`ExtensionContext` internals with `cordis` contexts; +xNet plugins become Cordis plugins. + +- ✅ Battle-tested scope/service machinery for free; HMR for free. +- ❌ **Trust mismatch is fatal**: Cordis composes objects in one realm. + xNet's `user` and `marketplace` tiers run behind an iframe/SES boundary + where only JSON-pure RPC crosses (`workspace-plugins/protocol.ts`). A + Cordis service edge cannot cross that boundary; we'd be adopting the + framework precisely where it can't reach. +- ❌ Bus factor 1, API mid-rc and churning (EffectScope→Fiber), docs + effectively zh-CN only. +- ❌ Proxy/prototype/declaration-merging magic contradicts the repo's + fail-loud, grep-able style ("a silently absent host is indistinguishable + from a broken shell" — `workbench/src/host.ts`). + +### Option B — Import the mechanisms, not the framework ⭐ + +Build three small, typed, boring pieces inside `packages/plugins`, behind the +seams that already exist: an effect-scope primitive, a service registry with +inject semantics, and the reload wiring. Host-realm (first-party) plugins get +direct service objects; sandboxed tiers get the same contract tunneled over +the existing RPC — the service *names and availability semantics* are shared, +the transport differs by trust tier. This keeps ADR-17 as the outer law and +Cordis's composition as the inner mechanics. + +- ✅ Fixes the `extraTools` class of bug structurally; unblocks agent tools + (0331/0447), lab tools, connector tools in one move. +- ✅ Unifies three disposal conventions; makes hot reload and per-feature + toggles correct instead of racy. +- ✅ Zero new dependencies; every piece is ~100–300 lines with tests. +- ❌ Real design work (service availability across async activation; the + RPC-tunneled variant for sandboxed tiers can ship later). + +### Option C — Status quo (VS Code contributions are enough) + +- ✅ No work. +- ❌ The `extraTools` gap stays a whack-a-mole: every future host of every + future service repeats the omission. 0452's ladder lands on a runtime with + unordered teardown and no plugin→plugin edges, and 0331's loop stays + shelf-ware. + +> [!IMPORTANT] +> This proposes no revenue lane, so Charter §6's three tests are not in +> play. It changes no wire format and no public manifest field — `inject` +> and `provides` enter the manifest as *optional* additions, which is why the +> door is two-way. + +--- + +## Recommendation + +**Option B**, in three steps ordered so each one ships value alone, aligned +with 0452's ladder (its step 1 registry work and this doc's step 2 service +work both live in `packages/plugins/src/`): + +1. **Effect scopes** (`packages/plugins/src/scope.ts`). One `EffectScope` + class: `use(disposable)`, `child()`, `dispose()` — reverse-order, awaited, + idempotent, re-entrancy-safe. `ExtensionContext.subscriptions` becomes a + scope; `PluginRegistry.deactivate` awaits it; + `packages/react/src/context.ts` awaits teardown before remount. Adopt one + `Disposable` type repo-wide (`() => void | Promise` accepted at the + boundary, normalized inside). + +2. **Service registry with inject semantics** + (`packages/plugins/src/services.ts`). `provide(name, value): Disposable` + and `inject(names, (services) => scopeBody)` where the body runs when all + names are available, is disposed when any disappears, and re-runs on swap + — Cordis's contract, minus proxies: explicit registration, typed lookup, + loud `ServiceUnavailableError`. First consumer: `AiSurfaceService` + resolves `agent-tools` providers from the registry, and + `ContributionRegistry.agentTools` gets its first reader. Wire all three + hosts through it; delete the three hand-threaded omissions. Manifest gains + optional `provides?: string[]` / `inject?: string[]` (validated, unlike + most contribution kinds today). + +3. **Reload wiring.** Mount the 0331 workspace-plugin host + hot reloader in + the workbench dev surface (the 0452 gate: "the honesty test can actually + be run by an agent"); route `PluginConfigDialog` saves through a + `registry.update(pluginId, config)` that bounces only the plugin's scope + (partial-accept à la `scope.accept` can come later; full bounce is + correct-if-slower first). + +```mermaid +sequenceDiagram + participant P as Plugin (activate) + participant SR as ServiceRegistry + participant AI as AiSurfaceService + participant M as Model session + P->>SR: provide('agent-tools:my-plugin', tools) + SR-->>AI: availability change (inject re-run) + AI->>AI: merge into extraTools (dedupe, first wins) + M->>AI: tools/list + AI-->>M: built-ins + plugin tools + Note over P,SR: plugin deactivates → scope disposes → provide() reverted + SR-->>AI: availability change + AI->>AI: tools removed — no restart, no stale dispatch +``` + +Explicitly **not** recommended: forked/multi-instance plugins (no current +need; revisit if a connector wants two accounts of one service), context +filtering (Koishi's session selectors have no xNet analogue), schemastery +(our config specs already exist), and any Proxy-based context sugar. + +## Example Code + +```ts +// packages/plugins/src/scope.ts — the temporal half (sketch) +export type Effect = { dispose(): void | Promise } | (() => void | Promise) + +export class EffectScope { + private effects: Effect[] = [] + private children = new Set() + private state: 'active' | 'disposing' | 'disposed' = 'active' + + use(effect: T): T { + if (this.state !== 'active') throw new ScopeDisposedError() + this.effects.push(effect) + return effect + } + + child(): EffectScope { + const scope = new EffectScope() + this.children.add(scope) + this.use(() => scope.dispose()) + return scope + } + + async dispose(): Promise { + if (this.state !== 'active') return + this.state = 'disposing' + // reverse order — later effects may depend on earlier ones + for (const effect of this.effects.reverse()) { + try { + await (typeof effect === 'function' ? effect() : effect.dispose()) + } catch (error) { + // loud, but one failed disposer must not strand the rest + console.error('[plugins] effect dispose failed', error) + } + } + this.effects = [] + this.state = 'disposed' + } +} +``` + +```ts +// packages/plugins/src/services.ts — the spatial half (sketch) +export class ServiceRegistry { + provide(name: string, value: T): Disposable + get(name: string): T // throws ServiceUnavailableError — never undefined + /** body runs when all names resolve; its scope is disposed when any + * provider goes away; re-runs if a provider is swapped. */ + inject(names: string[], body: (scope: EffectScope) => void | Promise): Disposable +} + +// The first consumer — AiSurfaceService resolves instead of being handed: +const surface = createAiSurfaceService({ store, schemas, retrieveContext, services }) +// inside: services.inject(['agent-tools'], (scope) => this.mergeExtraTools(...)) +// — agent-mcp-server.ts, cli mcp.ts, AiChatPanel.tsx no longer each +// need to remember; a plugin activating mid-session adds its tools live. +``` + +## Risks And Open Questions + +- **Scope creep into a framework.** The failure mode is rebuilding Cordis. + Guard: each piece needs a named first consumer before it merges (scopes → + registry teardown; services → `extraTools`; reload → 0331 host). No + speculative features. +- **Service edges across the sandbox boundary.** A `user`-tier iframe plugin + cannot receive a live object. The contract: sandboxed plugins see services + only as RPC-tunneled, JSON-pure facades, and *providing* a service from a + sandboxed plugin is out of scope until a real case exists. The registry + must refuse (loudly) to hand a host-realm object across the boundary — + this is where ADR-17's line must hold against convenience. +- **Availability semantics vs async activation.** `PluginRegistry.activate` + is async; inject bodies must not observe half-activated providers. Cordis + gates non-immediate services on `ready`; we need an equivalent rule + (provide only at the end of `activate`, enforced or linted). +- **Unload semantics for live sessions.** When a provider disappears + mid-conversation, in-flight tool calls need a defined failure (typed error + to the model, not a hang). The scope model makes this expressible; it + still has to be decided. +- **Does the agent actually want plugins?** dsh's beta feedback (agents + editing their own code rather than driving plugins) is a live risk for + 0331's loop. Mitigation is DX, not architecture: the `plugin_*` tools must + be cheaper for an agent than a source edit, and the preview/feedback loop + (`preview.ts`) is the leverage. +- **Open question:** should `FeatureModule.capabilities` and + `PluginPermissions` unify while we're in the file? (Today they're bridged + by an untyped cast at `registry.ts:542`.) Probably yes, but it's severable + and shouldn't ride this change. + +## Implementation Checklist + +**Status:** ░░░░░░░░░░ 0/10 items + +- [ ] `EffectScope` in `packages/plugins/src/scope.ts` with reverse-order, + awaited, idempotent disposal + tests (incl. re-entrancy and a failing + disposer not stranding the rest) +- [ ] Unify the `Disposable` conventions: one exported type in + `@xnetjs/plugins`, `packages/views` re-exports it, `slot-registry` / + `TypedRegistry.onChange` return it +- [ ] `ExtensionContext.subscriptions` backed by an `EffectScope`; + `PluginRegistry.deactivate` awaits scope disposal; + `packages/react/src/context.ts` awaits teardown before remount +- [ ] `ServiceRegistry` in `packages/plugins/src/services.ts` — + `provide`/`get`/`inject`, loud `ServiceUnavailableError`, availability + re-resolution on provide/dispose, + tests +- [ ] Optional `provides` / `inject` manifest fields with real validation + (unlike the 14 unvalidated contribution kinds — don't add a 15th) +- [ ] `AiSurfaceService` resolves agent-tool providers from the registry; + `agentToolsAsExtraTools` bridge registered as a provider reading + `ContributionRegistry.agentTools` (its first reader) +- [ ] Wire all three hosts (`apps/electron/src/main/agent-mcp-server.ts`, + `packages/cli/src/commands/mcp.ts`, + `packages/workbench/src/views/AiChatPanel.tsx`) through the resolved + surface; verify `plugin_*` and `WorkspaceAgentModule` tools reach a + live session on each +- [ ] Register `createWorkspacePluginAgentTools()` output as an + `agent-tools` provider (closes the 0331/0447 "built but unwired" gap) +- [ ] Mount the workspace-plugin frame host + `createWorkspacePluginHotReloader` + behind a dev-surface entry point (coordinate with 0452 rung + prerequisites) +- [ ] `PluginRegistry.update(pluginId, config)`: full scope bounce on config + save from `PluginConfigDialog` + +## Validation Checklist + +- [ ] Unit: disposing a parent scope disposes children first-in-reverse and + awaits async disposers; a throwing disposer doesn't strand later ones +- [ ] Unit: `inject` body re-runs on provider swap and is disposed when a + provider goes away; `get` on a missing service throws typed +- [ ] Integration: activate a plugin contributing `agentTools` mid-session → + `tools/list` over the MCP server includes it without restart; + deactivate → it disappears and an in-flight call fails typed +- [ ] Integration: all three hosts pass the same test above (no + per-host omission possible — the test constructs each host) +- [ ] E2E-ish: edit a `PluginSource` node → hot reloader rebuilds and swaps + the frame; a crashing build auto-disables with last-good pinned + (existing `workspace-plugins-watcher.test.ts` promoted to a wired host) +- [ ] `pnpm build && pnpm typecheck && pnpm test` green; api-report updated + for `@xnetjs/plugins` new exports; changeset written + +## References + +- [cordiverse/cordis](https://github.com/cordiverse/cordis) — repo; v3 README + (historical commit `261ee6be`) has the best English API guide +- [cordiverse/paper](https://github.com/cordiverse/paper) — "A Programming + Paradigm for Spatiotemporal Composability" (draft 2026-08-13; no named + authors) +- [Koishi plugin docs](https://koishi.chat/en-US/guide/plugin/) — plugin, + [service](https://koishi.chat/en-US/guide/plugin/service.html), and + [filter](https://koishi.chat/en-US/guide/plugin/filter.html) guides +- [registry.koishi.chat/index.json](https://registry.koishi.chat/index.json) + — 4,551 plugins (2026-08-21) +- [DeepSeek Harness cordis primer](https://deepseek-harness.github.io/deepseek-harness/reference/cordis-primer) +- [Justin3go's dsh review](https://justin3go.com/en/posts/2026/08/15-deepseek-harness-review) +- Repo: [0452](./0452_[_]_HOW_FAR_TO_PLUGINIZE_THE_KERNEL_THE_SHELL_AND_THE_LIFT_OUT_LADDER.md) + (lift-out ladder), [0331](./0331_[x]_DEVELOPING_XNET_FROM_INSIDE_XNET_SPEC_TO_PLUGIN_LOOP.md) + (workspace-plugin runtime), [0206](./0206_[_]_WHY_SO_FEW_FIRST_PARTY_PLUGINS.md) + (lift-out test), [0205](./0205_[_]_DECOMPOSING_THE_APP_INTO_PLUGINS.md), + [0194](./0194_[_]_EXTENSIBILITY_FABRIC_PLUGINS_LABS_AI_EDITOR.md) (unify the + four extensibility systems), [0397](./0397_[_]_AGENT_NATIVE_FRAMEWORK_LESSONS.md) + (prior framework-comparison doc), ADR-17 in + `site/src/content/docs/docs/architecture/decisions.mdx` diff --git a/docs/explorations/0456_[_]_ENTRY_VECTOR_THE_AGENT_DOOR_FIRST.md b/docs/explorations/0456_[_]_ENTRY_VECTOR_THE_AGENT_DOOR_FIRST.md new file mode 100644 index 000000000..d79890578 --- /dev/null +++ b/docs/explorations/0456_[_]_ENTRY_VECTOR_THE_AGENT_DOOR_FIRST.md @@ -0,0 +1,501 @@ +--- +title: Entry vector — the agent door first +status: draft +last_updated: 2026-08-21 +review: 2026-11-19 # one quarter of focused execution, then re-score against the dogfood gate and the Buzz/Notion lane movement. 90-day default genuinely fits: this is a focus decision, not research. +decider: Chris Smothers +door: two-way # a focus and sequencing decision — no wire format, no public API, no revenue lane changes; every deferred item stays in the repo with its exploration intact +tags: [strategy, focus, agents, plugins, go-to-market, roadmap] +--- + +# Entry vector — the agent door first + +> [!TIP] +> **TL;DR** — Pick one door: **the agent door**. The entry +> vector is `xnet connect claude-code` — "give your coding agent a workspace +> you own" — because it is the only surface in the repo that is already a +> single command, already on npm, already differentiated (0.11x MCP token +> benchmark), and sits in the one quadrant of the agent-workspace lane +> (local-first + agents extend the workspace *from within*) that Notion, +> Cowork, Buzz, and DeepSeek Harness have not taken. "Self-improving xNet" +> is not pie in the sky — 0331 already built the runtime and 0455 showed the +> loop is roughly three wiring PRs from closed. The open-source-Notion lane +> (~1% star→user conversion, Logseq dead of a rewrite) and the local-first +> SDK lane (zero breakouts) are not entry vectors; cloud becomes one only +> after something pulls people toward it. This doc adds **no new program** +> — it sequences four existing checklists (0335 → 0455 → 0447 → positioning) +> into one focus stack and names what is explicitly parked. + +## Problem Statement + +The founder's own words, condensed: *I want xNet to be self-improving — +agents integrate seamlessly and extend it from within — but that feels far +away. I want to ship something people actually use, but I don't know if +that's an open-source Notion, the framework/React hooks, xNet Cloud and easy +self-hosting, or the plugin substrate. I want Lego bricks: build once, every +developer and user after that is more productive. What's the first entry +vector?* + +This is not a new question for the repo. `docs/ROADMAP.md` (July 2026) +already bet on three pillars in dependency order — AI daily driver, then +effortless cloud, then the commons — gated on the founder's own daily use. +The overwhelm is real anyway, for three reasons this doc addresses head-on: + +1. **The site still offers every door at once.** The hero renders App / SDK / + Protocol as three equal doors (`site/src/components/sections/Hero.astro`); + `GetStarted.astro` lists three unranked paths. A stranger cannot tell what + xNet is *first*. +2. **The "self-improving" goal feels distant** — but the feeling is + miscalibrated. The audit in + [0455](./0455_[_]_CORDIS_LESSONS_FOR_XNET_PLUGIN_COMPOSITION.md) found the + agent-builds-plugins runtime (0331) fully built and tested with zero + callers, stranded behind one unpassed constructor argument. +3. **Intent accumulates without closing.** + [0421](./0421_[-]_FAST_WHAT_COLLISONS_LIST_MEASURES_AND_WHAT_XNET_LACKS.md) + measured it: +85 unstarted explorations/month, 524 docs, and "xNet's + problem is not that work moves slowly, it is that intent accumulates + without ever being closed" (0421's own words). The cure for overwhelm is + not another program; it is sequencing what exists. + +## Executive Summary + +| Question | Answer | +| --- | --- | +| What is the entry vector? | **The agent door**: `xnet connect claude-code` — one command that gives a coding agent a workspace the user owns. Everything else (SDK, cloud, app) becomes a *second* step people take after that door works for them. | +| Is "self-improving xNet" pie in the sky? | No — it's mislabeled near-done work. The 0331 iframe plugin runtime + hot reloader exist with 7 test files and zero callers; 0455's checklist closes the loop in ~3 PRs (service registry → `extraTools` wired → `plugin_*` tools live → host mounted). | +| Why not open-source Notion? | The lane converts stars to daily users at roughly 1% (AppFlowy ~70k★ / ~46k MAU), its most famous member (Logseq) stalled fatally mid-rewrite, and winners there won by *narrowing* (Outline = team wiki). Head-on Notion marketing is the weakest use of a solo founder's quarter. | +| Why not the SDK/framework? | No local-first framework has broken out (Electric pivoted to Postgres reads, Liveblocks open-sourced defensively, Jazz still pre-traction); even sympathetic engineers warn "local-first… is not a default." Keep the SDK shipped and honest; don't lead with it. | +| Why not cloud first? | Supabase/n8n prove self-host wedges work — but each rode a one-sentence job people already wanted. Cloud amplifies demand; it doesn't create it. xNet Cloud's deploy workflow is literally inert today (`deploy-cloud.yml`: "INERT BY DEFAULT"). Turn it on when the agent door creates pull. | +| Is the agent lane still open? | The lane is crowding (Notion agent hub May 2026, Anthropic Cowork Feb 2026, Block's Buzz July 2026, DeepSeek Harness Aug 2026) — but the specific quadrant **local-first, user-owned substrate where agents extend the workspace from within** is unclaimed. Buzz is the nearest neighbor (0416's thesis competitor, confirmed) and it is Nostr-relay-centric, not local-first, with a harness-out rather than workspace-in plugin story. | +| What about the Lego bricks? | The bricks the founder wants to build ARE pillar 1 — but the cold-start evidence (VS Code, Obsidian, Raycast vs ChatGPT plugins) says ecosystems thrive only on an existing devoted user base. At n≈1, that user base is **the founder plus their agents**. Build the bricks your own agents snap together this quarter; the community comes after the demo is undeniable. | +| What closes this doc? | The focus stack shipped (checklist below) and the roadmap's own gate: consecutive weeks of the founder's real work done inside xNet. | + +--- + +## Current State In The Repository + +The full shippability audit is summarized here; the load-bearing facts each +carry a path. + +### What is genuinely shippable today + +| Surface | Evidence | Verdict | +| --- | --- | --- | +| `xnet connect claude-code\|codex` | `packages/cli/src/commands/connect.ts` — idempotent, fenced CLAUDE.md edits, MCP registration, `xnet doctor --agent-access` self-check; read-only by default, `--writes` opt-in; `@xnetjs/cli@0.4.0` on npm | ✅ **The one single-command entry vector that already exists** | +| Agent lanes | `site/src/content/docs/docs/guides/agent-interfaces.mdx` — CLI verbs → vault checkout → MCP fallback; benchmark: **0.11x the tokens of an MCP toolset at equal success on 15 tasks** | ✅ Differentiated and measured | +| Web demo | `apps/web/src/boot/` + `/app?demo=1` — 2 steps, ~10–20 s cold start (SQLite WASM + OPFS), auto-seeded, never overwrites user content (`demo-seed.ts`) | ✅ Good | +| npm data layer | 18 public packages at 3.0.0 (`core, data, react, sync, sqlite, …`), OIDC + provenance releases (`.github/workflows/npm-release.yml`) | ✅ Real | +| React SDK standalone | `examples/minimal-app/` — outside the workspace, works against published npm, syncs via `wss://hub.xnet.fyi` | ✅ Under-marketed | +| Self-host hub | `packages/hub/Dockerfile`, `docker-compose.hub.yml`, root `railway.toml`, multi-arch ghcr image | ✅ Strongest distribution story | + +### What is not, despite appearances + +| Surface | Evidence | Verdict | +| --- | --- | --- | +| xNet Cloud | `apps/cloud/src/server.ts` is a real Hono control plane with ~30 test files — but `.github/workflows/deploy-cloud.yml` is **"INERT BY DEFAULT"**, defaults are in-memory providers, billing gateway 503s unset, and `site/src/data/status.json` is a two-month-stale snapshot while `site/src/data/pricing.ts` deep-links every CTA to `cloud.xnet.fyi/auth/start` | ❌ Largest claim/state gap in the repo | +| UI layer on npm | `ui, editor, views, canvas, workbench, dashboard, charts` all private/changeset-ignored | ❌ Devs can't install the components in the screenshots | +| Mobile | Expo Go demo only; `site/src/pages/mobile.astro` says so honestly | ❌ Deliberately deferred (roadmap) | +| Traction signal | `site/src/data/metrics.json` has `"sample": true`; `cloud-metrics.yml` inert; no waitlist, no testimonials, no user count anywhere; telemetry charter-banned | — Zero external signal exists, by design and by stage | +| Agent tools loop | `AiSurfaceService.extraTools` never passed by any of the three hosts; `plugin_*` (9 tools), `lab_*`, `WorkspaceAgentModule` tools all stranded (0455) | 🚧 Built, unwired | +| npm-facing story | `packages/cli/README.md` still describes schema-migration tooling; **does not mention `connect`, `checkout`, `commit`, or `mcp`** | 🚧 The best feature is unmarketed | + +> [!WARNING] +> One real hazard rides the current download page: +> `apps/electron/src/renderer/main.tsx:181` still defines `makeTestKey` +> (deterministic, source-derivable signing key; "DO NOT use in production!") +> and line 887 still calls it, while `secure-seed.ts` sits uncalled. This is +> release-blocker #1 from the +> [0335 audit](./0335_[_]_RELEASE_READINESS_AUDIT_WHAT_STANDS_BETWEEN_XNET_AND_A_WELL_RECEIVED_LAUNCH.md), +> unfixed, on a binary `site/src/pages/download.astro` distributes today. A +> product whose pitch is *you own your keys* cannot lead with a key any +> reader of the repo can reconstruct. It is first in the focus stack for +> that reason. + +### What the roadmap already decided + +`docs/ROADMAP.md` (July 2026): the bet is *"deep AI integration with total +visibility, on top of a malleable, sandboxed workspace"* — three assets +nobody else has (signed change log per node, workspace-as-Lego, plugins as +sandboxed xNet artifacts). Pillar order: AI daily driver → effortless cloud +→ commons. Principle 5: **"Dogfood is the metric"** — the gate is +consecutive weeks of the author's real work inside xNet. Deliberately +ignored: verticals, mobile parity, OS-level, marketplace-scale distribution. + +This exploration's job is to test that bet against August 2026 evidence and +convert it into a single entry vector with a closable checklist. Spoiler: +the bet survives, strengthened. + +--- + +## External Research + +Full sourcing in the research notes; the decision-relevant findings: + +### Lane 1 — "Open-source Notion": stars ≠ users + +- AppFlowy: ~70k stars, **~46k MAU** per third-party trackers (~0.66 MAU per + star). AFFiNE: ~60k stars, and its growth team publishes literal + how-to-get-stars playbooks — stars are a marketing KPI there, not a user + metric. Anytype: ~$13.5M raised, niche. +- **Logseq is the cautionary tale**: a database rewrite begun in 2022 + consumed the project; last stable release April 2024; by 2026 the + community writes migration guides away from it. +- The lane's survivors narrowed: Outline won "team wiki with Docker deploy," + not "all of Notion." + +### Lane 2 — Local-first frameworks: no breakout, ever + +- npm reality (2026): Convex ~100k weekly downloads (and it sells a + *backend*, not local-first), InstantDB ~20k, ElectricSQL ~15k after + pivoting to narrow Postgres read-path sync. Liveblocks open-sourced its + engine in Feb 2026 — a defensive move. Jazz remains pre-traction. +- The movement's own engineers say it: local-first "is not a default" + (Supabase engineer critique of teams adopting sync engines for 50-user + apps). Linear sold the *pattern*, not any framework — and built in-house. + +### Lane 3 — Self-host wedges: naming is the multiplier + +- Supabase went **8 → 800 hosted databases in three days** by renaming + itself "the open-source Firebase alternative" — same product. Now $170M + ARR / $10.5B (June 2026), lifted by AI-coding demand. n8n: $5.2B after SAP + (May 2026) as the default self-hosted agent-workflow layer. Cal.com rode + "open-source Calendly" — then went closed-source April 2026. +- The pattern: a one-sentence wedge naming a job people already want, plus a + one-click deploy. The deploy xNet has (`railway.toml`, ghcr image); the + sentence it doesn't. + +### Lane 4 — The agent-workspace lane: crowding, with one quadrant open + +- **Notion** turned its workspace into "a hub for AI agents" (developer + platform, May 13 2026) — top-down, cloud, 100M users. **Anthropic Cowork** + (Feb 2026): enterprise agent plugins/marketplaces. **Block's Buzz** + (July 21 2026, Apache 2.0, ~15–25k stars in weeks): self-hostable + workspace where agents are full members with cryptographic identity — + xNet's quadrant neighbor and 0416's predicted thesis competitor, **but** + Nostr-relay-centric (not local-first CRDT) and harness-out (ACP drives + Goose/Codex/Claude Code at the workspace) rather than workspace-in + (agents building sandboxed plugins *inside* it). **DeepSeek Harness** + (Aug 2026): "everything is a plugin," commoditizing plugin architecture + for harnesses — a harness, not a workspace (and per ADR-29 / 0416, xNet + is deliberately not a harness). +- Open as of today: **local-first, user-owned data as the agent substrate** + and **the agent extending the workspace by writing sandboxed plugins into + it**. Nobody has shipped that loop as a product. + +### The plugin cold-start table + +| Platform | Users first? | Plugin launch gap | Outcome | +| --- | --- | --- | --- | +| VS Code | Preview Apr 2015 | +7 mo | 500k MAU + 1,000 extensions at 1.0 | +| Obsidian | May 2020, small rabid base | +~6 mo | 6.8k plugins, 120M downloads, ~1.5M MAU | +| Raycast | Oct 2020 | +13 mo | 100+ community extensions in a month | +| Figma | 2016, $25M revenue first | +3 yr | Plugins became the PLG flywheel | +| ChatGPT plugins | **Plugins were the launch** | 0 | **Killed Apr 2024** — "most users never enabled plugins" | + +> [!IMPORTANT] +> The ecosystem the founder wants ("once I build it, every developer after +> me is more productive") historically only ignites on top of an existing +> devoted user base — even a tiny one. The corollary is not "abandon the +> plugin model"; it is **build the plugin loop for the user base you already +> have: yourself and your agents.** That is also exactly what the roadmap's +> dogfood gate demands. DeepSeek Harness is the apparent counterexample +> (plugins-first, exploding) — but its users *are* developers and the plugin +> *is* the product, which is precisely the agent-door framing, not the +> marketplace framing. + +### Solo-founder focus wisdom, the two load-bearing points + +- Paul Graham, *Do Things That Don't Scale*: recruit users manually; build + for one user at a time; growth rate on a tiny base beats a launch. +- Nadia Eghbal, *Working in Public*: for a solo maintainer, a big undirected + community is a **cost**; the failure mode is attention-consuming + low-value participation, not obscurity. Optimize for users, not + contributors — which cuts against investing in marketplace/community + mechanics before demand exists. + +--- + +## Key Findings + +1. **The decision was already made; the evidence now confirms it.** July's + pillar order (daily driver → cloud → commons) matches what the lane + research independently concludes: product-with-devoted-users before + ecosystem, demand before cloud, content before commons. The overwhelm is + a positioning and sequencing problem, not a strategy vacuum. + +2. **The entry vector already exists and is unmarketed.** `xnet connect + claude-code` is one command, on npm, benchmarked, differentiated, safe by + default (read-only until `--writes`), and aligned with every current + trend (agents everywhere, MCP fatigue, token cost pressure). Its own + README doesn't mention it. The Supabase lesson says fixing *that* — the + sentence and the door — is the highest-leverage cheap work in the repo. + +3. **"Self-improving xNet" is three PRs away, not a moonshot.** The + pie-in-the-sky feeling comes from mislabeling: 0331 built the sandboxed + plugin runtime (iframe host, builder, store RPC, hot reloader — 7 test + files); 0455 diagnosed the single missing wire (`extraTools` never + passed) and wrote the checklist (effect scopes → service registry → + wire three hosts → mount the dev surface). Hot module reloading isn't + the far future — it's the shelf. + +4. **The Lego-brick instinct is right; the audience is wrong-sized.** Bricks + compound only when someone is building. This quarter the builders are the + founder and their agents. Every brick should be judged by one test: *does + it make my own agent measurably better at doing my real work inside xNet + this week?* That test kills marketplace mechanics, community + infrastructure, and SDK marketing for now — and green-lights exactly the + 0455/0447 wiring. + +5. **Buzz's existence is clarifying, not threatening.** It validates the + quadrant (someone at Block believed enough to ship it) and leaves xNet's + two differentiators intact: local-first CRDT ownership (Buzz is + relay-centric) and the in-workspace plugin loop with a signed audit + trail (Buzz drives external harnesses). But it moves faster than a solo + founder on breadth — which argues for depth on the loop no one else has, + not breadth-matching. + +6. **One honest-to-goodness blocker gates all marketing:** the deterministic + Electron signing key (0335 #1). "Own your keys" cannot be the pitch while + the shipped desktop key is derivable from source. + +```mermaid +flowchart TD + subgraph LANES["The four lanes, scored"] + A["Open-source Notion
~1% star→user, Logseq ☠"] + B["Local-first SDK
zero breakouts"] + C["Cloud / self-host
works only WITH a wedge"] + D["Agent substrate
crowding, one quadrant open"] + end + D -->|"the open quadrant"| Q["local-first + user-owned +
agents extend from WITHIN"] + Q --> V["Entry vector:
xnet connect claude-code"] + V --> LOOP["The loop: agent builds sandboxed
plugins inside your workspace,
every change signed & visible"] + LOOP -->|"creates pull"| C + LOOP -->|"creates content"| COMMONS["Commons / Index (last)"] + A -.->|"not the entry"| V + B -.->|"supporting surface"| V +``` + +--- + +## Options And Tradeoffs + +### Option A — Open-source Notion: market the app to end users + +Polish the web/desktop app, launch on HN/Product Hunt as the local-first +Notion alternative. + +- ✅ The demo is genuinely good (2 steps, auto-seeded); the lane has proven + *star* demand. +- ❌ ~1% star→daily-user conversion; crowded (AppFlowy, AFFiNE, Anytype all + better-funded); a solo founder competing on end-user polish against teams + loses on breadth; and stars would flood a solo maintainer with exactly the + low-value participation Eghbal warns about. +- ❌ Desktop can't be marketed at all until the 0335 key blocker is fixed. + +### Option B — The framework: market the React SDK + +Lead with `@xnetjs/react`, publish the UI packages, build `create-xnet`. + +- ✅ `examples/minimal-app` is real; hooks are stable at 3.0.0. +- ❌ The lane has produced zero breakouts in five years; the UI layer is + private so the differentiating half isn't installable; "local-first + framework" is an architecture pitch, and architecture pitches don't + convert (Electric's pivot is the proof). + +### Option C — Cloud first: finish and launch xNet Cloud + +Turn on `deploy-cloud.yml`, swap in real providers, launch pricing. + +- ✅ The code is closer to done than it looks; Supabase/n8n prove the + self-host+cloud model. +- ❌ Wrong order: every self-host winner rode existing demand for a named + job. Standing up billing/provisioning/support for zero pulled users is + pure operational drag on a solo founder. Cloud is pillar 2 for a reason — + *"cloud = amplifier not landlord"* and amplifiers need a signal. + +### Option D — The agent door ⭐ + +One entry vector: **connect your coding agent to a workspace you own.** +Close the self-improving loop for an audience of one (founder + agents), +fix the key blocker, then say one sentence loudly. + +- ✅ Ships this quarter from existing checklists (0335, 0455, 0447); the + only lane quadrant still open; the only surface already reduced to one + command; matches the dogfood gate exactly; produces the demo no one else + can record (agent builds a sandboxed plugin inside the workspace, every + change signed, hot-reloaded live). +- ✅ Pulls the other lanes behind it: agent users need sync → cloud demand; + agent-built plugins need publishing → commons content; devs who see the + loop want the SDK. +- ❌ n=1 risk: the gate is subjective (the founder's own weeks-of-use) and + there is deliberately no telemetry to contradict self-report. Mitigation: + the validation checklist requires at least one outside person completing + the flow, recruited manually, PG-style. +- ❌ Lane risk: Notion/Buzz could ship the in-workspace loop. Watch, don't + match — depth on ownership + visibility is the moat they'd have to + rebuild their foundations to copy. + +### Option E — Status quo: keep all three doors open + +- ✅ No decision required. +- ❌ This *is* the overwhelm. Three unranked doors on the hero, a stale CLI + README, an inert cloud, and +85 unstarted docs/month is what "no entry + vector" looks like from the inside. + +> [!NOTE] +> No new revenue lane is proposed (existing cloud pricing stands), so the +> Charter §6 ground-rent tests are not triggered. The focus choice is +> two-way: if the quarter disproves the agent door, options A–C remain +> exactly where they are, minus nothing. + +--- + +## Recommendation + +**Option D — the agent door, as a strict sequence.** The quarter's rule: +every week's work must serve the sentence *"point your coding agent at a +workspace you own, and watch it build you tools inside it."* + +**The focus stack** (order matters; each unblocks the next): + +1. **Safety before speech** — fix 0335 blocker #1: Electron boots on + `secure-seed.ts` (safeStorage-backed), `makeTestKey` dies or becomes + test-only. Nothing gets marketed while the key story is false. +2. **Close the loop** — execute the 0455 checklist (effect scopes → service + registry → `extraTools` wired in all three hosts → `plugin_*` + + `WorkspaceAgentModule` tools live → workspace-plugin host and hot + reloader mounted behind a dev surface). This is 0447's "wire the loop," + now with a mechanism-level plan. Exit criterion: *from a Claude Code + session, an agent scaffolds, previews, and installs a sandboxed plugin + into the founder's real workspace, and the change log shows every step.* +3. **Dogfood ruthlessly** — the roadmap gate, made legible: a running + dogfood ledger (a page *in xNet*) logging each week the founder's real + work happened inside it, and what forced a fallback to other tools. Each + fallback is the next week's highest-priority fix. This is the + self-improvement flywheel at n=1 — the system improving because its user + lives in it, before any HMR mysticism. +4. **Say one sentence, everywhere** — reposition around the door: + `packages/cli/README.md` rewritten around `connect`/`checkout`/`mcp` + (it currently sells 2024's schema tooling); the hero's primary CTA + becomes the agent quickstart with app/SDK demoted to secondary doors; + one launch-register blog post (the corpus has 24 essays and zero + launches) with the recorded loop demo; the 0.11x-tokens benchmark made + reproducible (script + methodology in-repo) since it will be challenged + the moment it's quoted. +5. **Recruit manually** — PG-style: personally onboard 3–10 people who + already live in Claude Code/Codex, watch them run `xnet connect`, fix + what snags them. No waitlist, no community infrastructure, no Discord + moderation surface. (Eghbal: contributors are a cost; users are the + asset.) + +**Explicitly parked this quarter** (each keeps its exploration; none is +withdrawn): cloud GTM (tripwire to un-park: an outside user asks for hosted +sync twice), UI packages on npm, `create-xnet`, marketplace/community +mechanics, Index/commons build-out, mobile, matching Buzz features, +open-source-Notion positioning. + +```mermaid +sequenceDiagram + participant U as Founder (n=1 user) + participant CC as Claude Code + participant X as xNet workspace + participant P as Sandboxed plugin + U->>CC: xnet connect claude-code + CC->>X: read/query/edit via CLI lanes (0.11x tokens) + U->>CC: "build me a tool for this workflow" + CC->>X: plugin_scaffold / plugin_build / plugin_preview (0331 tools, wired by 0455) + X->>P: iframe host mounts, hot reloader watches + P-->>U: new capability inside the workspace + X-->>U: signed change log — every step visible + Note over U,P: the loop = the demo = the pitch.
Cloud, SDK, commons all inherit demand from it. +``` + +## Risks And Open Questions + +- **The n=1 gate can self-deceive.** With telemetry charter-banned, "I used + it all week" is unauditable. Mitigation: the dogfood ledger lives in the + workspace itself (its change log is signed and dated), and validation + requires outside humans completing the flow. +- **Incumbent speed.** Notion's agent hub or Buzz could ship an + in-workspace plugin loop. The response is written into the choice: depth + on the two things structurally hard for them (local-first ownership; + signed total visibility), not breadth. Revisit at review if either ships + the loop. +- **The agent door might onboard users into a thin room.** Someone arrives + via `xnet connect` with no existing xNet content — what does their agent + act *on*? The demo-seed path (`demo-seed.ts`) and vault checkout of + existing files partially answer this; the manual-onboarding step (5) is + where the real answer gets discovered. Open question to resolve during + the quarter. +- **Benchmark fragility.** The 0.11x claim rides 15 tasks; once public it + invites adversarial replication. Publishing methodology (step 4) converts + the risk into credibility. +- **Focus decay.** The repo's measured failure mode is accumulation + (0421). This doc itself must not become doc #525-that-nothing-closes: it + has a 90-day review, one decider, and a checklist whose items are + pointers into four existing checklists rather than new scope. + +## Implementation Checklist + +**Status:** ░░░░░░░░░░ 0/9 items + +- [ ] **0335 #1**: Electron uses `secure-seed.ts`; `makeTestKey` removed + from the production boot path (`apps/electron/src/renderer/main.tsx:887`) +- [ ] **0455 items 1–7**: effect scopes + service registry landed; + `extraTools` resolved (not hand-threaded) in all three hosts +- [ ] **0455 items 8–9 / 0447**: `plugin_*` tools + `WorkspaceAgentModule` + tools reach live sessions; workspace-plugin host + hot reloader + mounted behind a dev surface +- [ ] **Loop demo recorded**: one take, unedited — agent scaffolds → builds + → previews → installs a plugin in the founder's real workspace; + change-log view shown +- [ ] **Dogfood ledger** created *as an xNet page*; weekly entries; every + fallback-to-other-tools logged with a cause +- [ ] **`packages/cli/README.md` rewritten** around + `connect`/`checkout`/`commit`/`mcp` (npm-facing) +- [ ] **Hero repositioned**: one primary CTA (agent quickstart); + App/SDK demoted to secondary (`site/src/components/sections/Hero.astro`, + `GetStarted.astro`) +- [ ] **Launch post** published (first launch-register post in the blog) + + benchmark methodology committed and reproducible +- [ ] **3+ manual onboardings** of outside Claude Code/Codex users + completed, snags filed as issues/explorations + +## Validation Checklist + +- [ ] The recorded loop demo exists and required no code outside `main` +- [ ] Roadmap gate: ≥4 consecutive ledger weeks of the founder's real work + in xNet, with fallback count trending down +- [ ] At least one outside person completes `xnet connect` → agent reads + and writes their workspace, without founder intervention mid-flow +- [ ] `npm view @xnetjs/cli` README mentions `connect`; site hero has + exactly one primary door +- [ ] At review (2026-11-19): re-score the four lanes; if the agent door + produced zero outside pull after honest execution, re-open Options + A–C with this doc's evidence tables as the baseline + +## References + +- Repo: `docs/ROADMAP.md` (the three pillars, dogfood gate), + `docs/CHARTER.md`, + [0455](./0455_[_]_CORDIS_LESSONS_FOR_XNET_PLUGIN_COMPOSITION.md) (the + loop's mechanism-level plan), + [0447](./0447_[_]_LEARNING_FROM_MACRO_WIRE_THE_LOOP_BEFORE_WIDENING_THE_SUITE.md), + [0452](./0452_[_]_HOW_FAR_TO_PLUGINIZE_THE_KERNEL_THE_SHELL_AND_THE_LIFT_OUT_LADDER.md), + [0331](./0331_[x]_DEVELOPING_XNET_FROM_INSIDE_XNET_SPEC_TO_PLUGIN_LOOP.md), + [0416](./0416_[-]_AGENT_HARNESS_OR_AGENT_SUBSTRATE.md) (ADR-29; + Buzz prediction), + [0421](./0421_[-]_FAST_WHAT_COLLISONS_LIST_MEASURES_AND_WHAT_XNET_LACKS.md) + (accumulation metrics), + [0335](./0335_[_]_RELEASE_READINESS_AUDIT_WHAT_STANDS_BETWEEN_XNET_AND_A_WELL_RECEIVED_LAUNCH.md) + (key blocker), [0391](./0391_[x]_XNET_AS_THE_DAILY_DRIVER_AI_INTERFACE.md), + [0393](./0393_[_]_XNET_FROM_INSIDE_THE_CODING_AGENT.md) (`xnet connect`) +- External (as-of dates in text): Supabase origin story + (stacksync.com) + Series F (CNBC, 2026-06); n8n/SAP (Bloomberg, 2026-05); + Obsidian stats (obsidianstats.com); AppFlowy/AFFiNE/Anytype trackers + (third-party, soft numbers, flagged in research); Logseq stall coverage; + Notion agent hub (TechCrunch, 2026-05-13); Anthropic Cowork (Forbes, + 2026-02-25); Block's Buzz (opensourceforu.com, digitalapplied.com, + 2026-07); DeepSeek Harness (The Register, 2026-08-14); ChatGPT plugins + shutdown retrospectives; Paul Graham, *Do Things That Don't Scale*; + Nadia Eghbal, *Working in Public* diff --git a/docs/explorations/0457_[-]_AGENT_FIRST_SITE_REARCHITECTURE.md b/docs/explorations/0457_[-]_AGENT_FIRST_SITE_REARCHITECTURE.md new file mode 100644 index 000000000..e9f136743 --- /dev/null +++ b/docs/explorations/0457_[-]_AGENT_FIRST_SITE_REARCHITECTURE.md @@ -0,0 +1,525 @@ +--- +title: Agent-first site re-architecture — every page converts one door +status: draft +last_updated: 2026-08-21 +review: 2026-11-19 # same date as 0456, deliberately — this is 0456 step 4 specified; if the entry-vector bet is re-scored, this doc re-scores with it +decider: Chris Smothers +door: two-way # copy, information architecture, and one new route; no wire format, no public API, no pricing change. Every demoted page keeps its URL. +tags: [site, marketing, agents, positioning, conversion, docs-ia] +--- + +# Agent-first site re-architecture — every page converts one door + +> [!TIP] +> **TL;DR** — Rebuild the site's conversion spine around one action: +> `xnet connect claude-code`. Hero becomes a copyable command +> with per-agent tabs (the Bun / Claude Code pattern), a new `/agents` page +> becomes the conversion hub (per-client installs, safety model, the 0.11x +> benchmark), "Connect your agent" becomes the header button, the +> coding-agents guide moves from _item 9 of a collapsed accordion_ into +> **Start Here**, and llms.txt finally mentions `xnet connect` — today the +> string appears in exactly **one** file on the whole site +> (`coding-agents.mdx`) and in none of: nav, footer, hero, GetStarted, +> README, llms.txt. Nothing is deleted: App, SDK, Cloud, Why all keep their +> URLs and their depth pages, demoted one rank. Ship in two phases — +> **Phase A now** (repositioning what already works: connect, checkout, +> MCP, the benchmark), **Phase B when 0456's loop demo exists** (the +> agent-builds-plugins section). Everything a human reads gets a twin the +> _agent_ reads, because for this product the agent is present at the +> moment of conversion. + +## Problem Statement + +[0456](./0456_[_]_ENTRY_VECTOR_THE_AGENT_DOOR_FIRST.md) chose the entry +vector: the agent door, `xnet connect claude-code|codex`. Its step 4 said +"say one sentence, everywhere" and reserved one checklist line for the +hero. This exploration is that step, fully specified: what does the landing +page, the site IA, the docs, the README, and the agent-readable layer look +like when **everything converts toward the agent connection** — while the +app, the React SDK, xNet Cloud, and the movement pages all remain, one rank +down? + +The gap is stark. The site survey found: + +- `xnet connect` appears in **one** source file + (`site/src/content/docs/docs/guides/coding-agents.mdx`) — nowhere in + `Nav.astro`, `Footer.astro`, `Hero.astro`, `GetStarted.astro`, or the + root `README.md`. +- `public/llms.txt` — the file coding agents actually fetch — **omits the + coding-agents guide entirely** while listing 40+ other docs. +- The landing's agent section (`BuiltForAgents.astro`) is 4th of 7, + ~6 viewports down, and demos `xnet checkout` / `xnet query` — not the + one command we want typed. +- The docs landing (`docs/index.mdx`) opens "xNet is a local-first React + framework" and never links either agent guide. +- There is no `/agents` route, no agent data file, no OG images, no + sitemap, no site-wide robots.txt. + +The site is not wrong — it is even-handed. Even-handed is the problem +(0456, Option E). + +## Executive Summary + +| Question | Answer | +| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| The one sentence | **"Give your coding agent a workspace you own."** Sub-sentence: docs, databases, and canvases your agent can read, query, and build in — every change signed, synced, and yours. | +| The one action | A copy-button command in the hero, per-agent tabs: Claude Code · Codex · Cursor · VS Code · anything (MCP). Primary everywhere: header button, hero, GetStarted path 1, README section 1, docs Start Here. | +| What happens to the app/SDK/cloud? | Kept, demoted one rank. App = "the workspace behind the agent" (section + `/app` untouched); SDK = the developer depth pages (`/react`, `/build-with`); Cloud = a whisper ("Free to start · Pricing") per the Ollama pattern. No URL dies. | +| What's new? | One route: **`/agents`** (conversion hub + `src/data/agents.ts`); an agent-readable layer (llms.txt fix, `/agents.md`, install snippets/deeplinks); OG meta while we're in `Base.astro`. | +| What's honest to ship _today_? | Phase A: connect, three lanes, read-only default, agent passports, the 0.11x benchmark (with methodology published — a 0456 item). All shipped and true now. | +| What waits? | Phase B: the "agent builds tools inside your workspace" section and demo — gated on 0456's loop wiring (0455 checklist). The site must not market the loop before it's recordable. | +| Biggest execution risk | Build gates: `build-llms-full.ts` fails if a docs page isn't in `sidebar.mjs`; `validate-dist.ts` asserts route outputs (read before renaming anything); `pricing-claims.test.ts` regex-reads `pricing.ts` as text — don't reformat it. | + +--- + +## Current State In The Repository + +### The conversion spine today + +```text +Nav: [xNet] App Developers Open | Why Build Demos Blog [Docs] [Try the App] + │ +Hero: "Your data. Your devices. Your rules." ▼ + [Try the app — free, no account] [Read the docs] /app + doors: App(emerald) · SDK(indigo) · Protocol(purple) + "The app is built on the SDK. The SDK implements the protocol. + Start anywhere." ← three equal doors + +Sections: Hero → TheApp → ForDevelopers → BuiltForAgents → NoBlackBoxes + → HumaneByDesign → GetStarted (App / SDK / Movement — no agent path) +``` + +Everything routes to `/app?demo=1` or `/docs/quickstart/` (SDK). The agent +story is mid-scroll (`BuiltForAgents.astro`, showing `checkout`/`query`), +and its docs are behind a collapsed accordion: `coding-agents` is item 9 of +15 in **Guides**, three levels below Start Here. + +### Assets the restructure can reuse (no new machinery needed) + +| Asset | Path | Why it matters | +| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- | +| Terminal chrome component | `site/src/components/ui/CodeBlock.astro` — macOS traffic lights, `filename="terminal"`, hover copy button | The hero command block already exists as a component | +| Tab strip component | `site/src/components/ui/CodeTabs.astro` — dependency-free, group-synced, localStorage-persisted, no-JS fallback | Per-agent tabs (Claude Code/Codex/Cursor/VS Code) for free | +| The content itself | `docs/guides/coding-agents.mdx` (the `xnet connect` guide), `docs/guides/agent-interfaces.mdx` (three lanes + 0.11x benchmark), `docs/ai/understanding-xnet.mdx` | The `/agents` page is 80% assembly of existing prose | +| Changelog receipts | 12+ agent fragments in `site/src/data/changelog/` (e.g. `2026-07-24-use-xnet-from-claude-code-and-codex.json`, `2026-08-01-verify-what-your-agent-did.json`) | Social-proof strip: real dated receipts, no invented testimonials | +| llms-full pipeline | `site/scripts/build-llms-full.ts` + `site/src/sidebar.mjs` (single source of truth for docs order **and** llms-full order) | Reordering the sidebar reorders the agent-readable corpus too | +| Data-file pattern | `site/src/data/*.ts` driving every page | `agents.ts` slots in beside `pricing.ts`/`compare.ts` | + +### Constraints that will bite (from the survey) + +> [!WARNING] +> Four build gates constrain this work. (1) `build-llms-full.ts` **fails +> the build** if a docs content file is in neither `sidebar.mjs` nor its +> exclusion list — every new docs page needs a sidebar entry in the same +> PR. (2) `scripts/validate-dist.ts` was added after a half-built deploy +> wiped the homepage for ~30 min (2026-07-18); **read it before renaming +> or deleting any route** — it asserts route outputs exist. (3) +> `apps/cloud/src/pricing-claims.test.ts` reads `site/src/data/pricing.ts` +> as **text with a whitespace-sensitive regex** — do not reformat that file +> while touching Cloud copy. (4) `validate-metrics.ts` fails on overstated +> or >25%-stale figures — any new stat on the hero must come from +> `siteMetrics.ts`'s conservative-floor pattern. + +Also: `site/` installs `--ignore-workspace` and cannot import `@xnetjs/*` +(root `AGENTS.md`); the established workarounds are repo-root JSON imports +(`plugins.ts` → `registry/registry.json`) and committed snapshots — an +`agents.ts` data file follows the same pattern. Deploys ride +`deploy-site.yml` (site + `/app` + `/play` assembled onto `gh-pages`; the +"~9 min to live" figure from memory is not written anywhere in-repo — +verify empirically before quoting it in launch-day plans). + +--- + +## External Research + +### The command-first hero is a solved pattern + +From the 2026 survey of dev-tool landers: + +- **Bun**: headline + copyable `curl … | bash` with OS tabs + versioned + install button + a _replayable_ benchmark race above the fold. Its trophy + logos are agent products (Claude Code, Cursor, Midjourney, Railway). +- **Claude Code itself**: name + "Work with Claude directly in your + codebase…" + download button **and** install one-liner. MCP integrations + are a late section — the mirror image of xNet, which _is_ the + integration and should lead with the connect command. +- **Homebrew**: the page essentially is the command. **Ollama**: one + action; cloud reduced to "Free to start. See pricing." +- **Aider**: proof by quantified usage ("88% of new code in the latest + release written by Aider itself") — the dogfood-metric pattern 0456's + ledger can eventually feed. +- **Evil Martians' 100-lander study**: exactly **two** CTAs (one dominant, + one subordinate); specific verb copy over "Get started"; for + libraries/infra a code snippet _is_ the right hero visual; pricing on + its own page. + +### The "add to your agent" affordance stack (mid-2026 table stakes) + +- Per-client tab strip with copy buttons: `claude mcp add …` (Claude Code + convention — the snippet is the affordance), `~/.codex/config.toml` TOML + block (Codex), **Cursor deeplink** (`cursor://anysphere.cursor-deeplink/mcp/install?name=…&config=`, + official button assets; pair with a JSON fallback — deeplinks are + reported flaky), **VS Code badge** (`vscode:mcp/install?`). +- Directory distribution (Smithery ~16.8k MCPs listed; mcp.so ~20k + secondhand) is marketing reach, not a substitute for the page. +- Docs sites are now expected to _be_ agent-consumable: Mintlify ships + `/llms.txt`, `/llms-full.txt`, per-page "copy as Markdown"; GitBook + auto-exposes an MCP endpoint per docs site. + +### Marketing to the agent, not just the human + +Netlify named the category — "Agent Experience (AX)" — and in April 2026 +launched **netlify.ai, a site built for agents rather than humans** +(onboarding and build context for the agent itself). The nuance from the +llms.txt adoption data: no major AI vendor commits to llms.txt for +_search/training_, but **coding agents do fetch `/llms.txt` when pointed +at a docs site** — which is precisely xNet's conversion moment: an agent +is _running_ `xnet connect` while its human watches. xNet's llms.txt +currently forgets to mention the connect flow at all. + +One more external fact that shapes copy: **Continue.dev's lander is now an +acquisition notice and Goose's is a redirect stub.** Harness brands churn. +The site should anchor on "your coding agent" generically, with named +clients as tabs — never as the headline. + +--- + +## Key Findings + +1. **This is a repositioning, not a rebuild.** The components (terminal + chrome, tab strip), the content (two mature agent guides), and the + receipts (12 dated changelog fragments) all exist. What's missing is + rank: the agent story is mid-scroll on the homepage, item 9 of a + collapsed accordion in docs, and absent from nav, README, and llms.txt. + +2. **The conversion moment is a two-reader moment.** Uniquely for this + product, at the instant of conversion there are two readers: the human + deciding, and the agent about to execute `xnet connect` (and likely + fetching `/llms.txt` mid-run). Every conversion surface therefore needs + a human face and an agent twin. No competitor in the workspace lane + does this; Netlify proved the pattern in the deploy lane. + +3. **Honesty gates the section order.** The three lanes, read-only + default, passports, signed log, and the benchmark are shipped and true + — Phase A can say them loudly today. The loop ("your agent builds tools + _inside_ the workspace") is 0455/0456 wiring away; the site must not + promise it before the demo records. The Charter's own rule (every + promise ships with a receipt or is labeled not-yet) applies to + marketing exactly as to docs. + +4. **The benchmark is the single most quotable asset and the most + fragile.** "~9x cheaper than MCP tools" already appears on the landing + page; the hero will amplify it. 0456 already requires the methodology + to be published and reproducible — that item becomes a _prerequisite_ + of Phase A launch, not a follow-up. + +5. **Demotion must be visible-but-cheap.** The lesson from Ollama ("Free + to start. See pricing") and Evil Martians (pricing on its own page): + the app and cloud don't vanish — they compress to one honest line each + with a route. That satisfies "keep all the other features" without + re-splitting the funnel. + +```mermaid +flowchart TD + subgraph HUMAN["Human reader"] + H1[Hero: one sentence + command] --> H2["/agents — what your agent gets,
safety model, benchmark"] + H2 --> H3[Copies command / clicks deeplink] + end + subgraph AGENT["Agent reader"] + A1["/llms.txt — names connect flow"] --> A2["/agents.md — what am I
connecting to, which tools"] + A2 --> A3["SKILL.md installed by connect
(~500 tokens, exists today)"] + end + H3 --> C[xnet connect claude-code] + A3 --> C + C --> W[Agent reads/writes the workspace
three lanes, signed log] + W -->|"wants sync"| CLOUD["/cloud (demoted, intact)"] + W -->|"wants the UI"| APP["/app (demoted, intact)"] + W -->|"Phase B"| LOOP[Agent builds plugins inside
— gated on 0456 loop demo] +``` + +--- + +## Options And Tradeoffs + +### Option A — Copy-only touch-up + +Rewrite `Hero.astro` copy and promote `BuiltForAgents` to section 2; change +nothing else. + +- ✅ One PR, zero risk to build gates. +- ❌ Leaves the funnel broken where it actually converts: no `/agents` + page to send traffic to, docs still SDK-first, llms.txt still silent on + connect, README untouched. The header button still says "Try the App." + Half a repositioning reads as indecision — the current site's disease. + +### Option B — Full-stack repositioning in two phases ⭐ + +Phase A (now): hero + nav + `/agents` route + GetStarted + docs IA + +llms.txt/agents.md + README, all around what ships today. Phase B (gated +on 0456's loop demo): the plugin-loop section, the recorded demo, and the +dogfood-metric proof strip. + +- ✅ Converts the whole spine while every claim stays true; the two-phase + gate keeps marketing behind reality; touches no revenue mechanics. +- ✅ Each Phase A item is small and independently shippable (see + checklist) — no big-bang redesign, `--delete` rsync deploys stay safe. +- ❌ ~8–10 PRs across site, docs, README; sidebar/llms-full/validate-dist + gates need care; OG/meta work tempts scope creep (kept optional). + +### Option C — Separate agent microsite (agents.xnet.fyi or netlify.ai-style twin) + +- ✅ Maximum focus; the main site stays even-handed. +- ❌ Splits authority and maintenance for a solo founder; the survey shows + the main site's traffic surfaces (README, llms.txt, docs) are exactly + where the fix is needed; a microsite duplicates the Starlight/llms + pipeline. The agent-twin _pages_ (Option B) capture the netlify.ai idea + without a second property. + +### Option D — Docs-as-landing (Tailwind posture) + +Make `/docs` the homepage; kill the marketing site's hero. + +- ❌ Throws away the `/why`/Charter/blog narrative layer that is xNet's + actual differentiation vs Buzz/Notion, and the 25-essay corpus that + earns trust. Rejected without much agony. + +> [!NOTE] +> No revenue lane changes: Cloud pricing, plans, and CTAs are untouched +> except in rank. Charter §6 tests not triggered. + +--- + +## Recommendation + +**Option B.** The spec, surface by surface: + +### 1. Hero (`site/src/components/sections/Hero.astro`) + +```text +┌────────────────────────────────────────────────────────────────────┐ +│ [Alpha — shipping, and still moving fast] │ +│ │ +│ Give your coding agent a workspace you own. │ +│ │ +│ Documents, databases, and canvases your agent can read, query, │ +│ and build in — local-first, synced, every change signed. │ +│ │ +│ ┌ Claude Code ┊ Codex ┊ Cursor ┊ VS Code ┊ Any agent ─────────┐ │ +│ │ ● ● ● terminal [copy] │ │ +│ │ $ npx @xnetjs/cli connect claude-code │ │ +│ │ ✓ skill installed · mcp registered · read-only by default │ │ +│ └─────────────────────────────────────────────────────────────┘ │ +│ │ +│ [What your agent gets → /agents] [Try the app] (2nd CTA) │ +│ │ +│ ~9x cheaper than MCP toolsets* · read-only until you say so · │ +│ works offline · MIT *methodology → /agents │ +└────────────────────────────────────────────────────────────────────┘ +``` + +- Tabs via existing `CodeTabs.astro`; terminal via existing + `CodeBlock.astro` (`filename="terminal"`). Claude Code/Codex tabs show + `xnet connect …`; Cursor/VS Code tabs show the MCP deeplink button + + copyable JSON fallback; "Any agent" shows `xnet mcp serve`. +- Exactly two CTAs (Evil Martians): primary → `/agents`, secondary → + `/app?demo=1`. The three equal doors **go away**; the closing line + becomes "There's a full workspace app behind this — and an SDK under + both. [App] · [SDK] · [Protocol]" as small links. +- Verify the exact zero-install one-liner before shipping (`npx +@xnetjs/cli …` vs `npm i -g` — whichever `packages/cli` actually + supports; the checklist carries this). +- Static command block first; a typed-replay animation is a Phase B + nicety, not a blocker (Deno converts with no animation at all). + +### 2. New route: `/agents` (+ `site/src/data/agents.ts`) + +The conversion hub, assembled from existing content: + +1. Per-client install (the hero tabs, expanded — including + `claude mcp add` and Codex TOML for people who prefer raw MCP). +2. **What your agent gets**: the three lanes from `agent-interfaces.mdx` + (CLI verbs → vault checkout → MCP fallback), with the token benchmark + and a link to the published methodology. +3. **The safety model**: read-only by default, `--writes` opt-in, agent + passports, every change signed into the log — "verify what your agent + did" (reuse the changelog fragment's framing). +4. **Receipts strip**: the dated agent changelog fragments as cards (real + receipts instead of invented testimonials). +5. One-line demotions: "Prefer a UI? [Try the app]. Building your own? + [React SDK]. Want managed sync? [Cloud — free to start]." +6. Phase B slot: the recorded loop demo replaces a "what's next" teaser. + +`agents.ts` holds the per-client commands/deeplinks/labels so the hero +tabs, `/agents`, README snippets, and docs quickstart all render from one +source (same pattern as `pricing.ts`). + +### 3. Nav + footer (`Nav.astro`, `Footer.astro`) + +- Header: add **Agents** as the first page link; the filled conversion + button becomes **"Connect your agent" → `/agents`**; "Try the App" + moves to a plain link. Everything else stays. +- Footer: new first column **Agents** (Connect guide, Agent interfaces, + /agents, llms.txt, MCP/registry listings), then Product/Cloud/Develop/ + Resources/Community as today. + +### 4. Docs IA (`site/src/sidebar.mjs`, `docs/index.mdx`) + +- **Start Here** becomes: Introduction → **Connect your agent** + (`coding-agents.mdx`, retitled) → Quickstart (SDK) → Core Concepts. + `agent-interfaces` moves up alongside it or into a new "Agents" group + right under Start Here — either way, out of the collapsed accordion. +- `docs/index.mdx` opens with two cards — "Connect your coding agent" and + "Build with React" — replacing the SDK-only lede. +- Sidebar reorder automatically reorders `llms-full.txt` (same source of + truth); regenerate and commit in the same PR (`pnpm check:llms-full`). + +### 5. The agent-readable layer + +- `public/llms.txt`: add the connect flow at the **top** ("If you are a + coding agent: your human can connect you with `xnet connect `; + after connect you get these tools/lanes…"), plus the missing + coding-agents entry. +- New `public/agents.md` (the netlify.ai move, one page not a microsite): + what xNet is _to an agent_, the three lanes, tool list, safety + contract, where the SKILL.md comes from. Linked from llms.txt and + `/agents`. +- Optional same-PR cheap wins while in `Base.astro`: `og:title`/ + `og:description`/`twitter:card` (site has **zero** OG meta today), + `@astrojs/sitemap`, site-wide `robots.txt`. + +### 6. README (root) + +Mirror the site's new order: after the one-liner and screenshot, **Try it** +gains "Connect your coding agent" as the _first_ bullet (`npx @xnetjs/cli +connect claude-code`), before demo/download/hub; a short "Your agent, +your workspace" section (three lanes + benchmark + safety line) lands +above "Build with it". Zero agent mentions today → the second landing +surface gets the same spine. + +### 7. Explicitly unchanged + +`/why`, `/commitments`, `/blog`, `/compare`, `/open`, `/status`, +`/roadmap`, all legal pages, `/cloud` + pricing (rank only), `/plugins`, +`/download`, `/mobile`, `/demos`, `/react`, `/build-with`, `/devtool` — +URLs, content, and validators untouched. + +### Phase gate + +> [!IMPORTANT] +> **Phase A ships now** — every claim above is true of today's shipped +> `@xnetjs/cli@0.4.0`. **Phase B** (the loop section: "ask your agent for +> a tool; it builds a sandboxed plugin inside your workspace; watch every +> change in the signed log" + recorded demo + Aider-style dogfood metric) +> **is gated on 0456's checklist item "loop demo recorded"** — the site +> never gets ahead of the repo. The 0.11x benchmark methodology +> publication (a 0456 item) is a **Phase A prerequisite**, because the +> hero quotes it. + +## Risks And Open Questions + +- **The command must work flawlessly for strangers.** The hero promotes a + path so far run mostly by its author. 0456's manual-onboarding item is + the mitigation; sequence at least one outside run before the hero + flips. Also confirm `npx @xnetjs/cli connect` works without global + install (and without a pre-existing workspace — the "thin room" question + from 0456: what does a fresh agent connect _to_? The `/agents` page + should answer with the demo-seed or `xnet vault init` story). +- **Cursor/VS Code deeplinks are flaky** (documented forum failures) — + always render the copyable JSON beside the button; treat the deeplink as + progressive enhancement. +- **Benchmark exposure.** Quoting 0.11x in the hero invites replication + attempts; methodology must be in-repo and reproducible first + (prerequisite above). +- **validate-dist and route assembly.** Read `scripts/validate-dist.ts` + before the nav/route PR; add `/agents` to whatever it asserts. The + gh-pages rsync `--delete` means a bad build can blank pages — the + validator exists because it already happened once; keep it updated rather + than bypassed. +- **Alpha honesty vs conversion.** The alpha badge stays in the hero. The + 0335 key blocker (0456 item 1) must land before any launch push drives + desktop downloads. +- **Open question — the name of the door.** "Agents" vs "AI" vs "Connect" + in nav copy; "Agents" is assumed here (matches `/agents`, + survives harness churn), but test on the manual onboardings. +- **Open question — Plausible goals.** Cookieless Plausible is already + gated in; whether to define custom events (copy-click, tab-select) or + keep zero-measurement is a Charter-flavored decision left to the + decider. + +## Implementation Checklist + +**Status:** ░░░░░░░░░░ 0/12 items (Phase A: 1–10; Phase B: 11–12) + +- [x] Verify + document the canonical zero-install command (`npx +@xnetjs/cli connect claude-code` or equivalent) against + `packages/cli` as published; fix `packages/cli` if npx flow has + gaps +- [x] Publish the 0.11x benchmark methodology in-repo (0456 item, now a + Phase A prerequisite) and link target for the hero footnote +- [x] `site/src/data/agents.ts` — per-client commands, deeplinks, labels + (single source for hero tabs, `/agents`, README, docs) +- [x] `Hero.astro` rewrite: new headline/sub, `CodeTabs` + `CodeBlock` + command block, two CTAs, doors → small links +- [x] New `site/src/pages/agents.astro` per the section spec; update + `scripts/validate-dist.ts` expectations if route-asserting +- [x] `Nav.astro` (+Agents link; button → "Connect your agent") and + `Footer.astro` (+Agents column) +- [x] `GetStarted.astro`: path 1 becomes "Connect your agent" (command + block), App and SDK follow +- [x] Docs IA: `sidebar.mjs` — coding-agents into Start Here (retitled + "Connect your agent"), agent-interfaces promoted; `docs/index.mdx` + two-card lede; regenerate `llms-full.txt` (`pnpm check:llms-full`) +- [x] Agent-readable layer: `public/llms.txt` top section + coding-agents + entry; new `public/agents.md`; (optional, same PR: OG meta in + `Base.astro`, `@astrojs/sitemap`, `robots.txt`) +- [x] Root `README.md`: connect-first Try-it bullet + "Your agent, your + workspace" section above Build-with +- [ ] **Phase B**: loop demo section on `/` and `/agents` once 0456's + "loop demo recorded" item is checked; typed-replay animation of the + connect+session terminal +- [ ] **Phase B**: dogfood proof strip (ledger-derived metric, Aider + pattern) once the 0456 ledger has ≥4 weeks of data + +## Validation Checklist + +- [x] `cd site && pnpm build` green (all validators incl. llms-full check + and validate-dist) with the new route and reordered sidebar +- [ ] A fresh machine + `npx` run of the hero command succeeds verbatim + as printed, against the published npm package (not the repo) +- [ ] An agent given only `https://xnet.fyi` (via llms.txt/agents.md) can + explain what `xnet connect` will do and which tools it gets — + tested by actually asking Claude Code with a clean context +- [ ] Cursor deeplink and VS Code badge each install the MCP server on a + clean profile; JSON fallback verified when the deeplink fails +- [x] Every demoted page still reachable within two clicks of `/` + (nav or footer); no URL removed (`check:exploration-links`-style + manual sweep of site nav) +- [ ] At least one 0456 manual onboarding completed **through the new + site** without founder intervention — the site was the only guide +- [ ] Phase B additions appear only after the referenced 0456 items are + verifiably checked + +## References + +- Repo: [0456](./0456_[_]_ENTRY_VECTOR_THE_AGENT_DOOR_FIRST.md) (the + strategy this specifies), + [0455](./0455_[_]_CORDIS_LESSONS_FOR_XNET_PLUGIN_COMPOSITION.md) (loop + wiring behind Phase B), + [0384](./0384_[x]_TIGHTENING_THE_LANDING_PAGE_FROM_28_VIEWPORTS_TO_A_FOCUSED_FUNNEL.md) (the + teaser→route rule this doc obeys: teasers link depth pages, never + re-argue them), `site/src/components/sections/Hero.astro`, + `site/src/sidebar.mjs`, `site/scripts/build-llms-full.ts`, + `scripts/validate-dist.ts` (via `site/package.json` build), + `apps/cloud/src/pricing-claims.test.ts`, + `site/src/content/docs/docs/guides/coding-agents.mdx`, + `public/llms.txt` +- External: Bun (bun.sh) hero pattern; Claude Code product page + (claude.com/product/claude-code); Ollama (ollama.com) one-action page; + Aider (aider.chat) dogfood metric; Evil Martians "We studied 100 dev + tool landing pages" (2025) + LaunchKit; Cursor MCP install-links docs + (deeplink + button assets); VS Code "Agent mode meets MCP" (May 2025, + `vscode:mcp/install` badges); modelcontextprotocol/mcpb bundles; + Mintlify contextual menu / llms.txt tooling; GitBook docs-MCP + endpoints; Netlify Agent Experience + netlify.ai (Apr 2026); Smithery + (smithery.ai); llms.txt adoption surveys (secondary sources, directional + only); Continue.dev acquisition page + Goose redirect (harness churn) diff --git a/packages/cli/src/__tests__/connect-command.test.ts b/packages/cli/src/__tests__/connect-command.test.ts index 177e393f1..ba86c1672 100644 --- a/packages/cli/src/__tests__/connect-command.test.ts +++ b/packages/cli/src/__tests__/connect-command.test.ts @@ -13,9 +13,12 @@ import { MANAGED_BEGIN, MANAGED_END, mergeManagedBlock, + NPX_LAUNCHER, + resolveServerLauncher, runConnect, writeCodexConfig, writeMcpJson, + XNET_PATH_LAUNCHER, type ConnectOptions } from '../commands/connect.js' @@ -36,8 +39,22 @@ describe('xnet connect', () => { expect(buildServerEntry({ dir, db: '/d.db' }).args).toEqual(['mcp', 'serve', '--db', '/d.db']) }) + it('registers an npx launcher when xnet is not on PATH (zero-install connect)', async () => { + // A PATH with no xnet bin anywhere → the npx fallback, so the registered + // server survives after the `npx @xnetjs/cli connect …` cache is gone. + expect(resolveServerLauncher({ PATH: dir })).toEqual(NPX_LAUNCHER) + + // A PATH dir that does hold an xnet bin → register the real thing. + await writeFile(join(dir, 'xnet'), '#!/bin/sh\n') + expect(resolveServerLauncher({ PATH: `${dir}` })).toEqual(XNET_PATH_LAUNCHER) + + const entry = buildServerEntry({ dir, db: '/d.db' }, NPX_LAUNCHER) + expect(entry.command).toBe('npx') + expect(entry.args).toEqual(['-y', '@xnetjs/cli', 'mcp', 'serve', '--db', '/d.db']) + }) + it('claude-code writes skill, .mcp.json, and CLAUDE.md; is idempotent', async () => { - const changes = await runConnect('claude-code', { ...base, dir }) + const changes = await runConnect('claude-code', { ...base, dir }, XNET_PATH_LAUNCHER) const byPath = Object.fromEntries(changes.map((c) => [c.path.replace(dir, ''), c.status])) expect(byPath['/.claude/skills/xnet/SKILL.md']).toBe('created') expect(byPath['/.mcp.json']).toBe('created') @@ -48,12 +65,12 @@ describe('xnet connect', () => { expect(mcp.mcpServers.xnet.env).toEqual({ XNET_READONLY: '1' }) // Re-run: everything unchanged. - const again = await runConnect('claude-code', { ...base, dir }) + const again = await runConnect('claude-code', { ...base, dir }, XNET_PATH_LAUNCHER) expect(again.every((c) => c.status === 'unchanged')).toBe(true) }) it('codex writes AGENTS.md and .codex/config.toml with a valid server block', async () => { - const changes = await runConnect('codex', { ...base, dir, writes: true }) + const changes = await runConnect('codex', { ...base, dir, writes: true }, XNET_PATH_LAUNCHER) const byPath = Object.fromEntries(changes.map((c) => [c.path.replace(dir, ''), c.status])) expect(byPath['/AGENTS.md']).toBe('created') expect(byPath['/.codex/config.toml']).toBe('created') @@ -84,7 +101,7 @@ describe('xnet connect', () => { const original = '# My project\n\n@AGENTS.md\n\nHouse rules that took months.\n' await writeFile(join(dir, 'CLAUDE.md'), original) - await runConnect('claude-code', { ...base, dir }) + await runConnect('claude-code', { ...base, dir }, XNET_PATH_LAUNCHER) const merged = await readFile(join(dir, 'CLAUDE.md'), 'utf8') expect(merged).toContain('# My project') expect(merged).toContain('House rules that took months.') @@ -94,11 +111,11 @@ describe('xnet connect', () => { }) it('rewrites only the managed block on a re-run, leaving edits outside it', async () => { - await runConnect('claude-code', { ...base, dir }) + await runConnect('claude-code', { ...base, dir }, XNET_PATH_LAUNCHER) const first = await readFile(join(dir, 'CLAUDE.md'), 'utf8') await writeFile(join(dir, 'CLAUDE.md'), `${first}\n## My own section\n\nKeep me.\n`) - const again = await runConnect('claude-code', { ...base, dir }) + const again = await runConnect('claude-code', { ...base, dir }, XNET_PATH_LAUNCHER) const merged = await readFile(join(dir, 'CLAUDE.md'), 'utf8') expect(merged).toContain('## My own section') expect(merged).toContain('Keep me.') @@ -109,7 +126,7 @@ describe('xnet connect', () => { it('preserves an existing AGENTS.md on the codex path', async () => { await writeFile(join(dir, 'AGENTS.md'), '# Existing agent rules\n') - await runConnect('codex', { ...base, dir }) + await runConnect('codex', { ...base, dir }, XNET_PATH_LAUNCHER) const merged = await readFile(join(dir, 'AGENTS.md'), 'utf8') expect(merged).toContain('# Existing agent rules') expect(merged).toContain(MANAGED_BEGIN) diff --git a/packages/cli/src/commands/connect.ts b/packages/cli/src/commands/connect.ts index 8215f462b..aa737456f 100644 --- a/packages/cli/src/commands/connect.ts +++ b/packages/cli/src/commands/connect.ts @@ -18,9 +18,10 @@ * shell-less clients. */ +import { existsSync } from 'node:fs' import { mkdir, readFile, writeFile } from 'node:fs/promises' import { homedir } from 'node:os' -import { dirname, join, resolve } from 'node:path' +import { delimiter, dirname, join, resolve } from 'node:path' import { XNET_AGENT_SKILL_MD } from '@xnetjs/plugins/node' import { Command } from 'commander' import { parse as parseToml, stringify as stringifyToml } from 'smol-toml' @@ -52,14 +53,44 @@ export type McpServerEntry = { command: string; args: string[]; env?: Record { const dir = resolve(options.dir) const changes: ConnectChange[] = [] - const entry = buildServerEntry(options) + const entry = buildServerEntry(options, launcher) if (harness === 'claude-code') { changes.push( diff --git a/site/public/agents.md b/site/public/agents.md new file mode 100644 index 000000000..da147daa7 --- /dev/null +++ b/site/public/agents.md @@ -0,0 +1,64 @@ +# xNet, for the agent reading this + +You are (or are about to be) connected to a user's **xNet workspace**: their +documents, databases, and canvases, stored locally on their device in a +SQLite-backed, CRDT-synced store. The store is the source of truth. Every +write is signed and lands in a hash-chained change log the user can audit — +including yours. + +## How you were (or will be) connected + +``` +npx @xnetjs/cli connect claude-code # or: connect codex +``` + +That command installed a ~500-token `SKILL.md` (the full workflow contract), +registered the xNet MCP server as a fallback lane, and wrote a short section +into `CLAUDE.md` / `AGENTS.md`. It is idempotent and read-only by default; +write access exists only if your human passed `--writes`. + +## The three lanes, cheapest first + +1. **CLI.** `xnet search ""`, `xnet query --where field=value`, + `xnet db get ` — plain TSV/stdout you can grep and pipe. Reach here + first; it costs far fewer tokens than tool schemas. +2. **Vault checkout.** `xnet checkout --query ""` materializes a + _scoped_ slice of the workspace as Markdown + JSONL files. Read and Edit + them like any files, then `xnet commit` turns your edits into + schema-validated mutation plans. Never check out the whole workspace. +3. **MCP.** The registered `xnet` server, for when you have no shell. Slim by + design; read-only unless connected with `--writes`. + +## The safety contract (structural, not policy) + +- **You cannot corrupt the store.** Every write path — file edit, CLI, MCP — + funnels through the same plan → validate → apply core. Malformed or + stale writes are quarantined as conflict notes in `.xnet/conflicts/`, never + silently applied. +- **You sign as yourself.** Writes require an enrolled agent passport or an + explicit key. The CLI refuses to persist a write under a throwaway + identity. +- **Everything you do is auditable.** Your changes land in the signed change + log next to everyone else's. The user can verify what you did without + trusting you — behave accordingly. + +## Key CLI verbs + +| Command | Purpose | +| ----------------------------- | ------------------------------------------- | +| `xnet checkout` | Materialize / extend a scoped checkout | +| `xnet status` | Pending plans and conflicts | +| `xnet commit [--apply]` | Turn file edits into applied mutations | +| `xnet search ""` | Ranked full-text search | +| `xnet query --where f=v` | Query tables (TSV default) | +| `xnet db get/set` | Single-cell reads and writes | +| `xnet run ` | Sandboxed bulk work, write _proposals_ only | +| `xnet skill` | Print the SKILL.md contract | + +## Read more + +- Full docs, one file: https://xnet.fyi/llms-full.txt +- Connect guide: https://xnet.fyi/docs/guides/coding-agents/ +- Agent interfaces (checkout layout, benchmark methodology): + https://xnet.fyi/docs/guides/agent-interfaces/ +- Building _apps_ on xNet instead? https://xnet.fyi/docs/ai/understanding-xnet/ diff --git a/site/public/llms-full.txt b/site/public/llms-full.txt index 5e7626bb9..fb3aac9ce 100644 --- a/site/public/llms-full.txt +++ b/site/public/llms-full.txt @@ -15,8 +15,11 @@ Before reading this documentation, understand that xNet works differently: ## Table of Contents - What is xNet? + - Connect Your Agent - Quick Start - Core Concepts + - Agent Interfaces +- Understanding xNet (For AI Assistants) - Hooks Overview - useQuery - useMutate @@ -55,8 +58,6 @@ Before reading this documentation, understand that xNet works differently: - Identity & Keys - Real-time Collaboration - Plugin Development - - Agent Interfaces - - Use xNet from Claude Code / Codex - Hub Setup - Your Own Server - Use xNet From Any Framework @@ -76,7 +77,6 @@ Before reading this documentation, understand that xNet works differently: - Getting Started - Code Style - Testing Guide -- Understanding xNet (For AI Assistants) --- @@ -168,6 +168,107 @@ For most apps, you only install `@xnetjs/react` and `@xnetjs/data`. Everything e --- +## Connect Your Agent + +**You will learn** + +- How to connect a coding agent to your workspace with one command +- The three lanes an agent uses, cheapest first +- How writes stay safe when the app and the agent touch the same data + + +The same way people point Claude Code at an Obsidian vault, you can point it at +your **xNet workspace** — and read, search, query, and edit your pages, +databases, and canvases without leaving the terminal. The +[Agent Interfaces](/docs/guides/agent-interfaces/) guide covers the surfaces in +depth; this one is the quick on-ramp. + +## One-step connect + +No install needed — `npx` runs the published CLI directly: + +```bash +npx @xnetjs/cli connect claude-code # zero-install, project scope, read-only +``` + +Or, with the CLI installed (`npm install -g @xnetjs/cli`): + +```bash +xnet connect claude-code # project scope, read-only +xnet connect claude-code --user # also install the skill for all projects +xnet connect claude-code --writes # register the MCP server with write access +xnet connect codex # Codex: AGENTS.md + .codex/config.toml +``` + +`xnet connect` is **idempotent** — it installs the `SKILL.md`, registers the +xNet MCP server, writes a `CLAUDE.md`/`AGENTS.md` contract, and (with +`--vault `) bootstraps a scoped checkout with an index. Re-running it +reports what is already in place and changes nothing. It finishes by running +`xnet doctor --agent-access`, which confirms the backend, full-text search, and +signing identity are all reachable. + +The registered server launches however the CLI is actually available: as +`xnet` when the bin is on your PATH, or as `npx -y @xnetjs/cli` after a +zero-install connect — so the registration keeps working either way. + +## Works whether or not the app is running + +The agent verbs resolve a backend automatically: + +1. the desktop app's local API (`:31415`) when it is running, otherwise +2. a standalone SQLite store — discovered from the app's data directory, or + pointed at explicitly with `--db ` (or `$XNET_DB`). + +So an agent can work against your data even with the app closed, and web-only +users can point at any local store. Writes made this way are signed by a key you +provide (`--key`, `$XNET_SIGNING_KEY`) or by an +[enrolled agent passport](/docs/guides/identity/) (`--agent `); the CLI +refuses to persist a write under a silent throwaway identity. + +## Three lanes, cheapest first + +```mermaid +flowchart LR + A[Agent in Claude Code / Codex] -->|Bash| CLI[1 · xnet CLI] + A -->|Read / Edit / Grep| V[2 · Vault checkout] + A -->|MCP client| M[3 · xnet mcp serve] + CLI --> Core[(xNet store · signed change log)] + V <-->|checkout / commit| CLI + M --> Core +``` + +1. **CLI** — `xnet search`/`query`/`db get` print plain stdout. Reach here + first: it costs far fewer tokens than loading a wall of MCP tool schemas. +2. **Vault** — `xnet checkout --query "…"` materializes a **scoped** slice as + Markdown you edit, then `xnet commit` lifts the edits back through validated + mutation plans. Never check out the whole workspace. +3. **MCP** — the registered `xnet` server is the fallback for shell-less + clients (Claude Desktop, browser). It is **read-only** unless you connected + with `--writes`. + +## Safe when the app and the agent collide + +The store stays the source of truth, and every edit becomes a validated plan — +never a blind file-to-database sync. If the app writes a node after you checked +it out, committing your now-stale vault edit surfaces a **conflict** instead of +overwriting the newer value: + +```bash +$ xnet status +conflict Pages/shared-page.md stale-export based on updatedAt:10, live is updatedAt:11 +``` + +Fix the file against the current value and commit again. Stale edits never +silently win. + +## Further reading + +- [Agent Interfaces](/docs/guides/agent-interfaces/) — the checkout layout, the + full CLI, `SKILL.md`, and the slim MCP surface +- [Identity](/docs/guides/identity/) — agent passports and signing + +--- + ## Quick Start **You will learn** @@ -507,60 +608,398 @@ When you create or update a node, xNet: 3. Stores the signed change locally 4. Syncs the signed change to peers -When a peer receives a change, they **verify the hybrid signature** before applying it. This means: +When a peer receives a change, they **verify the hybrid signature** before applying it. This means: + +- You can prove who made every change +- Tampered data is rejected +- No server is needed to enforce trust +- Changes are protected against quantum forgery attacks + +The default **Level 1 (Hybrid)** signature carries both an Ed25519 component (64 bytes) and an ML-DSA-65 component (~3.3 KB). Both must verify — if either is invalid, the change is rejected. Level 0 (Ed25519 only) is available for high-frequency paths where the smaller signature size matters more than quantum resistance. + +## Identity is a key pair + +Your identity in xNet is a **DID:key** — a decentralized identifier derived from an Ed25519 public key: + +``` +did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK +``` + +No accounts. No passwords. No auth server. Your key pair is your identity. You can delegate permissions to other keys using **UCAN tokens** (capability-based authorization). + +## Authorization is encryption + +xNet enforces read access cryptographically. Every private node is encrypted with a **per-node content key**. Only users in the **recipients list** can unwrap the key and read the node — the hub never decrypts anything. + +You define access control in the schema itself: + +```ts +const TaskSchema = defineSchema({ + // ... + authorization: { + roles: { + owner: role.creator(), + editor: role.property('editors') + }, + actions: { + read: allow('owner', 'editor'), + write: allow('owner', 'editor'), + share: allow('owner') + } + } +}) +``` + +Then check permissions in components: + +```tsx +const { canWrite, canShare } = useCan(taskId) +const { grants, grant, revoke } = useGrants(taskId) +``` + +Grants are stored as ordinary nodes — they sync, replicate, and inherit the same CRDT conflict resolution as all your data. + +See the [Authorization Guide](/docs/guides/authorization/) for the full model including delegation, key recovery, and offline policy. + +## Next steps + +--- + +## Agent Interfaces + +**You will learn** + +- Why the filesystem is the primary agent surface +- The checkout layout and how edits flow back safely +- The `xnet` CLI verbs and the ~500-token SKILL.md +- When to fall back to MCP + + +## Files first + +Coding agents are already excellent at reading, grepping, and editing files — +so xNet's primary agent interface is a **filesystem projection of the +workspace**, not a wall of tool definitions. The store stays the source of +truth; the checkout is a working tree. Every surface (files, CLI, MCP) funnels +into the same plan → validate → apply core, so an agent can never corrupt the +workspace with a malformed write. + +Measured on a 15-task benchmark, the files+CLI surface completes the same work +at **~0.11x the tokens** of a traditional MCP toolset (0.05x on synthesis +tasks), with equal task success. The +[methodology and how to reproduce it](#benchmark-methodology) are below. + +## The checkout + +`xnet checkout` materializes a scoped slice of the workspace into a folder: + +``` +Pages/q3-planning.md # Markdown + YAML frontmatter (xnet.id, revision) +Databases/tasks.schema.json # the schema +Databases/tasks.rows.jsonl # one JSON object per line — edit/append/delete +Databases/tasks.tsv # read-only fast-read sidecar (large tables) +Canvases/roadmap.canvas # JSON Canvas projection +SKILL.md # the agent contract (~500 tokens) +.xnet/ # manifest, conflict notes +``` + +Filenames are semantic slugs; identity lives in frontmatter and the manifest, +never in filenames. Wikilinks (`[[Title]]`), `xnet-database` blocks, and +`{{xnet-ref}}` directives are live references and survive round-trips. + +Checkouts are **scoped and incremental** — by query, schema, kind, or node +(`xnet checkout --query "roadmap"`); repeated checkouts merge. Agents never +need (and are told not to) export the whole workspace. + +### Edits become validated plans + +File edits are diffed into **mutation plans**, validated against schemas, and +applied to the store — automatically with `xnet daemon` running, or on +`xnet commit`. Anything that doesn't validate, or collides with a newer +revision, is **quarantined as a conflict**: a Markdown note in +`.xnet/conflicts/` explains what happened and how to resolve it. Stale edits +never silently overwrite newer data. + +## The xnet CLI + +| Command | Purpose | +| --- | --- | +| `xnet checkout` | Materialize / extend a scoped checkout | +| `xnet status` | Pending plans and conflicts | +| `xnet commit [--apply]` | Turn file edits into applied mutations | +| `xnet search ""` | Ranked full-text search (TSV: id, slug, title, snippet) | +| `xnet query --where field=value` | Query tables (TSV default; `jsonl`/`json`/`md`) | +| `xnet db get/set` | Single-cell reads and writes | +| `xnet run ` | Sandboxed bulk work over a bounded `api` object | +| `xnet daemon` | Watch the checkout and auto-apply valid edits | +| `xnet skill` | Print SKILL.md | + +Output is TSV-first and concise by default (`--detailed` to opt out) — built +for being read by a model, not a human. + +`xnet run` executes scripts in a sandbox with bounded reads and **write +proposals** (`api.proposeUpdate`, `api.proposeCreate`) that flow through the +same validated plan pipeline as file edits. + +## SKILL.md + +Every checkout ships a cross-harness `SKILL.md` (~500 tokens) that teaches any +agent — Claude Code, Codex, Gemini CLI, Cursor — the layout and workflow above. +That's the entire standing context cost, versus ~5k tokens of always-loaded +tool definitions for a typical MCP server. Print it any time with +`xnet skill`. + +## Slim MCP + +MCP remains as the **no-shell fallback** for harnesses that can't run a CLI. +It's deliberately slim: five stable core tools are loaded eagerly and +everything else defers (the non-deferred payload is test-guarded at ≤1.5k +tokens). Responses are compact JSON by default with +`response_format: concise | detailed` on every tool. + +The doctrine, in order: **files for reading and editing, the CLI for what +files can't express, MCP only when there's no shell.** + +## Benchmark methodology + +The "~0.11x the tokens" claim comes from an in-repo, reproducible benchmark — +not a one-off measurement. What it is, precisely: + +- **The suite**: 15 tasks (read a page, edit a page, query a database, bulk + update, cross-node synthesis) run against a seeded workspace fixture. Every + task really executes against the same plan → validate → apply core the + product uses; **success is measured, not assumed**. +- **Three surfaces**: `files-cli` (vault checkout + file tools + `xnet` CLI), + `mcp-legacy` (all tool definitions standing, pretty-printed JSON), and + `mcp-slim` (core tools standing, compact JSON — today's MCP fallback). +- **What's counted**: a token cost model (~4 chars/token) over the *actual + bytes* each surface moves through model context — standing tool/skill + definitions, request arguments, tool responses, file contents read, edit + diffs, CLI commands and their outputs. +- **What's not counted**: model reasoning tokens. This is an **interface cost + model** — it measures what each surface forces into context, not how a + specific model thinks. Live harness runs with pinned versions are tracked + separately (exploration 0161). +- **Result**: files+CLI at 0.111x the total tokens of the legacy MCP surface + overall, 0.050x on synthesis tasks, 15/15 task success on every surface. + +Reproduce it from a repo checkout: + +```bash +pnpm bench:agent-surfaces +``` + +Source: `packages/plugins/src/benchmarks/agent-surface-benchmark.ts`. The +ratios are **regression-guarded in CI** by +`agent-surface-benchmark.test.ts`, so the numbers quoted here fail the build +if the surfaces drift. + +## Further reading + +- [For AI Assistants](/docs/ai/understanding-xnet/) — the mental model for + agents writing *app code* against xNet +- [Plugins](/docs/guides/plugins/) — the integration layer MCP belongs to + +--- + +## Understanding xNet (For AI Assistants) + +# Understanding xNet: A Guide for AI Assistants + +If you're an AI assistant helping a developer build with xNet, this guide will help you understand the fundamental paradigm differences. + +**Working inside a user's workspace?** +This page is about writing *application code* against xNet. If you are an +agent operating on a user's actual data — reading pages, editing databases, +searching their workspace — use the **files-first agent surface** instead: a +scoped checkout of the workspace plus the `xnet` CLI, taught by a ~500-token +`SKILL.md` (printable with `xnet skill`). It completes the same work at +roughly a tenth of the tokens of an MCP toolset. See +[Agent Interfaces](/docs/guides/agent-interfaces/). + +## The Key Insight + +**xNet has no backend.** + +This is not a simplification or a limitation — it's the core design principle. Data lives on the user's device and syncs peer-to-peer. + +## Mental Model Comparison + +### Traditional Architecture + +``` +User → Frontend → API → Backend → Database + ↑ + Auth Service +``` + +### xNet Architecture + +``` +User → React Hooks → Local Storage ←→ P2P Sync ←→ Other Devices + ↑ + Cryptographic Identity (built-in) +``` + +## What This Means Practically + +### There Are No API Endpoints + +When a user asks "create an API to save tasks", the correct response is: + +```typescript +// Don't create this: +// app.post('/api/tasks', handler) + +// Do this instead: +const { create } = useMutate() +await create(TaskSchema, { title: 'New task' }) +``` + +The `create` function writes to local storage. Sync happens automatically. + +### There Is No Auth System to Implement + +When a user asks "add authentication", the correct response is: + +```typescript +// Don't create OAuth/JWT flows + +// Identity is already built-in: +const { did, displayName } = useIdentity() + +// DID is a cryptographic identifier like: +// did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK +``` + +### Offline Just Works + +When a user asks "handle offline mode", the correct response is: + +```typescript +// Don't add service workers or cache layers + +// It already works: +const { data } = useQuery(TaskSchema) +// This reads from local storage +// Works whether online or offline +// Syncs when connectivity returns +``` + +### Real-time Is Built-in + +When a user asks "add real-time updates", the correct response is: + +```typescript +// Don't create WebSocket connections + +// Hooks are already reactive: +const { data: tasks } = useQuery(TaskSchema) +// When another device syncs changes, +// this component re-renders automatically +``` + +## Common Mistakes to Avoid + +| User Request | Wrong Approach | Correct Approach | +| ---------------- | ------------------ | -------------------------- | +| "Save data" | Create REST API | Use `useMutate().create()` | +| "Fetch data" | Use fetch/axios | Use `useQuery()` | +| "Add auth" | Implement OAuth | Use `useIdentity()` | +| "Handle offline" | Add service worker | Already works | +| "Real-time sync" | Add WebSockets | Already built-in | +| "Manage state" | Add Redux/Zustand | Hooks handle this | +| "Optimistic UI" | Manual rollback | Built into mutations | + +## The Schema System + +Schemas are central to xNet. They define data shapes AND provide type inference: + +```typescript + +const TaskSchema = defineSchema({ + name: 'Task', + namespace: 'xnet://myapp/', + properties: { + title: text({ required: true }), + status: select({ options: ['todo', 'done'] as const }), + assignee: relation({ schema: 'xnet://myapp/Person' }) + } +}) + +// Now hooks are fully typed: +const { data } = useQuery(TaskSchema) +data[0].title // string +data[0].status // 'todo' | 'done' +``` + +## When to Suggest Server Code + +The only times server code is appropriate: + +1. **Hub setup** — If deploying an always-on relay (optional) +2. **External integrations** — Webhooks, third-party APIs +3. **Heavy computation** — AI/ML that can't run on device + +Even then, the Hub is just a relay — it doesn't process or store application logic. -- You can prove who made every change -- Tampered data is rejected -- No server is needed to enforce trust -- Changes are protected against quantum forgery attacks +## Quick Reference -The default **Level 1 (Hybrid)** signature carries both an Ed25519 component (64 bytes) and an ML-DSA-65 component (~3.3 KB). Both must verify — if either is invalid, the change is rejected. Level 0 (Ed25519 only) is available for high-frequency paths where the smaller signature size matters more than quantum resistance. +### Reading Data -## Identity is a key pair +```typescript +// Single item +const { data: task } = useQuery(TaskSchema, taskId) -Your identity in xNet is a **DID:key** — a decentralized identifier derived from an Ed25519 public key: +// All items +const { data: tasks } = useQuery(TaskSchema) -``` -did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK +// Filtered +const { data: todos } = useQuery(TaskSchema, { + where: { status: 'todo' } +}) ``` -No accounts. No passwords. No auth server. Your key pair is your identity. You can delegate permissions to other keys using **UCAN tokens** (capability-based authorization). +### Writing Data -## Authorization is encryption +```typescript +const { create, update, remove } = useMutate() -xNet enforces read access cryptographically. Every private node is encrypted with a **per-node content key**. Only users in the **recipients list** can unwrap the key and read the node — the hub never decrypts anything. +// Create +const id = await create(TaskSchema, { title: 'New task' }) -You define access control in the schema itself: +// Update +await update(TaskSchema, id, { status: 'done' }) -```ts -const TaskSchema = defineSchema({ - // ... - authorization: { - roles: { - owner: role.creator(), - editor: role.property('editors') - }, - actions: { - read: allow('owner', 'editor'), - write: allow('owner', 'editor'), - share: allow('owner') - } - } -}) +// Delete +await remove(id) ``` -Then check permissions in components: +### Collaborative Editing -```tsx -const { canWrite, canShare } = useCan(taskId) -const { grants, grant, revoke } = useGrants(taskId) +```typescript +const { data: page, doc } = useNode(PageSchema, pageId) +// doc is a Y.Doc for Yjs collaborative editing ``` -Grants are stored as ordinary nodes — they sync, replicate, and inherit the same CRDT conflict resolution as all your data. +### User Identity -See the [Authorization Guide](/docs/guides/authorization/) for the full model including delegation, key recovery, and offline policy. +```typescript +const { did, displayName, avatarUrl } = useIdentity() +// did:key:z6Mk... is the user's cryptographic identity +``` -## Next steps +## Summary + +When helping with xNet: + +1. **Never suggest API endpoints** — Use hooks +2. **Never implement auth** — It's cryptographic and built-in +3. **Never manage state** — Hooks are reactive +4. **Never handle offline** — It just works +5. **Always use schemas** — They're the source of truth + +The paradigm shift: **data is local, sync is automatic, identity is cryptographic**. --- @@ -7425,229 +7864,38 @@ await registry.install(manifest, { provenance }) // provenance === 'ai-generated A plugin that declares `network` hosts is handed a `guardedFetch` instead of the ambient `fetch`. Every request is checked against the allowlist before it leaves; an undeclared host rejects with `CapabilityError`, and a plugin with no `network` grant gets no egress at all. -```ts - -const fetch = guardedFetch({ network: ['api.acme.com'] }, 'com.acme.kanban') -await fetch('https://api.acme.com/cards') // ok -await fetch('https://evil.example.com') // rejects: CapabilityError -``` - -## Publishing to the marketplace - -Your plugin's code stays in **your own repo** — getting listed on the -[plugins marketplace](/plugins) is a one-line pull request, not a code dump into -the monorepo. - -1. **Ship a Release.** Build your plugin into a single `plugin.js` and attach it, - with `manifest.json`, to a GitHub Release. (A copyable template with a release - workflow lives in - [`examples/xnet-plugin-template`](https://github.com/crs48/xNet/tree/main/examples/xnet-plugin-template).) - -2. **Add one entry** to [`registry/community.json`](https://github.com/crs48/xNet/tree/main/registry): - - ```jsonc - [{ "repo": "you/xnet-plugin-kanban", "category": "views" }] - ``` - -3. **Open a PR.** CI validates the submission; after merge, a daily job enriches - your entry from the GitHub API and points the install link at your Release's - `manifest.json`. Updates you ship afterward are picked up automatically. - -The app installs community plugins in a sandbox scoped to their trust tier, -after showing the user the capabilities the plugin requests and verifying its -provenance. See [`registry/README.md`](https://github.com/crs48/xNet/tree/main/registry) -for the full flow. - ---- - -## Agent Interfaces - -**You will learn** - -- Why the filesystem is the primary agent surface -- The checkout layout and how edits flow back safely -- The `xnet` CLI verbs and the ~500-token SKILL.md -- When to fall back to MCP - - -## Files first - -Coding agents are already excellent at reading, grepping, and editing files — -so xNet's primary agent interface is a **filesystem projection of the -workspace**, not a wall of tool definitions. The store stays the source of -truth; the checkout is a working tree. Every surface (files, CLI, MCP) funnels -into the same plan → validate → apply core, so an agent can never corrupt the -workspace with a malformed write. - -Measured on a 15-task benchmark, the files+CLI surface completes the same work -at **~0.11x the tokens** of a traditional MCP toolset (0.05x on synthesis -tasks), with equal task success. - -## The checkout - -`xnet checkout` materializes a scoped slice of the workspace into a folder: - -``` -Pages/q3-planning.md # Markdown + YAML frontmatter (xnet.id, revision) -Databases/tasks.schema.json # the schema -Databases/tasks.rows.jsonl # one JSON object per line — edit/append/delete -Databases/tasks.tsv # read-only fast-read sidecar (large tables) -Canvases/roadmap.canvas # JSON Canvas projection -SKILL.md # the agent contract (~500 tokens) -.xnet/ # manifest, conflict notes -``` - -Filenames are semantic slugs; identity lives in frontmatter and the manifest, -never in filenames. Wikilinks (`[[Title]]`), `xnet-database` blocks, and -`{{xnet-ref}}` directives are live references and survive round-trips. - -Checkouts are **scoped and incremental** — by query, schema, kind, or node -(`xnet checkout --query "roadmap"`); repeated checkouts merge. Agents never -need (and are told not to) export the whole workspace. - -### Edits become validated plans - -File edits are diffed into **mutation plans**, validated against schemas, and -applied to the store — automatically with `xnet daemon` running, or on -`xnet commit`. Anything that doesn't validate, or collides with a newer -revision, is **quarantined as a conflict**: a Markdown note in -`.xnet/conflicts/` explains what happened and how to resolve it. Stale edits -never silently overwrite newer data. - -## The xnet CLI - -| Command | Purpose | -| --- | --- | -| `xnet checkout` | Materialize / extend a scoped checkout | -| `xnet status` | Pending plans and conflicts | -| `xnet commit [--apply]` | Turn file edits into applied mutations | -| `xnet search ""` | Ranked full-text search (TSV: id, slug, title, snippet) | -| `xnet query --where field=value` | Query tables (TSV default; `jsonl`/`json`/`md`) | -| `xnet db get/set` | Single-cell reads and writes | -| `xnet run ` | Sandboxed bulk work over a bounded `api` object | -| `xnet daemon` | Watch the checkout and auto-apply valid edits | -| `xnet skill` | Print SKILL.md | - -Output is TSV-first and concise by default (`--detailed` to opt out) — built -for being read by a model, not a human. - -`xnet run` executes scripts in a sandbox with bounded reads and **write -proposals** (`api.proposeUpdate`, `api.proposeCreate`) that flow through the -same validated plan pipeline as file edits. - -## SKILL.md - -Every checkout ships a cross-harness `SKILL.md` (~500 tokens) that teaches any -agent — Claude Code, Codex, Gemini CLI, Cursor — the layout and workflow above. -That's the entire standing context cost, versus ~5k tokens of always-loaded -tool definitions for a typical MCP server. Print it any time with -`xnet skill`. - -## Slim MCP - -MCP remains as the **no-shell fallback** for harnesses that can't run a CLI. -It's deliberately slim: five stable core tools are loaded eagerly and -everything else defers (the non-deferred payload is test-guarded at ≤1.5k -tokens). Responses are compact JSON by default with -`response_format: concise | detailed` on every tool. - -The doctrine, in order: **files for reading and editing, the CLI for what -files can't express, MCP only when there's no shell.** - -## Further reading - -- [For AI Assistants](/docs/ai/understanding-xnet/) — the mental model for - agents writing *app code* against xNet -- [Plugins](/docs/guides/plugins/) — the integration layer MCP belongs to - ---- - -## Use xNet from Claude Code / Codex - -**You will learn** - -- How to connect a coding agent to your workspace with one command -- The three lanes an agent uses, cheapest first -- How writes stay safe when the app and the agent touch the same data - - -The same way people point Claude Code at an Obsidian vault, you can point it at -your **xNet workspace** — and read, search, query, and edit your pages, -databases, and canvases without leaving the terminal. The -[Agent Interfaces](/docs/guides/agent-interfaces/) guide covers the surfaces in -depth; this one is the quick on-ramp. - -## One-step connect - -```bash -xnet connect claude-code # project scope, read-only -xnet connect claude-code --user # also install the skill for all projects -xnet connect claude-code --writes # register the MCP server with write access -xnet connect codex # Codex: AGENTS.md + .codex/config.toml -``` - -`xnet connect` is **idempotent** — it installs the `SKILL.md`, registers the -`xnet` MCP server, writes a `CLAUDE.md`/`AGENTS.md` contract, and (with -`--vault `) bootstraps a scoped checkout with an index. Re-running it -reports what is already in place and changes nothing. It finishes by running -`xnet doctor --agent-access`, which confirms the backend, full-text search, and -signing identity are all reachable. - -## Works whether or not the app is running - -The agent verbs resolve a backend automatically: - -1. the desktop app's local API (`:31415`) when it is running, otherwise -2. a standalone SQLite store — discovered from the app's data directory, or - pointed at explicitly with `--db ` (or `$XNET_DB`). - -So an agent can work against your data even with the app closed, and web-only -users can point at any local store. Writes made this way are signed by a key you -provide (`--key`, `$XNET_SIGNING_KEY`) or by an -[enrolled agent passport](/docs/guides/identity/) (`--agent `); the CLI -refuses to persist a write under a silent throwaway identity. - -## Three lanes, cheapest first +```ts -```mermaid -flowchart LR - A[Agent in Claude Code / Codex] -->|Bash| CLI[1 · xnet CLI] - A -->|Read / Edit / Grep| V[2 · Vault checkout] - A -->|MCP client| M[3 · xnet mcp serve] - CLI --> Core[(xNet store · signed change log)] - V <-->|checkout / commit| CLI - M --> Core +const fetch = guardedFetch({ network: ['api.acme.com'] }, 'com.acme.kanban') +await fetch('https://api.acme.com/cards') // ok +await fetch('https://evil.example.com') // rejects: CapabilityError ``` -1. **CLI** — `xnet search`/`query`/`db get` print plain stdout. Reach here - first: it costs far fewer tokens than loading a wall of MCP tool schemas. -2. **Vault** — `xnet checkout --query "…"` materializes a **scoped** slice as - Markdown you edit, then `xnet commit` lifts the edits back through validated - mutation plans. Never check out the whole workspace. -3. **MCP** — the registered `xnet` server is the fallback for shell-less - clients (Claude Desktop, browser). It is **read-only** unless you connected - with `--writes`. +## Publishing to the marketplace -## Safe when the app and the agent collide +Your plugin's code stays in **your own repo** — getting listed on the +[plugins marketplace](/plugins) is a one-line pull request, not a code dump into +the monorepo. -The store stays the source of truth, and every edit becomes a validated plan — -never a blind file-to-database sync. If the app writes a node after you checked -it out, committing your now-stale vault edit surfaces a **conflict** instead of -overwriting the newer value: +1. **Ship a Release.** Build your plugin into a single `plugin.js` and attach it, + with `manifest.json`, to a GitHub Release. (A copyable template with a release + workflow lives in + [`examples/xnet-plugin-template`](https://github.com/crs48/xNet/tree/main/examples/xnet-plugin-template).) -```bash -$ xnet status -conflict Pages/shared-page.md stale-export based on updatedAt:10, live is updatedAt:11 -``` +2. **Add one entry** to [`registry/community.json`](https://github.com/crs48/xNet/tree/main/registry): -Fix the file against the current value and commit again. Stale edits never -silently win. + ```jsonc + [{ "repo": "you/xnet-plugin-kanban", "category": "views" }] + ``` -## Further reading +3. **Open a PR.** CI validates the submission; after merge, a daily job enriches + your entry from the GitHub API and points the install link at your Release's + `manifest.json`. Updates you ship afterward are picked up automatically. -- [Agent Interfaces](/docs/guides/agent-interfaces/) — the checkout layout, the - full CLI, `SKILL.md`, and the slim MCP surface -- [Identity](/docs/guides/identity/) — agent passports and signing +The app installs community plugins in a sandbox scoped to their trust tier, +after showing the user the capabilities the plugin requests and verifying its +provenance. See [`registry/README.md`](https://github.com/crs48/xNet/tree/main/registry) +for the full flow. --- @@ -11642,204 +11890,3 @@ Tests cover core algorithms and security-critical paths. Each core package has t --- -## Understanding xNet (For AI Assistants) - -# Understanding xNet: A Guide for AI Assistants - -If you're an AI assistant helping a developer build with xNet, this guide will help you understand the fundamental paradigm differences. - -**Working inside a user's workspace?** -This page is about writing *application code* against xNet. If you are an -agent operating on a user's actual data — reading pages, editing databases, -searching their workspace — use the **files-first agent surface** instead: a -scoped checkout of the workspace plus the `xnet` CLI, taught by a ~500-token -`SKILL.md` (printable with `xnet skill`). It completes the same work at -roughly a tenth of the tokens of an MCP toolset. See -[Agent Interfaces](/docs/guides/agent-interfaces/). - -## The Key Insight - -**xNet has no backend.** - -This is not a simplification or a limitation — it's the core design principle. Data lives on the user's device and syncs peer-to-peer. - -## Mental Model Comparison - -### Traditional Architecture - -``` -User → Frontend → API → Backend → Database - ↑ - Auth Service -``` - -### xNet Architecture - -``` -User → React Hooks → Local Storage ←→ P2P Sync ←→ Other Devices - ↑ - Cryptographic Identity (built-in) -``` - -## What This Means Practically - -### There Are No API Endpoints - -When a user asks "create an API to save tasks", the correct response is: - -```typescript -// Don't create this: -// app.post('/api/tasks', handler) - -// Do this instead: -const { create } = useMutate() -await create(TaskSchema, { title: 'New task' }) -``` - -The `create` function writes to local storage. Sync happens automatically. - -### There Is No Auth System to Implement - -When a user asks "add authentication", the correct response is: - -```typescript -// Don't create OAuth/JWT flows - -// Identity is already built-in: -const { did, displayName } = useIdentity() - -// DID is a cryptographic identifier like: -// did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK -``` - -### Offline Just Works - -When a user asks "handle offline mode", the correct response is: - -```typescript -// Don't add service workers or cache layers - -// It already works: -const { data } = useQuery(TaskSchema) -// This reads from local storage -// Works whether online or offline -// Syncs when connectivity returns -``` - -### Real-time Is Built-in - -When a user asks "add real-time updates", the correct response is: - -```typescript -// Don't create WebSocket connections - -// Hooks are already reactive: -const { data: tasks } = useQuery(TaskSchema) -// When another device syncs changes, -// this component re-renders automatically -``` - -## Common Mistakes to Avoid - -| User Request | Wrong Approach | Correct Approach | -| ---------------- | ------------------ | -------------------------- | -| "Save data" | Create REST API | Use `useMutate().create()` | -| "Fetch data" | Use fetch/axios | Use `useQuery()` | -| "Add auth" | Implement OAuth | Use `useIdentity()` | -| "Handle offline" | Add service worker | Already works | -| "Real-time sync" | Add WebSockets | Already built-in | -| "Manage state" | Add Redux/Zustand | Hooks handle this | -| "Optimistic UI" | Manual rollback | Built into mutations | - -## The Schema System - -Schemas are central to xNet. They define data shapes AND provide type inference: - -```typescript - -const TaskSchema = defineSchema({ - name: 'Task', - namespace: 'xnet://myapp/', - properties: { - title: text({ required: true }), - status: select({ options: ['todo', 'done'] as const }), - assignee: relation({ schema: 'xnet://myapp/Person' }) - } -}) - -// Now hooks are fully typed: -const { data } = useQuery(TaskSchema) -data[0].title // string -data[0].status // 'todo' | 'done' -``` - -## When to Suggest Server Code - -The only times server code is appropriate: - -1. **Hub setup** — If deploying an always-on relay (optional) -2. **External integrations** — Webhooks, third-party APIs -3. **Heavy computation** — AI/ML that can't run on device - -Even then, the Hub is just a relay — it doesn't process or store application logic. - -## Quick Reference - -### Reading Data - -```typescript -// Single item -const { data: task } = useQuery(TaskSchema, taskId) - -// All items -const { data: tasks } = useQuery(TaskSchema) - -// Filtered -const { data: todos } = useQuery(TaskSchema, { - where: { status: 'todo' } -}) -``` - -### Writing Data - -```typescript -const { create, update, remove } = useMutate() - -// Create -const id = await create(TaskSchema, { title: 'New task' }) - -// Update -await update(TaskSchema, id, { status: 'done' }) - -// Delete -await remove(id) -``` - -### Collaborative Editing - -```typescript -const { data: page, doc } = useNode(PageSchema, pageId) -// doc is a Y.Doc for Yjs collaborative editing -``` - -### User Identity - -```typescript -const { did, displayName, avatarUrl } = useIdentity() -// did:key:z6Mk... is the user's cryptographic identity -``` - -## Summary - -When helping with xNet: - -1. **Never suggest API endpoints** — Use hooks -2. **Never implement auth** — It's cryptographic and built-in -3. **Never manage state** — Hooks are reactive -4. **Never handle offline** — It just works -5. **Always use schemas** — They're the source of truth - -The paradigm shift: **data is local, sync is automatic, identity is cryptographic**. - ---- - diff --git a/site/public/llms.txt b/site/public/llms.txt index c14000eeb..bad7495a0 100644 --- a/site/public/llms.txt +++ b/site/public/llms.txt @@ -1,6 +1,16 @@ # xNet -> xNet is a local-first framework for building multiplayer applications with React. Data lives on the device, syncs peer-to-peer via CRDTs, and works offline. No backend required. +> xNet is a local-first workspace and framework. Data lives on the device, syncs peer-to-peer via CRDTs, and works offline. Coding agents are first-class users: one command connects Claude Code or Codex to a user's workspace. No backend required. + +## If you are a coding agent + +Your human can connect you to their xNet workspace in one step — no prior install: + +- `npx @xnetjs/cli connect claude-code` (or `connect codex`) — installs a ~500-token SKILL.md, registers the xNet MCP server (read-only by default), and self-checks. +- After connect, prefer lanes in this order: the `xnet` CLI (plain stdout, token-cheap), a scoped vault checkout (Markdown/JSONL files you can Read/Edit; `xnet commit` lifts edits into validated mutation plans), and MCP only when there is no shell. +- [What am I connecting to?](https://xnet.fyi/agents.md): the agent-facing summary — lanes, CLI verbs, safety contract +- [Connect Your Agent](https://xnet.fyi/docs/guides/coding-agents/): the one-step connect guide +- [Agent Interfaces](https://xnet.fyi/docs/guides/agent-interfaces/): checkout layout, CLI verbs, SKILL.md, slim MCP, benchmark methodology ## Key Concepts @@ -67,6 +77,7 @@ xNet is different from traditional client-server architectures: - [Identity](https://xnet.fyi/docs/guides/identity/): Cryptographic identity - [Collaboration](https://xnet.fyi/docs/guides/collaboration/): Real-time multiplayer - [Plugins](https://xnet.fyi/docs/guides/plugins/): Extend the platform +- [Connect Your Agent](https://xnet.fyi/docs/guides/coding-agents/): One-step xnet connect for Claude Code / Codex - [Agent Interfaces](https://xnet.fyi/docs/guides/agent-interfaces/): Files-first surface, xnet CLI, SKILL.md - [Hub Setup](https://xnet.fyi/docs/guides/hub/): Optional always-on relay - [Testing](https://xnet.fyi/docs/guides/testing/): Test your app diff --git a/site/public/robots.txt b/site/public/robots.txt new file mode 100644 index 000000000..e21254495 --- /dev/null +++ b/site/public/robots.txt @@ -0,0 +1,4 @@ +# xnet.fyi — everything public is crawlable. +# Agents: start at /llms.txt (index) or /agents.md (what connecting gives you). +User-agent: * +Allow: / diff --git a/site/src/components/sections/BuiltForAgents.astro b/site/src/components/sections/BuiltForAgents.astro index cc32b111b..a610d4e94 100644 --- a/site/src/components/sections/BuiltForAgents.astro +++ b/site/src/components/sections/BuiltForAgents.astro @@ -60,7 +60,10 @@ const points = [
- + + Connect your agent + + Agent Interfaces Guide diff --git a/site/src/components/sections/Footer.astro b/site/src/components/sections/Footer.astro index 764408c7b..725f621a6 100644 --- a/site/src/components/sections/Footer.astro +++ b/site/src/components/sections/Footer.astro @@ -1,4 +1,12 @@ --- +const agentLinks = [ + { label: 'Connect your agent', href: '/agents' }, + { label: 'Use from Claude Code', href: '/docs/guides/coding-agents/' }, + { label: 'Agent Interfaces', href: '/docs/guides/agent-interfaces/' }, + { label: 'agents.md', href: '/agents.md' }, + { label: 'llms.txt', href: '/llms.txt' }, +] + const productLinks = [ { label: 'Try the App', href: '/app' }, { label: 'Run on Mobile', href: '/mobile' }, @@ -58,7 +66,7 @@ const legalLinks = [