Observability logging

September 19, 2026 · View on GitHub

Auto mode can write a JSONL observability log for decisions and classifier usage. Persisted sessions use a sidecar next to the Pi session file. In-memory sessions use a global application directory.

Logging is off by default. Logging fails open, so a write error never changes an allow or block decision.

Enabling

Set autoMode.log in a Pi-owned configuration source. These sources include ~/.pi/agent/extensions/pi-automode/config.json, trusted .pi/automode.local.json, and PI_AUTOMODE_SETTINGS_JSON.

{
  "autoMode": {
    "log": {
      "enabled": true,
      "classifierIo": false
    }
  }
}
  • enabled — write one decision line per tool-call decision and one ccusage-compatible message line per classifier model response.
  • classifierIo — also write the classifier prompt, raw responses, and parsed decision for classifier-routed actions. The default is false. See Privacy.

Fields merge independently across configuration sources. For example, set enabled globally and set classifierIo for a trusted project.

Shared project .pi/automode.json cannot set log. The shared file cannot set any autoMode field. /automode config reports an invalid shape.

Pi-automode writes log entries only while auto mode is enabled. With auto mode off, no tool calls reach the hook. Thus, pi-automode writes no entries.

Log file location

Pi-automode stores the log next to the current Pi session file. It inserts -pi-automode before the extension:

<session-file>                       →  <dir>/<id>.jsonl
<session-file>-pi-automode.jsonl     →  <dir>/<id>-pi-automode.jsonl

For example, pi-automode can use ~/.pi/agent/sessions/<slug>/<id>-pi-automode.jsonl.

If a custom session manager provides an absolute session directory without a session file, pi-automode uses <sessionDir>/<sessionId>-pi-automode.jsonl.

Pi in-memory sessions have no session file or session directory. These sessions include --no-session and non-persisted subagents. Their logs use this absolute application path:

~/.pi/agent/extensions/pi-automode/logs/<encoded-session-cwd>/YYYY-MM-DD/<session-id>-pi-automode.jsonl

The project directory uses the same --path-with-dashes-- encoding as normal Pi session directories. The date partition uses UTC.

A custom session manager can supply an absolute sessionDir without a session file. In this case, pi-automode continues to use that directory. Run /automode config to see the resolved path.

Persisted sessions use one combined file per session. In-memory sessions use one file for each session ID and UTC day.

If a session crosses midnight, pi-automode continues in the file for the next day. Each line contains one JSON object with a type discriminator. Entries for the same tool call share a decisionId.

For persisted sessions, ccusage pi reports the sidecar as a separate -pi-automode session. Inspect in-memory logs directly because they are outside the normal Pi session tree.

Entry types

decision

Pi-automode writes one decision entry for each tool-call decision. Each allowed or blocked call has exactly one entry.

fieldmeaning
tsISO timestamp
decisionIdlinks to the related classifier entry. Local decisions have no related entry.
sessionIdPi session id
cwdworking directory
tooltool name, for example bash or write
summaryactionSummary — tool name + input JSON (truncated)
kindenforcement path: permissions.deny, permissions.ask, deterministic-hard-deny, deterministic-path-deny, permissions.allow, inside-working-directory, classifier, read-only, or setup
outcomeallow or block
reasonthe reason string (classifier reason, or the deterministic/permission reason)
classifierModelthe configured classifier model for a classifier-routed decision
reasoningclassifier reasoning mode and requested or effective level. See the examples below.

The reasoning field records either server-default mode:

{"mode":"server-default"}

or an explicit request after model-level clamping:

{"mode":"explicit","requestedLevel":"max","effectiveLevel":"xhigh"}

Classifier-routed decisions contain the effective level after model resolution. If classifierIo is off or authentication fails later, this field still exists.

If pi-automode cannot resolve the model, the entry contains requestedLevel without effectiveLevel. In this case, no model-supported level exists.

Local permission, deterministic, permissions.allow, and read-only decisions do not run the classifier. These decisions can omit effectiveLevel. Pi-automode cannot observe or infer the server-selected level in server-default mode.

message (classifier usage)

Pi-automode writes one message entry for each classifier response. This includes a malformed response that causes a retry.

When logging is enabled, pi-automode writes this entry before the matching decision line. The entry shape is compatible with ccusage pi:

fieldmeaning
timestampISO timestamp from the classifier response
message.rolealways assistant
message.modelmodel ID returned by the classifier provider
message.usageprovider-reported input, output, cache, total-token, and cost fields

For persisted sessions, ccusage reports this sidecar as a separate -pi-automode session. This entry contains no prompt or response text. When classifierIo is off, pi-automode still writes it.

classifier

If classifierIo: true, pi-automode writes a classifier entry. It writes this entry only for classifier-routed actions.

The entry follows all related classifier-usage message entries. It precedes the matching decision entry.

fieldmeaning
tsISO timestamp
decisionIdmatches the decision entry for the same call
modelclassifier model, for example anthropic/claude-haiku-4
reasoningserver-default, or the explicit requested and effective model-supported level
prompt.systemthe full system policy with environment/allow/soft_deny/hard_deny rules interpolated
prompt.contextthe shared context message: loaded project instructions + classifier transcript
prompt.actionthe complete, untruncated current tool action JSON
prompt.fastInstructionthe exact one-token filter instruction
prompt.detailedInstructionthe exact structured-review instruction
attemptsone entry per classifier model call (see below)
durationMstotal classifier time
parsedthe final decision that was acted on ({ decision, tier, reason })

Each attempts[] entry is { stage, attempt, response?, parsed?, error?, durationMs }:

  • stagefast for the one-token filter or detailed for structured review.
  • response{ stopReason, text, toolCalls?, model, timestamp, usage, errorMessage? }, including provider-reported errors and aborted requests.
  • response.toolCalls — each classifier tool name and parsed argument object. This field excludes provider call IDs and hidden reasoning.
  • parsed — the decision parsed from the response, or absent after a parse failure.
  • error — present after a network or authentication error. In this case, response is absent.

The array records both stages, retries, and fail-closed cases. A fast allow has one entry. A review with one successful retry has three entries.

Privacy

prompt.context contains project instructions and transcript evidence for both classifier stages. prompt.action contains the separate, untruncated current action. Pi-automode logs the final stage instructions separately.

See Auto-mode classifier flow → What is sent to the classifier for the evidence assembly rules.

The log records this payload locally. Pi-automode also sends the same data to the classifier endpoint for each classifier-routed call.

If classifierModel uses a cloud provider, the payload leaves the machine. Enable classifierIo only for classifier diagnosis or rule tuning. Leave it off for routine outcome logging.

Sizing

  • decision line: ~0.4–2 KB (driven by summary, which carries the tool input, capped at 6 KB).
  • classifier line: ~5 KB fixed policy plus loaded project instructions, selected transcript evidence, stage instructions, and recorded responses.

autoMode.maxUserTranscriptTokens and autoMode.maxToolTranscriptTokens limit transcript evidence separately. Both fields default to approximately 4000 tokens.

Pi-automode excludes assistant prose and tool results. Provider cache hits can reduce processed or billed input. The log still records the full classifier payload.