Reporter, Human, Terminal¶
Guide: Progress and questions.
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.
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: str, outcome: ToolOutcome) -> None
Right after a tool call ends. A call closed by state.deny gets this without on_tool_start.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
State
|
The State. |
required |
call
|
ToolCall
|
The tool call. |
required |
result
|
str
|
The string sent to the model. For a denied call, the denial reason. |
required |
outcome
|
ToolOutcome
|
How the call ended. Branch on |
required |
on_context_change
¶
on_context_change(state: State, change: ContextChange) -> None
When the context changes: compaction, start_from, clear_tool_results, or a rollback after
think fails.
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.
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)
done: is_answered (5 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_text
¶
on_text(state: State, chunk: str) -> None
Writes the model's text as it arrives, unless show_text is False.
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: str, outcome: ToolOutcome) -> None
Writes one line by outcome.kind: done 1.2KB (the result size), error {name}: ...,
aborted {name}: ..., denied {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: is_answered (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).
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. |