# UUU Agent Guide

## START HERE

1. Do not enumerate or read the entire workspace unless explicitly asked.
2. Start with the workspace overview or a search.
3. Read only the entities the task needs.
4. Prefer row and patch operations over replacing whole objects.
5. Use expected_version when modifying something you previously read.
6. Never permanently empty Trash automatically.
7. Sharing creates a public link — only on an explicit request.
8. Verify every mutation from the API response, not by guessing.

## What UUU is

One workspace shared by a person and their agents: notes and docs (markdown),
tables (CSV-like grids, no formulas), prompts (reusable skills with variables),
files including audio and video, days (a journal, one document per date), links
(saved copies of web pages), reminders, voice memos (a recording plus its
transcript as a note), agents (saved work that runs itself) and research
reports (a question answered from the web, with sources). There are no
separate "AI copies" — you edit exactly what the person sees in the browser,
and they see your edits within ten seconds.

## Authentication

Every call needs a header:

    Authorization: Bearer <token>

Named agent tokens can be limited to scopes: read, write, share, trash, admin.
The scope is checked on EVERY /api call, not only on the agent endpoints: with
"read" you can read the whole workspace and change nothing — writing, sharing,
trashing and emptying the Trash all answer 403 permission_denied. This guide
and GET /api are the only endpoints open without a token.

## Entity types and canonical refs

    uuu://note/plan         a note (markdown, title = first line)
    uuu://doc/product-spec  a document in the .md library
    uuu://prompt/market-scan a saved prompt with {{variables}}
    uuu://password/github   a note in the Passwords section: logins, passwords, API keys
    uuu://table/leads       a table (columns + rows, values are strings)
    uuu://file/1a2b3c4d5e6f7890  a file on the Disk (16-hex id)
    uuu://day/2026-09-05    the journal page of a date (created on first append)
    uuu://link/article-slug a saved copy of a web page (has a source url)
    uuu://agent/morning-brief a saved agent: its instruction is plain markdown
    uuu://research/why-x   a finished research report with its sources

Every agent operation takes and returns these refs.

## Recommended workflow

First run:   overview → search → read only what matters.
Later runs:  changes(cursor) → read only the refs that changed.

## Operations

    GET  /api/agent/overview                counts, pinned, recent, open todos, change cursor
    GET  /api/agent/search?q=&kinds=&limit= notes, docs, prompts, table cells, file names
    GET  /api/agent/read?ref=               one entity (see below)
    POST /api/agent/write                   {ref, content, mode, expected_version}
    POST /api/agent/append                  {ref, content, expected_version}
    POST /api/agent/patch                   {ref, operation, ...}
    POST /api/agent/table/rows              {ref, operation, ...}
    POST /api/agent/prompt/render           {slug, variables}
    POST /api/agent/file/upload             {name, content, encoding}
    POST /api/agent/share                   {ref, on}
    POST /api/agent/pin                     {ref, pinned}
    POST /api/agent/trash                   {ref}
    POST /api/agent/restore                 {ref}
    GET  /api/agent/changes?cursor=         incremental feed
    GET  /api/agent/history?ref=            past versions of a text
    POST /api/day/append                    {text, date?, tz?} — one line into the day journal
    POST /api/r                             {at, text, ref?, row_id?} — set a reminder (see below)
    GET  /api/r?state=open                  reminders: open | upcoming | overdue | done | all
    PATCH /api/r/{id}                       {done: true} — close one

The low-level REST API (/api/d, /api/m, /api/p, /api/t, /api/f) still works
unchanged and is documented at GET /api. The agent endpoints are a thin
semantic layer over the same data.

## Read

Text (note, doc, prompt): content, version, backlinks and, for a prompt, its
variables. Long text: add start_line and limit_lines instead of the whole document.

Table: columns and ONE PAGE of rows — q filters, limit (default 100, max 1000) and
offset page through. Never ask for a whole 27k-row table.

File: returns metadata; text files under 256 KB also come with content.
Anything bigger answers content_returned: false with the reason, and you fetch
bytes yourself: GET /api/f/{id} supports HTTP Range. Audio and video are the same
files (mime audio/* and video/*), nothing is converted, and the listening position
lives on the file — "continue where I stopped" works across devices.

## Create and update

Default mode is "update": writing to a ref that does not exist answers 404, so a typo
in a slug cannot silently create a junk note. Use "create" for a new item (409 if the
slug is taken) and "upsert" only when you truly mean "create it if missing".
For notes and docs the title is the FIRST LINE of the text.

## Concurrency — do not overwrite the person

Every read returns a version. Pass it back as expected_version when you write.
If the item changed in between, nothing is written and you get:

    {"error": "version_conflict", "message": "Item changed since it was read",
     "current_version": "..."}

Then re-read, merge, and retry. Without expected_version the write goes
through, so pass it whenever you read the item first.

## Tables

Edit rows, do not replace tables:

    {"ref": "uuu://table/leads", "operation": "add",
     "rows": [{"Company": "Acme", "Status": "New"}]}

    {"ref": "uuu://table/leads", "operation": "update",
     "row_id": 42, "cells": {"Status": "Contacted"}}

Operations: add, update, delete, move (pos between neighbours), color.
A new key in a row adds a column. POST /api/agent/write on a table replaces
every row — that is a deliberate action, not a way to edit one cell.

## Patch markdown

Three safe operations, no document rewrite:

    toggle_checkbox        {match: "Call Ivan", checked: true}
    append_after_heading   {heading: "Tasks", content: "- [ ] new"}
    replace_line_range     {start_line: 10, end_line: 12, content: "..."}

## Days: the journal

Every date has a page (uuu://day/YYYY-MM-DD) created on the first append, and it
fills itself: the person, the built-in agent's receipts, finished audio, voice
memos. "Remember that…", "note that…", "log…" → POST /api/day/append with ONE
line (a markdown link to a longer note is fine). tz is the person's IANA zone.

## Links

POST /api/l {"url"} fetches a page, extracts the article and stores it as a
document with its source url. An RSS/Atom url becomes a subscription: new items
arrive hourly as separate links. Read them like any doc (uuu://link/<slug>);
a link cannot be created with write — only from a url.

## Research

POST /api/rs {"query", "type"} orders a report: the engine searches the web,
reads what it finds and writes a document with the links it stands on.
type is "research_report" (default) or "deep" (slower, dearer). The answer comes
back at once with a slug — the work goes on in the background, one at a time;
GET /api/rs/{slug} is the state (queued | running | done | failed | stopped) and
GET /api/rs lists everything. POST /api/rs/{slug}/stop cancels. The finished
report is an ordinary document (uuu://research/<slug>) — read, quote and share it
like any other. It costs real tokens and takes minutes: order one when the person
asked to research something, not to look up a fact.

## Repos

A repo is a real git repository on the server. Read it without git:
GET /api/git/{name} (branches, head, root files, README), /tree?ref=&path=,
/blob?ref=&path= (text + the blob sha), /commits, /commit/{sha} (diff). Change a
file with PUT /api/git/{name}/blob?ref=main&path=src/x.js {"content", "message",
"expected": "<sha you read>"} — that is one commit, 409 if the file moved under
you. With git installed: git clone https://<host>/git/{name}.git, password = token.

## Reminders

POST /api/r {"at": "2026-09-06T09:00:00+07:00", "text": "call Ivan",
"ref": "uuu://note/plan"} — at is an ISO date-time WITH a zone; compute it
yourself from the current time given in the tool context. The person sees it
in any open uuu.bz tab (and in Telegram when the server has a bot). Agents can
add, list and close reminders (PATCH {done: true}); deleting is a human action.

## Agents: work that runs itself

An agent is a document whose text says, in plain words, what to do. Read it,
write it and run it like anything else here.

    GET   /api/ag                    every agent and its state
    POST  /api/ag                    {"title", "content", "unique": true} — create
    PATCH /api/ag/{slug}             {"schedule"} · {"watch"} · {"enabled"} · {"scopes"} ·
                                     {"target": "uuu://table/leads", "column": "Country"}
    PUT   /api/ag/{slug}             body = markdown — rewrite the instruction
    POST  /api/ag/{slug}/run         {"dry": true} — show the plan, change nothing
    GET   /api/ag/{slug}/runs        what it did, what it changed, what it cost

schedule: "daily 08:00" · "hourly" · "every 30m" · "mon,thu 09:00" · "weekdays 09:30".
watch: link · voice · note · doc · day · file · video · table:<slug>. target + column
make it a table pipeline: fills that column row by row where empty, continues next run.
An agent runs unattended: anything needing a person is skipped and reported, never done
quietly. Keys and limits live in /api/settings (admin scope — the person's money).

## Voice memos

POST /api/voice?name=&dur= with raw audio bytes (m4a, mp3, wav, ogg, webm) stores the
recording and queues transcription; the text becomes an ordinary, searchable note.
GET /api/voice lists memos with the transcript slug and status.

## Audio from text

POST /api/tts {"slug"} turns a note, doc or link into mp3 files in Audio — Microsoft
voices, no key, one job at a time; GET /api/tts is the queue. For when the person
asked to listen rather than read.

## Passwords

Logins, passwords, API keys and tokens live in the Passwords section as notes of
kind password (uuu://password/<slug>). They are left out of the "All" list, but
search finds them. Search by the service or domain name, not by the word
"password": most entries only say "API Key" or hold the bare value.

    uuu_search {"query": "cloudflare", "kinds": ["password"]}   then uuu_read the ref
    GET /api/agent/search?q=cloudflare&kinds=password
    GET /api/workspace/list?section=passwords&sort=title       the whole section, titles only

Nothing there? Search without kinds, then the project's .env, then ask the person.
New credentials go straight here: uuu_write uuu://password/<slug> mode "create";
a changed password is an edit of the same entry, not a second one. Never copy a
secret into prompts, commits, logs or public /s/ links; write {{VARIABLE}} instead.

## Prompts are skills

A prompt is text the person wrote, with {{variables}}. Render it and use the
result yourself:

    POST /api/agent/prompt/render {"slug": "market-scan", "variables": {"topic": "CRM"}}

Missing values answer missing_variables with the list. UUU never calls a model
on its own — it stores the skill, you run it.

## Changes

    GET /api/agent/changes?cursor=<from last time>
    → {"changes": [{ref, kind, action, at}], "next_cursor": "...", "has_more": false}

Actions: created, updated, trashed. The cursor is opaque and ordered, so two
events in the same millisecond are never skipped. The old
GET /api/changes?since=ISO still works.

## Sharing

POST /api/agent/share creates a PUBLIC read-only link that anyone with the URL
can open without a password. Only do it when the user explicitly asks.
{"ref": ..., "on": false} revokes it.

## Trash

POST /api/agent/trash is a soft delete — the item is recoverable with
/api/agent/restore. There is no agent operation for permanent deletion:
emptying the Trash needs the admin scope and is a human action.

## Errors

    {"error": "not_found", "message": "no note with slug 'plan'"}
    {"error": "version_conflict", "current_version": "..."}
    {"error": "permission_denied", "message": "Token does not have share permission"}
    {"error": "content_too_large", "message": "..."}
    {"error": "missing_variables", "missing": ["topic"]}

## MCP

POST /mcp speaks JSON-RPC 2.0 (Streamable HTTP, no SSE) with the same
Authorization header. Tools: uuu_overview, uuu_search, uuu_read, uuu_write,
uuu_append, uuu_patch, uuu_table_rows, uuu_render_prompt, uuu_changes,
uuu_history, uuu_file_upload, uuu_share, uuu_pin, uuu_trash, uuu_restore,
uuu_day_append, uuu_remind, uuu_reminders.
Resources are the refs above; every saved prompt shows up as an MCP prompt
whose arguments are its {{variables}}.

## Examples

    T='Authorization: Bearer <token>'
    B=https://uuu.bz

    curl -H "$T" $B/api/agent/overview
    curl -G -H "$T" --data-urlencode 'q=Bangkok' $B/api/agent/search
    curl -G -H "$T" --data-urlencode 'ref=uuu://note/trip' $B/api/agent/read
    curl -X POST -H "$T" -d '{"ref":"uuu://note/trip","content":"check the hotel","expected_version":"..."}' \
         $B/api/agent/append
    curl -X POST -H "$T" -d '{"ref":"uuu://table/leads","operation":"update","row_id":42,"cells":{"Status":"Contacted"}}' \
         $B/api/agent/table/rows
