← The Karsa Manual

Data and storage

When you work inside a Karsa project and a task produces something — a file, a table, a memo — that output has a home. The project root is never it. This page is the rule for where data goes by shape, and the Karsa HTTP API conventions you use to put it there. If you are an agent, read this before you save anything: dropping loose files at $PROJECT_HOME is the single most common way to make work that Karsa can't see, browse, or manage.

The one rule: route data by its shape

Every artifact a task produces has a shape, and each shape has a home that makes it a first-class, browsable record instead of loose bytes on disk:

If you are unsure where something belongs, ask. But the project root is never the answer — a file written there is invisible to Karsa, unversioned as a record, and lost to every view the user has.

The Karsa API: one transport

The daemon exposes a local API with one transport — a JSON-RPC endpoint — and the active project is already baked into one environment variable.

Everything below is a specific application: POST ${KARSA_API_BASE}/rpc with the right method, and let the API create the record. (The one non-rpc exception is the multipart file upload below — binary bytes don't fit a JSON body.)

Titling the chat

Once the user's intent is clear, give the conversation a short title — a undefined-undefined word noun phrase:

POST ${KARSA_API_BASE}/rpc
{ "method": "karsa/chat/update",
  "params": { "id": "<chat_id>", "patch": { "title": "Quarterly revenue cleanup" } } }

Do this once, early, when intent is clear. It's how the conversation becomes findable later.

Uploading a file (assets)

A file becomes a browsable asset record with a single multipart upload — field name file:

POST ${KARSA_API_BASE}/file       (multipart/form-data, field: file)

The response is the asset record. The bytes land under karsa/<project>/artifact/file/, and the file shows up in the project's Files view. Use this for any genuine file: a generated CSV, a downloaded PDF, an image, a dataset export. Don't cp it into the project tree and hope — upload it, so it's a record.

To show a file in a chat (a chart you made, a screenshot, a clip), upload it, then put its id (file_…) in your message. Karsa shows every file a message mentions under it: an image as a thumbnail, a video with ▶, anything else as a chip with its name and size; the person taps it to see it full screen. That's how the person's own 📎 attaches a file too.

Tabular data (sqldb + collections)

Tabular data goes into the project's Karsa SQL DB, and you make it browsable by laying a collection over the table.

karsa/sqldb/exec {"sql": "CREATE TABLE IF NOT EXISTS invoices (id INTEGER PRIMARY KEY, client TEXT, total REAL, issued TEXT)"}. Write your rows into a table in the project's SQL DB (karsa/<project>/sqldb/default.db), through the API: karsa/sqldb/exec {"sql": "CREATE TABLE IF NOT EXISTS invoices (id INTEGER PRIMARY KEY, client TEXT, total REAL, issued TEXT)"}, then karsa/sqldb/exec {"sql": "INSERT INTO invoices (client, total, issued) VALUES (?, ?, ?)", "params": ["Acme", 1200, "2026-10-01"]} (one statement each; ? binds params). Read with karsa/sqldb/query {"sql": "SELECT … WHERE total > ?", "params": [1000]}. A fn reaches them by declaring permissions: {ops: [sqldb/query, sqldb/exec]}. karsa/sqldb/exec {"sql": "INSERT INTO invoices (client, total, issued) VALUES (?, ?, ?)", "params": ["Acme", 1200, "2026-10-01"]}. Create a collection that exposes the table in the UI:

POST ${KARSA_API_BASE}/rpc
{ "method": "karsa/collection/create",
  "params": { "frontmatter": {
    "slug": "invoices",
    "source": "sqlite.row",
    "sqlite": { "table": "invoices" },
    "name": "Invoice",
    "plural": "Invoices"
  } } }

Now the table's rows are a typed, sortable, filterable collection in the project — not a spreadsheet nobody can find. It is reachable under Browse and through the API immediately; to put it in the sidebar, add a group to the frontmatter: "ui": { "group": "main" } puts it at the top, beside Home, Chat, Tasks, Apps and Files; any other group ("operate") puts it under Advanced. Collections are sidebar-opt-in: no ui.group, no sidebar row. See How Karsa works → What shows up in the sidebar. This is also the path to "expand to the cloud later": data in the Karsa SQL DB is data on the seam Karsa controls (see Building a Karsa app).

Prose (docdb docs)

A memo, notes, a writeup, a report — anything document-shaped — goes into a docdb collection as a markdown document (YAML frontmatter for typed fields, a markdown body), under artifact/. Create the document through the API against ${KARSA_API_BASE} so it becomes a real resource in a collection, cross-linkable by id and rendered in the UI — not a loose .md at the project root that no view will ever surface.

Where to go next

When the task grows from organizing data into building software on top of it, read Building a Karsa app — the app, like the data, belongs inside the project, on the Karsa SDK. And if you ever feel the pull to write a file to the project root or reach for an outside database, read Anti-patterns first.