Write in a working copy
Keep part of an instance on your machine with marfa device, read and write it with no connection, and send the writes when you can.
marfa device keeps a working copy of part of an instance in one file. It answers reads from the copy and puts each write in a queue that waits for the server. Use it for a script or an agent that must keep working offline. An app built on the Swift SDK keeps the same kind of copy.
Every marfa device command names the file with --db, or with MARFA_DB for a whole shell. A command that reaches the server, such as hydrate, catch-up or drain, needs a server and a credential, as Connect to a server shows. Reads and queued writes work on the copy alone.
Fill the copy
Choose the types and the tier to keep, then fill the copy:
export MARFA_DB=./copy.sqlite
marfa device hydrate --types core.note,core.task --tier library
marfa device status--types * keeps every type except system.*, and --tier all keeps the library and the feed. --edge-type keeps every edge of a type the credential reads; repeat it for more. A hydration that stops partway carries on from where it stopped when you run it again.
Check the copy
marfa device status says how the copy stands:
- Whether it's whole: the hydration is
never,in_progress,complete,staleorexpired. While a fill runs,items_listedagainstitems_expectedsays how far it has come. - When it last synced:
synced_atis when the copy last finished a pull from the server, by this machine's clock. A hydration or catch-up that completes sets it, and so does a follow that has caught up, at most every 30 seconds. A pull that fails or stops partway leaves it as it was, and a hydration that empties the copy clears it. - Its files: the file budget, and the attached files the copy doesn't hold, counted by reason, as Keep files on a device explains.
An app can show synced_at as "Updated 5 minutes ago" or "Offline since 10:42".
Read from the copy
marfa device items get, items list and search answer the way the server does, from the copy. A list or a search says on standard error, or as completeness under --json, whether the copy is complete, still filling (partial) or behind the server (stale).
| Command | Options the server also takes |
|---|---|
marfa device items list | --source, --updated-after (inclusive), --updated-before (exclusive), and --sort with --direction |
marfa device search <text> | --tier, --occurred-after and --occurred-before (both exclusive) |
--type and --state each take a list, comma-separated or repeated: up to 10 types, with every type beneath each, and any states. --all-states reads every state.
--updated-after lists in change order, oldest first. --sort updated-at or --direction asc alone keeps that order; any other sort or direction is refused with validation_error. A --sort properties.<field> puts numbers and booleans first, then text, then items where the field is missing, null, an object or an array, in either direction. Items that compare equal go by ID.
Read a view answers a saved view from the copy.
Read the bin
The copy keeps the items in its slice that go to the bin, whether you moved them there or another app did. marfa device items list --state trashed lists them with no connection, the most recent first, and marfa device items restore <id> takes one out.
marfa device items bin reads the instance's whole bin instead, a page at a time. It needs the server, and keeps nothing it reads in the copy.
Create an item
A create shows in the copy at once and waits in the queue. This note is captured at a place and is about another item:
marfa device items create --type core.note \
--properties '{"title": "Call with the builder"}' \
--capture-latitude 51.5072 --capture-longitude -0.1276 \
--edges '{"about": ["<item-id>"]}'A create is one write. The state from --state, the capture location and the edges go in the body of the write that creates the item, and each --tag goes as a write of its own after it.
- A create the instance would refuse as malformed is refused before it's queued, with the code the instance would answer: a
revokedstate, a coordinate that isn't a finite number, or an edge type or target that isn't valid. Its edges are judged as any edge write is. - If the instance refuses any edge, such as one whose target doesn't exist, it creates neither the item nor its edges. The write stays in the queue as
refused, with its body. - While the write waits, the copy shows each edge. When the instance answers, the copy holds the edges the instance made in their place. An answer carries at most 50 edges of one type, so an edge it leaves out stays shown until the next catch-up brings the instance's own.
Change an item
An update names the version it was based on, as it does at the server:
marfa device items update <id> --version 3 --properties '{"title": "Call with the architect"}'
marfa device items update <id> --version 4 --occurred-at 2026-10-30T09:00:00Z--clear-property <name> removes one property and leaves the rest; repeat it to remove more. --replace makes --properties the item's whole set, clearing any property it leaves out.
--occurred-at sets the item's own time. --clear-occurred-at gives the item no time of its own, so it sits at its creation time; if the item's create hasn't been sent yet, the copy shows the time the create was queued until the instance answers. An update can't name both.
Write edges
marfa device edges create, edges update and edges delete queue edge writes as the item commands do, and an edge write waits until the copy is whole. The copy refuses an edge write the instance would refuse before it queues anything, and marfa device edges check and edges check-move ask first: Write edges in a working copy covers both.
Send the queue
marfa device drain sends the queued writes in order and records each answer. A write that met a network error stays for the next drain.
marfa device drain
marfa device queuemarfa device queue lists every write, its body and its verdict. Act on the three that need you:
refused: read the body, fix it in a new write, then take the refused one out withmarfa device discard <id>.blocked: do what its reason needs, as Verdicts lists.marfa device release <id>sends it again, andmarfa device withdraw <id>takes it out and puts the copy back to what the instance holds.dead:marfa device release <id>sends it again.
marfa device forget clears the writes the instance has answered, which keeps later writes fast. A synced folder needs none: it clears its own as each drain begins.
Keep it current
marfa device catch-up applies what changed on the instance since the last hydration or catch-up. marfa device follow holds the event stream open and applies each change as it arrives. A copy away longer than the instance keeps events becomes stale, and one whose instance or reach changed expires: Staying in step says what to run for each.
A newer CLI keeps the queue of a copy an older one made. The first command run without --reader adds what the newer build needs to the file, and a command with --reader is refused until then.
The rules behind this guide are in the device and queue and verdicts chapters of the contract.