Reporter, Human, Terminal¶
Guide: Show progress and ask the person.
Reporter
¶
Shows progress. Every method does nothing by default, so a subclass overrides only the ones it needs.
Pass one as Agent(reporter=...); reporter=None shows nothing. A Reporter only watches: it does not
change the State or the flow. Exceptions it raises are not swallowed.
Methods may be called from several threads at once (a turn's tools run in parallel), so an implementation
must be thread-safe. Agents in different threads share the default Terminal.
A Reporter gets the State and the call, not the Agent. To read a tool's hints in on_tool_start, keep a
reference to the Agent yourself (reporter.agent = agent after creating both) and look the tool up in
agent.tool_map.
Example
class Log(Reporter):
def on_tool_start(self, state, call):
logging.info("turn %d: %s", state.turn, call.name)
on_think_start
¶
on_think_start(state: State) -> None
Right before a request to the model, from think and ask. In think, state.turn is already
this turn's number.
on_think_end
¶
Right after the reply ends. In think the reply is already recorded; ask records nothing to the
context but calls this too.
on_tool_start
¶
Right before a tool runs. Every call that gets this gets exactly one on_tool_end.
on_tool_end
¶
on_tool_end(state: State, call: ToolCall, result: ToolResultContent, outcome: ToolOutcome) -> None
Right after a tool call ends. A call a permission denied or cancelled did not run, so it gets this
without on_tool_start.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
State
|
The State. |
required |
call
|
ToolCall
|
The tool call. |
required |
result
|
ToolResultContent
|
What was sent to the model: a string, or a tuple of |
required |
outcome
|
ToolOutcome
|
How the call ended. Branch on |
required |
on_context_change
¶
on_context_change(state: State, change: ContextChange) -> None
When the messages change: compact (by the model or state.compact), clear_tool_results or
restore. Not for the import of State(messages=...), and not when a failed think is taken back.
on_model_event
¶
on_model_event(state: State, event: ModelEvent) -> None
When the Model reports something outside the reply, such as falling back to another model. Called while waiting for the reply.
on_run_end
¶
on_run_end(state: State, error: BaseException | None) -> None
When run() ends, always, even after an exception.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
State
|
The State. |
required |
error
|
BaseException | None
|
The exception that ended the run, or |
required |
Human
¶
Bases: ABC
Asks a person and gets an answer. Used by agent.ask_human.
A subclass implements ask, or async aask for a person reached asynchronously (a web page, a chat
app), or both. An implementation asks again when an answer does not fit the returns format, asks one
question at a time, and does not change the State.
Raises:
| Type | Description |
|---|---|
TypeError
|
When creating an instance of a subclass that implements neither |
Example
class WebHuman(Human):
async def aask(self, state, prompt, returns=str):
return parse_answer(await ask_on_the_page(prompt), returns)
ask
¶
ask(state: State, prompt: str, returns: Any = str) -> Any
Asks a person and returns the answer in the returns format, asking again when it does not fit.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
State
|
The State of the run that asks. |
required |
prompt
|
str
|
The question. |
required |
returns
|
Any
|
|
str
|
Raises:
| Type | Description |
|---|---|
TypeError
|
The Human implements only |
Terminal
¶
Terminal(*, output: TextIO | None = None, input: Callable[[str], str] | None = None, show_text: bool = True)
Shows progress in the terminal and asks the human in the terminal. It is the default reporter and
human of every Agent.
Output from several threads never interleaves, and while a question is being written, other output waits.
A subagent's lines are indented one level deeper per generation (below the tool line of the call that started
it) and start with its Agent's name, so its run shows up under that call. Only the structure lines of a
subagent are shown, not its streamed text (show_text is for the top-level run).
Example output::
[turn 1] thinking
Let me look at the repo layout first.
tool read_file(path="main.py")
done 1.2KB
[turn 2] thinking
context compacted: 121k → 18k tokens (cache rebuilds)
tool researcher(prompt="How does Redis evict keys?")
researcher started
[researcher turn 1] thinking
tool web_search(query="Redis eviction")
done 4.0KB
[researcher turn 2] thinking
researcher done: stopped by finish (2 turns, ~$0.01)
done 612B
[turn 3] thinking
done: stopped by waiting_for_user (3 turns, ~$0.42, cache hit 84%)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
output
|
TextIO | None
|
Where to write. |
None
|
input
|
Callable[[str], str] | None
|
The function that reads an answer. It gets the full prompt text and shows it itself.
|
None
|
show_text
|
bool
|
|
True
|
show_text
instance-attribute
¶
show_text = show_text
Whether the model's streamed text is shown. Can be changed between runs.
on_run_start
¶
on_run_start(state: State) -> None
Prints nothing for the top-level run. A subagent's run writes researcher started.
on_think_start
¶
on_think_start(state: State) -> None
Writes [turn N] thinking, with the Agent's name in a subagent's header:
[researcher turn N] thinking.
on_text
¶
on_text(state: State, chunk: str) -> None
Writes the model's text as it arrives, unless show_text is False or the run is a subagent's.
on_think_end
¶
Ends the line if the text stopped mid-line.
on_tool_start
¶
Writes tool read_file(path="main.py").
on_tool_end
¶
on_tool_end(state: State, call: ToolCall, result: ToolResultContent, outcome: ToolOutcome) -> None
Writes one line by outcome.kind: done 1.2KB (the result size, done 35.1KB (1 image) with
images), error {name}: ..., aborted {name}: ..., denied {name}: ...,
cancelled {name}: ..., or the input error itself.
on_context_change
¶
on_context_change(state: State, change: ContextChange) -> None
Writes context compacted: 121k → 18k tokens (cache rebuilds) and similar lines, under the next turn
header.
on_model_event
¶
on_model_event(state: State, event: ModelEvent) -> None
Writes model: {event.message}.
on_run_end
¶
on_run_end(state: State, error: BaseException | None) -> None
Writes one last line, such as done: stopped by waiting_for_user (5 turns, ~$0.42, cache hit 84%),
done: reached limit(50), the task may be unfinished (50 turns) or done: interrupted by user (3 turns).
A subagent's line starts with its name: researcher done: stopped by finish (2 turns).
ask
¶
ask(state: State, prompt: str, returns: Any = str) -> Any
Asks in the terminal and returns the answer in the returns format. Choices are shown after the
question (yes/no, a/b/c), and an answer that does not fit gets a hint and the question again.
Concurrent questions wait in line.
Raises:
| Type | Description |
|---|---|
EOFError
|
The input ended. |
KeyboardInterrupt
|
The person pressed Ctrl+C. |