Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ This skill handles read operations, notifications, and group management. For mes

## API Reference

See [references/urbit-api.md](references/urbit-api.md) for Urbit HTTP API details.
See [references/urbit-api.md](references/urbit-api.md) for Urbit HTTP API details. See [references/notes-api-proposal.md](references/notes-api-proposal.md) for proposed `%notes` API improvements for agent clients.

## License

Expand Down
208 changes: 208 additions & 0 deletions references/notes-api-proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
# `%notes` API proposal for agent clients

## Goal

Make `%notes` easy and safe for OpenClaw/Tlonbot-style agents to read and write without coupling every agent to private frontend implementation details.

The desired flow is:

1. `%notes` exposes a stable, documented machine API.
2. Agents use a small skill/client against that API.
3. The skill stays thin: no reverse-engineered action shapes, no UI-specific assumptions.

## Problems seen in current integration work

- Current v0 flag-based endpoints are usable, but not clearly documented as a stable external contract.
- Write shapes are nested `%notes-action` pokes and easy for agents to get subtly wrong.
- Notebook refs can be numeric ids, titles, flag names, or full `~host/name` flags; clients need a canonical resolver.
- Folder creation needs a concrete parent folder id; `null` parent folders may not appear under `/` in the UI.
- Migrations/restores sometimes need stronger primitives than normal create-note allocation, especially for preserving ids and mirror state.
- Agents need dry-run/validation and revision guards to avoid destructive writes.

## Proposed read API

All responses should be stable JSON and versioned.

```text
GET /~/scry/notes/api/version.json
GET /~/scry/notes/api/notebooks.json
GET /~/scry/notes/api/notebook/{notebook-ref}/folders.json
GET /~/scry/notes/api/notebook/{notebook-ref}/notes.json
GET /~/scry/notes/api/notebook/{notebook-ref}/note/{note-id}.json
```

`{notebook-ref}` should accept the canonical notebook flag, e.g. `~malmur-halmex/wiki-5`. Numeric ids and titles can remain convenience aliases, but responses should always include the canonical flag.

### Notebook shape

```json
{
"id": 5,
"flag": "~malmur-halmex/wiki-5",
"host": "~malmur-halmex",
"name": "wiki-5",
"title": "Wiki",
"rootFolderId": 6,
"visibility": "private",
"capabilities": {
"createNote": true,
"updateNote": true,
"moveNote": true,
"batchImport": true,
"preserveIds": false
}
}
```

### Folder shape

```json
{
"id": 158,
"notebook": "~malmur-halmex/wiki-5",
"parent": 6,
"name": "AI Infrastructure & Semiconductors",
"path": "/AI Infrastructure & Semiconductors"
}
```

### Note shape

```json
{
"id": 14,
"notebook": "~malmur-halmex/wiki-5",
"folder": 158,
"title": "%lore Manual",
"body": "# %lore Manual\n...",
"revision": 3,
"createdAt": 1779360000000,
"updatedAt": 1779361234567
}
```

## Proposed write API

Keep writes as Eyre channel pokes, but expose one documented action mark and one predictable envelope.

```json
{
"type": "notes-api-action",
"version": 1,
"requestId": "client-generated-idempotency-key",
"dryRun": false,
"action": { }
}
```

Recommended actions:

```json
{"type":"create-notebook","title":"Wiki","visibility":"private"}
```

```json
{"type":"create-folder","notebook":"~host/wiki-5","parent":6,"name":"Topic"}
```

```json
{"type":"create-note","notebook":"~host/wiki-5","folder":6,"title":"Title","body":"Markdown"}
```

```json
{"type":"update-note","notebook":"~host/wiki-5","id":14,"expectedRevision":3,"body":"Markdown"}
```

```json
{"type":"move-note","notebook":"~host/wiki-5","id":14,"folder":158}
```

```json
{"type":"delete-note","notebook":"~host/wiki-5","id":14,"expectedRevision":3}
```

```json
{
"type":"batch-import",
"notebook":"~host/wiki-5",
"folder":6,
"notes":[{"title":"A","body":"..."}]
}
```

## Migration/import extension

For trusted local migration tools only, consider an explicit migration mode:

```json
{
"type":"batch-import",
"mode":"migration",
"preserveIds": true,
"notebook":"~host/wiki-5",
"folder":6,
"notes":[{"id":14,"title":"A","body":"...","revision":0}]
}
```

If preserving ids is unsafe for normal clients, expose it behind a capability flag or a separate admin-only mark. The important part is that clients can discover whether preservation is supported instead of relying on placeholder-note allocation tricks.

## Validation and error handling

Every write should support `dryRun: true` and return a structured result without mutating state:

```json
{
"ok": true,
"dryRun": true,
"wouldAllocate": {"noteIds":[142],"folderIds":[158]},
"warnings": []
}
```

Errors should be machine-readable:

```json
{
"ok": false,
"error": {
"code": "revision-mismatch",
"message": "Expected revision 3, found 4",
"currentRevision": 4
}
}
```

Suggested error codes:

- `notebook-not-found`
- `folder-not-found`
- `note-not-found`
- `revision-mismatch`
- `invalid-parent`
- `permission-denied`
- `unsupported-capability`
- `invalid-request`

## Agent skill shape after API agreement

Once the API is documented/stable, the OpenClaw/Tlonbot skill should be thin:

```bash
notes list-notebooks
notes list-folders ~host/wiki-5
notes list-notes ~host/wiki-5
notes create-folder ~host/wiki-5 6 "Topic" --dry-run
notes create-note ~host/wiki-5 6 "Title" ./body.md --dry-run
notes update-note ~host/wiki-5 14 3 ./body.md --dry-run
notes move-note ~host/wiki-5 14 158 --dry-run
```

Default behavior should be dry-run for writes; `--apply` should be explicit.

## Non-goals

- Do not require agents to import frontend code.
- Do not require agents to scrape Tlon UI output.
- Do not expose ship codes or cookies in skill docs.
- Do not make normal create-note calls set arbitrary ids unless a migration capability explicitly allows it.