{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://leviath.dev/docs/stable/config.schema.json",
  "title": "Leviath machine configuration (config.toml)",
  "description": "Machine-wide settings, at ~/.leviath/config.toml (or wherever LEVIATH_HOME or LEVIATH_CONFIG_PATH points). Everything is optional: an install with one provider key needs nothing else. Written against the serde structs in crates/leviath-cli/src/config.rs and checked against a golden config by a test there. See https://leviath.dev/docs/configuration.",
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "default_provider": {
      "type": "string",
      "default": "anthropic",
      "description": "Which provider a stage falls back to when it states no preference of its own. `lev setup` points this at a provider you configured; leaving it on one you did not is what makes `lev doctor` report a resolve failure."
    },
    "default_model": {
      "type": "string",
      "description": "The model to pair with default_provider. Without it a stage naming no model falls through to a hard-coded last resort, which fails on a machine with no Anthropic key."
    },
    "agent_paths": {
      "type": "array",
      "description": "Extra directories to scan for blueprints. Read by `lev list` and the serve API, but not by `lev run <name>`, which resolves installed names and paths only.",
      "items": {
        "type": "string"
      }
    },
    "openrouter_api_key": {
      "type": "string"
    },
    "ollama_base_url": {
      "type": "string",
      "default": "http://localhost:11434"
    },
    "request_timeout_secs": {
      "type": "integer",
      "minimum": 1
    },
    "taint_tracking": {
      "type": "boolean",
      "default": false
    },
    "batch_tool_hint": {
      "type": "boolean",
      "default": true
    },
    "shell_hint": {
      "type": "boolean",
      "default": true
    },
    "providers": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "anthropic_api_key": {
          "type": "string"
        },
        "openai_api_key": {
          "type": "string"
        },
        "google_api_key": {
          "type": "string"
        },
        "claude_code_enabled": {
          "type": "boolean",
          "default": false
        },
        "claude_code_binary": {
          "type": "string"
        },
        "claude_code_effort": {
          "enum": [
            "low",
            "medium",
            "high",
            "xhigh",
            "max"
          ]
        },
        "anthropic_cache_ttl": {
          "description": "How long a cached prompt prefix survives. \"1h\" costs more to write and sends the beta header it needs.",
          "enum": [
            "5m",
            "1h"
          ]
        },
        "fallback_order": {
          "type": "array",
          "description": "Host-wide failover chain, best first, as \"provider/model\" strings. Tried after the blueprint's own list.",
          "items": {
            "type": "string"
          }
        }
      }
    },
    "limits": {
      "type": "object",
      "description": "The daemon reads these once at startup, so a change needs `lev daemon restart`.",
      "additionalProperties": false,
      "properties": {
        "max_concurrent_inferences": {
          "type": "integer",
          "minimum": 1,
          "default": 8
        },
        "max_concurrent_tools": {
          "type": "integer",
          "minimum": 1,
          "default": 8
        },
        "default_max_iterations": {
          "type": "integer",
          "minimum": 1,
          "default": 50
        },
        "exact_token_counting": {
          "type": "boolean",
          "default": false
        },
        "script_shell_timeout_secs": {
          "type": "integer",
          "minimum": 0,
          "default": 60
        },
        "mcp_idle_disconnect_secs": {
          "type": "integer",
          "minimum": 0,
          "default": 60,
          "description": "Disconnect an MCP server no agent has used for this long. It reconnects on next use."
        },
        "stall_timeout_secs": {
          "type": "integer",
          "minimum": 0,
          "default": 60
        },
        "dead_cycles_before_relief": {
          "type": "integer",
          "minimum": 0,
          "default": 10
        },
        "finished_retention_secs": {
          "type": "integer",
          "minimum": 0,
          "default": 300,
          "description": "How long a finished run stays in the `finished` list of `lev ps`."
        },
        "wedge_timeout_secs": {
          "type": "integer",
          "minimum": 0,
          "default": 0
        },
        "provider_failures_before_open": {
          "type": "integer",
          "minimum": 1,
          "default": 3
        },
        "provider_circuit_cooldown_secs": {
          "type": "integer",
          "minimum": 0,
          "default": 300
        },
        "interaction_timeout_secs": {
          "type": "integer",
          "minimum": 0,
          "default": 3600,
          "description": "How long a prompt waits for a person before resolving as cancelled. `0` waits forever. This is what releases an interaction point that holds under --yolo, so the default means such a run sits for an hour looking dead."
        },
        "inference_retry_attempts": {
          "type": "integer",
          "minimum": 1,
          "default": 4,
          "description": "How many times an inference is attempted, the first try included, before the agent is failed. Only transient failures are retried. `1` turns retrying off. A provider overload (429, or Anthropic's 529) uses a slower schedule of 15s, 30s, then 60s per further attempt, so raising this is how a run rides out a longer outage. The retries of one request may sleep at most five minutes in total whatever this is set to."
        },
        "inference_retry_base_ms": {
          "type": "integer",
          "minimum": 0,
          "default": 1000,
          "description": "The wait before the first inference retry, in milliseconds, doubling for each retry after it. This is the schedule for ordinary blips such as a reset connection or a 500; a provider overload does not use it."
        },
        "max_tool_call_write_bytes": {
          "type": "integer",
          "minimum": 0,
          "description": "Most bytes one tool call may write to disk. Absent is unlimited. Unset in code and written by `lev setup`, so deleting the key removes the limit rather than restoring a default. `write_file` is refused before it writes; a shell redirect is measured after the call, so it stops the call after the one that overran. Running out of disk is refused separately and is not configurable."
        },
        "max_run_write_bytes": {
          "type": "integer",
          "minimum": 0,
          "description": "Most bytes a whole run may write to disk. Absent is unlimited. Catches what a per-call ceiling cannot: several individually plausible calls that together fill a disk. Same defaulting as max_tool_call_write_bytes."
        }
      }
    },
    "security": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "allowed_workdirs": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "default": [],
          "description": "Directories a run's workdir may sit under without being confirmed. Empty asks only about a home directory or a filesystem root."
        },
        "allow_seed_commands": {
          "type": "boolean",
          "default": true,
          "description": "Whether a blueprint's `seed = { command = ... }` regions may run. They execute at spawn, before any approval prompt."
        },
        "allow_local_network": {
          "type": "boolean",
          "default": false
        },
        "allow_env_vars": {
          "type": "array",
          "description": "Credential-looking variables a Rhai script may read. Matched exactly and case-insensitively; \"*\" means a variable literally named `*`, not everything.",
          "items": {
            "type": "string"
          }
        },
        "allow_blueprint_read_paths": {
          "type": "boolean",
          "default": false
        },
        "allow_blueprint_safe_commands": {
          "type": "boolean",
          "default": false,
          "description": "Honour every blueprint's own [safe_commands]. Off by default: declaring is not granting, so an installed agent cannot pre-approve its own shell."
        },
        "allow_blueprint_permissions": {
          "type": "boolean",
          "default": false,
          "description": "Honour every blueprint's own [tool_permissions] even where it exceeds the built-in default for a tool you have not configured. Off by default: a blueprint may still pre-approve web_search and web_fetch, and anything else is clamped to the default."
        },
        "shell_env": {
          "type": "string",
          "enum": [
            "filtered",
            "strict",
            "custom",
            "inherit"
          ],
          "default": "filtered",
          "description": "Which of the daemon's environment variables a shell tool call, a Rhai shell(), and a command seed inherit."
        },
        "shell_env_withhold": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "default": [],
          "description": "Names withheld under shell_env = \"custom\". Ignored under every other mode."
        },
        "read_paths": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "credential_store": {
          "enum": [
            "file",
            "keychain"
          ],
          "default": "file"
        }
      }
    },
    "webhook": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "max_retries": {
          "type": "integer",
          "minimum": 0,
          "default": 3
        },
        "base_delay_ms": {
          "type": "integer",
          "minimum": 0,
          "default": 500
        },
        "max_delay_ms": {
          "type": "integer",
          "minimum": 0,
          "default": 30000
        },
        "timeout_secs": {
          "type": "integer",
          "minimum": 1,
          "default": 10
        }
      }
    },
    "observability": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "enabled": {
          "type": "boolean"
        },
        "exporter": {
          "enum": [
            "otlp",
            "stdout",
            "none"
          ]
        },
        "endpoint": {
          "type": "string",
          "description": "OTLP over HTTP/protobuf only. The gRPC port 4317 will not work."
        },
        "service_name": {
          "type": "string"
        }
      }
    },
    "nudge": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "enabled": {
          "type": "boolean"
        },
        "max": {
          "type": "integer",
          "minimum": 0
        },
        "text": {
          "type": "string"
        }
      }
    },
    "title": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "enabled": {
          "type": "boolean",
          "default": true
        },
        "provider": {
          "type": "string"
        },
        "model": {
          "type": "string"
        }
      }
    },
    "sandbox": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "kind": {
          "enum": [
            "none",
            "namespace",
            "container"
          ]
        },
        "image": {
          "type": "string"
        },
        "engine": {
          "enum": [
            "docker",
            "podman",
            "nerdctl",
            "finch"
          ]
        },
        "network": {
          "type": "boolean"
        },
        "mounts": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "persist": {
          "type": "boolean"
        },
        "on_unavailable": {
          "enum": [
            "error",
            "warn"
          ]
        }
      }
    },
    "tool_permissions": {
      "type": "object",
      "description": "Tool name to policy, machine-wide. A blueprint may tighten this, never loosen it.",
      "additionalProperties": {
        "enum": [
          "allow",
          "ask",
          "deny"
        ]
      }
    },
    "agent_tool_permissions": {
      "type": "object",
      "description": "Per-agent overrides, keyed by agent name. The escape hatch above the machine-wide ceiling.",
      "additionalProperties": {
        "type": "object",
        "additionalProperties": {
          "enum": [
            "allow",
            "ask",
            "deny"
          ]
        }
      }
    },
    "tool_script_permissions": {
      "type": "object",
      "description": "What a Rhai tool script may do. `inherit` defers to the equivalent built-in tool's policy.",
      "additionalProperties": false,
      "properties": {
        "http_get": {
          "$ref": "#/$defs/scriptPermission"
        },
        "http_post": {
          "$ref": "#/$defs/scriptPermission"
        },
        "shell": {
          "$ref": "#/$defs/scriptPermission"
        },
        "read_file": {
          "$ref": "#/$defs/scriptPermission"
        },
        "write_file": {
          "$ref": "#/$defs/scriptPermission"
        },
        "env_var": {
          "$ref": "#/$defs/scriptPermission"
        }
      }
    },
    "agent_read_paths": {
      "type": "object",
      "description": "Read grants outside the workdir, keyed by agent name. A blueprint's [read_paths] is inert until granted here.",
      "additionalProperties": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "allow": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      }
    },
    "safe_commands": {
      "type": "object",
      "additionalProperties": false,
      "description": "What runs without an approval prompt, for tools whose policy is `ask`. Entries are argument-scoped, in the same key space a \"for this run\" grant uses, and can only turn `ask` into `allow`.",
      "properties": {
        "defaults": {
          "type": "boolean",
          "default": true,
          "description": "Ship the built-in read-only verb list (ls, cat, grep, git status, ...)."
        },
        "tools": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Tools that never prompt whatever their arguments. Built-in names, or MCP names as advertised (server__tool)."
        },
        "shell": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "A program, optionally with the subcommand that narrows it: \"git status\", never \"git\" or \"cargo test --lib\"."
        }
      }
    },
    "agent_safe_commands": {
      "type": "object",
      "description": "Per-agent additions to [safe_commands], keyed by agent name.",
      "additionalProperties": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "tools": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "shell": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "allow_blueprint": {
            "type": "boolean",
            "default": false,
            "description": "Honour this agent's own [safe_commands] block."
          }
        }
      }
    },
    "rate_limits": {
      "type": "object",
      "description": "Keyed by built-in provider name.",
      "additionalProperties": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "requests_per_minute": {
            "type": "integer",
            "minimum": 1
          },
          "tokens_per_minute": {
            "type": "integer",
            "minimum": 1
          }
        }
      }
    },
    "model_capabilities": {
      "type": "object",
      "description": "Keyed by model id. Overrides what Leviath believes about a model.",
      "additionalProperties": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "supports_temperature": {
            "type": "boolean"
          },
          "supports_streaming": {
            "type": "boolean"
          },
          "supports_tools": {
            "type": "boolean"
          },
          "supports_system_prompt": {
            "type": "boolean"
          },
          "max_context_tokens": {
            "type": "integer",
            "minimum": 1
          },
          "max_output_tokens": {
            "type": "integer",
            "minimum": 1
          }
        }
      }
    },
    "model_providers": {
      "type": "object",
      "description": "Overrides for Rhai script providers, keyed by the name an agent references. Any key not listed here is forwarded verbatim to the script's initialize().",
      "additionalProperties": {
        "type": "object",
        "additionalProperties": true,
        "properties": {
          "script": {
            "type": "string"
          },
          "api_key": {
            "type": "string"
          },
          "base_url": {
            "type": "string"
          },
          "rate_limit": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "requests_per_minute": {
                "type": "integer",
                "minimum": 1
              },
              "tokens_per_minute": {
                "type": "integer",
                "minimum": 1
              }
            }
          }
        }
      }
    },
    "mcp_servers": {
      "type": "array",
      "description": "MCP tool servers to connect to. `${VAR}` interpolates in headers and env.",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "transport": {
            "enum": [
              "stdio",
              "http"
            ],
            "description": "Inferred from whichever of command or url is present when omitted."
          },
          "url": {
            "type": "string"
          },
          "headers": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "command": {
            "type": "string"
          },
          "args": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "env": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          }
        }
      }
    }
  },
  "$defs": {
    "scriptPermission": {
      "enum": [
        "allow",
        "deny",
        "inherit"
      ]
    }
  }
}
