Skip to content

Config reference — operator knobs (~/.personalclaw/config.json)

Most settings live in the dashboard (Settings → …). The fields below are deliberately backend-only: operator/deployment knobs edited via personalclaw config set <key> <value> or by editing config.json directly (the gateway needs a restart for most of them). Everything not listed here has a dashboard control — see GET /api/config/schema for the full registry with labels, help text, types, and defaults.

Conventions: a key like loops.max_cycles_hard_cap means {"loops": {"max_cycles_hard_cap": …}} in config.json.

KeyDefaultWhat it does
agent.providernativeDefault agent runtime for agents that don’t set their own: native (in-process loop, models governed by Settings → Models), acp, or acp:<cli> to pin a connected CLI runtime. Per-agent provider (Agents page) overrides this. Deployment-level; switching it mid-flight would strand live sessions, hence file-only + restart.

The chat model is not a config field — bind models per use case in Settings → Models (~/.personalclaw/active_models.json), or per agent on the Agents page.

KeyDefaultWhat it does
loops.max_cycles_hard_cap100Absolute ceiling on any loop’s cycle budget, regardless of the per-loop limit. Safety brake against runaway cost.
loops.default_idle_secs120Seconds between worker cycles when a loop doesn’t specify its own idle timer.
loops.trust_ttl_secs86400How long an Unattended loop’s workers run their tool calls without asking before the supervisor pauses the loop for you to resume it. An Attended loop asks you for each call and holds no such grant.
loops.stagnation_window5How many consecutive no-progress cycles stall a loop (STAGNANT → “needs direction”). The supervisor compares the last N findings on three signals: byte-identical cycle reports, an identical set of sources/files the cycle recorded, and the worker’s own new_findings_count. The first two are observed by the supervisor, so a worker claiming progress cannot suppress them; the third is a claim, and its absence no longer counts as progress. Clamped to 2–50 (a window of 1 could only compare a cycle with itself). Read per poll, so a change applies without a restart.
loops.judge_use_casereasoningWhich model axis the loop JUDGE rides — deliberately not the loops axis its worker rides, so a reviewer mistake is not correlated with the mistake it reviews. Any chat-family axis (reasoning, chat, code_tools, background, orchestration, or loops to put judge and worker back on one binding); an unrecognized value falls back to reasoning, never to loops. Read per judge call, so a change applies to the next cycle with no restart.

Per-loop values (set in the loop creation form) override the defaults; the hard cap binds everything.

The behavior toggles (L1 manifest, active recall, proactive check-ins, vault) are in Settings → Memory. These are the tuning constants under them:

KeyDefaultWhat it does
memory.semantic_confidence_threshold0.8Confidence (0-1) an automatically learned semantic fact needs before memory keeps it; facts you add yourself are always kept. Settings → Memory → Learned-fact confidence.
memory.episodic_dedup_threshold0.88Cosine similarity above which a new episodic record is treated as a duplicate and skipped.
memory.episodic_max_results8Episodic records recalled per query.
memory.episodic_max_count10000Episodic store size cap; oldest records are pruned past it.
memory.semantic_keys[]Extra top-level semantic-record prefixes (namespaces) beyond the built-ins.
memory.proactive_commitments_max_per_day3Daily cap on inferred proactive check-ins (the toggle itself is in the UI).
memory.active_recall_timeout_ms1500Budget for the just-in-time recall pass; the circuit breaker trips past it.
memory.auto_promote_enabledtruePeriodically promote the most-recalled facts into the L1 manifest.
memory.auto_promote_every_n10Run promotion every N consolidations.
memory.auto_promote_max_per_run5Max facts promoted per run.
memory.vault_pathmemory-vaultWhere the readable markdown vault is written, relative to ~/.personalclaw (absolute paths allowed). The mode selector (off/mirror/two_way) is in Settings → Memory. Only the DEFAULT path is a declared state entry, so a relocated vault is outside personalclaw snapshot.

Skill management (install/enable/proposals) is the Skills page; these tune the automatic skill machinery:

KeyDefaultWhat it does
skills.max_triggered3Max skills surfaced per message (semantic ∪ keyword match).
skills.auto_create_from_sessionsfalseAnalyze completed sessions and synthesize a reusable SKILL.md when a non-trivial procedure is detected (lands under skills/auto/).
skills.auto_refine_on_deviationfalseUpdate an auto-created skill when the agent succeeds via a different tool sequence (requires auto_create_from_sessions).
skills.auto_min_tool_calls5Minimum tool calls for a session to qualify for skill extraction.
skills.auto_similarity_threshold0.85Skip creation when an existing skill’s description overlaps ≥ this fraction.
skills.progressive_disclosure_threshold2When more skills than this match a turn, inject only their index (name+description) and let the agent pull bodies on demand. Clamped to max_triggered - 1, since the match list is already capped at max_triggered and a threshold at or above it can never be exceeded. 0 = always inline (never clamped).

The continuous self-improvement review that runs after learning-worthy turns (distinct from session-end consolidation). Kill switches + tuning:

KeyDefaultWhat it does
learning.enabledtrueMaster switch for the after-turn review (skipped for incognito/temporary sessions regardless).
learning.min_tool_calls4A turn with at least this many tool calls qualifies even without a correction signal.
learning.correction_heuristictrueTreat a correcting user message (“no, actually, …”) as a first-class learning signal.
learning.surface_chiptrueShow the quiet “Learned: …” chip in chat when something is captured.
learning.skill_laddertrueAllow the review to PROPOSE reusable skills (never auto-installed — proposals land in the Skill-proposals inbox).
KeyDefaultWhat it does
workflows.enabledtrueKill switch for SOP surfacing (inject the best-matching workflow above threshold).
workflows.match_threshold0.62Cosine gate for a workflow match (0–1).

Alert keywords, name-mention alerts, and retention are in the Inbox settings panel (entity store). Config-side:

KeyDefaultWhat it does
inbox.enabledfalsePolls the built-in drop folder, <home>/inbox/incoming/ (the UI “Poll the drop folder” toggle writes this). An installed inbox app is polled while its app is enabled, whatever this says.
inbox.user_id""Your user id on the connected channel (set by channel-app setup) — used to skip your own messages.
inbox.poll_interval_seconds60Poll cadence (min 30).
inbox.style_rules[]Voice/style lines injected into AI reply drafting (“Match this voice/style when replying”).
inbox.test_modefalseIngest your OWN messages too (demo/testing; also a CLI flag).
inbox.engagement_half_life_days0Engagement-ranking decay half-life; 0 = no decay. The ranking toggle is in the UI.

Resource ceilings and the environment allowlist applied to agent-influenced children (bash tools, hook and cron-script children, app backends, MCP servers).

KeyDefaultWhat it does
sandbox.nofile4096Soft RLIMIT_NOFILE for an agent-influenced child; 0 disables the cap. ACP session hosts are exempt by profile (they multiplex many MCP pipes).
sandbox.max_pids0 (off)RLIMIT_NPROC ceiling. Off by default because RLIMIT_NPROC is a per-user limit counting every process you already run — an absolute cap can break a busy host with “cannot fork”. It bounds a child’s whole process tree only through sandbox.cgroup_scopes, which needs Linux; Settings → Security says what it does on the gateway’s host.
sandbox.max_rss_mb0 (off)RLIMIT_AS ceiling in MB. Off by default: RLIMIT_AS is coarse and breaks memory-mapped toolchains. Never applied on macOS, whose kernel refuses a finite memory limit on a child; the gateway logs a warning and Settings → Security says so.
sandbox.env_passthrough[]Extra environment variable names that the processes PersonalClaw starts inherit from the gateway: the native agent’s bash commands, hook, cron-script and bash-action children, a loop’s check command, a workflow’s setup/teardown steps and effect teardowns, a runner CLI’s version probe, every ACP agent CLI, every git PersonalClaw runs, and everything it starts for an app (the pip and npm that install what an app declares, its engine’s install, its setup hooks, backend, worker, sidecar and MCP servers, and what an app’s provider starts through personalclaw.sdk.util.child_process_env). Those children do not inherit the gateway environment: they get a minimal base (PATH, SHELL, PWD, TERM, PYTHONPATH, PYTHONPYCACHEPREFIX, locale/TZ, HOME/TMPDIR/USER/XDG_*, proxy and CA-bundle settings, and PERSONALCLAW_HOME/_WORKSPACE/_PORT) plus the names you list here, and an install also gets its installer’s own PIP_* or npm_config_* settings. A run’s commands (an agent’s bash command, a hook’s, an automation’s, a loop’s check, a workflow’s setup and teardown, an agent CLI) also carry PERSONALCLAW_RUN, which names the run, so what they leave running ends with it. A login in an inherited value (http://ada:pw@proxy:3128, an index URL with a token) is taken out; a name you list here is passed as it is, login included. Use it when a script legitimately needs one more variable (e.g. SLACK_BOT_TOKEN for a notifier, or a language runtime’s variable), when an agent CLI signs in with an API key from the environment, or when a proxy needs its password. Names matching the credential floor (AWS_SECRET*, AWS_SESSION*, SSH_AUTH_SOCK, GNUPGHOME, GIT_ASKPASS) are refused even when declared; a git command that talks to a remote, and a program an app starts to sign in over ssh (rsync to your host), get SSH_AUTH_SOCK without it, so they sign in through your SSH agent. Withheld names are listed in the debug log at each spawn — run the gateway with --verbose to see what a script is missing.
KeyDefaultWhat it does
hooks{}The rules applied to messages and tool calls (auto-approve and auto-deny lists, auto-replies, transforms, context rules), plus webhook_token and auto_approve_sources (sources whose tool calls are auto-approved). webhook_token is kept in the credential store and the file holds a {{secret:…}} reference to it; personalclaw config set hooks.webhook_token <token> sets it, at least 32 characters. It is what a program sends to POST /api/hooks/agent (security). Managed via the Triggers UI/API (/api/hooks); documented here because the raw shape is config-visible.
hooks.auto_approve_subagent_spawnfalseStart a subagent without asking you: one a chat’s agent starts, a workflow step’s, an Invoke Agent step’s. It decides the start and nothing the agent then does: its calls are decided as any subagent’s are, and ask you unless a grant that approves a subagent’s calls stands (its chat’s Trust or YOLO, the step’s own approval, Approval mode Auto for an agent no chat started, or hooks.auto_approve_subagent_tools). The operator ceiling bounds it.
hooks.auto_approve_subagent_toolsfalseApprove a subagent’s own tool calls without asking you, an Invoke Agent step’s agent’s included, whose Allow then says so. The operator ceiling bounds it, and no call that reaches a host off your allowed hosts, or that deletes your home folder, the filesystem root or the folder the agent works in, is approved by it.
observe_max_messages200Channel-observation ring buffer size (messages kept per channel for context).
observe_ttl_hours168How long observed channel messages stay usable as context.
timezone"" (system)IANA timezone (e.g. Asia/Tokyo) for schedules, the clock the LLM sees, and the day spend is counted in (Settings → Usage, the daily spend caps, the monthly usage recap). Empty uses this machine’s timezone, else UTC. Set by personalclaw setup; per-job trigger timezones override it for that job only.
snapshot_dir"" (default dir)Where personalclaw snapshot writes/reads portability snapshots.
dashboard.url""Advertised dashboard origin (host:port) — written by personalclaw setup, consumed by the server bind/origin checks.
dashboard.auto_open_browsertrueOpen the dashboard in a browser when the gateway is started at a terminal (--no-open overrides per-run). A service, a container or a restart never opens one.
dashboard.terminal.enabledtrueKill switch for the built-in terminal (PTY) feature. Read raw with a 30s cache; dashboard.terminal.persist (tmux-backed persistence) is editable in the Terminal page.
dashboard.mcp_probe_timeout_secs15Per-server timeout (5–120 s) for MCP tool-discovery probes; the gateway’s MCP status sweep budget derives from it (+15 s). PATCH-editable via the config API, no dashboard control.
memory_stores.<name>.description—Optional description for a named memory store (stores are referenced by agent profiles).
  • GET /api/config/personalclaw — full config as JSON for the owner (sensitive defaults masked in the schema, not here). An app-scoped request gets only the fields its manifest declares in permissions.config, and 403 config_field_not_declared when it declares none.
  • PATCH /api/config/personalclaw {path, value} — single-field writes, allowlisted (_EDITABLE_CONFIG); non-editable paths return 400. A security setting (an entry declaring a SecurityControl) needs "confirm": true in the body (the JSON literal) for any write that loosens it, or it answers 400 confirmation_required with {field, consent, title, change} in error.detail — change is what the write changes from and to ("$33.50 → $10,033.50"), and a raise to ten times the value in effect or more adds caution, one more sentence — (a 200 marked X-PersonalClaw-Consent-Asked to a client that sends X-PersonalClaw-Consent: ask, as the dashboard does); tightening never needs it. An app-scoped request is refused 403 security_setting_owner_only for every security setting, in either direction, and 403 config_field_not_declared for any other field its manifest’s permissions.config does not name. Rules: config/edit_spec.py.
  • GET /api/config/schema — the full field registry (labels, help, types, defaults, deprecations) auto-derived from the config dataclasses.
  • personalclaw config get|set <key> [value] — CLI equivalent; set validates through the same loader. get withholds credential-named fields (api_key, bot_token, …) unless --reveal is passed.