Logging
NimbleBrain writes a structured workspace event log as JSONL (one JSON object per line) to daily rolling log files. These logs capture workspace-level events — connector lifecycle, skill and file changes, data and config mutations, tool-surface changes, and audit signals.
Configuration
Section titled “Configuration”Control logging through the logging object in nimblebrain.json:
{ "logging": { "dir": "~/.nimblebrain/logs", "disabled": false, "level": "normal", "retentionDays": 30 }}| Field | Type | Default | Description |
|---|---|---|---|
dir |
string |
{workDir}/logs |
Directory where log files are written. Created automatically if it does not exist. |
disabled |
boolean |
false |
Set to true to disable structured logging entirely. |
level |
"normal" | "debug" |
"normal" |
"debug" persists additional verbose fields (full tool inputs/outputs, raw provider responses). Larger files. |
retentionDays |
integer |
— | Auto-delete log files older than N days on startup. Omit for no cleanup. |
Structured logging is enabled by default. To disable it:
{ "logging": { "disabled": true }}Log file naming
Section titled “Log file naming”Log files follow a daily rolling pattern under a workspace/ subdirectory of dir:
{dir}/workspace/YYYY-MM-DD.jsonlA new file is created automatically each day. Examples:
~/.nimblebrain/logs/workspace/2026-03-25.jsonl~/.nimblebrain/logs/workspace/2026-03-26.jsonlLog entry format
Section titled “Log entry format”Every log line is a JSON object: a timestamp, the event type, and the event’s own data fields spread alongside them.
{"ts":"2026-03-25T14:30:00.123Z","event":"connector.installed","wsId":"ws_00622b820b0ec363","serverName":"postgres","connectorName":"https://mcp.example.com/mcp","version":"remote"}Common fields
Section titled “Common fields”| Field | Type | Description |
|---|---|---|
ts |
string |
ISO 8601 timestamp of when the event was recorded. |
event |
string |
Event type (e.g., connector.installed, config.changed, audit.permission_denied). |
Remaining fields vary by event type — they are the event’s payload, written inline.
Event types
Section titled “Event types”Only workspace-level events are written to this log; conversation streaming events (text.delta, tool.start, and the like) are not.
Connector lifecycle — connector.installed, connector.uninstalled. Track connectors entering and leaving the workspace.
{"ts":"2026-03-25T14:30:00.123Z","event":"connector.installed","wsId":"ws_00622b820b0ec363","serverName":"com-example-postgres","connectorName":"https://mcp.example.com/mcp","version":"remote"}config.changed — Workspace configuration was modified.
skill.created, skill.updated, skill.deleted — A workspace skill or context file changed. Carry the skill’s id (filesystem path), name, and scope.
{"ts":"2026-03-25T14:30:02.000Z","event":"skill.created","id":"/skills/triage/SKILL.md","name":"triage","scope":"workspace"}tool.promoted, tool.released — A tool entered or left the active tool set. Carry the runId and toolName; tool.released adds reason: "evicted" when the engine reclaimed a slot under the active-tool cap.
{"ts":"2026-03-25T14:30:04.000Z","event":"tool.promoted","runId":"abc123","toolName":"postgres__query"}bridge.tool.done — A bridged tool call completed.
http.error — An HTTP-level error surfaced in the workspace.
audit.auth_failure — Authentication failed for a request. Carries ip, method, and path.
{"ts":"2026-03-25T14:30:05.000Z","event":"audit.auth_failure","ip":"203.0.113.7","method":"POST","path":"/v1/workspaces/ws_3f9a1c7e0b2d4856/chat/start"}audit.permission_denied — A user declined a tool’s confirmation prompt. Carries the tool, the unprefixed action, and the target (or null).
{"ts":"2026-03-25T14:30:06.000Z","event":"audit.permission_denied","tool":"tasks__delete","action":"delete","target":"task-42"}audit.credential_read — A secret in the credential store was presented to a remote service. Carries the scope (instance, workspace:<wsId>, user:<userId>), the key, the caller (which code path), the purpose, and workspaceId / userId when the scope has one. Never the value. Emitted when a secret is used, not when its presence is probed.
{"ts":"2026-03-25T14:30:07.000Z","event":"audit.credential_read","scope":"workspace:ws_a1b2c3d4e5f60718","key":"acme.db_url","caller":"transport:provider:credential","purpose":"authenticate remote MCP request as workspace ws_a1b2c3d4e5f60718","workspaceId":"ws_a1b2c3d4e5f60718"}audit.admin_tool_call — A call to a tool a connector names in admin_tools, written whether the call was admitted or refused. Carries the workspaceId, the userId the call ran as, the connector and tool, the caller (chat, task, dispatch, app, mcp or api), the outcome (admitted or refused), the arguments, and the conversationId or runId when there is one. admitted means the role check passed, not that the connector accepted the call. A top-level argument is recorded as "[redacted]", whole, when "writeOnly": true appears anywhere in its schema: on the argument itself, in an anyOf / oneOf / allOf branch, on a nested property or item, or in a $defs definition it reaches through a local $ref. An argument whose $ref cannot be resolved is redacted too. When the tool’s schema cannot be read, every value is redacted and only the argument names are kept.
{"ts":"2026-03-25T14:30:08.000Z","event":"audit.admin_tool_call","workspaceId":"ws_a1b2c3d4e5f60718","userId":"user_01","connector":"com-example-crm","tool":"configure_endpoint","caller":"app","outcome":"admitted","arguments":{"url":"https://hooks.example.com/in","secret":"[redacted]"}}Querying logs
Section titled “Querying logs”Log files are plain JSONL, so you can use standard command-line tools:
# Connectors installed todaycat ~/.nimblebrain/logs/workspace/2026-03-25.jsonl \ | jq 'select(.event == "connector.installed") | {connectorName, version}'
# Audit events onlycat ~/.nimblebrain/logs/workspace/2026-03-25.jsonl \ | jq 'select(.event | startswith("audit."))'
# Who called an admin-only tool, and from wherecat ~/.nimblebrain/logs/workspace/2026-03-25.jsonl \ | jq 'select(.event == "audit.admin_tool_call") | {ts, userId, tool, caller, outcome, arguments}'
# Count events by typecat ~/.nimblebrain/logs/workspace/2026-03-25.jsonl \ | jq -s 'group_by(.event) | map({event: .[0].event, count: length})'Log rotation
Section titled “Log rotation”A new file is created each day. Set retentionDays to have NimbleBrain delete log files older than that threshold on startup. Without it, files accumulate and you can manage retention with standard tools:
# Delete workspace logs older than 30 daysfind ~/.nimblebrain/logs/workspace -name "*.jsonl" -mtime +30 -delete