Agent offline
An agent is offline when the studio has no live socket for it: it does not appear in the agent list,
or a command answers Agent is offline. The checklist below works through that situation in four
short passes over the network, the service, the authorisation and the recovery β because every one
of these causes produces the same symptom. Work the passes in order and stop when the agent comes
back.
Symptoms
| What you see | What it points at |
|---|---|
| The agent is missing from the list | The studio holds no live socket for it. Presence is derived from the live socket and is never stored. |
POST /api/agents/command β 400 {"error":"Agent is offline"} | The command is refused before it is queued, for the same reason. |
GET /api/agents β {"agents":[]} | No agent holds a live socket. |
POST /api/agents/command β 404 {"error":"Agent not found"} | The agentId you sent is not one the studio knows. |
POST /api/agent/register β 403 | The licence is unknown, revoked, expired, or the secret does not match the token. |
POST /api/agent/auth β 401 {"error":"Invalid API key"} | The key in data/agent.json is not the one the studio issued. |
The agent log reads Authentication failed | The socket handshake was rejected. The studio answers the auth request with ok: false and the code AUTH_FAILED, and closes the socket. |
Two endpoints answer most of your questions: GET /api/agents lists the agents that hold a live
socket, and GET /api/admin/agents lists every licence and every registered agent with its
connectionStatus.
curl -sS http://localhost:3000/api/agents
curl -sS http://localhost:3000/api/admin/agentsCheck the network
The agent dials out to the studio; the studio never dials an agent. Only http and https studio
URLs work, and the address in studioUrl must be reachable from the bench, not from the
laptop you are sitting at.
The address lives in data/agent.json on the agent, alongside agentId, apiKey and
agentSecret. The AGENT_NAME and STUDIO_URL entries in the agent's .env are not read, so
editing them changes nothing β edit the file.
- From the bench, reach the studio's REST API. A JSON body with a
statusfield means the studio is up and the path works. - From the bench, reach the socket endpoint on the same port. A Socket.IO handshake starts with
0{"sid":β¦}; a404, an HTML page or a refused connection means the socket endpoint is not served on that URL. - Ask the agent what it thinks.
{"connected":false}means it has never reached the studio.
curl -sS http://studio.lab.internal:3000/api/system/status
curl -sS "http://studio.lab.internal:3000/api/socket.io/?EIO=4&transport=polling"
curl -sS http://localhost:3001/api/connectThe two ports involved are the studio's PORT (3000 by default, carrying the UI, the REST API and
the socket endpoint) and the agent's own AGENT_PORT (3001), which stays on the bench.
Check the service
The studio serves the web UI, the REST API and the realtime socket from one server on one port,
with the socket endpoint mounted at /api/socket.io. Two things follow from that.
- Confirm the studio answers on its socket path. If the REST API answers but the socket path
does not, the process you started is not serving the socket endpoint. Check how the studio was
started, and check that
PORTin the studio's environment matches the port in the agent'sstudioUrl. The studio prints its socket path at startup:> WebSocket path: /api/socket.io.
curl -sS "http://studio.lab.internal:3000/api/socket.io/?EIO=4&transport=polling"- Confirm the studio can reach its own database. The socket handler reads the agent records
from the database the studio is configured with, so a database the studio cannot open makes
every handshake fail. The configuration lives in
.db-config.jsonin the studio's working directory βdbType,host,port,database,username,passwordβ and the studio reads that file relative to its own working directory. Start the studio from the directory that holds it.
cat .db-config.jsonCheck the authorisation
Three credentials are involved, and different endpoints check them. The licence code is checked by
POST /api/agent/register. The agent secret is checked by the socket handshake. The API key is
checked by POST /api/agent/auth, an endpoint the agent itself never calls.
- Register again with the studio URL, the agent name and a licence code, either from the
agent's page at
http://localhost:3001or directly:
curl -sS -X POST http://localhost:3000/api/agent/register \
-H 'Content-Type: application/json' \
-d '{"code":"<licence-code>","name":"bench-01","studioUrl":"http://studio.lab.internal:3000"}'The 403 body names the reason: invalid_code (no licence carries that code), used (the licence
was already spent on another machine), revoked, or expired.
-
Check the secret the handshake compares. The socket handshake compares
agent_secretonly. A valid API key with a wrong secret still produces anAUTH_FAILEDanswer. -
Give every bench its own licence. A licence authorises one registration. Presenting the same code again is refused, so a licence never creates a second agent. To bring an agent back with the credentials it already holds, register with its
agentIdandagentSecretβ that updates the agent in place, keeps itsagentIdandapiKey, and increasesregistrationCount. -
Know what a revocation does. A revoked licence stops further registrations. Restarting the agent is what takes a connected agent down, so that it has to authenticate again.
Recover
- Re-register and restart
Register again with the studio URL, the agent name and a licence code. A successful registration answers with
agentId,apiKeyandagentSecret, which the agent stores indata/agent.json. Restart the process so it reads that file and dials the studio again:pnpm --filter @dkstudio/agent dev. - Verify presence and the command path
The agent should appear in both places β
GET /api/agentscontains its id with statusonline, andGET /api/admin/agentsshowsconnectionStatus: "online". Then send a command withPOST /api/agents/commandand read the result atGET /api/agents/commands/{id}. - Confirm the polling fallback
The agent polls
GET /api/agent/commands?agentId=every 5000 ms and posts results toPOST /api/agent/commands/result. Both paths write the sameagent_commandsrows, so a command still gets through when the socket is down β it arrives up to five seconds later. The agent log lineStarting command polling every 5000mstells you the loop is running.
Whether an agent is online is derived from its live socket. The lastSeen column next to it is
written by POST /api/agent/heartbeat and is not a liveness signal.
See also
- Unstable connections β agents that come and go.
- Agent registration and entitlements β licences and revocation.
- Network requirements β who dials whom, and which ports.
- Socket protocol β the handshake and its messages.
- Error codes β what each refusal says.