Every tool call — whether through factorly call or factorly serve — is logged to JSONL. Project configs get a per-project log at <project>/.factorly/audit.jsonl; the global config falls back to ~/.config/factorly/audit.jsonl:
{"timestamp":"2026-04-03T09:15:32Z","interface":"cli","tool":"web.fetch","params":{"url":"https://example.com"},"status":"success","duration_ms":215,"output":"<!doctype html>..."}| Field | Description |
|---|---|
timestamp |
ISO 8601 timestamp |
interface |
cli, mcp, exec, or vault |
tool |
Tool name |
params |
Parameters passed to the tool |
status |
success, error, or blocked |
duration_ms |
Execution time in milliseconds |
output |
Truncated response (max 500 chars) |
error |
Error message if failed (max 500 chars) |
shadow_action |
Oversight outcome: allowed, denied, confirmed, rate_limited, loop_warning, loop_blocked |
highlight_params |
Selected params from log_params config (for audit filtering) |
agent_id |
MCP session identifier (tracks per-agent activity) |
original_bytes |
Output size before compression/truncation |
processed_bytes |
Output size after compression/truncation |
prev_hash |
SHA-256 hash of the previous log entry (hash chain) |
hash |
SHA-256 hash of this entry (for tamper detection) |
- Project-scoped (when a project
factorly.yamlis active):<project>/.factorly/audit.jsonl - Global fallback:
~/.config/factorly/audit.jsonl - Override with
FACTORLY_LOG_PATH=<path> - Disable with
FACTORLY_NO_LOG=1
Use factorly logs to view and query the audit log:
factorly logs # last 20 entries
factorly logs -n 50 # last 50 entries
factorly logs --tool github # filter by tool name
factorly logs --status blocked # filter by status
factorly logs --detail # show full entry details
factorly logs -f # follow mode (tail -f)
factorly logs stats # summary statistics (includes chain integrity)
factorly logs verify # verify hash chain integrity
factorly logs repair # repair broken hash chainRecent calls — factorly logs shows the most recent entries with tool name, status, and duration:
$ factorly logs -n 5
2026-04-17 09:15:32 github.list_repos success 215ms
2026-04-17 09:15:34 github.get_repo success 187ms
2026-04-17 09:15:40 exec success 312ms
2026-04-17 09:15:42 clipboard.copy success 45ms
2026-04-17 09:15:50 factorly.shell blocked 0ms
Summary statistics — factorly logs stats gives a quick overview of call volume, success rates, most-used tools, output savings, and blocked calls:
$ factorly logs stats
Log: ~/.config/factorly/audit.jsonl (194 entries)
By Status:
success 162 (83.5%)
error 29 (14.9%)
blocked 3 (1.5%)
By Tool (top 10):
github.list_repos 42
slack.post_message 31
github.get_repo 28
exec 22
stripe.list_charges 15
gmail.send_message 12
linear.list_issues 9
github.profile 8
jira.get_issue 6
notion.query_database 5
Output Savings:
21 calls processed, 34.4 KB → 33.4 KB (3% saved)
Blocked Calls:
3 total (3 denied)
Filter by status — find all blocked calls to investigate oversight triggers:
$ factorly logs --status blocked
2026-04-17 08:02:11 factorly.shell blocked 0ms
2026-04-17 08:14:55 factorly.file.read blocked 0ms
2026-04-17 09:15:50 factorly.shell blocked 0ms
Filter by tool — narrow down to calls for a specific tool:
$ factorly logs --tool github
2026-04-17 09:15:32 github.list_repos success 215ms
2026-04-17 09:15:34 github.get_repo success 187ms
2026-04-17 09:16:01 github.profile success 142ms
Vault operations (get, set, delete, list) are logged with interface: "vault". Secret values are never logged — only the operation and key name.
factorly logs --tool vault 12:34:05 vault.set success —
12:34:10 vault.get success —
12:35:02 vault.list success —
12:35:15 vault.delete success —
The params field contains the key name (e.g., {"key": "GITHUB_TOKEN"}) but never the secret value.
When output processing is enabled (compression or truncation), the log records original_bytes and processed_bytes for each call. Query savings with jq:
cat ~/.config/factorly/audit.jsonl | jq 'select(.original_bytes) | {tool, saved: (.original_bytes - .processed_bytes)}'Run factorly tools status for an aggregate output savings summary across all tools.
MCP sessions are tracked by agent_id (session ID). Each agent gets independent rate-limit quotas. Use the field to filter logs per agent:
cat ~/.config/factorly/audit.jsonl | jq 'select(.agent_id == "session-abc")'Every log entry includes a SHA-256 hash chain. Each entry's hash field is computed from its prev_hash (the previous entry's hash) plus the entry payload. The first entry chains from a zero hash.
This creates a tamper-evident log — modifying or deleting any entry breaks the chain from that point forward.
# Verify the full chain
$ factorly logs verify
Chain verified: 194 entries OK
# Stats also show chain status
$ factorly logs stats
...
Hash Chain:
194 entries verifiedEntries written before hash chaining was added are skipped during verification.
If the chain is broken (e.g., from an upgrade or a bug), repair it:
$ factorly logs repair
Chain reset marker appended. Run `factorly logs verify` to confirm.
$ factorly logs verify
Chain verified: 5 entries OK (224 entries before chain reset skipped)Repair appends a chain_reset marker to the end of the log. The marker's prev_hash is a SHA-256 hash of the entire file contents, binding it to the complete history. New entries chain from the marker. Existing entries are never modified.
- Log file is created with
0600permissions (owner read/write only) - Output and error fields are truncated to 500 characters to prevent secrets from leaking via large API responses
- Sensitive parameter values are redacted in verbose output but logged as-is in the call log (the log file is owner-only)
- Hash chain provides tamper-evident audit trail (AARM R5)