Remote queue
As of 2026-10-14 the remote queue (
remote.pyand thedira remotecommand) is still being built. This chapter describes the finished behavior. If the terminal sayscommand not found: dira, your shell cannot finddira; see the PATH note under Connecting. If you getdira remote: 클라이언트가 설치되지 않았습니다("the client is not installed"), the remote queue has not reached your install yet.
A web service hands out jobs, and dira on your computer picks them up and runs them. The server only creates and distributes jobs; it never runs a model. Sessions run on this computer, and the results and the ontology originals stay in this computer's queue. No copy of the ontology is uploaded to the server either. Each time the web asks to see it, this computer reads it and answers (Viewing the ontology on the web).
You connect one project at a time. A project you have not connected runs exactly as before. If the
queue root has no remote.json, every remote step does nothing and exits.
The server sees this computer as a runtime. A runtime is one computer paired with one workspace, and every project you connect to that workspace attaches to that one runtime. Connect three projects on the same computer to the same workspace and the server shows one runtime with three projects under it.
Connecting
From a terminal, run this with the queue root of the project you want to connect. <server> is the
address the web service gives you.
dira remote connect <server> --root ~/Projects/myproject/.diraThe dira command lives in ~/.config/dira/bin. If your shell is zsh or bash, opening the dira app
once is enough. On launch the app appends a block that adds this directory to PATH (it starts with
# >>> dira >>>) to the end of ~/.zshrc for zsh or ~/.bash_profile for bash, once. Terminal
windows that were already open have not read that block, so open a new window after starting the
app. If the file already mentions .config/dira/bin, the app leaves it alone.
If you use the engine without the app, or another shell such as fish, add one line yourself. The commands below do nothing when the line is already there, so running them twice is safe.
# zsh (for bash, use ~/.bash_profile instead of ~/.zshrc)
grep -qs '.config/dira/bin' ~/.zshrc || echo 'export PATH="$HOME/.config/dira/bin:$PATH"' >> ~/.zshrc
# fish
fish_add_path ~/.config/dira/binYou are ready when command -v dira in a new terminal window prints a path ending in
/.config/dira/bin/dira.
- The command prints a confirmation code (
user_code) and an address (verification_uri). - Open that address in a browser and enter the code to approve. This computer joins the workspace you approved it for.
- Meanwhile the command keeps asking the server whether you approved, and once you have, it does the rest. If the code expires first, it exits with rc 1; run it again from the start.
After approval the command does four things.
- It writes the credentials to
~/.config/dira/remote/<workspace_id>/credentials.json(mode0600) and the connection settings toremote.jsonin the queue root. There is one credentials file per workspace. - It registers this computer as the workspace's runtime and records the runtime id in
runtime.jsonin the same directory. The registration also reports the names and versions of the AI tools in~/.config/dira/bin/engines/(claudecodexagygrok), and each tool's login is checked once. - If the queue has no worker, it creates
workers/w1.shand adds the two crontab lines. If this computer does not have the two clock lines yet, it adds those too. - It ends by printing two reminders: grant Full Disk Access, and get a long-lived token with
claude setup-token. Sessions started by cron cannot run without both. The steps are in Authentication.
A project connects to one workspace only. Connecting an already-connected project to a different
workspace is refused with 이 프로젝트는 이미 다른 워크스페이스에 연결돼 있습니다 ("this project is
already connected to another workspace") and rc 2. To switch workspaces, disconnect first.
Connecting a second project
Adding another project to the same workspace does not need a new confirmation code. This computer
already holds that workspace's credentials, so run the same command with a different --root.
dira remote connect <server> --root ~/Projects/second/.diraThe command adds the project to the runtime's project list without a browser approval and exits right away. Its first line reads:
이미 연결된 워크스페이스 <workspace_id>에 프로젝트를 더했습니다("added the project to the already-connected workspace <workspace_id>")
If this computer has two or more workspaces connected to the same server, the command cannot tell
which one you mean. It prints the candidate ids and exits with rc 2. Pick one with --workspace and
run it again.
dira remote connect <server> --root ~/Projects/second/.dira --workspace <workspace_id>To get a confirmation code anyway, add --new. You need it in two cases:
- You want this project on another workspace that this computer has not connected yet. Without
--new, it goes to the workspace that is already connected. - The command exits with rc 1, saying the server rejected the stored credentials, and tells you to
connect again with
--new.
If the workspace you approve with --new already has a runtime, dira registers a new runtime with
the new credentials and then deletes the old one from the server. The workspace's other projects
move to the new runtime; you do not have to connect them again.
Project names must be unique within a workspace. A project's name is the name of its queue root's
parent directory, so if you connect two myproject/.dira roots at different paths to the same
workspace, the second is refused with
같은 이름의 프로젝트가 이 워크스페이스에 이미 연결돼 있습니다: <queue_root> ("a project with the
same name is already connected to this workspace") and rc 2.
If you move a queue root to a different path, dira can no longer find that project. Connect again from the new path.
If you already connected each project separately
Older dira took separate credentials for each project and so made one runtime per project. Projects connected that way do not need to be connected again. The first time the new engine's clock runs, it keeps one still-valid set of credentials per workspace and deletes the other runtimes from the server. From then on the server shows one runtime for that workspace, with every project under it.
If the server rejects every set of credentials, nothing is changed and sync.log gets one line,
REMOTE unauthorized <workspace_id>. Connect one of those projects again with --new.
Disconnecting
dira remote disconnect --root ~/Projects/myproject/.diraDisconnecting removes only that project. The command deletes remote.json in its queue root and
the subdirectory the project used inside the workspace directory. If other projects remain on the
same workspace, the server is not called. The next heartbeat sends a project list without this one,
and the remaining projects keep syncing on the same runtime.
If it was the workspace's last project, the runtime is released too. dira deletes the runtime on the
server and removes runtime.json and credentials.json from the workspace directory. When no
connected workspace is left on this computer, it also removes the two clock lines from the crontab
and stops the read process.
The worker's two crontab lines stay. The worker keeps running, but now handles local tickets only. To stop the worker too, remove those two lines as described in cron.
The computer clock
Once any remote queue is connected, the crontab gets two lines like these, separate from the worker lines.
* * * * * python3 "<home>/.config/dira/bin/engines/remote.py" sync-all >> "<home>/.config/dira/remote/sync.log" 2>&1
* * * * * sleep 30; python3 "<home>/.config/dira/bin/engines/remote.py" sync-all >> "<home>/.config/dira/remote/sync.log" 2>&1If you connected with the engine from the repository, the remote.py path is that engine's path.
Together the two lines run sync-all every 30 seconds. That is this computer's clock. However many
projects you connect, the computer has one pair of clock lines.
Each sync-all run does two things. First it sends one heartbeat per workspace, then it runs a
separate sync for each connected project. If one project's sync fails or does not finish within 20
seconds, the heartbeat has already gone out and the other projects still sync. A failed project
carries a one-line reason on the next heartbeat.
Because the heartbeat is separate from the workers, a queue whose worker stops before picking a ticket, or never runs at all, does not make this computer look disconnected on the web. The other way round: if the web shows this computer's heartbeat has stopped, check the clock first.
crontab -l | grep sync-all
tail -n 50 ~/.config/dira/remote/sync.logIf the sync-all lines are missing, they come back the next time a worker of a connected project
runs. If the lines are there but no heartbeat arrives, the last lines of sync.log tell you why.
Once the log passes 1 MB it is moved to sync.log.1 and starts fresh.
The always-running read process
On macOS, one process stays running alongside the clock. It is called dev.dira.remote-reads, and
launchd starts it and restarts it if it dies. It keeps a connection to the server open and takes
ontology read requests from the web and wakes. Heartbeats,
project syncs, and fetching jobs stay with the clock.
It is installed together with the two clock lines when you connect a remote queue. If it goes missing, it comes back the next time a worker of a connected project runs. To check that it is alive:
launchctl print gui/$(id -u)/dev.dira.remote-reads
tail -n 50 ~/.config/dira/remote/reads.logIf launchctl print cannot find the service, or its output has no state = running, the process is
not running. Until it is back, the clock takes reads and wakes in its place, so they do not stop.
There is just a gap of about 1 second in every 30 when nothing is taken. On a computer that is not
macOS this process is never installed, and the clock always takes them.
The log is ~/.config/dira/remote/reads.log, and once it passes 1 MB it is moved to reads.log.1.
If the process fails before it even starts, the reason goes to reads.launchd.log in the same
directory.
There is no command that stops only this process. It goes away together with the two clock lines when you disconnect the last remote connection on this computer.
Why a job from the web starts late
Most of the talking to the server is done by processes cron starts every 30 seconds. One round of that is called a sync, and it happens in two places: the clock above syncs every project in turn, and each queue's worker syncs its own project right before picking a ticket. A job you start on the web comes down to this computer at the next sync, and whether the clock or a worker fetched it, its session starts in that same sync.
Keeping a connection to the server open and waiting for requests is called the long wait. On macOS the read process keeps the long wait going without a break. Without that process, the clock does the long wait from right after the heartbeat until 1 second before the next clock tick, which takes up about 29 of every 30 seconds. Project syncs run separately from the long wait, so even a slow sync does not hold it back. If you start a job on the web during the long wait, the server wakes this computer and dira syncs that project right away. While that sync runs, the long wait keeps taking the next requests. The job comes down and starts within 3 seconds. Answers to questions and ontology edits made on the web come down the same way, within 3 seconds.
A job started outside the long wait waits for the next clock tick. While the read process runs, the long wait has no gaps; you are outside it only while a dropped connection to the server is being reopened. When the clock does the long wait, the gap of about 1 second between ticks is outside it, and so are the last 3 seconds of the long wait, when a wake no longer starts a new sync. The next tick is a few seconds away, so this does not take long. If a network error keeps the long wait from reaching the server, no wake arrives, the job comes down at the next sync, and you may wait up to about 30 seconds.
A remote job does not wait for your local workers, even when they are all busy, because a fetched
job starts in a remote slot rather than on a worker. A remote slot is a place to run that is separate
from the workers, named remote1, remote2, and so on. So even when local tickets occupy every
worker, the job starts at the next clock tick as described above, at most about 30 seconds later.
How many remote jobs run at once is set by slots in the queue root's remote.json. The default is
2. When every slot is taken, the job stays on the server until one finishes. To change the number,
edit the integer in remote.json, for example "slots": 3. Each sync reads the file again, so the
change applies from the next sync.
Set slots to 0 to turn remote slots off and get the earlier behavior. Each sync then fetches only
as many jobs as the queue has free workers, so if every worker is holding another ticket, the job
stays on the server until one finishes.
On every heartbeat, the clock reports, for each project, when it will next fetch jobs, plus each worker's next run time and the ticket it currently holds. The web screen can use those values to show when a job will start.
A fetched job becomes an ordinary ticket in your queue. The only difference is
remote_job: <job_id> in its frontmatter. It looks like any other ticket in the app, and it is
claimed and finished by the same rules.
When a remote job waits for an answer
Like a local ticket, a remote job goes into waiting for an answer when its session needs to ask a
person something. The same question then shows up both in the dira app and on the web. You can answer
on either the web or the dira app. Whichever answer arrives first is written as
tickets/<question ticket hash>.done.md, and at the next dispatch a session reads it and carries on
with the same job.
During the long wait, both directions take under 3 seconds. An answer written on the web comes down as soon as the server wakes this computer, and a question the session leaves goes up to the web right away. Outside the long wait, both wait for the next clock tick.
While it waits, the web shows the job's state as awaiting. Its job token stays in place, so the
session that starts after the answer talks to the server with the same token.
What goes to the server, and what does not
This is everything that goes to the server.
| What | When |
|---|---|
| Computer name, names and queue root paths of the connected projects | On connect |
| AI tool names, versions, and login status | On connect and every heartbeat |
| Each project's workers: next run time and the hash of the ticket each holds | Every heartbeat (30 seconds) |
| A one-line reason (500 B at most) the project's last sync failed | Every heartbeat after a failure |
A remote job's state (running awaiting done failed) | When it changes. Within 3 seconds during the long wait, otherwise at the next sync |
| Progress - step names and the shell commands the session ran | While a remote job runs. At most once every 5 seconds during the long wait, otherwise at the next sync |
The ## 질문 n (question) section that asks a person | When a remote job waits for an answer. Within 3 seconds during the long wait, otherwise at the next sync |
| Answers to ontology reads the web requested | Each time a request comes in |
There are four progress steps: the first time the session reads a file (read), the first time it
edits one (wrote), when it creates a new ticket (handoff), and when it starts waiting for an
answer (awaiting). Each shell command is cut at 2,000 bytes.
During the long wait, dira checks the remote job's ticket file every second. When the job starts, finishes, or leaves a question, the ticket file changes and the state and question go up right away. Progress piles up the whole time a session runs, so it goes up at most once every 5 seconds.
What never goes to the server:
- Your subscription login token. The token from
claude setup-tokenis never read or sent. - Session transcripts. What the model wrote, what tools returned, and its reasoning are not sent.
- The state and progress of local tickets that are not remote jobs.
The runtime token the server issues is kept only under ~/.config/dira/remote/. It is never written
to the queue root, tickets, or logs. Each job also gets its own job token, kept in
~/.config/dira/run/remote-jobs/, passed only to that job's session as environment variables
(DIRA_REMOTE_URL DIRA_REMOTE_TOKEN). That file is deleted only when the job finishes or the
server takes the job back; it stays while the job waits for an answer.
What comes down from the server
- Jobs. They become tickets, as described above.
- Answers to questions. Written as
tickets/<question ticket hash>.done.md. If that file already exists it is not overwritten; dira takes it as a person having answered locally first. - Persona bundles. The server's personas are installed into
personas/<name>/, with a.remotefile marking them as coming from the server. If you already have your own persona with the same name, it is not overwritten; only that name is skipped. - Ontology schema bundles. Type definitions are added under
_ontology/. If a file at the same path exists with different contents, the whole bundle is refused. - Ontology edits. Objects edited on the web are written under
objects/andversions/. Right before writing, dira checks that the file is still the version the web saw. If it was changed on this computer in the meantime, it does not write and reports a conflict.
Persona and schema bundles are checked for a new version every 10 minutes.
Viewing the ontology on the web
The ontology is not uploaded to the server as a copy. When you open the ontology on the web, the server sends a read request to this computer, and dira here reads the files at that moment and answers. Every request reads the files fresh, so the web shows exactly what is on this computer now.
On macOS, read requests are taken by the
always-running read process. It keeps a connection to the server
open, answers each request as soon as it arrives, and keeps taking new requests while answers are
still going out. Without that process, or on a computer that is not macOS,
the computer clock takes them instead. Then sync-all waits for requests from
right after the heartbeat until 1 second before the next clock tick, so for about 1 second in every
30 no request is taken. Either way, reads do not wait for a slow project sync or for a sync started
by a wake. The web cannot show the ontology when:
- this computer is off or asleep
- the network is down
- the read process is not running and the crontab does not have the two clock lines either
Once it is back, the ontology shows again within about 30 seconds.
Only three folders under the project's ontology location are read.
| Folder | What the web sees |
|---|---|
objects/ | The object list, one object's body and links, search |
versions/ | An object's past versions and the contents of each |
_ontology/ | Object type and link type definitions |
Only .md files under these three folders are read, and symlinks are not followed. Files placed
directly in the ontology location, and any other folder, never reach the web.
A file whose frontmatter cannot be parsed is left out of the object list. It is not skipped silently: the answer carries its path and one of the reasons below. If an object is missing on the web, check whether its file hits one of these.
| Reason | Meaning |
|---|---|
not-utf8 | The file could not be read as UTF-8 |
no-frontmatter | The first line is not --- |
unclosed-frontmatter | There is no closing --- for the frontmatter |
duplicate-name | Another file at a different path has the same name. Only the first file in path order becomes an object |
read-failed | It could not be read for some other reason |
Earlier versions of dira uploaded the files under objects/ and versions/ whenever they changed.
dira no longer uploads them. dira does not delete the copies uploaded back then; the remote service
cleans them up on its side. Objects edited on the web are still written to this computer as in
Ontology edits above.
When something goes wrong
A failing remote step never stops the worker. Local tickets keep running, and one line,
REMOTE <reason>, goes to the worker log for a sync the worker ran, or to
~/.config/dira/remote/sync.log for a sync the clock ran. When one project's sync fails, the reason
is also kept in last-error in that project's subdirectory, and the next successful sync clears it.
| Log or state | Meaning and what to do |
|---|---|
REMOTE unauthorized | The server rejected the runtime token. Sync for that workspace has stopped; connect one of its projects again with --new |
| The web shows this computer's heartbeat has stopped | The clock is not running. Check the crontab and sync.log as in The computer clock |
A tool is reported as needs_check | That AI tool failed its login check, and it gets no jobs. Log in to it again (Authentication) |
remote_revoked: true in a ticket's frontmatter | This computer went quiet for too long and the server took the job back. That ticket's state is no longer reported |
remote_failed: no-token in a ticket's frontmatter | The job's token file was missing, so dira closed the ticket without starting a session. The ticket is .done but the work is not, and the server is told failed. This computer cannot recreate the token; issue the job again from the remote service |
REMOTE persona-skip <name> | You have your own persona with that name, so the server's persona was not installed |
launchctl print gui/$(id -u)/dev.dira.remote-reads cannot find the service | The read process is missing, so the clock is taking reads in its place. It is installed again the next time a worker of a connected project runs. If it is installed and still not running, check ~/.config/dira/remote/reads.launchd.log |
reads <workspace> <reason> in sync.log | The server does not know read requests, or the network failed, so this clock run takes no more read requests for that workspace. The next clock run tries again |
The login check runs on connect, every 6 hours, and right after a session on that tool ends with an authentication error.
Next is Troubleshooting.