Plato Agent
Plato Agent is an open agent that shows its work: every step is recorded,
replayable, and auditable. It is the first application shell built on
platonic-core.
New here? Start with docs/QUICKSTART.md — build, run, and test in five minutes.
The bootstrap surface is intentionally small:
plato "question"runs one bounded CLI invocation, streams live assistant text to stderr, and writes the run ledger to the platform user-state path.plato -c "follow-up"continues the latest workspace session from the SQLite ledger.plato --events <file> "question"writes an explicit JSONL ledger.plato replay <file>validates and prints a deterministic JSONL readback without network calls or tool execution.plato replay [--run <id>]replays the default SQLite ledger; omitted--runselects the latest session.plato replay --db[=<path>] [--run <id>]replays an explicit SQLite ledger.
Configuration
Config resolution order:
--config <path>$PLATO_CONFIG./plato.toml~/.config/plato/config.tomlon Unix or%APPDATA%\plato\config.tomlon Windows- built-in defaults
Auto-discovered ./plato.toml cannot set provider.api_key_env or
provider.base_url. Use --config, $PLATO_CONFIG, or the user config for
provider credentials and custom endpoints.
Leading ~ expands in explicit config paths. Relative explicit paths resolve
against the workspace root. Built-in defaults use OpenRouter:
[]
= "open_router"
= "~openai/gpt-latest"
= "OPENROUTER_API_KEY"
[]
= 4000
= 1024
= 8
[]
= ["file.read", "file.list", "file.write", "file.edit", "shell.exec"]
OpenAI-compatible direct OpenAI config remains available:
[]
= "open_ai"
= "gpt-5.5"
= "OPENAI_API_KEY"
file.read and file.list are auto-allowed. file.write, file.edit, and
shell.exec require stdin approval and default to no. shell.exec runs from
the workspace root with a scrubbed child environment, no provider credentials,
bounded stdout/stderr, and a timeout. It uses sh -c on Unix and
cmd.exe /C on Windows; timeout or cancellation terminates the full process tree.
Use --yolo to auto-approve enabled workspace-write tools that would otherwise
prompt. Yolo mode does not enable disabled or unknown tools, approve network
tools, permit deny-class effects such as external side effects or secret access,
approve shell.exec, or bypass workspace path checks.
SQLite Ledgers
- Bare
plato "..."writes to the default platform user-state path. plato -c "..."continues the latest session from that store.--dbalso writes to the default platform user-state path.--db=<path>writes to that SQLite file; relative paths resolve against the current workspace.- On Unix, default ledger directories are
0700and the database and SQLite sidecars are0600; explicit--db=<path>permissions remain caller-managed. - Use
=for explicit paths because--dbalso has a bare default form. - Live assistant text,
run_id,ledger_path, and replay hints print to stderr. Stdout remains only the final answer. - Replay shows final assistant messages, not partial streaming deltas.
- Ledger, approval, replay, and typed-transcript tool call ids are host-minted per run; provider ids remain provider-facing.
- Streamed runs request provider usage chunks; providers that omit usage still record zero usage.
plato replaywithout arguments replays the latest session from the default platform SQLite ledger.plato replay --run <id>replays a single run.--events <file>is the explicit JSONL export/debug path.- If the workspace daemon lock is held, SQLite CLI run/replay paths fail closed instead of competing with the daemon-owned store.
Replay forms:
Daemon
plato-agentd is the local runtime daemon for session-facing clients such as
the future plato-tui. The runtime topology and verb set are defined in
docs/ARCHITECTURE.md and issue
#11.
Start it for a workspace:
On startup it prints:
workspace_id: <workspace-id>
socket_path: <daemon-endpoint>
ledger_path: <state-path>/agent.db
Default paths are keyed by the workspace id:
- Unix socket:
${XDG_RUNTIME_DIR:-<system-temp>/plato-agent-<uid>}/plato-agent/workspaces/<workspace-id>/agent.sock - Unix lock:
${XDG_RUNTIME_DIR:-<system-temp>/plato-agent-<uid>}/plato-agent/workspaces/<workspace-id>/agent.lock - Unix ledger:
${XDG_STATE_HOME:-$HOME/.local/state}/plato-agent/workspaces/<workspace-id>/agent.db - Windows pipe:
\\.\pipe\plato-agent-<workspace-id> - Windows lock and ledger:
%LOCALAPPDATA%\plato-agent\workspaces\<workspace-id>\agent.lockandagent.db
Runtime directories are restricted to 0700 and the daemon socket to 0600.
A custom Unix --socket parent is restricted to 0700 at startup. Windows
pipe and lock ACLs grant access only to the current user and reject remote pipe
clients. Windows clients limit server impersonation to identity inspection,
authenticate the server's user before sending protocol bytes, and bound
busy-pipe connection waits.
The daemon holds the lock while it is active. SIGINT and SIGTERM on Unix, and
Ctrl-C or Ctrl-Break on Windows, trigger a graceful shutdown: the daemon stops
accepting new connections, then removes its endpoint and lock before exiting.
On Windows the daemon keeps the exact lock file pinned until delete-on-close.
Do not remove a lock for a live daemon.
Live assistant deltas are transient events.stream events and are not written
to the ledger. After a lagged response, omitting from_offset resumes at the
current tip; transcript.read returns ledger-backed status and final answer.
hello advertises transcript.read.typed. Successful transcript.read
responses preserve the legacy transcript string and add ordered typed.runs
with structured chat, tool, policy, and approval entries.
hello also advertises transcript.read.pending_approval; while a run is
paused, its transcript response includes the complete pending approval and
omits it immediately after a decision or cancellation.
hello advertises daemon.shutdown_if_idle for graceful control. The request
omits params (an empty object is also accepted). It returns refused_active
without changing the daemon while a run or approval is active; otherwise it
closes run admission, returns shutdown, then exits and removes its socket and
lock. Duplicate shutdown and run-admission requests dispatched before teardown
fail with daemon_shutting_down; after the shutdown response, connection
close is expected and lock removal confirms success.
On Windows, installer control validates every current-user lock against its
workspace and live process image. Exact sidecars also require the expected pipe
server PID and hello response before control:
plato-agentd control list-workspaces
plato-agentd control shutdown-if-idle --workspace C:\path\to\workspace
plato-agentd control shutdown-if-idle
The commands emit NDJSON. The aggregate shutdown validates the whole namespace
before sending any shutdown request, attempts every validated daemon, and exits
nonzero if a daemon is active or any lock cannot be validated. Locks are never
removed by the control client. A missing targeted daemon reports not_running;
a shutdown result is sent once and confirmed by process exit and lock removal.
Minimal NDJSON-over-Unix-socket check, using the workspace_id and
socket_path printed by the daemon:
WORKSPACE_ROOT="" \
WORKSPACE_ID="<workspace-id>" \
SOCKET_PATH="<socket-path>" \
NDJSON run.start and message.append default to wait: false, returning a
running response immediately. Send "wait": true only when the connection can
block until the run finishes.
Desktop (Development)
The desktop shell renders full typed session history, streams the selected run,
and supports new or continued messages, approval decisions, and cancel.
Provider credentials remain with the daemon. Linux development attaches to a
manually started daemon. On Windows, the shell first attaches to a valid daemon
for the selected workspace; when none is listening, it starts the absolute
sibling plato-agentd.exe sidecar and retries for a bounded interval.

# Terminal 1, from the repository root
# Terminal 2
On first launch, choose the daemon workspace. The shell remembers its canonical path as the next-launch seed and returns to the picker if that directory disappears; each running shell keeps its own selected root. New chat clears the selected session; otherwise the composer continues it. Switching chats does not cancel their active runs. Closing the Windows shell never stops a daemon or run. The ready shell checks daemon health without restarting it; a child crash shows the disconnected screen, and only Reconnect attempts one new start. The shell never removes a daemon lock, and reports the endpoint and lock paths when startup remains blocked. Only one Windows shell may own a workspace at a time. Linux development requires the Tauri system dependencies.
Windows Installer (Unsigned Development)
Phase A targets x64 Windows 10 22H2 or newer. Build the per-user NSIS installer on Windows:
cd desktop
npm ci
npm run tauri:bundle:windows
The installer writes Plato under %LOCALAPPDATA%, bundles the same-revision
plato-agentd.exe, and downloads the WebView2 Evergreen bootstrapper when the
runtime is absent. Upgrade and uninstall first close the desktop, block new
installed-sidecar starts, and make one bounded aggregate
plato-agentd control shutdown-if-idle invocation. An active daemon or
unvalidated lock aborts before installed Plato binaries or user files change;
idle daemons exit and remove their locks. These unsigned artifacts are for
development proof only and are not distributed.
Linux AppImage (Private Release)
Linux releases target x86-64 Ubuntu 24.04 on the WebKitGTK 4.1 ABI. The
AppImage contains the same-revision plato-agentd sidecar. It first attaches
to a valid workspace daemon; if none is available, it restores only the user's
login-shell PATH, starts the bundled sidecar, and retries for a bounded
interval. Closing Plato detaches without stopping the daemon or active runs.
Startup failures report the sidecar, socket, and lock paths and never delete a
lock or fall back to a system daemon.
Authenticated private-release download and integrity check:
Ubuntu 24.04 needs its WebKitGTK 4.1 runtime and libfuse2t64; Rust, Node,
and development packages are not runtime dependencies. These private artifacts
are not a public community launch. Build the AppImage on Ubuntu 24.04 with
npm run tauri:bundle:linux -- --ci -- --locked from desktop/.
Discord Gateway
plato-gateway-discord receives Discord messages over an outbound WebSocket
and sends replies through Discord's REST API. Add the bot token variable name
and numeric owner user ids to plato.toml:
[]
= "DISCORD_BOT_TOKEN"
= [123456789]
With the workspace daemon already running, start the gateway in an environment that contains the bot token but no provider credentials:
Enable the bot's Message Content intent and grant it View Channel and Send
Messages where it will run. Messages from other user ids are ignored. Each
allowed channel or DM continues one daemon session; final answers are recovered
from the ledger after daemon reconnects. Approval-required runs post one bounded
notification with the tool, effect, and preview; grant or deny the request
locally in plato-tui. The gateway never sends approval decisions. Failed runs
post Run failed. Inspect it locally with: plato replay; canceled and
interrupted runs stay silent.
Allowed-owner messages over 4,096 UTF-8 bytes or matching the fixed unsafe-input
markers are rejected before daemon access with Message rejected: unsafe or oversized Discord input. Accepted messages are forwarded unchanged.
TUI
plato --tui is the interactive local entrypoint. It attaches to the workspace
daemon if one is running, or starts an embedded daemon for the TUI session.
It renders a chat-first transcript surface with an intro, live activity,
status rule, composer, session picker, and approval modal.
plato-tui remains a terminal client for a manually started plato-agentd. It
does not spawn, supervise, restart, or stop the daemon, and it does not call
providers, execute tools, or write SQLite directly.
Assistant text appears live through daemon events.stream; replay remains
based on final ledger messages.
Session picker statuses are running, finished, failed, canceled, or
interrupted; interrupted means a daemon restart closed a previously running
session so it can be resumed.
On attach, the TUI selects the latest session by default; submitted messages
continue that session until /new clears the selection.
Use --socket <path> when connecting to a non-default socket, --config <path>
to pass a config file to daemon-started runs, and --run <run_id> to open a
specific transcript.
Keys:
Enter: submit the composer to the daemon. A session can have only one active run./sessions: open the session picker.Enterresumes the focused session;Esccloses the picker./new: clear the selected session so the next submitted message starts fresh.g/d: grant or deny the focused approval request.Ctrl-C: requestrun.cancelfor the active run; a secondCtrl-Cexits the TUI. Exiting the TUI does not stop the daemon.r: reconnect and reload daemon state.qorEsc: exit the TUI.
Commands
Dogfood Recipe
tmp=
OPENROUTER_API_KEY="" \
Boundary
platonic-core remains pure. Provider calls, local tools, approval prompts, ledger files, SQLite, daemon runtime, TUI, and connectors belong in this repo.
License
Licensed under either of
at your option.