Skip to content

Identity

A transcript proves a machine did something. It does not prove a person did.

Before this shipped, EventBase carried id, seq, ts, sessionId, turnId, causationId and source — and source is a category (user | model | tool | plugin | system | compactor), not a principal. Grepping the runtime for userId, actorId or principal returned nothing.

Three things were impossible as a direct result:

  • Audit. "Who ran bash rm -rf" had no answer.
  • RBAC. There was no subject to attach a role to.
  • Cost attribution. Usage accounted per session, and sessions were anonymous.

The Principal

ts
interface Principal {
  readonly id: string;                    // stable, e.g. "u:ada@example.com"
  readonly kind: 'human' | 'service';
  readonly displayName?: string;
  readonly claims?: Record<string, unknown>;
}

It is stamped on EventBase and on ToolCallContext, so every event in the log and every tool call carries the subject that caused it.

json
{
  "type": "tool_call_requested",
  "seq": 118,
  "sessionId": "01JZ…",
  "turnId": "01JZ…",
  "source": "model",
  "principal": { "id": "u:ada@example.com", "kind": "human", "displayName": "Ada L." },
  "tool": "bash"
}

source and principal answer different questions, and both are kept. source: "model" says the model asked for the call; principal says on whose behalf the session is running.

Where it comes from

Identity resolves at the channel boundary, because that is the only place that knows who is actually there:

SurfacePrincipal
Terminalthe OS user
HTTPthe authenticated token's subject
Slack, Telegram, other chatthe paired account
Mobilethe paired device's token subject
Runnerper connection
Schedule, webhooka service principal naming the trigger

A surface that cannot establish a human gets an explicit service principal rather than a blank field, so "a job did this" never looks the same as "nobody recorded it".

Why it landed first

It is a protocol change. It touches EventBase, the runner wire protocol (which bumped RUNNER_PROTOCOL_VERSION) and the desktop IPC contract. Doing it before the rest of the governance work was far cheaper than retrofitting it after, and the audit trail is only meaningful because the records it writes have a subject.

Because derived state is a pure fold over the log, adding the field invalidated nothing already written: older events simply carry no principal.

Multi-client sessions

The runner is explicitly multi-client, and cross-client abort is permitted by default. MOXXY_RUNNER_STRICT_ABORT=1 denies it, so one attached client cannot cancel another's turn. Worth setting anywhere two people may hold the same session.

Next

Open source · self-hosted · MIT