Supervisor sessions
List, inspect, create and terminate supervised sessions, plus the two session-scoped reads that sit outside this prefix.
Five routes, nested at /api/supervisor. This is the surface the dashboard's session list
and its terminate button use.
| Method | Path |
|---|---|
| GET | /api/supervisor/sessions |
| POST | /api/supervisor/sessions |
| GET | /api/supervisor/sessions/{id} |
| POST | /api/supervisor/sessions/{id}/kill |
| DELETE | /api/supervisor/sessions/{id} |
Two auth layers apply to all five: the dashboard token on every method once one exists, and
x-grith-csrf on the three mutations. GETs use the general rate bucket, mutations the
write bucket.
These are not the routes grith exec uses. A supervised CLI session
registers itself over the daemon-token IPC surface at /api/ipc/sessions - see
Daemon IPC.
GET /api/supervisor/sessions
{sessions, total}, where each entry is a summary:
{
"id": "9c4f...",
"tool_name": "claude-code",
"project_name": "api",
"cwd": "/home/u/projects/api",
"tty": "pts/21",
"root_pid": 17421,
"uptime_seconds": 742,
"last_activity_seconds": 12,
"stats": {
"total_intercepted": 88214,
"total_allowed": 4702,
"total_queued": 96,
"total_denied": 14,
"total_filtered_noise": 83402,
"foreign_abi_denied": 0
},
"containment_remaining_seconds": 480
}total_intercepted counts every trapped syscall including noise, so it dwarfs the other
three. Allowed plus queued plus denied is the number of calls the proxy actually scored.
last_activity_seconds is driven by scored calls only, so background noise does not keep a
session looking busy. containment_remaining_seconds is absent unless the session is under
a windowed containment.
GET /api/supervisor/sessions/{id}
id, tool_name, root_pid, uptime_seconds, process_tree_pids, the same stats, and
containment_remaining_seconds when the session is contained. A non-UUID is
400 INVALID_ID; an unknown id is 404 SESSION_NOT_FOUND.
POST /api/supervisor/sessions
Starts a supervised session inside the daemon rather than from a terminal. tool_name is
required; supply root_pid to attach to a running process, or command to spawn one.
start_interception defaults to true.
{ "tool_name": "claude-code", "command": ["claude"], "start_interception": true }The response is {id, tool_name, root_pid, interception_started}.
Admission is refused before anything is attached or spawned, which is the point: an empty
tool_name, or start_interception with neither root_pid nor command, is
400 INVALID_REQUEST; a quarantined audit chain is 503 AUDIT_QUARANTINED and a read-only
audit database is 503 AUDIT_READ_ONLY; no free seat under the licence ceiling is
429 SESSION_LIMIT_REACHED. A daemon that cannot record a decision does not start a session
it would have to run unrecorded.
Ending a session
POST /api/supervisor/sessions/{id}/kill and DELETE /api/supervisor/sessions/{id} do the
same thing - they share one handler: stop the interception task, remove the session from
the registry, and return {id, status, tool_name, final_stats, interception_stopped} with
status set to terminated.
Two session reads that are not under this prefix
| Method | Path | What |
|---|---|---|
| GET | /api/inventory/{session_id} | The binaries pinned at session start |
| GET | /api/sessions/{session_id}/listener-rewrites | Every wildcard bind the supervisor clamped to loopback |
Note the second path: /api/sessions/..., not /api/supervisor/sessions/.... Both are
plain sensitive reads.
The inventory response carries session_id, binaries_pinned, total_scanned,
truncated and entries of {path, sha256}. truncated means the walk hit its 5000-file
cap and stopped short, which is a signal to narrow routine_exec_roots.
Each listener rewrite carries id, timestamp, pid, tool, original_addr,
rewritten_addr and clamp_profile_entry - the profile entry that authorised the clamp.
An empty list is the common case.