The tree view
The shape of a run — every call named with its status, decisions marked where they happened, redrawn in place as it grows.
The transcript reads like a colleague talking: prose is the content, work is a quiet receipt underneath. That is right when you are reading the answer.
Sometimes you want the opposite — the shape of the run:
Agent started
│
├─ Understanding request
│ Read the inbox, check the guest list, then save.
│
├─ Tool group: Read 2 files
│ ├─ read_file completed 4ms
│ └─ read_file completed 6ms
│
├─ Decision
│ Guest was not found. Create a new RSVP record.
│
├─ Approval required
│ Used Slack #events comms.send
│
└─ Final answer
RSVP successfully recorded.
15,470 tokens · claude-opus-5Same rows, same grouping, same verbs as the transcript — only the shape changes.
What it looks like
Agent started │
├─ Understanding request │ Read the inbox, check the guest list, then save. │
├─ Tool group: Read 2 files │ ├─ read_file completed 4ms │ └─ read_file completed 6ms │
├─ Decision │ Guest was not found. Create a new RSVP record. │
├─ Approval required │ Used Slack #events comms.send │
└─ Final answer RSVP successfully recorded.
On a terminal this redraws in place: the trunk stays open — ├─ working… —
until the run ends, and the finished tree replaces the draft exactly once.
Two ways to get one
# Live, in a terminal — redraws in place as the run proceeds
agent.run_live("Process the latest RSVP", style="tree")
# A finished run, as text
from shipit_agent.narrate import render_tree
print(render_tree(result.events, model="claude-opus-5"))In a notebook, watch(agent, prompt, shape="tree") draws it in the live
panel.
Live behaviour
On a terminal the tree redraws in place: the shape grows downward,
statuses flip from running to completed where they stand, and the trunk
stays open until the run ends:
├─ Tool group: Reading guests.csv
│ └─ read_file running
│
├─ working…Drawing the final corner early would claim the agent had finished. When the run closes, the draft is erased and the finished tree is written once, so your scrollback holds one clean copy rather than every intermediate frame.
Piped output never animates — it gets a single buffered write with no escape
codes, so > run.txt is clean.
TreeRenderer(live=True) # force in-place updates
TreeRenderer(live=False) # force one buffered writeThe row labels
| Label | What it is |
|---|---|
Understanding request | The opening prose, before any tool ran — what the agent intends |
Decision | Prose between tool groups, or an agent_decision event |
Observed | An agent_observation event — what the tools returned |
Tool group | A run of calls, each named with its status and duration |
Delegated | A sub-agent's work, attributed to it |
Approval required | A queued side-effecting call |
Connection needed | The agent asking you to connect something |
Artifact | A file the run produced |
Final answer | The last prose in the run |
The deep tree
detail=True opens every call — what it was called with, and the first lines
of what came back:
print(render_tree(result.events, detail=True, output_lines=6))├─ Tool group: Searched for def summarize
│ └─ grep_files completed 204ms
│ ↳ pattern='def summarize', path='shipit_agent'
│ shipit_agent/narrate/verbs.py:589:def summarize(name: str, …output_lines=None shows every line, at the cost of the tree's compactness.
Alignment
Statuses sit at a fixed column, and nesting is accounted for — a row two levels deep starts further right, so without that the column would drift and the fixed column would be pointless. Colour never shifts it either: padding is computed on visible width, ignoring ANSI escapes.
On a terminal that cannot encode │├└─, the tree falls back to |+- rather
than raising.
When to reach for it
- Debugging — a tool that fired twice is obvious in a tree and invisible in prose.
- Explaining — "here is what the agent did" pastes cleanly into a PR or a ticket.
- Long autonomous runs — the live tree is a progress display that does not scroll away.
See also
- The live UI panel — the same run as a chat card
- The UI timeline — the same run as JSON for a frontend
- Progress narration — where
DecisionandObservedrows come from