Skip to content

Latest commit

 

History

History
297 lines (232 loc) · 12.3 KB

File metadata and controls

297 lines (232 loc) · 12.3 KB

Configuration reference

lecode reads TOML config only (config.toml):

  • Global: ~/.config/lecode/config.toml (the directory is overridable with the LECODE_CONFIG_DIR env var). Auto-created with commented defaults on first run.
  • Project: .lecode/config.toml, found by walking from the cwd up to the git root. Deep-merged over the global config: dicts merge recursively, scalars and lists replace.
  • CLI flags apply on top of both (--model, --provider, --base-url, --api-key, …).

Unknown keys produce startup warnings. schema_version = 1 is the current on-disk schema; older files are migrated forward automatically.

The API key resolution chain is: --api-key > provider env var (OPENROUTER_API_KEY, OPENAI_API_KEY, or [custom_providers.*].api_key_env)

[llm].api_key in the config file. If you store the key in the file, keep it owner-only (chmod 600); lecode --setup does that for you.

[llm]

field default meaning
provider "openrouter" openrouter or a [custom_providers] name
model "deepseek/deepseek-v4-flash" default model id
api_key unset provider key (env vars preferred)
base_url unset override the provider's endpoint
thinking "medium" none | low | medium | high
connect_timeout_s 10.0 HTTP connect timeout
read_timeout_s 300.0 HTTP read (streaming) timeout
auth_policy "auto" auto | required | none — fail when no key found
tls_verify true set false for self-signed endpoints

The model catalog is fetched live from the provider's /models endpoint at startup (context windows, pricing, modalities). When the fetch fails the catalog is empty — models lose pricing/modality annotations and costs report as unknown until the provider reports usage — and nothing is cached on disk. Plain OpenAI-shaped /models responses (id only) get a 128k default context window and zeroed pricing.

[llm.system_prompt]

field default meaning
style "minimal" minimal | rich base prompt
custom unset replaces the base prompt entirely
persona unset persona name (prompts/personas/<name>.md) appended in rich style

[compaction]

field default meaning
enabled true auto-compact when approaching the context window
buffer_tokens 20000 headroom kept below the window
on_overflow "continue" continue | pause when even compaction can't fit
mid_turn_threshold unset absolute token count that triggers compaction on tool-loop rounds after the first (instead of window − buffer)

[agent]

field default meaning
max_turns 500 hard turn budget per prompt
context_window 200000 assumed model window for compaction
turn_cooldown_ms 0 sleep between turns
tool_idle_timeout_s 300 abort a turn after this much provider silence
subagent_model unset model for subagents (/model-subagent); inherits main

[tools]

field default meaning
enabled {} per-tool on/off, e.g. enabled = { bash = false }
allowlist [] when non-empty, only these tools are registered

[worktree]

field default meaning
validation [] ordered list of validation commands run in the worker checkout before integration
[worktree]
validation = ["uv run ruff check", "uv run python -m pytest"]

Lists replace inherited lists, so a project can explicitly set validation = []. Empty means an explicit human gate, not automatic approval or successful validation. Integrating without checks requires a human decision for that operation, passed as allow_unvalidated=True. Tool auto-approval never supplies that decision. Blank commands are rejected. Configuring checks does not grant permission to run them.

Worker lifecycle integration API

These WorktreeManager primitives are for the worker/tool caller to wire at an idle boundary, after stopping or joining the worker:

  • await reconcile(name, *, recreate=False) -> WorktreeInspection verifies the checkout against its sidecar and its pinned immediate parent. It merges the parent's latest committed HEAD into a clean worker for follow-up work. Dirty or in-progress work is returned unchanged. A conflicting merge raises WorktreeError and remains visible in the worker. It never resets or rebases. Missing checkouts raise a clean error unless recreate=True was explicitly requested. Recreation uses the surviving worker branch, or the recorded base commit if the branch is gone, then merges current parent progress. Missing uncommitted content cannot be recovered.
  • await integrate(name, *, reviewed_head, validation, validation_runner, allow_unvalidated=False) -> MergeResult requires the parent model to review the actual diff against the pinned destination and approve the exact full commit hash. The mechanical gate cannot judge whether that review was honest. ValidationRunner is Callable[[str, Path], Awaitable[ProcResult]], using lecode.extras.proc.ProcResult. The caller must dispatch every command through ToolRegistry and map denials/failures to nonzero results. There is no built-in shell runner. If merging destination progress changes the candidate HEAD, integration stops with WorktreeError requiring a fresh diff review and hash. Checks validate that exact candidate. Changed HEADs, dirty checkouts, in-progress Git operations, and replaced or switched destinations prevent integration. Only a fast-forward of the pinned parent is performed. No push occurs.
  • await cleanup_worker(name, *, discard=False) -> WorktreeInfo proves that the clean worker's current HEAD is an ancestor of its current pinned destination, then removes the checkout and uses safe branch deletion (-d). Extra commits after integration prevent cleanup. Only explicit human discard=True permits deleting dirty or unmerged work and force-deleting its branch. Checkout identity checks still apply. Sidecars and session data are retained.

These operations serialize by canonical common Git directory and destination branch using a cancellable, nonblocking flock. Lock sidecars are never unlinked. The caller must also keep workers idle and prevent concurrent tool writes to the worker or parent during the operation. Destination path, repository identity and branch are pinned; the helper never silently switches branches. A nested worker integrates into its immediate parent, not directly into the repository's main branch.

[ui]

field default meaning
collapse_thinking true collapse reasoning blocks in the feed
show_welcome true show the welcome cheat-sheet on startup
hidden_models [] model ids hidden from /models
no_color false disable colored output

The theme is fixed: one dark, Kalvad-purple palette ("kalvad"). There is no theme selection, no theme files, and no color overrides.

[permissions]

mode is one of yolo (default; everything allowed) or readonly (read-class tools allowed, everything else denied). Legacy mode values (standard, restrictive, planwrite, guarded) still load but are coerced to yolo with a deprecation warning. Rules live under [permissions.rules] as allow / ask / deny tables mapping tool names to pattern lists:

[permissions]
mode = "yolo"

[[permissions.rules.allow.bash]]
pattern = "git status"

[[permissions.rules.ask.write]]
pattern = "*.py"

[[permissions.rules.deny.bash]]
pattern = "rm -rf*"
kind = "glob"   # "glob" (default) or "regex"

Last match wins within a table; deny rules are unbypassable (even in yolo), and ask rules still prompt. Read-class tools (read, grep, find_files, list_dir, lsp_diagnostics, memory_read, memory_search, task, and Exa/context7/grep.app MCP tools) are the only tools allowed in readonly. A 3rd identical consecutive call escalates Allow → Ask, the 4th is denied (doom-loop guard).

[notifications]

Sound notifications (afplay/paplay/aplay, terminal bell fallback) and desktop notifications (osascript on macOS, notify-send on Linux).

field default meaning
enabled true master switch (/notifications on|off toggles per session)
volume 0.5 0.0–1.0 (afplay only)
sound true sound channel (player or bell)
desktop true desktop-notification channel
on_finish / on_error / on_approval true per-event toggles (both channels)

[signals]

Lifecycle status events as JSON datagrams over a Unix socket — for status bars and tmux integration.

field default meaning
enabled false emit start / stop / git-conflict events
socket_path <config_dir>/lecode.sock datagram destination

Payload: {"event", "session", "ts", …}; failures are dropped silently.

[mcp]

field default meaning
enable_exa true auto-configure Exa web search when EXA_API_KEY is set
enable_context7 false auto-configure the context7 docs server

[mcp.servers.<name>] entries:

field default meaning
transport "stdio" stdio | http (streamable HTTP) | sse (legacy)
command / args / env — stdio: argv and extra environment
url / headers — http/sse: endpoint and extra headers (static bearer tokens go here)
auth — http/sse: "oauth" enables the interactive OAuth 2.1 flow; credentials in <config_dir>/mcp-auth/ (0600)
timeout_s 30.0 per-call timeout
enabled true disabled servers are skipped

See mcp.md.

[lsp]

field default meaning
enabled true spawn language servers, append diagnostics to write/edit

[lsp.servers.<lang>] overrides the built-in registry (command, file_patterns) or defines a new language. Diagnostics are fail-open: LSP trouble never blocks the agent.

[memory]

field default meaning
enabled true persistent markdown memory + the four memory_* tools
max_bytes 32768 injection cap for MEMORY.md

See memory.md.

[pierre]

Post-task reviewer: after every completed task, a second model compares the request with the result and gives feedback in the feed.

field default meaning
enabled false review every finished task + /pierre
model unset reviewer model id; /pierre on requires one, different from the main model

[hooks]

Event name → list of shell commands:

[hooks]
PreToolUse = ["./ci/check-tool.sh"]
Stop = ["say done"]

Events: PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, PermissionResult, UserPromptSubmit, Stop, SessionStart, SessionEnd, SubagentStart, SubagentEnd, PreCompact, PostCompact, Interrupt, Notification. Hooks can only narrow permission verdicts; only PreToolUse and UserPromptSubmit deny-verdicts are enforced. See hooks.md.

[custom_providers]

  • [custom_providers.<name>] — extra OpenRouter-compatible providers:
[custom_providers.local]
base_url = "http://localhost:11434/v1"
api_key = "sk-..."              # optional
api_key_env = "LOCAL_API_KEY"   # optional
auth_policy = "none"            # auto | required | none
# headers = { X-Team = "infra" }

[telemetry]

Opt-in Sentry error reporting + OpenTelemetry metrics. Requires the telemetry extra (uv tool install 'lecode[telemetry]'); every failure mode is fail-open (telemetry never breaks the agent).

field default meaning
enabled false master switch
sentry_dsn unset Sentry (or GlitchTip) DSN for error reports
otlp_endpoint unset OTLP/HTTP base URL for metrics (e.g. http://localhost:4318)
export_interval_s 60.0 metric export interval
service_name "lecode" OTel service.name resource
environment "dev" Sentry/OTel environment tag

Metrics: lecode.turns, lecode.turn.duration_s, lecode.tokens.input, lecode.tokens.output, lecode.cost_usd, lecode.tool_calls, lecode.tool.duration_s (all tagged by model / tool / stop reason). Sentry receives provider and tool errors with send_default_pii = false.