Part X: Frontiers
Chapter 35: AI and Society

Observability, Testing, and CI/CD for Agent Workflows

"You cannot improve what you cannot observe, and you cannot deploy what you cannot test."

Sage Sage, Observability First AI Agent
Big Picture

Deploying an agent to production is not the end of the engineering process; it is the beginning. Agents operate in open-ended environments where the space of possible inputs, tool interactions, and reasoning paths is effectively unbounded. Traditional testing approaches that enumerate expected inputs and outputs cannot cover this space. Instead, production agent engineering requires a layered strategy: distributed tracing to understand what the agent is doing, eval-driven testing to measure whether it is doing it well, regression suites built from production traces to prevent regressions, and deployment strategies that limit the blast radius of changes. This section provides the engineering practices needed to operate agent systems with confidence.

Prerequisites

This section extends the observability fundamentals from Chapter 30 and the evaluation frameworks from Chapter 29. It assumes familiarity with agent architectures from Chapter 22 and the reliability patterns from Section 35.5.

1. Tracing Multi-Step Agent Execution

When a traditional web service returns a wrong answer, you check the logs. When an agent returns a wrong answer, you need to reconstruct an entire chain of reasoning, tool calls, and intermediate decisions that may span dozens of steps. Observability for agents requires tracing that captures not just HTTP requests and responses, but the entire reasoning trajectory: which tools were called, in what order, with what arguments, what the model's intermediate reasoning was, and how each step contributed to the final outcome. OpenTelemetry provides the foundation, but agent tracing requires custom instrumentation on top of it.

1.1 OpenTelemetry for Agent Workflows

The standard OpenTelemetry span model maps naturally to agent execution. Each agent run is a root span. Each LLM call is a child span with attributes for the model name, token counts, and latency. Each tool call is another child span with attributes for the tool name, arguments, and result. The key extension for agents is capturing the reasoning context: the prompt sent to the model, the model's response (including any chain-of-thought reasoning), and the decision that led to the next action.


# Instrument an agent loop with OpenTelemetry: each agent run is a root span,
# each LLM call and tool call is a child span with token counts, latency,
# and reasoning context captured as structured attributes.
from opentelemetry import trace
from opentelemetry.trace import StatusCode
import json

tracer = trace.get_tracer("agent.workflow")

class TracedAgentExecutor:
 """Agent executor with OpenTelemetry tracing."""

 def __init__(self, agent, tools: dict):
 self.agent = agent
 self.tools = tools

 def run(self, task: str, max_steps: int = 20) -> str:
 with tracer.start_as_current_span(
 "agent.run",
 attributes={"agent.task": task, "agent.max_steps": max_steps},
 ) as root_span:
 messages = [{"role": "user", "content": task}]
 step = 0

 while step < max_steps:
 step += 1

 # Trace LLM call
 with tracer.start_as_current_span(
 "agent.llm_call",
 attributes={
 "agent.step": step,
 "llm.model": self.agent.model_name,
 "llm.input_tokens": 0, # filled after call
 },
 ) as llm_span:
 response = self.agent.invoke(messages)
 llm_span.set_attribute(
 "llm.input_tokens", response.usage.input_tokens
 )
 llm_span.set_attribute(
 "llm.output_tokens", response.usage.output_tokens
 )

 # Check if agent wants to use a tool
 if response.tool_calls:
 for tool_call in response.tool_calls:
 result = self._execute_tool_traced(
 tool_call, step
 )
 messages.append(
 {"role": "tool", "content": result}
 )
 else:
 # Agent produced a final answer
 root_span.set_attribute("agent.total_steps", step)
 root_span.set_attribute(
 "agent.outcome", "completed"
 )
 return response.content

 root_span.set_attribute("agent.outcome", "max_steps_reached")
 root_span.set_status(
 StatusCode.ERROR, "Agent exceeded step limit"
 )
 return "Task could not be completed within step budget."

 def _execute_tool_traced(self, tool_call, step: int) -> str:
 tool_name = tool_call.function.name
 tool_args = json.loads(tool_call.function.arguments)

 with tracer.start_as_current_span(
 f"agent.tool.{tool_name}",
 attributes={
 "agent.step": step,
 "tool.name": tool_name,
 "tool.arguments": json.dumps(tool_args),
 },
 ) as tool_span:
 try:
 result = self.tools[tool_name](**tool_args)
 tool_span.set_attribute(
 "tool.result_length", len(str(result))
 )
 return str(result)
 except Exception as e:
 tool_span.set_status(StatusCode.ERROR, str(e))
 tool_span.record_exception(e)
 return f"Error: {e}"
Code Fragment 35.6.1: An OpenTelemetry-instrumented agent loop that creates hierarchical trace spans for each reasoning step and tool call. Each span captures tool arguments, result length, and error status, providing the structured observability needed to debug multi-step agent failures in production.

1.2 Trace Analysis for Debugging

Raw traces become useful when you can query them for patterns. Common trace queries for agent debugging include: finding all runs where the agent called the same tool more than three times consecutively (indicating a potential loop); identifying runs where total token usage exceeded a threshold (indicating context bloat); and locating runs where a tool error was followed by an incorrect final answer (indicating poor error recovery).

Tools like Langfuse, Arize Phoenix, and LangSmith provide agent-specific trace visualization that displays the full reasoning tree, not just a flat list of spans. These visualizations make it possible to see where an agent's reasoning diverged from the expected path, which is essential for debugging semantic failures that do not produce error codes.

2. Testing Strategies for Agent Systems

Agent testing requires a layered approach because no single testing strategy can cover the full range of agent behaviors. The testing pyramid for agents has different layers than the traditional software testing pyramid.

2.1 Unit Tests for Individual Tools

Each tool function should have standard unit tests that verify correct behavior for valid inputs, appropriate error handling for invalid inputs, and proper formatting of return values. Tool unit tests are deterministic and fast. They form the foundation of agent testing.


# Unit tests for agent tools: verify correct behavior for valid inputs,
# proper error handling for invalid inputs, and consistent return structure.
# These deterministic tests form the foundation of agent test suites.
import pytest

def test_lookup_order_valid():
 """Tool returns correct structure for a valid order ID."""
 result = lookup_order("ORD-12345")
 assert "order_id" in result
 assert "status" in result
 assert result["status"] in ("pending", "shipped", "delivered", "cancelled")

def test_lookup_order_invalid():
 """Tool raises ValueError for malformed order IDs."""
 with pytest.raises(ValueError, match="Invalid order ID format"):
 lookup_order("not-an-order")

def test_lookup_order_not_found():
 """Tool returns None for an order that does not exist."""
 result = lookup_order("ORD-99999")
 assert result is None
Code Fragment 35.6.2: Unit tests for an individual tool function, covering valid input, invalid input, and not-found cases. These deterministic tests form the foundation of the agent testing pyramid and verify that each tool produces correctly structured outputs before integration testing begins.

2.2 Integration Tests for Workflows

Integration tests verify that the agent can complete end-to-end workflows using real (or realistic mock) tools. These tests are more expensive than unit tests because they involve actual LLM calls. To manage cost, use a smaller model for integration tests and reserve the production model for evaluation runs. Integration tests should cover the happy path (agent completes the task correctly), the error path (a tool fails and the agent recovers), and the boundary path (the task is ambiguous and the agent asks for clarification or escalates).

2.3 Eval-Driven CI

The most important innovation in agent testing is eval-driven continuous integration. Rather than asserting specific outputs, eval-driven CI runs the agent on a curated set of tasks and scores the results using an evaluation function. The CI pipeline passes if the average score exceeds a threshold and fails if it drops below.


# Eval-driven CI for agents: run a curated task suite, score outputs with
# an evaluation function, and pass/fail the build based on score thresholds.
# Replaces brittle assertion-based tests with statistical quality gates.
from dataclasses import dataclass

@dataclass
class EvalCase:
 """A single evaluation case for an agent."""
 task: str
 expected_outcome: str
 scoring_rubric: list[str]
 max_steps: int = 15
 timeout_seconds: float = 120.0

def run_eval_suite(
 agent_executor,
 eval_cases: list[EvalCase],
 judge_model: str = "gpt-4o",
 pass_threshold: float = 0.85,
) -> dict:
 """Run an evaluation suite and return aggregate results.

 Each case is scored by a judge model against
 the rubric. The suite passes if the mean score
 exceeds the threshold.
 """
 scores = []
 failures = []

 for case in eval_cases:
 result = agent_executor.run(
 case.task, max_steps=case.max_steps
 )
 score = judge_with_rubric(
 task=case.task,
 expected=case.expected_outcome,
 actual=result,
 rubric=case.scoring_rubric,
 model=judge_model,
 )
 scores.append(score)
 if score < pass_threshold:
 failures.append({
 "task": case.task,
 "score": score,
 "result": result[:200],
 })

 mean_score = sum(scores) / len(scores) if scores else 0.0
 return {
 "mean_score": mean_score,
 "passed": mean_score >= pass_threshold,
 "total_cases": len(eval_cases),
 "failures": failures,
 }
Code Fragment 35.6.3: An eval-driven CI framework that runs an agent against a curated set of test cases and scores results using a judge model with rubrics. The suite passes or fails based on mean score against a threshold, replacing brittle assertion-based tests with flexible quality evaluation.
Key Insight

Eval-driven CI inverts the traditional testing relationship. In conventional software, tests define correctness and code is written to pass them. In agent systems, the evaluation function defines correctness and the agent's behavior emerges from the model, the prompt, and the tools. When an eval-driven CI run fails, the fix might be a prompt change, a tool modification, a model swap, or a guardrail addition. The eval suite is the source of truth; everything else is implementation detail.

3. Regression Testing with Golden Traces

A golden trace is a recorded execution of the agent on a specific task that has been verified as correct by a human reviewer. Golden traces serve as regression tests: after any change to the agent (prompt update, model change, tool modification), re-run the golden tasks and compare the new execution against the verified trace.

3.1 Building a Golden Trace Library

Start by recording traces from production (with user consent and privacy safeguards). Select traces that represent the most common and most critical task types. Have a human reviewer verify each trace: confirm that the agent's reasoning was sound, the tool calls were appropriate, and the final answer was correct. Store these verified traces alongside metadata including the model version, prompt version, and tool versions that produced them.

3.2 Drift Detection

When you re-run golden traces after a change, exact output matching is too brittle because LLM outputs are non-deterministic. Instead, compare at the semantic level. Check whether the agent called the same tools in a similar order, whether the final answer conveys the same information, and whether the total step count and token usage are within acceptable bounds of the golden trace. Use an LLM judge to compare the new output against the golden output and flag meaningful differences.

Drift detection should also run continuously on production traffic, not just during CI. Compare a sample of today's agent executions against the distribution of executions from the previous week. Statistical shifts in step count distributions, tool usage frequencies, or outcome categories signal that something has changed, even if no explicit deployment occurred (because model providers sometimes update hosted models without notice).

4. Deployment Strategies for Agent Systems

Deploying agent changes is higher risk than deploying traditional software changes because the impact of a bad prompt or model change is unpredictable. A one-word change in a system prompt can dramatically alter agent behavior across all task types. Deployment strategies must therefore limit blast radius and enable rapid rollback.

4.1 Canary Releases

Route a small percentage of traffic (1% to 5%) to the new agent version while the majority continues using the current version. Monitor the canary's SLIs (task completion rate, correctness score, step count, token cost) and compare them against the baseline. Promote the canary to full traffic only if SLIs remain within acceptable bounds for a defined soak period (typically 24 to 48 hours for agent systems, because some failure modes take time to manifest).

4.2 A/B Testing for Agent Behaviors

When the change is a prompt revision or a new tool, A/B testing provides statistical rigor. Randomly assign users to the control group (current agent) or treatment group (modified agent) and measure outcome differences. The key challenge for agent A/B tests is choosing the right metric. Task completion rate is often too coarse. More informative metrics include user satisfaction (collected via feedback buttons), escalation rate, and downstream business outcomes (e.g., did the customer actually resolve their issue?).

4.3 Shadow Mode Deployment

Shadow mode runs the new agent version in parallel with the production version, but only the production version's output is served to users. The shadow version's output is logged for comparison. This approach is especially valuable for high-stakes agent changes because it provides evaluation data without any user-facing risk. The cost is running two agent instances per request, which doubles LLM API costs during the shadow period.

Tip

Version everything. Agent behavior is determined by the combination of model, prompt, tools, and guardrails. A change to any one of these can alter behavior. Maintain a version manifest that records the exact version of each component for every deployment. When a regression is detected, the manifest tells you exactly what changed and enables precise rollback. Store this manifest alongside your traces so that every execution can be reproduced with the same configuration.

5. Putting It Together: A CI/CD Pipeline for Agents

A complete CI/CD pipeline for agent systems integrates all the practices described above into a coherent workflow. The pipeline stages are as follows.

Stage 1: Pre-commit. Run tool unit tests and prompt linting (check for common anti-patterns like missing system instructions or ambiguous tool descriptions). These are fast and deterministic.

Stage 2: Integration tests. Run the agent on a small set of representative tasks using a cost-effective model. Verify that the agent can complete basic workflows without errors.

Stage 3: Eval suite. Run the full evaluation suite using the production model. Compare scores against the baseline. Fail the pipeline if the mean score drops below the threshold or if any individual task's score drops by more than a configurable delta.

Stage 4: Golden trace regression. Re-run golden traces and check for drift. Flag any trace where the agent's behavior diverges significantly from the verified golden path.

Stage 5: Canary deployment. Deploy to the canary tier and monitor SLIs for the soak period. Auto-promote if SLIs hold; auto-rollback if they degrade.

Exercises

Exercise 35.6.1: Instrumenting an Agent with OpenTelemetry (Coding) Coding

Using the TracedAgentExecutor class from this section as a starting point, add custom span attributes that capture: (a) the full prompt sent to the model (truncated to 1000 characters), (b) the model's chain-of-thought reasoning (if present), and (c) a boolean attribute indicating whether the step required a tool call or produced a direct answer. Export traces to the console using the ConsoleSpanExporter and verify that your attributes appear correctly.

Exercise 35.6.2: Building a Golden Trace Regression Suite (Analysis) Analysis

Design a golden trace regression system for a customer support agent that handles order status inquiries, return requests, and billing questions. Define: (a) what information a golden trace record should contain, (b) the criteria for selecting production traces to include in the golden set, (c) the comparison logic for detecting meaningful drift, and (d) the alerting thresholds that should trigger a rollback versus a human review.

Key Takeaways

What Comes Next

In the next section, Section 35.7: Memory Architectures That Improve Execution, we examine how persistent memory systems enable agents to learn from experience and improve over time.

References & Further Reading
Observability Tools

OpenTelemetry Authors. (2024). "OpenTelemetry Specification." opentelemetry.io.

The vendor-neutral open standard for distributed tracing, metrics, and logging that this section recommends as the foundation for agent observability. Enables consistent instrumentation across heterogeneous agent components.

🛠 Tool

Langfuse Team. (2024). "Open Source LLM Engineering Platform." langfuse.com.

An open-source platform purpose-built for tracing and evaluating LLM applications, with support for prompt versioning and cost tracking. The most mature open-source LLM observability tool at the time of writing.

🛠 Tool
Testing & Evaluation

Breck, E. et al. (2017). "The ML Test Score: A Rubric for ML Production Readiness and Technical Debt Reduction." IEEE International Conference on Big Data.

Proposes a practical rubric for assessing ML system readiness across data, model, infrastructure, and monitoring dimensions. Provides the quality framework adapted for agent systems in this section.

📄 Paper

Shankar, S. et al. (2024). "Who Validates the Validators? Aligning LLM-Assisted Evaluation of LLM Outputs with Human Preferences." arXiv:2404.12272.

Examines the reliability of using LLMs to evaluate other LLMs, finding significant alignment gaps with human judgment. Directly relevant to this section's discussion of automated testing for agent outputs.

📄 Paper
Continuous Delivery

Humble, J. and Farley, D. (2010). Continuous Delivery. Addison-Wesley.

The foundational text on continuous delivery practices including deployment pipelines, automated testing, and release strategies. Provides the CI/CD principles that this section adapts for agent deployment workflows.

📖 Book