Skip to content

Config

All config is read from environment variables with the LANGCLAW__ prefix and __ as the nesting delimiter (e.g. LANGCLAW__BUS__BACKEND=rabbitmq). Provider API keys are the exception — they use the provider's own plain env var (ANTHROPIC_API_KEY, OPENAI_API_KEY, …), not a LANGCLAW__ key.

Typo'd keys are surfaced, not silently dropped

An unrecognized LANGCLAW__ env var (e.g. …__BOT_TOKEN when the field is …__TOKEN) is logged as a WARNING at load time, naming the stray key. Set LANGCLAW__STRICT_ENV=true to make an unknown key raise instead. Only LANGCLAW__-prefixed keys are checked — plain provider vars are left alone.

Common environment variables

Env var Default Notes
LANGCLAW__STRICT_ENV false raise on an unknown LANGCLAW__ key instead of warning
LANGCLAW__AGENTS__MODEL anthropic:claude-sonnet-4-5-20250929 provider:model spec
LANGCLAW__AGENTS__RATE_LIMIT_RPM 60 requests/min
LANGCLAW__AGENTS__BACKEND__BACKEND local_shell local_shell (file tools + unsandboxed execute), filesystem (file tools only), state, store
LANGCLAW__CHECKPOINTER__BACKEND sqlite sqlite | postgres
LANGCLAW__CHECKPOINTER__SQLITE__DB_PATH ~/.langclaw/state.db
LANGCLAW__CHECKPOINTER__POSTGRES__DSN "" required for postgres (needs langclaw[postgres])
LANGCLAW__BUS__BACKEND asyncio asyncio | rabbitmq | kafka
LANGCLAW__BUS__RABBITMQ__AMQP_URL amqp://guest:guest@localhost/ needs langclaw[rabbitmq]
LANGCLAW__BUS__RABBITMQ__QUEUE_NAME langclaw.inbound
LANGCLAW__BUS__KAFKA__BOOTSTRAP_SERVERS localhost:9092 needs langclaw[kafka]
LANGCLAW__BUS__KAFKA__TOPIC langclaw.inbound
LANGCLAW__PERMISSIONS__ENABLED false turn on RBAC
LANGCLAW__PERMISSIONS__DEFAULT_ROLE viewer role for unlisted users
LANGCLAW__CRON__ENABLED false sqlite store needs sqlalchemy+aiosqlite
LANGCLAW__CRON__DATA_STORE__BACKEND sqlite separate from the checkpointer
LANGCLAW__WORKFLOWS__ENABLED false
LANGCLAW__WORKFLOWS__DURABLE_STEPS false memoize completed steps
LANGCLAW__WORKFLOWS__RESUME_ON_STARTUP false re-drive interrupted runs (requires DURABLE_STEPS)
LANGCLAW__INTERPRETER__ENABLED false sandboxed eval tool (needs langclaw[interpreter])
LANGCLAW__CHANNELS__TELEGRAM__ENABLED false
LANGCLAW__CHANNELS__TELEGRAM__TOKEN "" bot token (needs langclaw[telegram])
LANGCLAW__CHANNELS__TELEGRAM__USER_ROLES {} id:role comma list, e.g. 123:admin,@alice:analyst
LANGCLAW__CHANNELS__WEBSOCKET__ENABLED false bundled, no extra
LANGCLAW__CHANNELS__WEBSOCKET__PORT 18789

The same enabled / token / user_roles pattern applies to the Discord, Slack, and Matrix channels. Full field-level docs (types and defaults) are rendered below.

Top-level config

langclaw.LangclawConfig

Bases: BaseSettings

Root configuration object. Merges JSON file + env vars.

Environment variable format (double-underscore delimiter): LANGCLAW__AGENTS__MODEL=openai:gpt-4.1 LANGCLAW__BUS__BACKEND=rabbitmq

LLM provider keys use standard env vars (loaded from .env via load_dotenv): ANTHROPIC_API_KEY, OPENAI_API_KEY, etc.

debug = False class-attribute instance-attribute

When True, error responses sent back to the channel include a truncated traceback (up to 500 characters) to aid debugging. Never enable in production — tracebacks may expose internal paths and library details.

Override via env var: LANGCLAW__DEBUG=true.

log_level = 'WARNING' class-attribute instance-attribute

Minimum log level for both stdlib logging and loguru.

Common values: "DEBUG", "INFO", "WARNING", "ERROR". Override via env var: LANGCLAW__LOG_LEVEL=INFO.

strict_env = False class-attribute instance-attribute

When True, an unknown LANGCLAW__ env var raises at load time instead of logging a warning. Unknown keys are otherwise silently ignored by pydantic-settings, which makes typos (e.g. …__BOT_TOKEN for …__TOKEN) impossible to self-diagnose. Override via LANGCLAW__STRICT_ENV=true.

langclaw.load_config()

Load and return the merged LangclawConfig.

Also calls load_dotenv() so that standard provider env vars (ANTHROPIC_API_KEY, OPENAI_API_KEY, etc.) from .env are available in os.environ for init_chat_model.

Unknown LANGCLAW__ env vars are surfaced via :func:validate_env_keys (warning by default; raising when LANGCLAW__STRICT_ENV=true).

Source code in langclaw/config/schema.py
def load_config() -> LangclawConfig:
    """Load and return the merged LangclawConfig.

    Also calls ``load_dotenv()`` so that standard provider env vars
    (``ANTHROPIC_API_KEY``, ``OPENAI_API_KEY``, etc.) from ``.env``
    are available in ``os.environ`` for ``init_chat_model``.

    Unknown ``LANGCLAW__`` env vars are surfaced via :func:`validate_env_keys`
    (warning by default; raising when ``LANGCLAW__STRICT_ENV=true``).
    """
    from dotenv import load_dotenv

    load_dotenv(override=False)
    config = LangclawConfig()
    validate_env_keys(strict=config.strict_env)
    return config

Sub-schemas

langclaw.config.schema.AgentConfig

Bases: BaseModel

backend = Field(default_factory=BackendConfig) class-attribute instance-attribute

Deepagents filesystem/shell backend selection. See BackendConfig.

workflows_dir property

Host directory holding runtime-authored (saved) workflow .js files.

Rooted at the agent's filesystem backend root so it matches where the agent's own write_file lands: backend.root_dir when set, else the workspace dir. (For state/store backends there is no host root and file-authoring is unavailable — see WorkflowsConfig.)

langclaw.config.schema.BackendConfig

Bases: BaseModel

Selects which deepagents filesystem backend the agent runs on.

deepagents abstracts the agent's file tools (ls/read_file/ write_file/edit_file/glob/grep and, for shell backends, execute) behind a swappable backend. This config drives the common, purely-declarative cases; advanced backends that need live objects (StoreBackend with a custom store/namespace, CompositeBackend, a sandbox, …) are injected as instances via Langclaw(backend=...) / create_claw_agent(backend=...).

  • "local_shell" (default) — real files under root_dir plus an execute tool that runs shell commands on the host. Enables shell access; virtual_mode still sandboxes file paths to root_dir but the execute command itself is unsandboxed (subprocess on the host). Disable by selecting another backend if that is too broad.
  • "filesystem" — real files under root_dir, no execute tool.
  • "state" — files live in LangGraph thread state (no host filesystem).
  • "store" — files live in a LangGraph BaseStore (cross-thread).

backend = 'local_shell' class-attribute instance-attribute

Which backend to construct. Default "local_shell" to expose the execute tool.

env = Field(default_factory=dict) class-attribute instance-attribute

local_shell only — extra environment variables for spawned commands.

execute_timeout = 120 class-attribute instance-attribute

local_shell only — per-command wall-clock timeout in seconds.

inherit_env = False class-attribute instance-attribute

local_shell only — when True the host environment is inherited by spawned commands. Off by default; combine with env for an explicit set.

max_output_bytes = 100000 class-attribute instance-attribute

local_shell only — cap on captured command output bytes.

root_dir = '' class-attribute instance-attribute

Filesystem root for local_shell/filesystem backends. Empty means 'use the agent's workspace directory'. Ignored by state/store.

virtual_mode = True class-attribute instance-attribute

Sandbox file paths to root_dir (reject ../ traversal). Applies to the filesystem-rooted backends only.

langclaw.config.schema.ChannelsConfig

Bases: BaseModel

langclaw.config.schema.TelegramChannelConfig

Bases: BaseModel

streaming_enabled = False class-attribute instance-attribute

Stream AI responses token-by-token by sending one message then editing it in place as new content arrives.

.. warning:: Enabling this may degrade reliability. Telegram enforces a global rate limit of ~20 message edits per minute per bot. Under moderate load (multiple concurrent users) this limit is easily exceeded, causing RetryAfter errors and delayed delivery. The 300 ms edit throttle reduces — but does not eliminate — the risk.

Enable only when the live-typing UX is more important than reliability,
and only in low-traffic environments.  Leave disabled (default) to
receive the full response as a single message after generation completes.

Env: LANGCLAW__CHANNELS__TELEGRAM__STREAMING_ENABLED=true

user_roles = Field(default_factory=dict) class-attribute instance-attribute

Maps Telegram user IDs / @usernames to permission roles. Env format: 123456:admin,@alice:editor

langclaw.config.schema.DiscordChannelConfig

Bases: BaseModel

streaming_enabled = False class-attribute instance-attribute

Stream AI responses token-by-token by sending one message then editing it in place as new content arrives.

.. warning:: Enabling this may degrade reliability. Discord allows at most 5 edits per second per message and enforces a global 50 req/s REST limit per bot. High-frequency edits during generation can trigger 429 Too Many Requests errors, cause visible lag, or result in dropped updates. The 300 ms throttle mitigates but does not prevent this under concurrent load.

Enable only when the live-typing UX is more important than reliability,
and only in low-traffic environments.  Leave disabled (default) to
receive the full response as a single message after generation completes.

Env: LANGCLAW__CHANNELS__DISCORD__STREAMING_ENABLED=true

user_roles = Field(default_factory=dict) class-attribute instance-attribute

Maps Discord user IDs to permission roles. Env format: 123456:admin,789012:viewer

langclaw.config.schema.SlackChannelConfig

Bases: BaseModel

app_token = '' class-attribute instance-attribute

Slack App-Level Token for Socket Mode (starts with xapp-). Get from https://api.slack.com/apps -> Basic Information -> App-Level Tokens

bot_token = '' class-attribute instance-attribute

Slack Bot User OAuth Token (starts with xoxb-). Get from https://api.slack.com/apps -> OAuth & Permissions

reaction_complete = 'white_check_mark' class-attribute instance-attribute

Emoji name for 'complete' reaction. Default: 'white_check_mark' (✅).

reaction_feedback_enabled = True class-attribute instance-attribute

Enable reaction emoji feedback (👀 while processing, ✅ when done).

reaction_processing = 'eyes' class-attribute instance-attribute

Emoji name for 'processing' reaction. Default: 'eyes' (👀).

streaming_enabled = False class-attribute instance-attribute

Stream AI responses token-by-token by posting one message then updating it in place via chat_update as new content arrives.

.. warning:: Enabling this may degrade reliability. Slack's chat_update API is Tier 3 (~50 req/min per app). Rapid edits during generation can exhaust this quota, causing ratelimited errors and stalled responses. The 300 ms update throttle reduces — but does not eliminate — the risk, especially with multiple concurrent users sharing the same bot quota.

Enable only when the live-typing UX is more important than reliability,
and only in low-traffic environments.  Leave disabled (default) to
receive the full response as a single message after generation completes.

Env: LANGCLAW__CHANNELS__SLACK__STREAMING_ENABLED=true

user_roles = Field(default_factory=dict) class-attribute instance-attribute

Maps Slack user IDs to permission roles. Env format: U123456:admin,U789012:viewer

langclaw.config.schema.MatrixChannelConfig

Bases: BaseModel

access_token = '' class-attribute instance-attribute

Long-lived access token obtained via /login or matrix-commander --login. Starts with syt_ on Synapse.

allow_from = Field(default_factory=list) class-attribute instance-attribute

Whitelist of Matrix user IDs, e.g. @alice:matrix.org,@bob:matrix.org. Empty list means 'allow everyone'.

auto_join_invites = True class-attribute instance-attribute

Automatically join rooms the bot is invited to. When allow_from is non-empty the inviter must be on the allow-list.

device_id = '' class-attribute instance-attribute

Device ID associated with access_token. Required by matrix-nio for token-only authentication.

e2ee_enabled = False class-attribute instance-attribute

Reserved for future support of end-to-end-encrypted rooms.

.. warning:: Not implemented in this release. Starting the channel with e2ee_enabled=True raises an error at startup — the bot will not silently fall back to unencrypted mode, which would leak otherwise-encrypted messages on sync. Leave False for now; set it to opt in once E2EE support ships.

homeserver_url = '' class-attribute instance-attribute

Full homeserver URL, e.g. "https://matrix.org".

store_path = '' class-attribute instance-attribute

Optional directory for nio's state store. Defaults to ~/.langclaw/matrix_store when left empty.

user_id = '' class-attribute instance-attribute

Bot user ID in fully-qualified form, e.g. "@mybot:matrix.org".

user_roles = Field(default_factory=dict) class-attribute instance-attribute

Maps Matrix user IDs to permission roles. Env format: @alice:matrix.org:admin,@bob:matrix.org:viewer

langclaw.config.schema.WebSocketChannelConfig

Bases: BaseModel

streaming_enabled = True class-attribute instance-attribute

Stream AI responses token-by-token, emitting {"type": "ai_chunk"} events as content is generated, followed by {"type": "ai_stream_end"}.

Unlike Telegram, Slack, and Discord, WebSocket streaming carries no rate-limit risk — chunks are pushed directly over the open socket without any platform API calls. Clients should accumulate ai_chunk payloads and render them incrementally.

Defaults to True. Set to False to receive a single {"type": "ai"} event with the complete response instead.

Env: LANGCLAW__CHANNELS__WEBSOCKET__STREAMING_ENABLED=false

user_roles = Field(default_factory=dict) class-attribute instance-attribute

Maps WebSocket user IDs to permission roles. Env format: user1:admin,user2:viewer

langclaw.config.schema.CheckpointerConfig

Bases: BaseModel

langclaw.config.schema.BusConfig

Bases: BaseModel

langclaw.config.schema.PermissionsConfig

Bases: BaseModel

Global RBAC definitions.

Role definitions (role name -> allowed tools) live here. User -> role mappings live per-channel alongside allow_from.

default_role = 'viewer' class-attribute instance-attribute

Role assigned to users not listed in any channel's user_roles.

enabled = False class-attribute instance-attribute

Enable per-user tool permission filtering.

roles = Field(default_factory=dict) class-attribute instance-attribute

Role name -> RoleConfig. Define in config.json::

{"roles": {"admin": {"tools": ["*"]}, "viewer": {"tools": ["web_search"]}}}

langclaw.config.schema.RoleConfig

Bases: BaseModel

Defines which tools (and interpreter subagents) a role may use.

subagents = Field(default_factory=list) class-attribute instance-attribute

Subagent types this role may invoke from an interpreter script via tools.task({subagent_type}). Default-deny — an empty list means the role cannot spawn any subagent from a script. Use ["*"] to allow every registered subagent. This is a separate axis from tools: a role with tools=["*"] still cannot reach subagents unless they are listed here.

tools = Field(default_factory=list) class-attribute instance-attribute

Tool names this role is allowed to invoke. Use ["*"] to grant access to all tools.

workflows = Field(default_factory=list) class-attribute instance-attribute

Workflow names this role may invoke — via the workflow_<name> tool, a /workflows command, cron, or (Phase 2) tools.workflow.<name> inside an interpreter script. Default-deny like subagents — an empty list means the role may invoke no workflows. Use ["*"] to allow every registered workflow. A third RBAC axis alongside tools and subagents — all three resolve through the one descriptor-driven :func:langclaw.rbac.resolve_capability (a new axis = one :class:~langclaw.rbac.CapabilityAxis + one field here).

langclaw.config.schema.CronConfig

Bases: BaseModel

enabled = False class-attribute instance-attribute

Off by default. Enabling the SQLite data store (the default) requires the sqlalchemy and aiosqlite packages. Set LANGCLAW__CRON__ENABLED=true to opt in.

langclaw.config.schema.WorkflowsConfig

Bases: BaseModel

Operator-authored Workflow primitive configuration (issue #38).

Opt-in and off by default. When enabled, workflows registered with @app.workflow() become invocable three ways: the LLM calls the workflow_<name> tool; an operator runs /workflows run <name>; or a message with origin="workflow" (e.g. a cron-fired job) dispatches one through the gateway. Each is typed, multi-step, and RBAC-gated by role.

Runtime authoring (mode="saved"): when this and interpreter are enabled (and the backend is filesystem-rooted), the agent can save a workflow by writing a file with its ordinary write_file tool — there is no bespoke save tool. After running an ad-hoc job with eval, the user can say "save that workflow"; the agent writes the same JS to workflows/<name>.js (with // @description / // @uses header comments). The gateway watches that folder, reconciles the file into the registry, and rebuilds the default agent, so the new workflow_<name> tool goes live in the same session and reloads on every restart. (Requires the interpreter extra — a saved body runs in the same QuickJS sandbox as eval. state/store backends have no host folder, so file-authoring is unavailable there.)

RBAC is enforced at the invocation boundary: the workflow_<name> tool gate, the /workflows command, cron dispatch, and bus dispatch all consult the role's default-deny workflow allowlist. A workflow's steps, however, run in-process by calling tool.ainvoke directly — they bypass the graph, so the per-request ToolPermissionMiddleware does not filter a step's toolset. A workflow can therefore reach any tool in the default agent's toolset; restrict reachable tools via the workflow's uses_tools, not per-role tool RBAC. Bus dispatch runs a whole workflow as one bus message; full bus → gateway re-entry per step (inheriting rate limiting, channel context, per-step checkpointing, and step-level RBAC) is not yet wired.

Env: LANGCLAW__WORKFLOWS__ENABLED=true

durable_steps = False class-attribute instance-attribute

When True, completed workflow step results are persisted to a LangGraph BaseStore (a sibling SQLite file or the Postgres DSN, matching the checkpointer backend) instead of an in-process dict, so they survive a process restart. Off by default.

NOTE: this only persists step results — it does not re-run anything on its own. Set resume_on_startup for that. The store currently has no TTL or pruning, so it grows unbounded; keep an eye on it for long-lived deployments.

enabled = False class-attribute instance-attribute

Enable the Workflow primitive. Off by default — registering workflows is inert until this is set.

max_concurrent_runs = 16 class-attribute instance-attribute

Global ceiling on simultaneously-running workflow runs across the host, regardless of any single workflow's own budget.

max_depth = 2 class-attribute instance-attribute

Maximum nesting depth — how many levels a workflow may invoke other workflows. Bounds recursive fan-out.

max_steps_per_run = 1000 class-attribute instance-attribute

Hard backstop on total steps a single run may execute. Guards against a runaway loop in an operator-authored body.

resume_on_startup = False class-attribute instance-attribute

When True, workflow runs left incomplete by a previous process (a crash / kill) are re-run on startup from the run journal: completed steps replay from the durable step store and only the unfinished tail executes. Off by default. Requires durable_steps (the step store + run journal share one BaseStore); without it, enabling this logs a warning and does nothing.

Caveats developers should know before relying on it:

  • Python-mode workflows only. Runs whose spec is no longer registered, or whose mode is llm_authored, are skipped (logged), not resumed.
  • Crash vs. clean failure. A killed process leaves a run running and so resumable; a workflow that raised a normal exception is marked failed and is not retried (avoids looping on a deterministic bug).
  • No step-result invalidation. Resume matches cached steps by a deterministic step_id (<phase>#<seq>). Editing a workflow body between crash and restart can shift those IDs and replay stale results — bump the workflow name or clear the store after changing a body you may resume.
  • Resumes under the default agent's permissions. The resume step executor is built from the default agent's role-filtered toolset, not the original invoker's role/named-agent context — a resumed run may see a different toolset than the run that crashed.
  • Blocking at startup. Incomplete runs are replayed sequentially before the gateway begins serving traffic, so a slow or hanging resumed run delays startup.
  • One attempt. A run that raises again during resume is marked failed and not retried — even if the cause was transient.

langclaw.config.schema.InterpreterConfig

Bases: BaseModel

Sandboxed code-interpreter (RLM) configuration.

Opt-in and off by default. When enabled, the agent gains an eval tool backed by langchain-quickjs's CodeInterpreterMiddleware: it writes a sandboxed JavaScript program that can loop, branch, retry, and fan out over a role-filtered allowlist of tools (Programmatic Tool Calling), including tools.task({subagent_type}) to orchestrate registered subagents.

The QuickJS sandbox has no filesystem, network, or shell access; the real trust boundary is the exposed tools, so the PTC allowlist defaults to a read-only set and mutating/egress tools require explicit allow_tools opt-in. Requires the interpreter extra::

uv add 'langclaw[interpreter]'

Env: LANGCLAW__INTERPRETER__ENABLED=true

allow_tools = Field(default_factory=list) class-attribute instance-attribute

Operator opt-in beyond the read-only default PTC allowlist. Add mutating/egress tool names here to expose them inside scripts, or ["*"] to expose every available tool (subject to per-role RBAC).

enabled = False class-attribute instance-attribute

Enable the eval code-interpreter tool. Off by default.

max_concurrent_subagents = 4 class-attribute instance-attribute

Advisory cap on concurrent tools.task fan-out width.

.. note:: Reserved for forward compatibility — the current langchain-quickjs CodeInterpreterMiddleware does not expose a concurrency knob, so this value is documented but not yet enforced by the sandbox. The effective bound today is max_ptc_calls plus timeout.

max_ptc_calls = 256 class-attribute instance-attribute

Maximum total tools.* bridge calls allowed during one eval. Bounds runaway fan-out / PTC-call DoS.

max_result_chars = 4000 class-attribute instance-attribute

Caps what a script return (and captured console.log) sends back to the agent turn.

memory_limit = 64 * 1024 * 1024 class-attribute instance-attribute

Bytes the QuickJS heap may use per eval. Default 64 MiB.

snapshot_between_turns = False class-attribute instance-attribute

Persist REPL state across eval calls (snapshot after / restore before).

Off by default: a fresh context per eval avoids cross-call const/ let redeclaration errors when the model retries with the same variable names. Enable only when a workflow genuinely needs to accumulate state across separate eval calls.

timeout = 5.0 class-attribute instance-attribute

Per-eval wall-clock budget in seconds, including awaited tools.task subagent runs.

langclaw.config.schema.ToolsConfig

Bases: BaseModel

Configuration for built-in agent tools (web search, fetch, etc.).

brave_api_key = '' class-attribute instance-attribute

Brave Search API key. Required when search_backend = "brave". Obtain one at https://api.search.brave.com/app/dashboard

gmail = Field(default_factory=GmailConfig) class-attribute instance-attribute

Gmail tool configuration. See GmailConfig.

search_backend = 'brave' class-attribute instance-attribute

Search backend to use. One of "brave", "tavily", or "duckduckgo".

tavily_api_key = '' class-attribute instance-attribute

Tavily Search API key. Required when search_backend = "tavily". Obtain one at https://app.tavily.com