Temporal provides more than fault tolerance during outages. It also gives you a durable trace of every agent call or tool use, making it easier to iterate quickly and debug production sessions.
When building agents on Temporal, a single reusable Activity for LLM and tool calls keeps the codebase clean, maintainable, and allows for more easily rate limiting a given API endpoint. When Workflow history grows, each step becomes indistinguishable from a glance without digging into the payloads.
This blog post outlines how to use an existing Temporal SDK feature to enhance observability and still leverage this reusable pattern.
Setting an Activity summary#
We’ll use the agentic example of coding agent. This follows a plan/edit/test/eval loop:
- Call an LLM API to plan the code change
- Edit the file
- Run tests
- Call an LLM API to judge if the tests pass
- Timer: Wait for CI to run
If the CI fails, it repeats this loop. Below is what this would look like in the Temporal UI if this agent completed this loop 3x. As the logic for a Workflow gets more complex, it very quickly gets more difficult for human operators to quickly scan for context.
Setting the Summary field on the Activity options improves this view significantly.
await workflow.execute_activity(
call_llm,
planner_prompt,
summary="Plan changes",
start_to_close_timeout=timedelta(seconds=60),
retry_policy=retry,
)
The timeline and compact history view now has human and model-readable labels directly on the scheduled events without having to create custom Activity names for each agent type.
On an Activity failure, it’s now much more simple to see what step the Workflow was on before digging into payloads.
These labels also show under the Compact History View
Applying Labels to Adaptive Workflows#
Adaptive Workflows choose their execution path at runtime. For agentic systems, this is the typical case where nondeterministic decisions can change from one run to the next.
Recalling the coding agent example from above, it shows a happy path where every step eventually completes. If the Run tests ExecuteTool Activity is never going to pass, after this Activity fails, we may want to start a Recovery loop where an agent follows a diagnose/act/recheck loop in an effort to keep the agent from spending tokens on untrue assumptions.
During this loop, adding details about the debugging to the loop such as “restart test runner” to an ExecuteTool call shows what an agent was acting on without stepping through the entire debug session.
For more on setting the summary field, see your language’s Enriching UI docs:
Labelling Workflows#
A Workflow can also display a summary, along with two other useful fields for agent builders.
-
Workflow Summary. The static_summary field is a general label for the Workflow. Consider using this as explanation for a generic Workflow, including child Workflows spawned as subagents to do work in parallel.
This field is immutable after being set. This is for smaller bits of text up to 400 bytes.
handle = await client.start_workflow(
CodingAgentWithLabels.run,
task,
id="coding-agent",
task_queue="coding-agent-demo",
static_summary="Coding agent: " + task,
)
-
Workflow Details. The static_details field is a larger space for more general usage like model or configuration that is known at Workflow start.
This field is immutable after being set. This is for medium length text up to 20KB.
handle = await client.start_workflow(
CodingAgentWithLabels.run,
task,
id="coding-agent-with-labels",
task_queue="coding-agent-demo",
static_details="Task: " + task + "\nModel: Claude Sonnet\nMax iterations: 5",
)
-
Current Details. This is the only mutable field after the Workflow is scheduled. It can be set within the Workflow code and each call replaces the previous value. It can be used to transfer information to a high level view.
This value is only shown while the Workflow is in running state, and isn’t shown on Workflow completed, failed, or other terminal states. It’s stored as mutable Workflow state on the server, but not persisted to event history.
It can be called as many times as is necessary in the Workflow, and it has a limit of 20 KB.
workflow.set_current_details(f"Step {i+1}/{max_iterations}: executing tools")
# ... run tools ...
workflow.set_current_details(f"Step {i+1}/{max_iterations}: judging completeness")
# ... run judge ...
workflow.set_current_details("Completed loop")
Workflow Summary and Details are used to label what the Workflow is, and Current Details is a way to track state.
Below is how the Workflow Summary, Details, and Current Details look in a Workflow under the User Metadata tab. Note that the Current Details are not visible because the Workflow is not in a Running state.
For more on setting Workflow User Metadata fields, refer to the SDK docs in your chosen language:
| SDK | Documentation |
|---|---|
| Go | Workflow Information with Go |
| Python | Workflow Information with Python |
| TypeScript | Workflow Information with TypeScript |
| Java | Workflow Information with Java |
| .NET | Workflow Information with .NET |
| PHP | Workflow Information with PHP |
| Ruby | Workflow Information with Ruby |
Labelling Timers#
Labels on Timers can also be useful to indicate to the operator why the Workflow is waiting. As a last example, this is how to add the Summary field to a Timer and indicate that we’re waiting for a CI to run before rechecking:
await workflow.sleep(timedelta(seconds=5), summary="Wait for CI")
This is visible both in the Timeline and the Compact History view
SDK Availability#
UserMetadata is available across all 7 SDKs. Refer to each SDK’s documentation for further information:
| SDK | Documentation |
|---|---|
| Go | Enriching UI with Go |
| Python | Enriching UI with Python |
| TypeScript | Enriching UI with TypeScript |
| Java | Enriching UI with Java |
| .NET | Enriching UI with .NET |
| PHP | Enriching UI with PHP |
| Ruby | Enriching UI with Ruby |