Configuration — for developers
Every key and what it does is documented for users at https://llembas.eu/docs/cli-configuration.html. This page is about the machinery: where configuration comes from, in what order, what a project may and may not set, and what to touch when adding a key.
The files
| File | Scope | Notes |
|---|---|---|
~/.config/lembas/connections.yaml |
global only | Endpoints, dialects, models, keys. Written chmod 600. A project can never define or override a connection. |
~/.config/lembas/config.yaml |
global | Everything else. |
<project>/.agent/config.yaml |
project | Read only once the directory is trusted. .lembas/ is used instead when .agent/ already belongs to another tool (project/root.ts). |
~/.config/lembas/{agents,commands,skills,memory}/ |
global | Subagents, custom commands, skills, memory. The project's .agent/agents, .agent/commands and .agent/skills count only when trusted. |
~/.local/share/lembas/ |
data | sessions.db, the library, snapshots, worktrees, mcp-auth.json. |
~/.local/state/lembas/ |
state | learned.json, prompt history, the hub socket, the service's status. |
All three roots follow XDG; LEMBAS_HOME roots all three at once, which is what
the tests and the smoke test use (config/paths.ts).
Loading (src/config/load.ts)
- Read the YAML, then translate legacy keys and former names (a mode such as
unrestricted, a renamed permission key) with a note for the user. - Drop a project's global-only keys before validation, so their shape cannot
break the file:
GLOBAL_ONLY=hardline_extra,hardline_disable,voice,update,settings_tool,embedding,remote,library,instructions,personality_custom. Each one dropped is a warning. A repository must not be able to weaken the floor, redirect speech or updates, or write words into the system prompt in the user's voice. - Validate with zod, then substitute.
{env:NAME}and{file:path}are filled after validation (config/substitute.ts); a missing variable or file is an error, never an empty string. A URL field accepts a placeholder and is checked again after substitution.mcp,voice,searchandupdateare filled one entry at a time, so one broken server or service turns off that one thing, not the whole file. A connection whose substitution fails lands inbrokenwith the reason. - Merge: objects key by key, everything else replaced, project over global. Permission rules stack instead (global first, then project), and a project's rule cannot loosen a global one.
webuiconnections are expanded (lembas/webui.ts): one entry naming a LLeMbas instance becomes the OpenAI-chat connection it is spoken to as, with the account's models from/v1/models— refreshed at every start, kept from the last good read when the instance cannot be reached. The account's personalization replaces the local keys while logged in (lembas/personal.ts). Models get refs (provider/refs.ts): an instance's are<provider>/<model>.
Settings while running (src/config/settings.ts, src/settings.ts)
One list, SETTINGS, is read three ways: /settings, lembas config get|set|list, and the agent's settings tool. Each entry has a kind, the scopes
it may be set at (session, global, project), whether the agent may
change it (with approval; a less strict mode is asked every time), whether it
needs /reload, and what it is when unset. A value is resolved from the session,
then the project's file, then the global file, then the fallback. Writing uses
yaml's parseDocument … setIn, so the user's comments and layout survive.
Not in the list, on purpose: permission rules, the hardline, MCP servers,
connections, the update source and key, and settings_tool itself. Those are
edited by hand, never by the agent.
JSON Schemas
schema/config.schema.json and schema/connections.schema.json are generated
from the zod schemas (config/jsonschema.ts) by bun run schema, and written
beside a user's config by lembas config schema, so an editor with a YAML
language server completes and checks keys. Regenerate them in the same commit as
a schema change; a test compares them.
Adding a key
- Add it to the zod schema in
config/schema.tswith a.describe()— the description is what the JSON Schema and the editor show. - Decide whether a project may set it. If a cloned repository could use it to
weaken a check, redirect traffic or a key, or speak in the user's voice, add
it to
GLOBAL_ONLY. - If it may be changed while running, add it to
SETTINGSwith its scopes, and say whether the agent may change it. The default is no. - Write its reader in the same change, with a test that reads the effect — a key the schema accepts and nothing reads is a promise the program does not keep.
bun run schema, and document it on the website's configuration page.