Skip to content

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_HOME wins, otherwise .xun/ under the current working directory. The container image pins XUN_HOME=/.xun.
  • The same directory also holds: conversation/ (history), extensions/ (extensions), x/xunx.db (the xunx user database).
  • The reading entry point load_config(force_reload: bool = False) -> AgentConfig caches in-process; Agent.config is 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.json is written by hand by the user; to_json() exists only for the /config printout, and changes made by /yolo or by extensions live in memory only.

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
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)
model.capabilities set[str] ["vision"] only "vision" for now, 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,
  "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_, otherwise RuntimeError: 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/int by pydantic in lax mode. So XUN_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, AgentConfig

cfg = AgentConfig.default()
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). The AGENTS.md convention used by compaction also comes from the prompt text: the framework asks the agent to look at AGENTS.md in the working directory first and to follow it — this is a prompt-level convention, the code does not parse that file.