Agent Initialization¶
The recommended path is the Agent class itself: every part of it is a replaceable object.
Use setup_agent when you just want something running quickly — it wires up the built-in tools, system prompt, session commands, and display layer in one call and returns an initialized agent.
Shortest path: setup_agent¶
from xun import setup_agent, interactive_session
def weather(city: str) -> str:
"""Query the weather for a given city."""
return f"Sunny in {city}"
agent = setup_agent(tools=[weather]) # initialize() has already run
interactive_session(agent, task="What is the weather in Paris?")
def setup_agent(
name: str = "agent",
tools: list[Callable] = [],
default_tools: bool = False,
default_system_prompt: bool = True,
default_commands: bool = True,
display: DisplayAbstract | None = None,
workdir: Path | str | None = None,
) -> Agent[Agent.T.Init]: ...
| Parameter | Default | Description |
|---|---|---|
name |
"agent" |
Agent name; appears in the display layer and in events |
tools |
[] |
Additional custom tool functions |
default_tools |
False |
Whether to load the 33 built-in tools; also enables the sub-agent derivation tools and tool-related session commands |
default_system_prompt |
True |
Writes the built-in system prompt |
default_commands |
True |
Registers the built-in session commands (/help, etc.) |
display |
None |
None uses the console display Display(); pass NullDisplay() to stay silent in programmatic calls |
workdir |
None |
Working directory; None means the current directory |
Initialization happens last
setup_agent calls initialize() only after all configuration is done,
so the after_initialize hook sees a fully configured agent.
Full path: the Agent class¶
Agent is a dataclass, and each field is a replaceable core object:
| Field | Default | Inherited on derivation | Purpose |
|---|---|---|---|
name |
agent-<uuid8> |
New name | Identifier in the display layer and in events |
identifier |
uuid | New uuid | Process-unique ID; the display layer distinguishes agents by it |
display |
NullDisplay() |
Shared reference | Display layer, see Display Extensions |
conversation |
Conversation() |
Not copied by default | Message sequence and system prompt sections |
toolbox |
ToolBox() |
Cloned | Toolbox, see Tool Setup |
command |
CommandRegistry() |
Cloned | Session commands |
workspace |
Workspace() |
Shares the same object | Working directory + lazy temp directory |
cancel_event |
ChainedEvent() |
Attached under the parent event | Cancellation chain, see Chained Cancellation |
config |
load_config().clone() |
Cloned | Independent copy of the configuration for this agent |
api_call_semaphore |
3 | Shared reference | Process-wide ceiling on concurrent model calls |
extension_loader |
Global loader | Shared reference | Extensions are replayed on every initialize() |
state |
AgentState() |
Not inherited | Typed state container |
hooks |
Hooks() |
Not inherited | Lifecycle hooks |
compactor |
AutoCompactor() |
Not inherited | Conversation compaction strategy |
from xun import Agent, ToolBox
def weather(city: str) -> str:
return "The weather in " + city + " is sunny."
agent = Agent(toolbox=ToolBox().register(weather)).initialize()
answer = agent.instruct("What is the weather in Paris?").execute()
print(answer.unwrap())
The chained form (as used in the README):
greeting = (
Agent() # Agent[Uninit]
.initialize() # Agent[Init]
.instruct("Hi~") # Agent[Init]
.execute() # Result[str, ErrorInfo]
.unwrap() # str
)
What initialize() does¶
flowchart TD
A["Agent(...)"] --> B["initialize()"]
B --> B1["Replay extension setup"]
B1 --> B2["Auto-detect model when the name is empty"]
B2 --> B3["display.bind(agent)"]
B3 --> B4["workspace.prepare()"]
B4 --> B5["compactor.install(hooks)"]
B5 --> B6["after_initialize hook"]
B6 --> B7["Sync AGENTS.md"]
B7 --> C["Agent[Init]: ready to execute"]
C --> D["finalize()"]
D --> D1["before_finalize hook"]
D1 --> D2["display.unbind + AgentUnbindEvent"]
D2 --> E["Agent[Final]: no longer usable"]
An agent must be initialized before execute(); the runtime checks this as well and raises a RuntimeError with a hint.
Details of the state transitions are in Lifecycle and Type States.
Execution and return values¶
result = agent.instruct("Write a 100-word story").execute() # Result[str, ErrorInfo]
story = agent.instruct(...).execute(schema=StoryModel) # Result[StoryModel, ErrorInfo]
agent.execute_command("help") # Result[None, ErrorInfo]
execute / execute_command are wrapped by except_safe: exceptions are always converted into Result.Err(ErrorInfo),
except CancelledError and KeyboardInterrupt, which are raised as-is. The Result interface:
| Method | Behavior |
|---|---|
is_ok() / is_err() |
Decide which branch you are on |
unwrap() / unwrap_err() |
Take the value; raises a plain Exception on the wrong branch |
value |
Raw value (either T or E) |
value_str() / value_json() |
String / JSON form |
Result.from_payload(data, ok_factory, err_factory) |
Restore from JSON |
max_iterations defaults to 512; exceeding it raises RuntimeError, which surfaces as Result.Err.
The object passed via context= is handed to tools as ToolCallContext.value, see Tool Setup.
Deriving sub-agents¶
Agent.inherit(parent, ...) returns an uninitialized child agent, and lets you choose which parts to share:
child = Agent.inherit(
parent,
share_workspace=True, # Same Workspace object (same workdir and lazy tempdir)
share_display=True, # Same display layer
copy_toolbox=True, # Shallow copy of the toolbox
copy_command=True, # Shallow copy of the command registry
copy_conversation=False, # Conversation is independent by default
inherit_cancel_event=True, # Cancel event attached to the parent chain
)
config is always cloned, and state / hooks / compactor are always fresh.
The child agent goes through the defaults again for both extensions and the compaction strategy; see Sub-agent Derivation for how this combines with the built-in derivation strategies.
Attaching parts manually¶
There is no add_tool()-style method — all configuration means attaching something to the corresponding object:
agent.toolbox.register(my_tool) # Tool (returns the ToolBox, chainable)
agent.command.register(my_command) # Session command
agent.hooks.before_tool_call.add(handler) # Hook
agent.conversation.set_system_persistent_section("rules", "…") # Persistent section of the system prompt
agent.state["counter"] = JsonEntry(0) # State entry
agent.system("You are a ...") # Replaces the system prompt body (persistent sections are not replaced)