Architecture
DK Studio is two processes and one database, and the split is what makes the product usable in a lab. The platform holds every piece of state β agents, licences, users, command history β and gives you one browser view of every bench. The agent runs on the machine under test and does the physical work. Because the agent dials out, your benches need no public address, no port forward and no VPN client.
ββββββββββββββββββββββββββββββββ
β DK Studio (server) β
β β
β Web UI Β· REST API β
β Socket.IO server β
β β β
β ββββββΌββββββ β
β β Database β SQLite / β
β β β PostgreSQL / β
β β β MySQL β
β ββββββββββββ β
βββββββββββββ¬βββββββββββββββββββ
β outbound only
β Socket.IO (websocket, polling fallback)
β HTTP (register, auth, poll, result)
βββββββββββββΌβββββββββββββββββββ
β DK Studio Agent β
β (bench / PC / test rig) β
β β
β Shell Β· System info β
β Serial ports Β· Device events β
ββββββββββββββββββββββββββββββββ
The platform
The platform is a Node application that serves the web UI, the REST API and the realtime socket from a single server on a single port. Browser and agents therefore share one origin, one port and one authentication story.
| Concern | Implementation |
|---|---|
| Web UI | Server-rendered pages under / and /dashboard, /agents, /settings, /admin |
| REST API | Route handlers under /api |
| Realtime | Socket.IO mounted at /api/socket.io, transports websocket and polling |
| Database | Raw SQL over three interchangeable drivers |
| Auth | Session cookie, three roles: admin, engineer, viewer |
Database drivers
The schema is written three times β once per dialect β in lib/db/init.<driver>.sql. The driver is
chosen during setup and recorded in .db-config.json, so the same codebase runs on:
- SQLite β the default. A single file under
DATA_DIR. Best for a single-server deployment. - PostgreSQL β for a shared or clustered deployment.
- MySQL β for organisations that already run MySQL.
The driver is picked once, at setup, and every query in the platform goes through a small factory that speaks the right dialect. There is no ORM in the middle.
The agent
The agent is a small Node service that runs on the bench, next to the reader or the ECU. It does three things: it holds credentials, it keeps a connection to the studio, and it executes the commands it is handed.
| Concern | Implementation |
|---|---|
| Identity | data/agent.json β agentId, apiKey, agentSecret, name, studioUrl, registeredAt |
| Connection | Socket.IO to the studio, with an HTTP polling fallback every 5000 ms |
| Execution | shell, get_system_info, get_serial_ports, open_serial, close_serial, serial_write |
| Events | device_event messages carrying serial metadata |
| Local page | The agent's own serial console on port 3001 |
The agent writes the script it is given to <cwd>/.agent_script_<Date.now()>.sh and runs it with
/bin/bash. The default command timeout is 300000 ms, enforced on the agent: a command that
outruns it is reported back as a timeout result, not as a hung request.
How a command travels
Two transports carry a command, and the agent uses both. They differ only in how the command reaches the agent; everything after that is identical.
The transport
| Path | Request | Reply |
|---|---|---|
| Push | The studio emits a command request over the socket, carrying command, args and timeout | The agent emits a command response, carrying status, output and error |
| Poll | The agent calls GET /api/agent/commands?agentId= every 5000 ms and claims what it is given | The agent posts the outcome to POST /api/agent/commands/result |
Every message on the socket uses the same envelope: v, type, kind, id, timestamp, payload.
The command lifecycle
Both paths write to the same agent_commands table, so the state you see in the UI is the same
either way.
pending βββΆ in_progress βββΆ completed
A row moves to in_progress when the agent claims it, and to completed when the result is
posted. The row keeps command_type, the command_args JSON, the status, and a result JSON
holding stdout, stderr, exitCode and durationMs. The result's own status is success,
error or timeout.
Where the state lives
| Data | Lives in | Notes |
|---|---|---|
| Users, agents, licences, command history | The platform database | The source of truth: users, agents, agent_licenses, agent_commands. |
| Licence entitlement | agent_licenses.status and expires_at | Checked when an agent registers: available, used or revoked. |
| Agent credentials | The agent's data/agent.json | Issued at registration and never re-shown. |
| Presence | The live socket map | online and offline are derived from the connection, never stored as a flag. |
| Studio configuration | .db-config.json, .studio-license.json | Written by the setup wizard. |
| Setup progress | .setup-progress.json, .setup-complete | The marker file gates the setup wizard. |
| Serial byte streams | The agent | The studio records serial metadata β path, manufacturer, serial number, vendor and product id. |
Where it is going
Every connection is initiated by the agent. It dials out to the studio over Socket.IO and REST and authenticates there; the studio never dials back to a bench.
If the studio can reach the agent, something is misconfigured. The agent must be able to reach the studio; the reverse is neither required nor used.
The agent reaches the studio over two surfaces: the persistent socket for commands and events, and
HTTP for registration, authentication, heartbeats and polled results. The studio URL you configure
on the agent β http://localhost:3000, or the address of your own server β is the only address the
bench ever needs.
Related pages
- Supported Protocols β the protocol families the platform is built for.
- Connect Your First Agent β get a bench talking to the platform.
- Remote Shell and Serial β drive a bench from the browser.