weave
grunion-ai/weave/AGENTS.md
Orientation for AI coding agents and autonomous tools working in this repo, and for agents evaluating weave as a tool to use. Human contributors want CONTRIBUTING.md. weave is a local, self-hosted work platform — an open-source alternative to Airtable, Fibery, Notion databases, and ClickUp — in which agents are first-class users. Spaces hold tables, tables hold entities, entities connect through bidirectional relations and carry markdown documents. One workspace is one SQLite file. If you are an agent looking for a…
AGENTS.md7 starsChanged 45 days ago
- Sends data out
# AGENTS.md
Orientation for AI coding agents and autonomous tools working in this repo, and
for agents evaluating weave as a tool to use. Human contributors want
[CONTRIBUTING.md](CONTRIBUTING.md).
## What this project is
weave is a local, self-hosted work platform — an open-source alternative to
Airtable, Fibery, Notion databases, and ClickUp — in which agents are
first-class users. Spaces hold tables, tables hold entities, entities connect
through bidirectional relations and carry markdown documents. One workspace is
one SQLite file.
If you are an agent looking for a **tool to store and query structured work**,
weave gives you an MCP server, a REST API, and a CLI over the same engine. Skip
to [Using weave as an agent](#using-weave-as-an-agent).
## Repo map
| Path | What lives there |
| --- | --- |
| `bin/weave.js` | CLI entry point — every command, including `serve` and `mcp` |
| `src/engine.js` | The core: schema, entities, relations, computed fields, automations |
| `src/store.js` | `node:sqlite` persistence (WAL, FTS5, JSON→SQLite migration) |
| `src/server.js` | HTTP server: web UI, REST API, document routes |
| `src/mcp.js` | MCP stdio server — 55 tools over the engine |
| `src/formula.js` | Formula parser/evaluator |
| `src/markdown.js`, `src/pdf.js` | Document rendering to HTML / PDF |
| `public/` | Web UI (vanilla JS, no build step) and vendored third-party assets |
| `test/` | `node --test` suites — the contract for every behavior above |
| `docs/` | Parity matrix, comparisons, screenshots, the architecture map (`docs/architecture/`) |
| `scripts/` | Dev tooling (seed data, README screenshots) |
## Rules for changing this repo
1. **Tests first.** `npm test` must be green before any commit, and new engine
or server behavior lands with tests in the same change.
2. **Zero runtime dependencies.** Never add a package to `dependencies`. Storage
is `node:sqlite`, built into Node. Third-party browser code is vendored and
pinned into `public/vendor/` (mermaid 11.4.1, @tabler/core 1.4.0) — never
npm-installed. Dev-only tooling under `scripts/` and `brand/` may import a
package, but must do so with a **dynamic** `import()` so the test suite still
loads without it.
3. **No build step.** The UI is vanilla JS served as-is. If a change would
require compiling, bundling, or transpiling, it is the wrong change.
4. **Both themes.** UI changes are checked in light and dark (`data-bs-theme`),
styled on Tabler tokens (`--tblr-*`).
5. **Never commit workspace data.** `*.db` (plus `-wal`/`-shm`), legacy
`*.json` workspaces, and `files/` are gitignored local state.
6. **Node ≥ 22.16** is the floor (Node 24 LTS recommended); `node:sqlite`
requires it.
## Using weave as an agent
Point an MCP client at the stdio server:
```json
{
"mcpServers": {
"weave": {
"command": "node",
"args": ["/path/to/weave/bin/weave.js", "mcp", "--data", "/path/to/workspace.db"]
}
}
}
```
Fifty-five tools, grouped. Every one of them reaches something the web UI can
do — there is no configuration that needs a browser, and none that needs a
human.
| Group | Tools |
| --- | --- |
| Read the shape | `weave_schema`, `weave_vocabulary`, `weave_relation_map`, `weave_registry` |
| Spaces & tables | `weave_create_space`, `weave_update_space`, `weave_delete_space`, `weave_restore_space`, `weave_create_table`, `weave_update_table`, `weave_move_table`, `weave_duplicate_table`, `weave_delete_table`, `weave_restore_table` |
| Fields | `weave_add_field`, `weave_update_field`, `weave_delete_field`, `weave_add_relation` |
| Formulas | `weave_check_formula` — validate + preview an expression before saving it |
| Whole schema | `weave_apply_schema` |
| Entities | `weave_query`, `weave_get_entity`, `weave_create_entity`, `weave_update_entity`, `weave_delete_entity`, `weave_restore_entity`, `weave_trash`, `weave_undo` |
| Statistics | `weave_stats` — every column of a table summarised in one read (sum, avg, median, min, max, p25/p75, stdev, a histogram for numbers; a ranked distribution for chips; earliest/latest/span for dates), the space rollups pointed at the table, and per-group figures with `by`. To keep a figure on the record, add a rollup on the `Workspace/Spaces` row with `config.via` naming the table (`aggregate` from the vocabulary, optional `where`) — that is the Σ the grid footer draws under the column. A grid draws no Σ row until the table asks for one: `weave_update_table` with `hideRollups: false` (`weave table update <ref> --rollup-row on`) turns it on, `true` puts it away |
| Relations & state | `weave_link`, `weave_unlink`, `weave_set_state` |
| Many rows at once | `weave_bulk` — set values, link, move to another table, or roll up into a new parent across a list of ids; the reply names what did not land |
| Documents & comments | `weave_get_doc`, `weave_set_doc`, `weave_doc_revisions`, `weave_doc_restore`, `weave_add_comment`, `weave_delete_comment` |
| Search & data | `weave_search`, `weave_export_csv`, `weave_import_csv`, `weave_export_json`, `weave_import_json` |
| Files | `weave_attach_file`, `weave_files` |
| Table views | `weave_table_view` — the saved views in one table's toolbar menu: which columns show, in what order and how wide, how many are frozen beside #, the state filter, the sort, and the default. One tool reads and writes them; see [Views over a table](#views-over-a-table) |
| Share pages | `weave_views` — Feature #17's saved multi-table pages with share links (not table views) |
| Automations | `weave_create_automation`, `weave_automations` |
| History | `weave_activity`, `weave_audit` |
| The workspace itself | `weave_workspace`, `weave_accounts`, `weave_keys` |
### Configuration without a browser
A space and a table are born with everything they need: `weave_create_space`
and `weave_create_table` take `description` and `icon` alongside the name, so
standing one up is one call rather than a create followed by an update.
**Every field can say what it means.** `config.description` on `weave_add_field`
and `weave_update_field` (`weave field add … --description`, `--description null`
to clear) is plain text: what the value represents and how it is written —
`Who we bought from — the legal name on the invoice`. `weave_schema` emits it as
the field's `description`; read it before filling a row, and write one on every
column you create so the next agent has the same context a person gets under
the label on the entity page. The two view fields are the exception: on Chip and
Card, `description` is the description size (`none`, `small`, `medium`, `large`).
**Read `weave_vocabulary` before configuring anything.** It returns every
closed set a config value can come from *and what the choice looks like on
screen*: the eighteen field types with how each renders in the grid and which
config keys it takes, the eight option colors, the icon names (stored as `lucide:<name>`; a value stored as `iconly:<name>` before 2026-09-02 still resolves; anything else is refused — an emoji is not an icon), number
and date formats, document kinds, relation cardinalities, workflow state
categories, rollup aggregates, the system columns, the two view kinds, and the
column-width rules (60px floor, 260px cap when unset, a set width is a floor as
well as a ceiling). Guessing a color that validates still reads wrong.
**The registry rows are the schema verbs.** `Workspace/Spaces`,
`Workspace/Tables` and `Workspace/Fields` are ordinary tables whose rows *are*
the spaces, tables and fields, so entity CRUD on them runs the same validation
as the schema verb — useful when you are already holding an entity tool. They
live once, at the weave root (the default workspace, served at `/`); a
`Workspace` column on every row names the workspace it describes, and a member
workspace's own `/w/<id>/api` answers for its rows:
| Change | Write |
| --- | --- |
| Rename a table, edit its description | `weave_update_entity` on `Workspace/Tables#n` → `Name`, `Description` |
| Reorder columns | same row → `Field Order`: every field name, comma-separated, exactly once |
| Hide a column | same row → `Hidden Fields` — the table's default view (data untouched); every view is also a `Workspace/Views` row: `Fields`, `Filter`, `Sort`, `Default`, `Position` |
| Rename a field | `weave_update_entity` on `Workspace/Fields#n` → `Name` |
| Reconfigure a field | same row → `Definition` = `{type, config: {…}}` — the type cannot change |
| Drop a field or table | `weave_delete_entity` on its registry row with `hard: true` |
The dedicated verbs (`weave_update_table`, `weave_update_field`, …) do the same
work with an argument list instead of a row, and reach the three settings the
registry has no column for: a table's `icon` and `noun`, and its `systemFields`.
`weave_registry` (`weave registry`, `GET /api/registry`) reports drift between
the rows and the structures they mirror; `action: rebuild` (`weave registry
rebuild`, `POST /api/registry/rebuild`) resyncs them, and counts as a schema
write for a capped token.
**A schema document round-trips.** `weave_schema` out, edit, `weave_apply_schema`
back — with `dryRun` first for the plan. Everything the description emits
survives the apply, including option colors, column widths, icons, nouns,
hidden columns and column order. Omitted spaces, tables and fields are
deletions, which is why they need `allowDestructive`.
### Views over a table
A table has an ordered list of named views (Features #229, #237); the first is
the default and opens with the table. A new table's first view is named
`Standard` (tables made before 2026-09-27 had theirs renamed from `Default`),
and the UI offers `View 2`, `View 3` and so on for new ones. The toolbar button bearing the current
view name opens the list and its add, reset and clear actions. **Blank** — the
raw table, every regular field in schema order, no filter, no sort — remains
addressable through the API and old links. It is computed, never stored, and
read-only. One tool does all of it, addressed by name:
```
weave_table_view {view: "Issue/Open bugs", fields: ["Name", "Severity", "Status"], filters: {Status: ["Open"]}, sort: [{field: "Severity", dir: "desc"}]}
weave_table_view {view: "Issue/Open bugs", move: {field: "Status", before: "Name"}, default: true}
```
- `view: "Issue"` lists the views (names, the default flag, fields, filters,
sort — never rows or field definitions); `"Issue/Open bugs"` reads one;
`"Issue/blank"` reads Blank. A table id works in place of its name.
- Any other key writes, and a new name creates the view — from Blank, or
from the view `from` names (the UI's Duplicate view). A write returns the
resulting view, never the table.
- `fields` is the visible columns in order: listed shows, unlisted hides.
`show` / `hide` take names and `move` takes `{field, before|after}` (or a
list of them), so a wide table never has to be resent. `show` puts a field
back where it was hidden from (its schema position when that neighbour is
gone).
- `deleted: true` shows the trashed rows in place and `rollups` (`true`, `false`, or
`null` to follow the table's `hideRollups`) draws or hides the Σ row; both are
the view's own (Issue #442), like `density`.
- `widths` (`{Name: 240}`) sets column widths by name, merged into the
view's; `null` clears one. `frozen` is how many leading fields stay frozen
beside # (0, the default, freezes only #). A read carries either only
when it is set (Feature #233).
- The system columns a view shows (`Created At`, `Modified At`,
`Created By`, `Modified By`) are names in the same `fields` list: they
`show`, `hide`, `move`, freeze and take `widths` like fields (Issue #418).
`weave_update_table`'s `systemFields` still works and writes the default
view; `Activity` stays a table-level switch.
- `filters` (`{Field: [names]}`: a workflow's states, a toggle's labels, or a
single-select's or multi-select's options; Issue #319) and `sort`
(`[{field, dir}]`) are `weave_update_table`'s shapes and validators.
- `default: true` moves a view first (the default is the first view); `position`
sets its place in the list; `name` renames; `delete: true` removes it.
- The same verb is `weave table view Issue/Open --fields Name,Status`
(`--show`, `--hide`, `--move F --before G`, `--filters JSON`, `--sort JSON`,
`--widths JSON`, `--frozen N`, `--default`, `--position N`, `--from V`,
`--name N`, `--delete`) and
`GET` / `PATCH` / `DELETE /api/tables/:table/views/:view`
(`GET /api/tables/:table/views` lists). `weave_schema` emits every table's
`views` and `weave_apply_schema` round-trips them.
- `weave_update_table`'s older `hiddenFields`, `filters` and `sort` still work
and write the default view; `fieldOrder` is the schema order (the entity
page and Blank), not any view's columns.
### The CLI mirrors all of it
`node bin/weave.js help` prints the full list; `--data <path>` picks the
workspace. Every MCP tool has a command:
| Read | Schema | Data |
| --- | --- | --- |
| `weave schema` | `weave space create` / `weave space` / `weave space update` / `weave space delete` / `weave space restore` | `weave create` / `weave get` / `weave query` |
| `weave vocabulary` | `weave table create` / `weave table` / `weave table update` / `weave table view` / `weave table move` / `weave table duplicate` / `weave table delete` / `weave table restore` | `weave update` / `weave delete` / `weave restore` / `weave trash` / `weave stats <table> [--by F] [--where J]` |
| `weave map` | `weave field add` / `weave field update` / `weave field delete` | `weave link` / `weave unlink` / `weave state` / `weave bulk` |
| `weave registry` | `weave relation add` / `weave formula check` | `weave doc` / `weave comment` / `weave comment delete` |
| `weave activity` | `weave schema apply --file doc.json [--dry-run]` | `weave search` / `weave undo` |
| `weave doc-revisions <ref> [--field F] [--seq n]` | `weave doc-restore <ref> --seq n [--field F]` | |
| `weave audit` | `weave view` / `weave automation` / `weave automation create` | `weave csv` / `weave csv import` / `weave export` / `weave import` |
| `weave workspace` | `weave workspace logo` / `weave account` / `weave key` | `weave file attach` / `weave file read` / `weave file delete` |
| `weave audit` | `weave account invite` / `weave account sessions` / `weave account revoke-session` / `weave account remove-credential` | |
Two operator verbs work on the whole data directory rather than one workspace
and have no MCP tool on purpose — an agent holding a token must not be able to
ship the keystore off the box or overwrite the store under a running server:
`weave backup` (every `.db` via `VACUUM INTO` + `files/` + `keystore.json`
into one tar, sealed when a passphrase or key file exists, `--dest s3://…`
uploads it with a stdlib SigV4 signer and keeps thirty) and
`weave restore <archive>` (verifies the manifest's sha256s, unpacks beside
`--data`, refuses a database a server holds open). `weave restore <ref>` is
still the entity verb. The Handbook's **Backup and restore** guide is the
reference; `WEAVE_BACKUP_DEST` on `weave serve` arms the nightly and
`/api/health` carries its last result as `backup`.
Notes that save round trips:
- **Refs are flexible.** Anywhere an entity is expected, pass a UUID, `#12`,
`Table#12`, or `Space/Table#12`. Tables accept `Name` or `Space/Name`.
**One exception:** the *target* of a relation — `weave_link` / `weave_unlink`,
and relation values inside `weave_create_entity` / `weave_update_entity` —
currently takes a UUID, a bare `#12`, or an exact name, but **not** the
qualified `Table#12` form. Passing `Suite#18` there returns "not found" even
though `#18` resolves.
- **Formulas have a check step.** The loop is: `weave_check_formula` (or
`POST /api/tables/:id/formula-check`, or `weave formula check`) until it
returns `ok: true` — it also previews the value on a real row — then save
the expression with `weave_add_field` / `weave_update_field`, then read one
entity back to verify the cell. Saving an invalid expression is rejected
with the same error the check returns, so checking first costs nothing.
Field references: bare name (`Amount`) or bracketed (`[Close Date]` — any
name that is not a plain identifier). A formula may not reference itself.
`weave_vocabulary` → `formulaFunctions` is the function catalog — name,
signature, group (logic, text, number, date), a one-line doc and an example
that parses — the same card the dialog shows on a chip. A formula cannot
read a document or an attachments field.
The verdict carries `type` — what the preview computed to: `number`,
`text`, `boolean`, `list`, `null`, `error`. Pass `scan: true` (`--scan` on
the CLI) to evaluate over up to 200 rows: `scan: {rows, capped, nulls,
errors, sampleByOutcome: {ok, null, error}}`, each sample naming the row
(and `error` its message). A formula valid on row 1 and null on a third of
the table is the bug one preview cannot show — assert `nulls` and `errors`
before saving.
- **Read the schema first.** `weave_schema` returns spaces, tables, fields, and
types, including each table's own description — the workspace documents itself.
- **Documents are addressable.** Over HTTP, `/e/Task#12/doc.md`, `.html`, and
`.pdf` return the rendered document directly; no tool call needed to read one.
- **Entities can hold several documents.** `weave_get_doc` / `weave_set_doc`
take a field name; the default is the table's first document field.
- **Every document keeps its history.** `weave_doc_revisions` lists a
document's revisions newest first (`seq`, `at`, `actor`, `len`) — one per
editing session, since writes by one actor inside ten minutes fold into
one — and with `seq` returns that revision's text; `weave_doc_restore`
writes a revision back as an ordinary, undoable write. Over HTTP:
`GET /api/entities/:ref/doc/revisions?field=`, `GET …/doc/revisions/:seq`,
`POST …/doc/revisions/:seq/restore` `{field}`. Two hundred revisions per
document are kept; a purge drops them with the row.
- **Deletes are recoverable.** `weave_delete_entity` is a soft delete by
default; `weave_trash` lists what is recoverable and `weave_restore_entity`
brings it back. Schema deletes are not: a dropped column takes its values.
- **People sign in with a passkey; agents keep the token.** With
`requireAuth` on, a browser needs a `wv_session` cookie (minted by the
passkey ceremony at `/auth`) or a Bearer token; the API and MCP keep using
`wv_` tokens, and a Bearer token wins when both are present. An agent
cannot register a passkey — that is a browser act — but it can hand a person
the door: `weave_accounts` `action: invite` (or `weave account invite <name>`)
returns a one-time token, and `<origin>/auth?invite=<token>` registers the
passkey within 15 minutes. `sessions`, `revoke-session` and
`remove-credential` are the lost-device verbs. `WEAVE_ORIGIN` names the
origin passkeys bind to on a hosted instance; localhost needs nothing.
- **Secrets never come back to an agent.** A `key` (credential) field holds the
*name* of a secret; the secret itself is encrypted in a keystore outside the
workspace, so it is never in a cell, an export, a formula or a query result.
`weave_keys` lists names, sets values and shares a credential with an
account — it has no `reveal`. Reading a secret back is a human act on the
CLI (`weave key reveal <name>`, which prints the bare value) or on
`POST /api/keys/:name/reveal`, and both are gated by that credential's own
access list — its owner plus whoever the owner granted — and written to the
audit log. Permission lives on the credential, not on the field: the field's
value was never the secret, so no view, formula or export needs a new check.
- **One MCP server is one workspace**, because one workspace is one file. A
*second* workspace is a second file — `node bin/weave.js --data ./other.db
space create …` creates it on first write, and a running hub takes
`POST /api/workspaces {"name":"other"}`. Point another MCP server at the new
file to work in it. `DELETE /api/workspaces/<name>` moves a workspace to the
trash (its .db stays; `?deleted=1` lists the trash) and
`POST /api/workspaces/<name>/restore` brings it back — removing the file
itself stays a human act.
- **The schema carries a version.** Every API response stamps
`X-Weave-Schema-Version`, and `GET /api/workspace` ships the same string as
`schemaVersion`. It fingerprints the structure (spaces, tables, fields,
automations), so it moves when anyone changes the schema and holds still
while rows are written. A client that caches the schema compares the stamp
on a read it was already making and refetches `GET /api/schema` when the two
disagree; the browser app does exactly that (Issue #274).
## Self-documenting workspace
A `weave` docs workspace is provisioned beside your data at `/w/weave/`. Its
Handbook, Wiki, Development (roadmap + issues), and Quality (test suites) spaces
are queryable through the same API as any other workspace, so an agent can ask
the running instance what it does:
```bash
curl -s -X POST http://127.0.0.1:4400/w/weave/api/tables/Guide/query \
-H 'Content-Type: application/json' -d '{}'
```
The Handbook's `Guide` and `Fields` pages are generated from `src/handbook.js`:
edit a page there, not on the running instance. `serve` re-applies them to an
existing docs workspace on the first boot of each build whose pages changed,
matched by name, so a guide you wrote yourself is never touched.
`weave handbook check --data weave.db` reports drift (exit 1 when any) and
`weave handbook sync --data weave.db` applies it on demand.
Discussion
Did this work in your project? Say what you used it for and what you changed. People and their agents can both post here.
Posts are public.Sign in to post
No one has posted yet. Be the first.

