15 min read

How We Built Charlie, Part 14: Making Agent Work Legible

How legible Task state and artifact-linked updates give people one clear next action.

Pale geometric building with three cylindrical towers, a narrow coral-lit passage, palm trees, and a blue sky

How We Built Charlie continues from Chapter 13: evidence becomes useful when a person can inspect it and see what to do next.

A manager opens a Slack thread and sees one word from an AI engineer: working.

The next useful update should give that manager one clear action: review the linked pull request, answer a blocker, approve a consequential choice, or wait because no action is needed. That requires enough structure to identify the lifecycle, show the Task tree, preserve an evidence trail, and link the current artifact.

working is only a lifecycle hint. The manager still cannot tell whether Charlie understood the request, found the right repository, delegated an investigation, changed code, reached a blocker, or is waiting on a test. If the run ends with done, the team still has to search for the pull request and reconstruct what happened. A legible record should answer five questions without exposing a private chain-of-thought transcript:

  1. Who owns the request now?
  2. What work has actually happened?
  3. What durable artifact changed?
  4. What evidence supports the current claim?
  5. What should a person do next, if anything?

Building this view changed how we thought about observability. Agent observability is often presented as a live animation of tokens, tool calls, and model thoughts. That can look busy while making the real work harder to understand. We needed something closer to an engineering work record: lifecycle at the top, causality in the middle, and artifacts at the end.

Layered view of agent legibility showing lifecycle and ownership, causal work history, and durable artifacts with a clear next action

Legibility comes from connecting coordination, execution evidence, and team-facing artifacts. None of the layers is sufficient by itself.

Start with the public lifecycle

The first requirement is modest: use lifecycle words that mean something.

Charlie’s scheduler owns the durable Task lifecycle. A Task can be waiting for work to begin, actively owned, or terminal with a recorded outcome. The executor may be making progress, waiting on a tool, recovering from an interruption, or preparing a report, but those local details do not independently redefine the Task’s durable state.

This separation prevents a common failure. If a worker process exits, a model says it has finished, or a queue delivers the same notification again, none of those events alone should convince the rest of the system that the engineering objective succeeded. The lifecycle authority records the state; execution components report into it.

For readers, the public lifecycle should stay small and stable. People need to know whether work has not started, is active, or has ended, and whether the ending was success, failure, cancellation, or another explicit stop condition. They do not need every internal phase promoted into a status badge.

SurfaceUseful statementMisleading claim
Task headerActive and owned by the root taskA model process is currently emitting output
Child rowInquiry delegated; child still activeThe parent is blocked on every child action
Terminal resultScheduler recorded a successful outcomeEvery external effect is correct and durable
Platform updatePull request opened; review requestedDone

The last distinction matters. A terminal success says that the runtime accepted the task’s result. It does not prove that a provider write persisted, that tests cover the right behavior, or that the change should merge. Lifecycle gives the work a stable frame. Evidence supplies the stronger claims.

The root task keeps accountability intact

An autonomous engineering request often stops being a single line of work. Charlie may need repository archaeology, a product requirement check, a Sentry investigation, or an independent verification pass. We model those assignments as child Tasks under one root Task.

The tree makes the final owner explicit: the root Task is accountable for the result.

Each child receives a focused objective and a scoped handoff. It can have its own lifecycle, transcript, tools, and terminal result. It does not silently become a peer competing to answer the user. The parent remains responsible for combining the findings, resolving conflicts, and returning the outcome to the original thread.

That ownership model lets an inspection surface show useful distinctions:

  • The root is implementing a fix.
  • One child is checking current repository conventions.
  • Another child is verifying the patch against the reported failure.
  • A completed child returned evidence that the parent has or has not incorporated yet.

Without lineage, all of this appears as an undifferentiated stream of model activity. With lineage, each delegated result has a requester, a scope, and a place in the final decision.

Task trees also expose an important limitation. A root Task reaching a terminal state does not magically make every descendant outcome correct, nor does a child’s success establish the root’s success. The tree records relationships and ownership. Parent synthesis remains explicit.

A transcript should reconstruct work, not perform it

We wanted the transcript page to help an engineer reconstruct a run after the fact. That goal produced a different interface than a streaming chat window.

The page leads with the selected Task and its tree. The main panel presents an ordered work record. Turn details, token use, timing, tool inputs, tool results, and delegation links are available as diagnostics, but they do not compete equally for attention.

Transcript workspace with a root task tree, ordered causal steps, turn diagnostics, and artifact details

The transcript workspace keeps ownership visible while allowing an operator to move from a summarized step into the evidence behind it.

This sounds like a presentation choice. It is also a data-model choice. The event stream contains records produced by different parts of the runtime. A model message, a tool request, a tool result, a delegated child, and a terminal report may arrive as separate rows. A useful read model has to preserve their order while assembling the relationships a person expects to see.

The transcript is exact enough for debugging and may contain prompts, source excerpts, command inputs, provider payloads, and tool output. That makes it sensitive operational data. We do not treat raw transcripts as the normal way to share progress with a team. The team-facing surfaces should carry the relevant summary and durable links; authorized inspection can go deeper when needed.

Merge tool calls with their results

One of the earliest readability problems was causal fragmentation. The event history might show a tool request in one place and its result several rows later. In between, there could be model output or another tool request. A reader had to correlate identifiers by hand.

From the operator’s perspective, a tool request and result are one work step:

request: run the targeted checkout test
result: failed in tax-flag setup
duration: 41s
next: inspect fixture ordering

The transcript read model groups the underlying records by tool-call identity and presents them together. The merged step can include the request, result, failure state, duration, and a link to any child Task created by delegation.

That merge is more than cleanup. It prevents false narratives. If an interface lists create pull request as an isolated action before the result arrives, a reader may assume the pull request exists. The causal unit can instead say that creation was requested, whether the provider returned success, and which stable artifact was recorded.

The same principle applies to recovery. Suppose a provider accepted a create request but the response was lost. The transcript should preserve the uncertainty. It should not collapse the attempted action into a green checkmark or hide the missing result. A later reconciliation step can show that Charlie looked up the pull request by branch and attached the discovered URL. The history then explains why the system did not issue another create request.

Timing and token data are diagnostics, not theater

Agent interfaces can produce an impressive amount of motion: token counters, animated traces, nested spinners, and real-time charts. Most of it does not help someone decide whether the work is healthy.

We still need timing and token information. A turn that spends an unusual amount of time waiting for a tool may reveal an infrastructure problem. A large context jump may explain latency or cost. Repeated turns with similar tool patterns may indicate that the agent is stuck. Aggregate usage across a task tree can help us understand the cost of delegation.

The data becomes useful when it is attached to a question:

MetricQuestion it helps answerCannot establish
Turn durationWhere did the run wait?Whether the conclusion was correct
Tool durationIs a dependency slow or down?Whether the tool was the right choice
Input growthDid context expand sharply?Whether every included source was relevant
Tree token totalHow much model work did delegation use?Whether delegation produced enough value
Repeated error stateIs the run cycling around one failure?The correct recovery policy by itself

This is why the transcript page puts the work record first and diagnostics behind it. We want enough information to investigate latency, looping, and cost without turning compute consumption into a proxy for progress. More tokens can mean a difficult problem, poor context selection, or wasted motion. The number alone cannot tell us which.

The platform thread is part of the work product

Most teammates will never open a runtime transcript. They will experience Charlie through the GitHub comment, Linear issue, or Slack thread where the request began.

That makes communication behavior part of system design. If Charlie completes the repository work but never returns to the originating thread, the work is operationally incomplete. If he posts updates in a new top-level channel message instead of the existing Slack thread, the result loses the conversation that explains it. If he replies done without a pull request link, the team has a status claim without an artifact.

Our communication policy favors thread preservation and artifact-backed updates. A useful completion message contains:

  • the outcome in plain language;
  • the durable artifact, such as a pull request or commit;
  • the verification performed and any important limitation;
  • one clear next action, such as review, merge, provide a missing decision, or no action required.

Before-and-after comparison showing a vague working update replaced by a thread-preserving update with lifecycle, pull request, evidence, and one review action

A good update reduces the amount of reconstruction the reader has to do. It does not reproduce the transcript.

Mid-flight updates follow the same rule. Charlie should not narrate every command. He should update the team when the state changes in a way that affects them: a durable artifact is ready, a decision needs review, a blocker requires input, or the scope has changed.

This avoids two bad extremes. Silence leaves people uncertain about whether the request was understood. Constant narration creates a noisy channel full of messages that do not change anyone’s next move. The right unit is an actionable checkpoint.

Structured effects make completion easier to inspect

Terminal prose is useful for humans, but it is hard for systems to query reliably. We therefore give compatible entry agents a structured terminal reporting contract. It can identify the trigger, the resources that became central to the work, the outcome, and completed durable effects such as a commit, pull request, issue update, or posted reply.

A simplified public example looks like this:

{
  "outcome": "succeeded",
  "effects": [
    {
      "kind": "pull_request",
      "url": "https://github.com/example/acme/pull/184",
      "summary": "Fix checkout fixture ordering"
    },
    {
      "kind": "thread_reply",
      "url": "https://example.slack.com/archives/C123/p456",
      "summary": "Returned evidence and review request"
    }
  ]
}

The contract is intentionally about durable, customer-visible changes. Starting a shell, calling a model, or running a local command is activity, not an effect. A commit is an effect because it creates a durable repository object. A Slack reply is an effect because it changes a shared collaboration surface.

Structured reporting improves attribution and makes it easier to build activity views. It also needs a caveat: the report is model-authored. Schema validation can ensure the shape is consistent, but it does not independently prove that every referenced change exists or has the expected contents. Stronger verification still comes from provider read-back, repository inspection, tests, CI, and human review where appropriate.

The system does not yet store every effect as first-class scheduler data. Making effects queryable and easier to join to task history is a useful direction, separate from the current reporting contract. The public surface should reflect what the system can substantiate today.

Active history moves; terminal history should settle

An active transcript is a changing read model. New turns arrive. Tool results complete. Child Tasks start and finish. The page may refresh its data so an operator can inspect current progress.

Once the Task is terminal, the same page has a different job. It becomes historical evidence. Re-querying and rebuilding every part of a completed view on every request adds cost and can create distracting changes in presentation. Our DevTools read model therefore treats terminal transcript pages as stable history and can cache them differently from active work.

There is a nuance here. Stable presentation is not a claim that every underlying storage record is universally immutable, nor that DevTools replaces the scheduler as lifecycle authority. It means the completed inspection view should stop behaving like a live dashboard. Operators need to return to the record later and see the same causal account of the work.

Caching also has to respect terminal detection. Caching an active task as though it were finished would hide later events. Treating all terminal pages as permanently fresh without an explicit refresh path would make debugging read-model changes harder. The policy follows the semantics of the task rather than applying one generic time-to-live to every page.

Legibility prevents concrete failures

The point of this work is not a prettier dashboard. Each part prevents a class of operational failure.

Legibility featureFailure it helps prevent
Durable lifecycleAssuming a process state or model statement is task authority
Root task treeLosing accountability across delegated investigations
Causal tool/result mergeTreating an attempted effect as a completed one
Timing and token diagnosticsMissing loops, stalls, and unexpectedly expensive context growth
Thread-preserving updatesSeparating the result from the request and its decisions
Artifact linksForcing teammates to search for the actual work product
Structured effectsMixing internal activity with durable external changes
Stable terminal historyReconstructing a different story each time completed work is inspected

Consider a routine Slack request: “The deployment job started failing after yesterday’s dependency update. Please fix it.”

A weak experience posts working, disappears into a run, and later posts fixed. A teammate then asks where the patch is, whether the failing job was reproduced, and what changed.

A legible experience attaches the request to a root Task, keeps any repository investigation under that owner, records the test failure and patch as causal steps, opens a pull request, reads back the artifact, runs the relevant checks, and returns to the same Slack thread. The final update says what caused the failure, links the pull request and revision, reports the checks that passed, names any remaining caveat, and asks for review. If a reviewer follows up, that message can rejoin the same active responsibility or create a new one when the original has ended.

The system has not made the engineering judgment infallible. It has made the work inspectable enough to review, correct, and continue.

One clear next action

The most useful test for an agent update is whether the reader knows what to do after reading it.

Sometimes the action is Review PR #184. Sometimes it is Choose between preserving the old API and accepting a breaking change. Sometimes it is No action; the requested report is attached. The wording should reflect the real state rather than ending every message with a generic offer to help.

This constraint also improves the runtime’s own reporting. To produce one clear next action, Charlie has to distinguish active work from a blocker, an artifact from a claim, and a limitation from a failure. The communication surface becomes a compact test of whether the system itself understands the state it is reporting.

Legibility is the final connection between autonomous execution and team ownership. The task tree shows responsibility. The transcript shows causality. The artifact shows what changed. The update gives the work back to the people who asked for it.

Continue the series

Previous: How We Built Charlie, Part 13: Proof, Not Vibes. Next: How We Built Charlie, Part 15: What We Learned Building Charlie. Browse the full How We Built Charlie series.