Configuration¶
File location and loading¶
flowchart TD
A["XUN_HOME, else ./.xun"] --> B["config.json"]
B --> C["deep-merged onto the built-in template"]
C --> D["substitute ${XUN_...} placeholders"]
D --> E["pydantic validation"]
E --> F["Agent.config takes a deep clone"]
get_home_dir():XUN_HOMEwins, otherwise.xun/under the current working directory. The container image pinsXUN_HOME=/.xun.- The same directory also holds:
conversation/(history),extensions/(extensions),x/xunx.db(thexunxuser database). - There can also be
skills/(the skill package root of the Skills extension). Whenxunc/xunxcreate a container they copy onlyHOME_COPY_INCLUDE(config.json,extensions,skills) from the host xun home. xun_version()(exported at package level) returns the installed release version, orNonewhen running from source without package metadata; the extension version gate relies on that, see Extension system.- The reading entry point
load_config(force_reload: bool = False) -> AgentConfigcaches in-process;Agent.configis a deep copy of it by default, so changing one agent's configuration does not affect the global one. load_dotenv()is called while loading; keep secrets in.env.- There is no write-back API:
config.jsonis written by hand by the user;/config key.sub=value,/yoloand assignments inside extensions change only the agent's in-memory clone, so a process restart restores the content of the file.
Structure and fields¶
Every configuration model is extra="forbid": a misspelled field name raises an error instead of being silently ignored.
| Field | Type | Default | Description |
|---|---|---|---|
auto_confirm |
bool |
${XUN_AUTO_CONFIRM}, falls back to false |
automatically confirm prompts (choices take the default option, source="auto"); does not write to the allowlist; the ask_preference tool skips auto-confirmation and always asks |
enable_extensions |
bool |
true |
whether extensions are loaded; internal helper agents (risk assessment, summarisation) turn it off themselves |
load_project_instructions |
bool |
true |
whether the AGENTS.md file in the working directory is injected into the system prompt, see Prompts |
extension_settings |
dict[str, dict[str, JsonType]] |
{} |
user configuration bucketed by extension name; an extension reads its own bucket with ctx.settings |
provider.openai_base_url |
str |
${XUN_OPENAI_BASE_URL} |
OpenAI-compatible endpoint, an error is raised when missing |
provider.openai_api_key |
str |
${XUN_OPENAI_API_KEY} |
the API key, an error is raised when missing |
model.name |
str |
${XUN_OPENAI_MODEL}, falls back to "" |
when empty, the first entry of models.list() is taken during initialization (a warning is raised when there is more than one, an error when there is none) |
model.capabilities |
set["vision"] |
["vision"] |
the value domain is only vision for now (ModelCapabilityType), any other value fails validation; used to filter tools by required_capabilities |
model.temperature |
float \| None |
None |
when None, the parameter is not sent |
model.reasoning_field |
str \| None |
None |
reasoning or reasoning_content; None means detect it automatically on first sight and cache the result |
model.reasoning_effort |
"none"\|"minimal"\|"low"\|"medium"\|"high"\|"xhigh"\|"max" | None |
None |
some models support only a few of these levels |
auto_compact.enabled |
bool |
true |
switch for automatic compaction |
auto_compact.token_threshold |
int |
192000 |
compaction is triggered once the tokens of the previous model call exceed this value |
The built-in template (the configuration file is deep merged over it field by field):
{
"auto_confirm": "${XUN_AUTO_CONFIRM}",
"enable_extensions": true,
"load_project_instructions": true,
"auto_compact": { "enabled": true, "token_threshold": 192000 },
"provider": {
"openai_base_url": "${XUN_OPENAI_BASE_URL}",
"openai_api_key": "${XUN_OPENAI_API_KEY}"
},
"model": { "name": "${XUN_OPENAI_MODEL}", "capabilities": ["vision"] }
}
Placeholder rules¶
- A placeholder must start with
XUN_, otherwiseRuntimeError: Invalid placeholder .... - Resolution order:
os.environ→ the built-in fallback table (XUN_OPENAI_MODEL="",XUN_AUTO_CONFIRM="false") → if neither is present,RuntimeError: Missing environment variable .... - Substitution happens before JSON parsing, and the substituted value is always a string, converted to
bool/intby pydantic in lax mode. SoXUN_AUTO_CONFIRM=""(present but empty) is not rescued by the fallback value and fails during validation.
Environment variable list¶
| Variable | Read by | Purpose |
|---|---|---|
XUN_HOME |
get_home_dir() |
root directory for configuration/extensions/history; excluded when container environment variables are forwarded |
XUN_OPENAI_BASE_URL |
configuration template | the endpoint |
XUN_OPENAI_API_KEY |
configuration template | the API key |
XUN_OPENAI_MODEL |
configuration template | model name, auto-detected when empty |
XUN_AUTO_CONFIRM |
configuration template | automatic confirmation |
_XUN_DEFAULT_MAX_ITER |
agent.py |
the default max_iterations of execute() (512 built in) |
_XUN_INFO_TOOL_OVERRIDE |
toolbox.py |
print tool override/replacement information |
XUN_* / _XUN_* |
container entry points | the pattern always forwarded by xunc --env / xunx serve --env |
Names starting with _XUN_ are internal switches (get_internal_env() / get_internal_env_bool()), not user-facing configuration options.
Common ways to change it¶
{
"model": { "name": "my-model", "temperature": 0.2, "reasoning_effort": "low" },
"auto_compact": { "token_threshold": 64000 }
}
export XUN_OPENAI_BASE_URL=https://api.example.com/v1
export XUN_OPENAI_API_KEY=sk-...
export XUN_AUTO_CONFIRM=true
from xun import Agent
from xun.config import load_config
cfg = load_config().clone() # config.json (and .env), deep-copied
cfg.model.name = "my-model"
cfg.auto_compact.enabled = False
agent = Agent(config=cfg)
# $XUN_HOME/extensions/use_weak_model.py
"""Pin a small model for every agent."""
from xun import ExtensionContext
def setup_extension(ctx: ExtensionContext) -> None:
ctx.agent.config.model.name = "small-model"
Extensions run before the model is auto-detected, so an override here takes effect.
Prompts¶
The system prompt is not a configuration option but a constant in src/xun/prompt.py: the main agent uses get_system_prompt(), sub-agents use get_subagent_prompt(); compaction has its own templates (see Session and auto compaction). To replace it:
agent = setup_agent(default_system_prompt=False)
agent.system("You are a terse assistant. Answer in one sentence.")
system() overwrites message 0 of the session and can be called again at any time (summary compaction rewrites this message as well).
Project instructions AGENTS.md¶
When load_project_instructions is true (the default), the framework reads the AGENTS.md file in the working directory itself (probing AGENTS.md / Agents.md / agents.md in that order) and writes it, under the heading ## Project instructions (<file name>), into a persistent section of the system message, SystemPrompt.persist_sections:
- When it is refreshed: at the end of
initialize(), before and after everyexecute(), and after/loadhas loaded a history — edit the file mid-session and the next execution picks it up. - When summary compaction rewrites the system message the persistent section is kept as is (the summariser does not see it);
system()does not affect it either. If the file is missing or its content is empty, the section is not produced.