# Aqwas > Shared state. Realtime. Everywhere. One SDK, any runtime, sub-50ms latency, built-in permissions and audit logs. ## Pages - [Home](/): Overview, features, live sync demo, FAQ, and waitlist for Aqwas. - [Docs](/docs): Full documentation — concepts, quickstart, client API, tokens, REST API, error codes, and limits. - [Demos](/demos): Live examples built with Aqwas. - [Scramble vs Words](/demos/scramble-vs-words): Two-player realtime word race built with @aqwas/core. - [Sign in](/join): Account sign-in and signup. - [Dashboard](/dashboard): Manage stores, tokens, usage, and audit logs (requires sign-in). ## What it is Aqwas is a shared state layer for the network. Connect a client (browser, Node.js, Deno, AI agent) with a token and a `storeId`. Write JSON state with `set()`, read with `get()`, subscribe to changes with `subscribe()`. Updates broadcast to all connected clients in <50ms. Tokens control which stores and keys each client can access. Stores auto-expire after a configurable inactivity TTL. Built for: - AI agents that need persistent memory across restarts - Multiple apps/services that need to share state without a message broker - Realtime collaboration (humans + agents, multiplayer games, live dashboards) - Cross-app signaling — two independent apps agree on a store ID and exchange signals instantly ## Install ``` npm install @aqwas/core ``` ## Core Concepts - **Store**: a named keyspace (e.g. `agents/alice`, `game/lobby-42`). Shared by any client that knows the ID and holds a valid token. No hierarchy. - **Key**: an identifier inside a store mapping to an arbitrary JSON value (string, number, bool, object, array, nested). - **Token**: prefix `aqw_*`. Authenticates WebSocket connections. Scoped to allowed stores and key-name read/write patterns. - **Personal key**: prefix `aqwp_*`. Account credential for MCP servers and CLI tools running locally. Never embed in client code — grants full account access. - **Conflict resolution**: last-write-wins per key. For workflows where every update matters (e.g., multi-agent pipelines), give each agent its own key namespace. - **TTL (time-to-live)**: stores auto-delete after inactivity. Perfect for ephemeral coordination — sessions, game matches, temporary task state. ## Quickstart ```ts import { AqwasClient } from "@aqwas/core"; const client = new AqwasClient({ url: "wss://api.aqwas.com/ws", storeId: "agents/alice", token: "aqw_...", persist: true, // buffer writes offline, flush on reconnect ttl: "7d", // store auto-deletes after 7 days of inactivity }); await client.connect(); // Write — broadcasts to all connected clients in <50ms client.set("memory", { task: "analyze Q1", updatedAt: Date.now() }); // Read — synchronous, never hits the network const memory = client.get("memory"); // Subscribe to changes (from any client, local or remote) client.subscribe("memory", (value) => { console.log("memory updated:", value); }); ``` ## Client API (@aqwas/core) ### Constructor ```ts new AqwasClient({ url: string; // wss://api.aqwas.com/ws storeId: string; // "agents/alice", "game/lobby-42", etc token: string; // "aqw_..." persist?: boolean; // buffer writes offline (default: false) ttl?: string; // "30m", "1h", "7d", "30d" — auto-delete on inactivity }); ``` ### Methods - `await client.connect()` — open WebSocket, authenticate, join store. Resolves when initial sync completes. - `client.get(key)` — synchronous read. Returns value or undefined. Generic type for TypeScript. - `client.getAll()` — returns the full state object `{ key: value, ... }`. - `client.set(key, value)` — write to key. Broadcasts to all connected clients. - `client.delete(key)` — remove key. Broadcasts. - `client.subscribe(key, handler)` — register handler to fire on any change to that key. Returns unsubscribe function. - `client.on(event, handler)` — listen to connection and state events (see Events below). - `client.disconnect()` — clean close. If `persist: true`, flushes buffered writes first. ### Events ```ts client.on("connect", () => { ... }) // joined store and authenticated client.on("disconnect", () => { ... }) // connection lost (auto-reconnects) client.on("sync", (state) => { ... }) // full state snapshot received client.on("change", (key, value) => { ... }) // any key changed (local or remote) client.on("error", (code, msg) => { ... }) // server error (AUTH_FAILED, RATE_LIMITED, etc) client.on("destroyed", (reason) => { ... }) // store TTL expired or manually destroyed ``` ### Updating an object field Values are replaced wholesale on `set()`. To update one field: ```ts const player = client.get<{ name: string; score: number }>("player") ?? { name: "", score: 0 }; client.set("player", { ...player, score: player.score + 10 }); ``` ## Tokens & Permissions Each token has three permission fields: - **Allowed stores**: comma-separated store IDs (`"agents/alice,shared/task"`) or `"*"` for any store. - **Readable keys**: glob patterns (`["*"]` for all, `["memory", "status"]` for specific keys). - **Writable keys**: same glob format. ### Multi-agent example Mint one token per agent. Each gets access to its own store plus a shared coordination store. ```ts // Analyst agent token: { name: "analyst", stores: "agents/analyst,shared/task", read: ["*"], write: ["*"] } // Writer agent token: { name: "writer", stores: "agents/writer,shared/task", read: ["*"], write: ["*"] } ``` Both agents can read/write the shared task store, but each has its own private store. No shared secrets, no API key in shared code. ### Cross-app signaling Two independent apps (different codebases, different services) can share a store: ```ts // App A (writer): const clientA = new AqwasClient({ storeId: "signals:prod", token: tokenWithWriteAccess, }); clientA.set("status", "processing"); // App B (reader, different codebase): const clientB = new AqwasClient({ storeId: "signals:prod", token: tokenWithReadAccess, }); clientB.subscribe("status", (value) => { console.log("App A says:", value); // fires in <50ms }); ``` ## MCP Server (@aqwas/mcp) `@aqwas/mcp` exposes Aqwas tools to Claude Code, Cursor, Windsurf, and other AI coding tools. ### Tools available to AI - `create_token(name, stores, read_keys?, write_keys?)` — mint an SDK token. Returns ready-to-paste code snippet. - `revoke_token(token_id)` — revoke by UUID. - `get_store_state(store_id)` — read current state snapshot of a store. ### Setup 1. Create a personal key from the dashboard (go to Tokens → Personal Keys). 2. Add to your MCP config (Claude Code `.mcp.json` or equivalent): ```json { "mcpServers": { "aqwas": { "type": "stdio", "command": "npx", "args": ["-y", "@aqwas/mcp"], "env": { "AQWAS_PERSONAL_KEY": "aqwp_..." } } } } ``` 3. Restart your IDE. Now you can ask Claude "add @aqwas/mcp to my project and mint a token." ### Environment variables - `AQWAS_PERSONAL_KEY` (required) — your `aqwp_*` personal key from the dashboard. - `AQWAS_URL` (optional, default `https://api.aqwas.com`) — custom server URL (self-hosted). ## REST API All endpoints require authentication: `Authorization: Bearer ` where credential is either a Supabase JWT (browser session) or personal key (`aqwp_*`). ### Token management - `POST /user/tokens` — create SDK token. Body: `{ name, storePattern, read[], write[] }`. - `GET /user/tokens` — list your SDK tokens (keys hidden). - `DELETE /user/tokens/:id` — revoke token by ID. ### Store & data - `GET /user/stores` — list all stores you have access to. - `GET /user/stores/:id` — get current state snapshot. - `GET /user/stores/:id/events` — replay store events (with timestamps). ### Audit & usage - `GET /user/audit` — audit log (who changed what, when, with which token). - `GET /user/usage` — usage summary (events/month, connections, etc). ### Personal keys - `POST /user/personal-keys` — create personal key (requires Supabase JWT, NOT personal-key auth). Body: `{ name }`. - `GET /user/personal-keys` — list personal keys. - `DELETE /user/personal-keys/:id` — revoke personal key. ## Error codes WebSocket errors emit on the `error` event: - `AUTH_REQUIRED` — no token sent and auth is required - `AUTH_FAILED` — token invalid, expired, or revoked - `STORE_ACCESS_DENIED` — token not scoped to this store - `WRITE_DENIED` — token lacks write permission for this key - `READ_DENIED` — token lacks read permission for this key - `RATE_LIMITED` — token hit its per-minute rate limit - `INVALID_MESSAGE` — malformed WebSocket message ## Limits - Max 25 SDK tokens per user - Max 3 personal keys per user - Max value size per key: 256 KB - Max keys per store: 10,000 ## Self-hosting Full open-source server (Deno + PostgreSQL). Run locally with Docker: ```bash git clone https://github.com/iHani/aqwas-core cd aqwas/server cp .env.example .env docker-compose up ``` Point your SDK at it: `url: "ws://localhost:3000/ws"`. ## Resources - Web docs: https://aqwas.com/docs - Dashboard: https://aqwas.com/dashboard - GitHub: https://github.com/iHani/aqwas-core - npm packages: @aqwas/core, @aqwas/mcp