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:
- A file — a CSV, PDF, image, dataset, export, anything you'd hand someone as a file → upload it as an asset. It lands under
karsa/<project>/artifact/file/as a record the UI can list and preview, not stray bytes the daemon can't see. - Tabular data you'd filter, sort, join, or aggregate → the project's Karsa SQL DB (
karsa/<project>/sqldb/default.db), not a stray spreadsheet on disk. To make a table browsable in the UI, put a collection over it. - Prose — a memo, notes, a writeup, a report → a markdown document in a docdb collection under
artifact/, not a loose.mdat the project root.
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.
- Always use
$KARSA_API_BASE. It encodes the daemon host and the active project — every call you make through it operates on this project. Every write is aPOST ${KARSA_API_BASE}/rpcwith body{ "method": "karsa/{resource}/{verb}", "params": { … } }(e.g.karsa/deal/create,karsa/chat/update). See The API for the full shape and the CRUD verbs. - Never hard-code
/api/...paths or a host. Don't writehttp://localhost:1688/api/...or assume a path prefix.$KARSA_API_BASEis the only base you need; hard-coding anything else will hit the wrong project or no project at all. - You're already cwd'd into the project. Your working directory is
$PROJECT_HOME, so filesystem tools act on the real project tree — but records (assets, rows, docs) are created through the API, not by writing files intoartifact/by hand.
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.