Metadata-Version: 2.4
Name: synth-agent-sdk
Version: 2.2.2
Summary: Autonomous agents, engineered. A Python SDK for building production-grade AI agents and multi-agent systems.
License-Expression: MIT
Requires-Python: >=3.10
Requires-Dist: click>=8.0
Requires-Dist: httpx>=0.27
Requires-Dist: prompt-toolkit>=3.0
Requires-Dist: pydantic>=2.0
Requires-Dist: rich>=13.0
Requires-Dist: typing-extensions>=4.0
Provides-Extra: agentcore
Requires-Dist: bedrock-agentcore-starter-toolkit>=0.1.0; extra == 'agentcore'
Requires-Dist: bedrock-agentcore>=0.1.0; extra == 'agentcore'
Requires-Dist: boto3>=1.42.87; extra == 'agentcore'
Requires-Dist: playwright>=1.40; extra == 'agentcore'
Requires-Dist: pyjwt>=2.8; extra == 'agentcore'
Requires-Dist: requests>=2.31; extra == 'agentcore'
Provides-Extra: agentcore-runtime
Requires-Dist: bedrock-agentcore>=0.1.0; extra == 'agentcore-runtime'
Requires-Dist: boto3>=1.42.87; extra == 'agentcore-runtime'
Requires-Dist: pyjwt>=2.8; extra == 'agentcore-runtime'
Requires-Dist: requests>=2.31; extra == 'agentcore-runtime'
Provides-Extra: all
Requires-Dist: anthropic>=0.39; extra == 'all'
Requires-Dist: boto3>=1.42.87; extra == 'all'
Requires-Dist: cryptography>=42; extra == 'all'
Requires-Dist: ddtrace>=2.0; extra == 'all'
Requires-Dist: gitpython>=3.1; extra == 'all'
Requires-Dist: google-genai>=1.0; extra == 'all'
Requires-Dist: langfuse>=2.0; extra == 'all'
Requires-Dist: libhoney>=2.0; extra == 'all'
Requires-Dist: mcp>=1.0; extra == 'all'
Requires-Dist: ollama>=0.4; extra == 'all'
Requires-Dist: openai>=1.0; extra == 'all'
Provides-Extra: anthropic
Requires-Dist: anthropic>=0.39; extra == 'anthropic'
Provides-Extra: aws
Requires-Dist: bedrock-agentcore-starter-toolkit>=0.1.0; extra == 'aws'
Requires-Dist: bedrock-agentcore>=0.1.0; extra == 'aws'
Requires-Dist: boto3>=1.42.87; extra == 'aws'
Requires-Dist: playwright>=1.40; extra == 'aws'
Requires-Dist: pyjwt>=2.8; extra == 'aws'
Requires-Dist: requests>=2.31; extra == 'aws'
Provides-Extra: aws-runtime
Requires-Dist: bedrock-agentcore>=0.1.0; extra == 'aws-runtime'
Requires-Dist: boto3>=1.42.87; extra == 'aws-runtime'
Requires-Dist: pyjwt>=2.8; extra == 'aws-runtime'
Requires-Dist: requests>=2.31; extra == 'aws-runtime'
Provides-Extra: bedrock
Requires-Dist: boto3>=1.42.87; extra == 'bedrock'
Provides-Extra: cdk
Requires-Dist: aws-cdk-lib>=2.100.0; extra == 'cdk'
Requires-Dist: constructs>=10.0.0; extra == 'cdk'
Provides-Extra: cloud
Requires-Dist: httpx>=0.27; extra == 'cloud'
Requires-Dist: keyring>=25.0; (sys_platform != 'linux') and extra == 'cloud'
Provides-Extra: cloud-test
Requires-Dist: hypothesis>=6.0; extra == 'cloud-test'
Requires-Dist: pytest-asyncio>=0.23; extra == 'cloud-test'
Requires-Dist: respx>=0.21; extra == 'cloud-test'
Provides-Extra: datadog
Requires-Dist: ddtrace>=2.0; extra == 'datadog'
Provides-Extra: dev
Requires-Dist: boto3>=1.42.87; extra == 'dev'
Requires-Dist: hypothesis>=6.0; extra == 'dev'
Requires-Dist: jsonschema>=4; extra == 'dev'
Requires-Dist: moto>=5.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: pyyaml>=6.0; extra == 'dev'
Requires-Dist: respx>=0.21; extra == 'dev'
Requires-Dist: tomli>=2.0; (python_version < '3.11') and extra == 'dev'
Provides-Extra: enterprise
Requires-Dist: boto3>=1.42.87; extra == 'enterprise'
Requires-Dist: opentelemetry-api>=1.20; extra == 'enterprise'
Requires-Dist: opentelemetry-exporter-otlp>=1.20; extra == 'enterprise'
Requires-Dist: opentelemetry-sdk>=1.20; extra == 'enterprise'
Provides-Extra: google
Requires-Dist: google-genai>=1.0; extra == 'google'
Provides-Extra: honeycomb
Requires-Dist: libhoney>=2.0; extra == 'honeycomb'
Provides-Extra: knowledge
Requires-Dist: boto3>=1.42.87; extra == 'knowledge'
Provides-Extra: langfuse
Requires-Dist: langfuse>=2.0; extra == 'langfuse'
Provides-Extra: mcp
Requires-Dist: mcp>=1.0; extra == 'mcp'
Provides-Extra: ollama
Requires-Dist: ollama>=0.4; extra == 'ollama'
Provides-Extra: openai
Requires-Dist: openai>=1.0; extra == 'openai'
Provides-Extra: otel
Requires-Dist: opentelemetry-api>=1.20; extra == 'otel'
Requires-Dist: opentelemetry-exporter-otlp>=1.20; extra == 'otel'
Requires-Dist: opentelemetry-sdk>=1.20; extra == 'otel'
Provides-Extra: platform
Requires-Dist: boto3>=1.42.87; extra == 'platform'
Requires-Dist: pyyaml>=6.0; extra == 'platform'
Provides-Extra: pricing-refresh
Requires-Dist: gitpython>=3.1; extra == 'pricing-refresh'
Requires-Dist: httpx>=0.27; extra == 'pricing-refresh'
Provides-Extra: quickstart
Requires-Dist: anthropic>=0.39; extra == 'quickstart'
Requires-Dist: openai>=1.0; extra == 'quickstart'
Provides-Extra: schema
Requires-Dist: jsonschema>=4; extra == 'schema'
Provides-Extra: signing
Requires-Dist: cryptography>=42; extra == 'signing'
Provides-Extra: testing
Provides-Extra: toolkit
Requires-Dist: synth-tools; extra == 'toolkit'
Provides-Extra: ui
Requires-Dist: fastapi>=0.115; extra == 'ui'
Requires-Dist: uvicorn>=0.30; extra == 'ui'
Description-Content-Type: text/markdown

# Synth

> Autonomous agents, engineered.

**Version:** 2.2.1 | [PyPI](https://pypi.org/project/synth-agent-sdk/) | [Changelog](CHANGELOG.md)

Synth is a Python SDK and platform for building, deploying, and governing production-grade AI agents. It covers the full agent lifecycle — from a 3-line prototype to multi-agent systems running across AWS accounts with policy-as-code guardrails, drift detection, and centralized observability.

```python
from synth import Agent, tool

@tool
def get_weather(city: str) -> str:
    """Get the current weather for a city."""
    return f"Sunny, 72°F in {city}"

agent = Agent(model="claude-sonnet-4-6", instructions="You are helpful.", tools=[get_weather])
result = agent.run("What's the weather in Tokyo?")
print(result.text)
```

Synth serves two audiences:

**For agent developers** — a composable API with tools, memory, guards, structured output, streaming, multi-agent orchestration (pipelines, graphs, teams), 50+ pre-built tool integrations, and model-agnostic provider support. Switch between Claude, GPT, Gemini, Ollama, and AWS Bedrock by changing a single string.

**For platform and operations teams** — deploy agents to AWS AgentCore with versioned rollbacks and CDK infrastructure constructs. Manage gateway resources, runtimes, and memory across AWS accounts. Govern agent fleets with Cedar authorization policies, RBAC, drift detection, and deployment planning. Monitor with integrated observability through AgentCore, Langfuse, Datadog, or Honeycomb.

---

## What's New in v2.2.0

### Multi-Agent Orchestration Enhancements

- **Improved hybrid router** — the `router="hybrid"` strategy now makes faster routing decisions with reduced latency on direct-reply paths
- **Team topology selection at init** — `synth init` now presents a topology picker (pipeline, graph, team, debate, critic-review, consensus) with live previews before scaffolding
- **Multi-agent init flow refactor** — cleaner project scaffolding for multi-agent teams with per-agent Dockerfiles and a unified `agentcore.yaml`
- **A2A external interoperability** — `A2AClientTool` and `A2AServerAdapter` now support external A2A endpoints with SSRF protection and schema validation

### Platform & Cloud

- **Synth Dev Space — cloud deployment** — the browser-based testing dashboard can now be deployed to AWS (CloudFront + Fargate + Cognito) with a single `synth deploy --target cloud` command. Features include:
  - Custom branded login page with Synth design language
  - Per-user conversation history persisted to DynamoDB (survives container restarts)
  - Team management panel for admins (invite, promote, reset passwords)
  - Agent registry with team card grouping from `agentcore.yaml`
  - VPC endpoints for AgentCore/Bedrock (eliminates NAT Gateway latency — warm-container response time drops from 15-30s to 1-3s)
  - Pre-warm on connect — containers warm before the user types their first message
- **AgentCore observability native** — X-Ray traces, CloudWatch metrics, and AgentCore runtime telemetry now flow through the Dev Space UI without additional configuration
- **AgentCore memory and evaluations** — conversation history loads from AgentCore Memory; evaluation results surface in the Evaluations tab

### Cost & Pricing Accuracy

- **Unified pricing feed** — a signed, versioned pricing feed (`synth/pricing/data/`) covers all supported models with per-token costs, context windows, and capability flags
- **Real-time cost tracking** — `RunResult.cost` now uses the live pricing feed rather than hardcoded estimates; accuracy within 1% of provider invoices
- **Cost alerts** — `Guard.max_cost(dollars=N)` now uses the same feed for consistent enforcement

### Agent Registry

- **AgentCore registry publishing** — `synth agents publish` registers deployed agents in the AgentCore Agent Registry with capability declarations
- **Registry stack clarification** — `synth agents list` now clearly distinguishes local registry records, live AgentCore runtimes, and registry-published records with source chips in the UI

### Developer Experience

- **`synth init` multi-topology selection** — interactive topology picker with animated previews; supports pipeline, graph, team, debate, critic-review, and consensus patterns
- **Improved error messages** — `SynthConfigError` now includes direct links to relevant documentation and `synth doctor` checks
- **`synth doctor` enhancements** — checks for AgentCore runtime availability, VPC endpoint configuration, and pricing feed freshness

### Breaking Changes

None. v2.2.0 is fully backwards-compatible with v2.1.x.

---

## Table of Contents

### Part 1 — Agent Developer Tools

1. [Installation](#installation)
2. [Quick Start](#quick-start)
3. [Core Concepts](#core-concepts)
4. [Creating an Agent](#creating-an-agent)
5. [Tools](#tools)
6. [Built-in Tools](#built-in-tools)
7. [Agent-as-Tool Composition](#agent-as-tool-composition)
8. [MCP Integration](#mcp-integration)
9. [Tool Middleware](#tool-middleware)
10. [Dependency Injection (RunContext)](#dependency-injection-runcontext)
11. [Running Your Agent](#running-your-agent)
12. [Streaming](#streaming)
13. [Streaming Structured Output](#streaming-structured-output)
14. [Model Providers](#model-providers)
15. [Provider Fallback Chains](#provider-fallback-chains)
16. [Memory](#memory)
17. [Conversation Management](#conversation-management)
18. [Guards](#guards)
19. [Guard Composition](#guard-composition)
20. [Structured Output](#structured-output)
21. [Pipelines](#pipelines)
22. [Graphs](#graphs)
23. [Graph Debugging](#graph-debugging)
24. [Graph Parallel Execution](#graph-parallel-execution)
25. [Human-in-the-Loop](#human-in-the-loop)
26. [Agent Teams](#agent-teams)
27. [Agent-to-Agent (A2A) Protocol](#agent-to-agent-a2a-protocol)
28. [synth-tools — Pre-Built Tool Integrations](#synth-tools--pre-built-tool-integrations)
29. [Tracing and Observability](#tracing-and-observability)
30. [Trace Exporters](#trace-exporters)
31. [Trace-to-Eval Pipeline](#trace-to-eval-pipeline)
32. [Evaluation](#evaluation)
33. [Eval Suites](#eval-suites)
34. [Benchmarking](#benchmarking)
35. [Testing Infrastructure](#testing-infrastructure)
36. [Testing Dashboard](#testing-dashboard)
37. [Checkpointing](#checkpointing)
38. [Enterprise Capabilities](#enterprise-capabilities)
39. [CLI Commands](#cli-commands)

### Part 2 — Platform Management: Agents at Scale in the Enterprise

40. [Deploying to AWS AgentCore](#deploying-to-aws-agentcore)
41. [AgentCore Observability](#agentcore-observability)
42. [AgentCore Evaluations](#agentcore-evaluations)
43. [Agent Registry and Lifecycle](#agent-registry-and-lifecycle)
44. [Platform Governance](#platform-governance)
45. [CDK Infrastructure Constructs](#cdk-infrastructure-constructs)

### Reference

46. [Error Handling](#error-handling)
47. [Environment Variables](#environment-variables)
48. [FAQ](#faq)

---

# Part 1 — Agent Developer Tools

---

## Installation

Requires Python 3.10+.

```bash
pip install synth-agent-sdk[anthropic]     # Recommended starting point
```

### Provider Extras

```bash
pip install synth-agent-sdk[anthropic]     # Anthropic Claude
pip install synth-agent-sdk[openai]        # OpenAI GPT
pip install synth-agent-sdk[google]        # Google Gemini
pip install synth-agent-sdk[ollama]        # Local Ollama models
pip install synth-agent-sdk[bedrock]       # AWS Bedrock
pip install synth-agent-sdk[quickstart]    # Claude + GPT (tutorials/demos)
```

### AWS and Deployment Extras

```bash
pip install synth-agent-sdk[aws]           # Full AWS support (Bedrock + AgentCore + browser + auth)
pip install synth-agent-sdk[agentcore]     # AWS AgentCore deployment
pip install synth-agent-sdk[cdk]           # CDK infrastructure constructs
pip install synth-agent-sdk[platform]      # Platform governance (multi-account, Cedar, drift detection)
```

### Observability Extras

```bash
pip install synth-agent-sdk[langfuse]      # Langfuse trace exporter
pip install synth-agent-sdk[datadog]       # Datadog APM trace exporter
pip install synth-agent-sdk[honeycomb]     # Honeycomb trace exporter
pip install synth-agent-sdk[otel]          # OpenTelemetry native exporter
```

### Enterprise Extras

```bash
pip install synth-agent-sdk[knowledge]     # Knowledge base / RAG (boto3, vector retrieval)
pip install synth-agent-sdk[enterprise]    # All enterprise capabilities
```

### Integration Extras

```bash
pip install synth-agent-sdk[mcp]           # Model Context Protocol (external tool discovery)
pip install synth-agent-sdk[ui]            # Browser-based testing dashboard
pip install synth-agent-sdk[toolkit]       # synth-tools companion package (50+ pre-built tools)
```

### Install Everything

```bash
pip install synth-agent-sdk[all]           # All providers + observability extras
```

> **Important:** The package name is `synth-agent-sdk`, not `synth`. Running `pip install synth` installs an unrelated C++ template engine. Always use `synth-agent-sdk`.

### Recommended: Install in a Virtual Environment

```bash
# macOS / Linux
python3 -m venv .venv
source .venv/bin/activate

# Windows
python -m venv .venv
.venv\Scripts\activate
```

Then install:

```bash
pip install synth-agent-sdk[anthropic]
```

### macOS Notes

**Apple Silicon (M1/M2/M3/M4):** If you install the `bedrock` or `agentcore` extras, the `botocore[crt]` dependency pulls in `awscrt`, a compiled C extension. If the build fails:

1. Make sure Xcode Command Line Tools are installed:
   ```bash
   xcode-select --install
   ```
2. If using pyenv, ensure your Python was built with the correct architecture:
   ```bash
   python3 -c "import platform; print(platform.machine())"
   # Should print "arm64" on Apple Silicon
   ```
3. If the `awscrt` wheel still fails, install without CRT (slightly slower S3 transfers but fully functional):
   ```bash
   pip install botocore boto3
   pip install synth-agent-sdk[agentcore] --no-deps
   pip install synth-agent-sdk
   ```

**Homebrew Python:** If you use Homebrew's Python, create a venv first — installing packages globally into Homebrew Python is [externally managed](https://peps.python.org/pep-0668/) and will be rejected by pip.

### Global Install with pipx

If you want the `synth` CLI available globally without activating a venv each time, use [pipx](https://pipx.pypa.io/):

```bash
# Install pipx if you don't have it
# macOS
brew install pipx
pipx ensurepath

# Linux / Windows
pip install --user pipx
pipx ensurepath
```

Then install Synth:

```bash
pipx install synth-agent-sdk[anthropic]
```

To add extra providers to an existing pipx install:

```bash
pipx inject synth-agent-sdk anthropic openai       # add provider SDKs
pipx inject synth-agent-sdk boto3 'botocore[crt]'   # add Bedrock/AWS support
```

This gives you the `synth` CLI globally (`synth init`, `synth dev`, `synth doctor`, etc.) while keeping dependencies isolated. For project work that imports `from synth import Agent`, you'll still want a venv with `pip install synth-agent-sdk` so your project can access the library.

Set your API key:

```bash
export ANTHROPIC_API_KEY="your-key-here"   # Claude
export OPENAI_API_KEY="your-key-here"      # GPT
export GOOGLE_API_KEY="your-key-here"      # Gemini
# AWS Bedrock uses standard IAM credentials — no Synth-specific key needed
```

Verify your setup:

```bash
synth doctor
```

---

## Quick Start

The fastest way to get going is `synth init`, which scaffolds a complete project interactively:

```bash
mkdir my-agent && cd my-agent
synth init
```

This walks you through provider selection, model choice, tools, and features — then generates a ready-to-run project:

```
  SYNTH INIT
  Interactive project setup

  Project type (single, multi) [single]:
  Project name [my-agent]:
  Description [An AI agent built with SynthAgentSDK]:

  Available providers:
    anthropic              Anthropic Claude
    openai                 OpenAI GPT
    google                 Google Gemini
    ollama                 Local Ollama
    bedrock                AWS Bedrock
    agentcore              AWS AgentCore

  Provider [anthropic]:
  Model [claude-sonnet-4-5]:
  Agent instructions [You are a helpful assistant.]:

  ...tool wizard, MCP wizard, feature toggles...

  Summary:
    Name:         my-agent
    Provider:     Anthropic Claude
    Model:        claude-sonnet-4-5
    Features:     memory, guards
    Files:        agent.py, README.md, synth.toml

  Create project? [Y/n]:

  How would you like to test?
    ui                     Launch the browser-based testing dashboard
    cli                    Open the interactive CLI shell

  Testing mode [cli]:
```

Once generated, run your agent:

```bash
synth dev agent.py          # Interactive REPL with streaming + trace UI
synth run agent.py "Hello"  # One-shot execution
```

For multi-agent projects, select `multi` at the project type prompt to configure multiple agents with orchestration (Pipeline, Graph, AgentTeam, or Human-in-the-Loop).

Or skip the wizard and write an agent directly:

```python
from synth import Agent

agent = Agent(model="claude-sonnet-4-5", instructions="You are a helpful assistant.")
result = agent.run("What is the capital of France?")
print(result.text)
# => "The capital of France is Paris."
```

---

## Core Concepts

| Concept | What It Is |
|---------|-----------|
| `Agent` | The main building block. Wraps an AI model with tools, memory, and guards. |
| `Tool` | A Python function your agent can call. |
| `ToolKit` | A bundle of related tools. |
| `AgentTool` | Wraps an Agent as a tool for another Agent (hierarchical composition). |
| `MCPClient` | Discovers and registers tools from MCP servers. |
| `BuiltinTool` | Pre-built tools for file I/O, shell, HTTP, and web search. |
| `BaseToolMiddleware` | Hooks that wrap every tool call (caching, logging, rate limiting). |
| `RunContext` | Typed dependency injection container for tools. |
| `RunResult` | Returned by `agent.run()` — text, token usage, cost, latency, trace. |
| `Memory` | Lets your agent remember previous conversations. |
| `ConversationManager` | Automatic context window management (sliding window / summarize). |
| `Guard` | A safety rule applied to input or output. |
| `Pipeline` | Chains agents sequentially. |
| `Graph` | A workflow with branching, loops, parallel execution, and conditional logic. |
| `AgentTeam` | Multiple agents coordinated by an orchestrator. |
| `Trace` | A detailed record of everything that happened during a run. |
| `BaseTraceExporter` | Pluggable interface for shipping traces to Langfuse, Datadog, Honeycomb. |
| `TraceToEval` | Converts production traces into evaluation datasets. |
| `Checkpoint` | A saved snapshot of a run's state for resumption. |
| `Eval` | Structured evaluation runner with scoring and comparison. |
| `BaseEvalSuite` | Pre-built evaluation suites (tool selection, hallucination, grounding). |
| `A2AClientTool` / `A2AServerAdapter` | Agent-to-Agent protocol for inter-agent communication. |
| `ImageInput` / `AudioInput` / `FileInput` | Multi-modal tool inputs with base64 encoding. |
| `TestModel` | Deterministic mock provider for unit testing agents. |
| `FunctionModel` | Custom test provider driven by a user function. |
| `VCRRecorder` | Records and replays real LLM interactions for integration tests. |
| `NodeExecution` | Debug record of a single graph node execution. |
| `PartialOutputEvent` | Stream event for incrementally validated structured output fields. |
| `DurableRunner` | Wraps Agent/Graph execution with step-level journaling and replay on restart. |
| `SmartRouter` | Complexity-based model tier selection — routes prompts to appropriate models. |
| `Cache` | LLM response caching middleware with exact-match and semantic similarity. |
| `Knowledge` | RAG document retrieval with citation tracking and pluggable retrievers. |
| `TenantContext` | Per-tenant scoping of memory, storage, and cost across execution. |
| `TimeTravelDebugger` | Fork and replay graph runs from any checkpoint with modified state. |
| `OTelExporter` | Native OpenTelemetry span emission following `gen_ai.*` semantic conventions. |
| `Trigger` | Declarative event sources: webhook, schedule, event bus, SQS. |
| `Version` / `CanaryRouter` | Immutable version snapshots with weighted traffic splitting and shadow mode. |
| `AuditLog` | Structured, immutable audit records with hash chain integrity for compliance. |

---

## Creating an Agent

```python
from synth import Agent, Guard, Memory

agent = Agent(
    model="claude-sonnet-4-5",        # AI model to use
    instructions="You are helpful.",   # System prompt
    tools=[my_tool, my_toolkit],      # Optional tools
    memory=Memory.thread(),           # Optional memory
    guards=[Guard.no_pii_output()],   # Optional safety rules
    output_schema=MyModel,            # Optional Pydantic schema
    max_retries=3,                    # Retry on transient errors
    retry_backoff=1.0,                # Base delay between retries (seconds)
    deps=my_dependencies,             # Optional dependency injection
    tool_middleware=[CachingMiddleware(ttl_seconds=300)],  # Optional middleware
    fallback=["gpt-4o", "claude-haiku-3-5"],              # Optional fallback chain
    parallel_guards=True,             # Evaluate guards concurrently
)
```

All parameters except `model` are optional. Default model is `claude-sonnet-4-5`.

---

## Tools

Tools are Python functions your agent can call. Mark them with `@tool` — Synth auto-generates JSON schemas from type hints and docstrings.

```python
from synth import tool

@tool
def get_weather(city: str) -> str:
    """Get the current weather for a city."""
    return f"The weather in {city} is sunny, 72°F."

agent = Agent(
    model="claude-sonnet-4-5",
    instructions="You are a weather assistant.",
    tools=[get_weather],
)
```

Rules: every parameter needs a type annotation, and the function needs a docstring. Missing either raises `ToolDefinitionError` immediately.

Group related tools with `ToolKit`:

```python
from synth import ToolKit

math_tools = ToolKit([add, multiply, divide])
agent = Agent(model="gpt-4o", tools=[math_tools, get_weather])
```

Inspect tool calls after a run:

```python
for tc in result.tool_calls:
    print(f"{tc.name}({tc.args}) → {tc.result}  [{tc.latency_ms:.1f}ms]")
```

---

## Built-in Tools

Synth ships with commonly needed tools ready to use — file I/O, shell commands, HTTP requests, and web search:

```python
from synth.tools.builtins import BuiltinTool, read_file, write_file, http_request

# Use individual tools
agent = Agent(model="claude-sonnet-4-5", tools=[read_file, write_file])

# Or bundle all built-in tools with safe defaults
agent = Agent(model="claude-sonnet-4-5", tools=[BuiltinTool.all()])
```

Security defaults: shell is disabled, HTTPS is enforced, file paths are validated against traversal attacks.

```python
# Enable shell with explicit opt-in
kit = BuiltinTool.all(allow_shell=True, allowed_dir="/workspace")

# Configure individually
shell_tool = BuiltinTool.shell(allowed=True, timeout=60)
```

| Tool | Description | Default |
|------|-------------|---------|
| `read_file(path)` | Read file contents | Path traversal protected |
| `write_file(path, content)` | Write file, create dirs | Path traversal protected |
| `shell(command)` | Execute shell command | Disabled by default |
| `http_request(url, method, body)` | HTTP request | HTTPS enforced |
| `web_search(query, limit)` | Web search via Brave/SerpAPI/Tavily | Auto-detects API key |

---

## Agent-as-Tool Composition

Use one Agent as a tool for another, enabling hierarchical delegation:

```python
from synth import Agent, AgentTool

researcher = Agent(model="claude-sonnet-4-5", instructions="You research topics thoroughly.")
writer = Agent(model="claude-sonnet-4-5", instructions="You write clear articles.")

# The writer can delegate research to the researcher
editor = Agent(
    model="claude-sonnet-4-5",
    instructions="You coordinate research and writing.",
    tools=[
        AgentTool(researcher, name="research", description="Research a topic"),
        AgentTool(writer, name="write", description="Write an article"),
    ],
)

result = editor.run("Write an article about quantum computing.")
```

The child agent's `RunResult` (cost, tokens, tool calls) is accessible via the parent's trace.

---

## MCP Integration

Connect to Model Context Protocol servers to dynamically discover and use external tools:

```python
from synth import Agent, MCPClient

# HTTP/SSE transport
mcp = MCPClient("https://mcp.example.com/tools")
await mcp.connect()

# Or stdio transport
mcp = MCPClient(["npx", "my-mcp-server"])
await mcp.connect()

# Use discovered tools in an agent
agent = Agent(model="claude-sonnet-4-5", tools=[mcp])
```

MCP tools are validated against their declared JSON schemas before forwarding. Per-tool timeout defaults to 30 seconds.

```bash
pip install synth-agent-sdk[mcp]  # Install the optional MCP dependency
```

---

## Tool Middleware

Wrap every tool invocation with cross-cutting concerns — caching, logging, rate limiting:

```python
from synth import Agent, BaseToolMiddleware
from synth.tools.middleware import CachingMiddleware, LoggingMiddleware

agent = Agent(
    model="claude-sonnet-4-5",
    tools=[my_tool],
    tool_middleware=[
        LoggingMiddleware(level="INFO"),      # Log tool calls
        CachingMiddleware(ttl_seconds=300),   # Cache results for 5 min
    ],
)
```

Middleware executes in declaration order (first in list wraps outermost). Write custom middleware:

```python
class RateLimitMiddleware(BaseToolMiddleware):
    async def call(self, name, args, next_fn):
        await self.check_rate_limit()
        result = await next_fn(name, args)
        return result
```

---

## Dependency Injection (RunContext)

Pass database connections, HTTP clients, or config objects into tools without globals:

```python
from dataclasses import dataclass
from synth import Agent, RunContext, tool

@dataclass
class Deps:
    db_url: str
    api_client: object

@tool
def lookup_user(user_id: str, ctx: RunContext[Deps]) -> str:
    """Look up a user by ID."""
    db = ctx.deps.db_url  # Access injected dependencies
    return f"User {user_id} found at {db}"

agent = Agent(
    model="claude-sonnet-4-5",
    tools=[lookup_user],
    deps=Deps(db_url="postgres://...", api_client=my_client),
)
```

`RunContext` also carries `run_id`, `thread_id`, and `retry_count` metadata. Tools that don't declare a `RunContext` parameter work unchanged.

---

## Running Your Agent

**Synchronous:**

```python
result = agent.run("Explain quantum computing in simple terms.")
print(result.text)        # Response text
print(result.tokens)      # TokenUsage(input, output, total)
print(result.cost)        # Estimated cost in USD
print(result.latency_ms)  # Latency in milliseconds
print(result.tool_calls)  # Tools that were called
print(result.trace)       # Full execution trace
print(result.output)      # Parsed structured output (if output_schema set)
```

**Asynchronous:**

```python
import asyncio

async def main():
    result = await agent.arun("What is 2 + 2?")
    print(result.text)

asyncio.run(main())
```

---

## Streaming

```python
from synth import TokenEvent, ToolCallEvent, ToolResultEvent, DoneEvent, ErrorEvent

for event in agent.stream("Write a short poem about coding."):
    if isinstance(event, TokenEvent):
        print(event.text, end="", flush=True)
    elif isinstance(event, ToolCallEvent):
        print(f"\n[Calling: {event.name}]")
    elif isinstance(event, DoneEvent):
        print(f"\n\nTokens: {event.result.tokens.total_tokens}")
```

Async streaming:

```python
async for event in agent.astream("Write a haiku."):
    if isinstance(event, TokenEvent):
        print(event.text, end="", flush=True)
```

| Event | When |
|-------|------|
| `TokenEvent` | Model produced a text token |
| `ToolCallEvent` | Model decided to call a tool |
| `ToolResultEvent` | Tool finished executing |
| `ThinkingEvent` | Model produced a reasoning token |
| `DoneEvent` | Stream completed — contains full `RunResult` |
| `ErrorEvent` | Something went wrong |

---

## Streaming Structured Output

When using `output_schema` with streaming, Synth emits `PartialOutputEvent` as individual fields are validated:

```python
from synth import Agent, PartialOutputEvent, TokenEvent, DoneEvent
from pydantic import BaseModel

class Analysis(BaseModel):
    sentiment: str
    confidence: float
    summary: str

agent = Agent(model="claude-sonnet-4-5", output_schema=Analysis)

async for event in agent.astream("Analyze this review: Great product!"):
    if isinstance(event, TokenEvent):
        print(event.text, end="")
    elif isinstance(event, PartialOutputEvent):
        print(f"\n  ✓ {event.field_name}: {event.field_value}")
    elif isinstance(event, DoneEvent):
        analysis = event.result.output  # Fully validated Analysis instance
        print(f"\nSentiment: {analysis.sentiment}")
```

The final `DoneEvent.result.output` always contains the fully validated Pydantic model, identical to the non-streaming path. If validation fails, the same retry logic applies.

---

## Model Providers

Switch providers by changing the `model` string — no other code changes needed.

| Provider | Model String Examples | Extra | API Key |
|----------|----------------------|-------|---------|
| Anthropic | `"claude-sonnet-4-5"`, `"claude-haiku-3-5"` | `synth[anthropic]` | `ANTHROPIC_API_KEY` |
| OpenAI | `"gpt-4o"`, `"gpt-4o-mini"` | `synth[openai]` | `OPENAI_API_KEY` |
| Google | `"gemini-2.0-flash"` | `synth[google]` | `GOOGLE_API_KEY` |
| Ollama | `"ollama/llama3"`, `"ollama/mistral"` | `synth[ollama]` | None (local) |
| AWS Bedrock | `"bedrock/claude-sonnet-4-5"` | `synth[bedrock]` | AWS IAM |

Custom endpoint:

```python
agent = Agent(model="my-model", base_url="https://my-proxy.example.com/v1")
```

---

## Provider Fallback Chains

Automatically try alternative models when the primary fails:

```python
agent = Agent(
    model="claude-sonnet-4-5",
    fallback=["gpt-4o", "claude-haiku-3-5"],
    max_retries=3,
)
```

When the primary model fails after all retries, Synth iterates through the fallback list. Each fallback gets its own full retry cycle. Fallback transitions are recorded in the trace as `"fallback"` spans.

```python
result = agent.run("Hello")
for span in result.trace.spans:
    if span.type == "fallback":
        print(f"Fell back from {span.metadata['failed_model']} → {span.metadata['next_model']}")
```

Fallback works with both `run()`/`arun()` and `stream()`/`astream()`.

---

## Memory

By default each `run()` is stateless. Add memory to persist conversations.

**Thread memory** (in-process, fast):

```python
agent = Agent(model="claude-sonnet-4-5", memory=Memory.thread())

agent.run("My name is Alice.", thread_id="user-123")
result = agent.run("What's my name?", thread_id="user-123")
print(result.text)  # "Your name is Alice."
```

**Persistent memory** (Redis, survives restarts):

```python
agent = Agent(model="gpt-4o", memory=Memory.persistent("redis://localhost:6379"))
```

**Semantic memory** (vector embeddings, retrieves most relevant context):

```python
agent = Agent(model="gemini-2.0-flash", memory=Memory.semantic(embedder=my_embedder_fn))
```

---

## Conversation Management

Automatically manage context window size on long-running conversations:

```python
# Sliding window — keep the most recent 50 messages
agent = Agent(
    model="claude-sonnet-4-5",
    memory=Memory.managed(strategy="sliding_window", max_messages=50),
)

# Summarize — compress older messages when token count exceeds threshold
agent = Agent(
    model="claude-sonnet-4-5",
    memory=Memory.managed(
        strategy="summarize",
        model="claude-haiku-3-5",  # Lightweight model for summarization
        max_tokens=80_000,
    ),
)
```

`ConversationManager` wraps any memory backend transparently. Summaries are inserted as system-level context messages (not fabricated user messages) to prevent prompt injection.

---

## Guards

Declarative safety rules applied automatically to every run.

```python
from synth import Guard

agent = Agent(
    model="claude-sonnet-4-5",
    guards=[
        Guard.no_pii_output(),             # Block PII in responses
        Guard.max_cost(dollars=0.50),       # Stop if cost exceeds $0.50
        Guard.no_tool_calls(["delete_*"]), # Block tools matching glob
        Guard.custom(my_check_fn),          # Your own check function
    ],
)
```

Guards run in order. First failure stops execution and raises `GuardViolationError`.

---

## Guard Composition

Combine guards with logical operators and add rate limiting:

```python
from synth import Guard

agent = Agent(
    model="claude-sonnet-4-5",
    guards=[
        Guard.all(                          # All must pass (AND)
            Guard.no_pii_output(),
            Guard.max_cost(dollars=1.00),
        ),
        Guard.any(                          # At least one must pass (OR)
            Guard.custom(check_allowlist),
            Guard.custom(check_admin),
        ),
        Guard.rate_limit(calls_per_minute=30),  # Sliding window rate limit
    ],
    parallel_guards=True,  # Evaluate independent guards concurrently
)
```

`Guard.all()` short-circuits on first failure. `Guard.any()` short-circuits on first success. When `parallel_guards=True`, top-level guards run via `asyncio.gather()` for reduced latency.

---

## Structured Output

Get typed Pydantic objects back instead of raw text:

```python
from pydantic import BaseModel

class MovieReview(BaseModel):
    title: str
    rating: float
    summary: str
    recommended: bool

agent = Agent(
    model="claude-sonnet-4-5",
    instructions="You are a movie critic.",
    output_schema=MovieReview,
)

result = agent.run("Review the movie Inception.")
review = result.output  # MovieReview instance

print(review.title)        # "Inception"
print(review.rating)       # 9.2
print(review.recommended)  # True
```

If parsing fails, Synth retries with a corrective prompt up to `max_retries` times.

---

## Pipelines

Chain agents sequentially — output of each becomes input of the next:

```python
from synth import Pipeline

researcher = Agent(model="claude-sonnet-4-5", instructions="You research topics.")
writer = Agent(model="claude-sonnet-4-5", instructions="You write clear articles.")
editor = Agent(model="claude-sonnet-4-5", instructions="You edit for clarity.")

pipeline = Pipeline([researcher, writer, editor])
result = pipeline.run("The history of the internet")
```

Run stages in parallel with `ParallelGroup`:

```python
from synth.orchestration.pipeline import ParallelGroup

pipeline = Pipeline([
    writer,
    ParallelGroup([fact_checker, style_checker]),  # Run concurrently
    editor,
])
```

Stream with stage labels:

```python
for stage_event in pipeline.stream("Write about AI"):
    print(f"[{stage_event.stage_name}] {stage_event.event}")
```

---

## Graphs

Directed-graph workflows with branching, loops, and conditional logic:

```python
from synth import Graph, node

graph = Graph()

@node(graph)
def classify(state):
    state["priority"] = "high" if "urgent" in state["text"].lower() else "low"
    return state

@node(graph)
def handle_urgent(state):
    state["response"] = "Escalating immediately."
    return state

@node(graph)
def handle_normal(state):
    state["response"] = "We'll respond within 24 hours."
    return state

graph.set_entry("classify")
graph.add_edge("classify", "handle_urgent", when=lambda s: s["priority"] == "high")
graph.add_edge("classify", "handle_normal", when=lambda s: s["priority"] == "low")
graph.add_edge("handle_urgent", Graph.END)
graph.add_edge("handle_normal", Graph.END)

result = graph.run({"text": "This is urgent! Server is down!"})
print(result.output["response"])
```

Loops are supported. Synth enforces `max_iterations=100` by default to prevent infinite loops.

Visualize your graph:

```python
print(graph.visualise())  # Outputs a Mermaid diagram
```

---

## Graph Debugging

Inspect state transitions and trace node execution:

```python
result = await graph.arun({"text": "help!"}, debug=True)

# Execution history with input/output state, latency, timestamps
for node_exec in graph.history():
    print(f"{node_exec.node_name}: {node_exec.latency_ms:.1f}ms")
    print(f"  In:  {node_exec.input_state}")
    print(f"  Out: {node_exec.output_state}")
```

With `debug=True`, the graph emits detailed `DEBUG`-level log messages for node entry/exit, edge evaluation, checkpoint saves, and pause events. The `visualise()` method styles the entry node with a double border and deduplicates END nodes.

---

## Graph Parallel Execution

When multiple unconditional edges fan out from a single node, Synth automatically executes the target nodes concurrently:

```python
graph = Graph()

@node(graph)
def start(state):
    return state

@node(graph)
def fetch_prices(state):
    state["prices"] = get_prices()
    return state

@node(graph)
def fetch_reviews(state):
    state["reviews"] = get_reviews()
    return state

@node(graph)
def merge(state):
    return state

# Fan-out: both fetch nodes run concurrently
graph.add_edge("start", "fetch_prices")
graph.add_edge("start", "fetch_reviews")
graph.add_edge("fetch_prices", "merge")
graph.add_edge("fetch_reviews", "merge")
graph.add_edge("merge", Graph.END)
graph.set_entry("start")
```

Each concurrent node receives a deep-copied state — mutations in one node don't affect others. Results are merged with a shallow dictionary merge by default, or a custom merge function:

```python
graph.with_parallel(merge_fn=lambda states: {k: v for s in states for k, v in s.items()})
```

If any concurrent node raises, all others are cancelled and the error propagates as `GraphRoutingError`.

---

## Human-in-the-Loop

Pause a graph at specific nodes for human review before continuing:

```python
graph.with_human_in_the_loop(pause_at=["draft_email"], timeout=3600)
graph.with_checkpointing()

result = graph.run({"customer": "Alice"}, run_id="email-001")
# result is a PausedRun — inspect result.state["draft"] here

final = graph.resume("email-001", human_input="Looks good, send it.")
```

---

## Agent Teams

Coordinate multiple specialized agents under an orchestrator:

```python
from synth import AgentTeam

team = AgentTeam(
    orchestrator="claude-sonnet-4-5",
    agents=[researcher, writer, analyst],
    strategy="auto",   # orchestrator decides who does what
)

result = team.run("Write a report on renewable energy trends.")
print(result.answer)
print(result.contributions)   # Each agent's individual contribution
print(result.total_cost)
```

Use `strategy="parallel"` to run all agents concurrently.

### Collaboration Styles

For richer multi-agent workflows, use `collaboration_style` instead of `strategy`:

```python
team = AgentTeam(
    orchestrator="claude-sonnet-4-5",
    agents=[researcher, analyst, writer],
    collaboration_style="a2a_collaborative",  # Phased conversation with message bus
    team_goal="Research and produce a comprehensive report",
    max_qa_rounds=3,
    execution_order=["researcher", "analyst", "writer"],
)
```

| Style | Description |
|-------|-------------|
| `"orchestrator"` | Orchestrator delegates tasks via handoff tool calls |
| `"a2a_collaborative"` | Phased conversation — agents run in order, orchestrator synthesizes |
| `"agent_as_tool"` | Each agent is wrapped as a tool the orchestrator can call |

### Deploying Teams to AgentCore

Teams can be deployed to AWS AgentCore as independent runtimes that communicate via the `InvokeAgentRuntime` API. See [Deploying Multi-Agent Teams](#deploying-multi-agent-teams) in Part 2.

---

## Tracing and Observability

Every run automatically records a detailed trace:

```python
result = agent.run("Summarize this document.")
trace = result.trace

print(f"Tokens: {trace.total_tokens}")
print(f"Cost: ${trace.total_cost:.4f}")
print(f"Latency: {trace.total_latency_ms:.1f}ms")

result.trace.show()                    # Open visual timeline in browser
path = result.trace.export()           # Export as OpenTelemetry JSON
```

Auto-forward all traces to an OTel collector:

```bash
export SYNTH_TRACE_ENDPOINT="https://my-otel-collector.example.com/v1/traces"
```

When deployed to AWS AgentCore, Synth integrates with AgentCore's native observability capabilities — invocation logs, health monitoring, memory inspection, and cost dashboards are available through the AgentCore console and the Synth testing dashboard's AgentCore tab. No additional configuration is needed; the adapter handles trace forwarding automatically. See [AgentCore Observability](#agentcore-observability) in Part 2 for details.

---

## Trace-to-Eval Pipeline

Convert production traces into evaluation datasets for continuous quality improvement:

```python
from synth.eval import TraceToEval

# Collect traces from production runs
traces = [result1.trace, result2.trace, result3.trace]

# Filter and convert to eval cases
pipeline = (
    TraceToEval(traces)
    .filter(min_latency_ms=100, has_tool_calls=True)
    .filter(custom=lambda t: t.total_tokens > 50)
)

# Create an Eval pre-populated with cases from traces
evaluation = pipeline.to_eval(agent=my_agent)
report = evaluation.run()

# Or export as a JSON dataset for sharing
pipeline.export("eval_dataset.json")
```

The `labeler` parameter overrides expected values (default is the actual output for regression testing):

```python
evaluation = pipeline.to_eval(
    agent=my_agent,
    labeler=lambda prompt, output: "expected_value",
)

---

## Checkpointing

Save and resume graph execution state:

```python
graph.with_checkpointing()
result = graph.run(initial_state, run_id="my-run-001")

# Later, even in a different process
result = graph.resume("my-run-001")
```

Redis backend for distributed systems:

```python
from synth.checkpointing.redis import RedisCheckpointStore

graph.with_checkpointing(store=RedisCheckpointStore("redis://localhost:6379"))
```

---

## Evaluation

Run structured tests against your agent:

```python
from synth import Eval

evaluation = Eval(agent=agent)
evaluation.add_case(input="Capital of France?", expected="Paris")
evaluation.add_case(input="Capital of Japan?", expected="Tokyo")

report = evaluation.run()
print(f"Score: {report.overall_score}")

for case in report.cases:
    status = "PASS" if case.passed else "FAIL"
    print(f"  [{status}] {case.input} → {case.actual}")
```

Custom checker:

```python
def contains_keyword(output: str, expected: str) -> float:
    return 1.0 if expected.lower() in output.lower() else 0.0

evaluation.add_case(input="Explain photosynthesis.", expected="chlorophyll", checker=contains_keyword)
```

---

## Testing Infrastructure

Synth provides three testing tools at different abstraction levels — no API keys needed.

**TestModel** — deterministic canned responses for fast unit tests:

```python
from synth.testing import TestModel

agent = Agent(model=TestModel(responses=["Hello!", "Goodbye!"]))
result = agent.run("Hi")       # Returns "Hello!"
result = agent.run("Bye")      # Returns "Goodbye!"
result = agent.run("Again")    # Cycles back to "Hello!"
```

**FunctionModel** — custom test logic with full message access:

```python
from synth.testing import FunctionModel

def my_logic(messages):
    if "weather" in messages[-1]["content"]:
        return "Sunny, 72°F"
    return "I don't know"

agent = Agent(model=FunctionModel(fn=my_logic))
```

**VCRRecorder** — record real LLM interactions and replay them deterministically:

```python
from synth.testing import VCRRecorder

# Record mode — makes real API calls, saves to file
with VCRRecorder("tests/cassettes/greeting.json", record=True):
    result = agent.run("Hello")

# Replay mode — no network calls, deterministic
with VCRRecorder("tests/cassettes/greeting.json"):
    result = agent.run("Hello")  # Returns recorded response
```

All three are importable from `synth.testing`. `TestModel` is also available via the `"test"` model string prefix.

---

## Enterprise Capabilities

All enterprise features are opt-in and introduce no breaking changes. Install individual extras or get everything with:

```bash
pip install synth-agent-sdk[enterprise]
```

### Durable Execution

Step-level journaling with automatic replay on restart. `DurableRunner` wraps Agent or Graph execution, recording each LLM call, tool execution, and state transition to a journal backend.

```python
from synth.durable import DurableRunner

runner = DurableRunner(agent, backend="local")  # Also: "redis", "dynamodb"
result = runner.run("Perform a multi-step task", run_id="job-42")

# Process crashes and restarts — completed steps are replayed from journal
result = runner.run("Perform a multi-step task", run_id="job-42")  # Resumes
```

### Smart Model Router

Complexity-based model tier selection. Classifies prompts as simple, moderate, or complex and routes to the appropriate model. Falls back to higher tiers on failure.

```python
from synth.providers.smart_router import SmartRouter

router = SmartRouter(
    simple="claude-haiku-3-5",
    moderate="claude-sonnet-4-5",
    complex="claude-opus-4-6",
)
agent = Agent(model=router, instructions="You are helpful.")
result = agent.run("What is 2+2?")       # Routes to haiku
result = agent.run("Design a database schema for a hospital system.")  # Routes to opus
```

### Semantic & Prompt Caching

LLM response caching with exact-match and semantic similarity. The `Cache` middleware checks the cache before making a provider call, reducing latency and cost for repeated or similar prompts.

```python
from synth.cache import Cache

agent = Agent(
    model="claude-sonnet-4-5",
    cache=Cache(backend="local", similarity_threshold=0.92),  # Also: "redis", "dynamodb"
)
result = agent.run("What is the capital of France?")  # Cache miss — calls provider
result = agent.run("What's France's capital?")         # Cache hit — semantic match
```

### RAG Knowledge Base

Document retrieval with citation tracking. The `Knowledge` class retrieves context from pluggable retrievers and injects it into the system prompt automatically.

```python
from synth.knowledge import Knowledge, VectorRetriever

knowledge = Knowledge(
    retriever=VectorRetriever(index_path="./docs_index"),  # Also: S3Retriever, BedrockKBRetriever
    max_chunks=5,
    cite=True,
)
agent = Agent(model="claude-sonnet-4-5", knowledge=knowledge)
result = agent.run("How do I configure VPC peering?")
print(result.citations)  # Source documents with relevance scores
```

```bash
pip install synth-agent-sdk[knowledge]     # Installs boto3 and vector dependencies
```

### Multi-Tenant Isolation

Per-tenant scoping of memory, storage, and cost. `TenantContext` flows through execution, and all storage backends support a `tenant_id` parameter for data isolation.

```python
from synth.tenancy import TenantContext

with TenantContext(tenant_id="acme-corp"):
    result = agent.run("Summarize last quarter's sales.")
    # Memory, checkpoints, and audit logs are scoped to "acme-corp"
```

### Time Travel Debugging

Fork and replay graph runs from any checkpoint. Inspect past state, modify it, and re-execute from any step.

```python
result = graph.run(initial_state, run_id="run-001")

# Fork from step 3 with modified state
forked = graph.fork("run-001", step=3)
replayed = graph.replay("run-001", step=3, modified_state={"budget": 5000})
```

### OpenTelemetry Exporter

Native OTEL span emission following `gen_ai.*` semantic conventions. Integrates with any OpenTelemetry-compatible collector.

```python
from synth.tracing.exporters.otel import OTelExporter

agent = Agent(
    model="claude-sonnet-4-5",
    trace_exporter=OTelExporter(),  # Reads SYNTH_TRACE_ENDPOINT or OTEL_EXPORTER_OTLP_ENDPOINT
)
```

```bash
pip install synth-agent-sdk[otel]
```

### Webhook & Event Triggers

Declarative event sources for triggering agent execution from external systems.

```python
from synth.triggers import Trigger

webhook = Trigger.webhook(path="/ingest", method="POST")
scheduled = Trigger.schedule(cron="0 9 * * MON")
event = Trigger.event(source="orders", detail_type="OrderCreated")
queue = Trigger.sqs(queue_name="tasks")

agent = Agent(model="claude-sonnet-4-5", triggers=[webhook, scheduled])
```

### Agent Versioning & Canary Deployment

Immutable version snapshots with SHA-256 integrity. `CanaryRouter` enables weighted traffic splitting and shadow mode for safe rollouts.

```python
from synth.versioning import Version, CanaryRouter

v1 = Version.snapshot(agent, tag="v1.0.0")
v2 = Version.snapshot(agent_v2, tag="v2.0.0")

router = CanaryRouter(
    stable=v1,
    canary=v2,
    canary_weight=0.1,   # 10% traffic to v2
    shadow=False,         # Set True to run both, return stable
)
result = router.run("Hello")
```

### Compliance Audit Trail

Structured, immutable audit records with hash chain integrity. Supports HIPAA and SOC2 compliance metadata. Every agent action is logged with tamper-evident chaining.

```python
from synth.audit import AuditLog

audit = AuditLog(backend="dynamodb", compliance=["hipaa", "soc2"])
agent = Agent(model="claude-sonnet-4-5", audit=audit)

result = agent.run("Process patient record #1234")
# Audit record: who, what, when, input hash, output hash, chain hash
```

---

## CLI Commands

Run `synth` with no arguments to launch the interactive shell:

```bash
synth
```

```
synth> run agent.py "Hello"
synth> create agent my-bot
synth> doctor
synth> exit
```

All commands also work directly:

```bash
synth init                                  # Interactive project setup wizard
synth create agent my-bot                   # Scaffold an agent project
synth create agent my-bot -p openai         # Skip prompt, use OpenAI
synth create agentcore my-service           # AWS AgentCore project
synth create team my-team                   # Multi-agent team + pipeline
synth create tool my-tools                  # Standalone tools file
synth create mcp my-server                  # MCP server with FastMCP
synth create ui my-ui                       # Local browser testing dashboard
synth dev my_agent.py                       # Rich terminal UI with hot-reload
synth run my_agent.py "prompt"              # Execute agent, print result
synth bench my_agent.py "prompt" --runs 20  # Benchmark latency/cost
synth bench my_agent.py "Hello" --compare --models "claude-sonnet-4-5,gpt-4o"
synth eval my_agent.py --dataset cases.json # Run evaluation suite
synth eval my_agent.py --suite tool-selection  # Run a pre-built eval suite
synth trace <run_id>                        # Open trace in browser
synth deploy --target agentcore             # Deploy to AWS AgentCore
synth deploy --target agentcore --dry-run   # Validate without deploying
synth deploy --target agentcore --rollback  # Roll back to previous version
synth deploy --target agentcore --version 1.2.3  # Deploy with explicit version
synth ui my_agent.py                        # Launch browser testing UI
synth edit agent agent.py                   # Modify existing agent config
synth agents list                           # List deployed agents with live status
synth agents status my-agent                # Detailed agent info
synth agents start my-agent                 # Start a deployed agent
synth agents stop my-agent                  # Stop a deployed agent
synth agents destroy my-agent               # Tear down and remove from registry
synth platform publish manifest.yaml --env prod  # Publish resources to gateways
synth platform plan --env staging           # Show pending deployment changes
synth platform status --env production      # Current deployment state
synth platform doctor --resources --env prod  # Detect infrastructure drift
synth platform ui                           # Launch Platform Space dashboard
synth doctor                                # Check env, credentials, deps
synth info --extra anthropic                # Show package info
synth help                                  # Quick reference card
```

### `synth init`

The fastest way to start a new project. Walks you through:

1. **Project type** — single agent or multi-agent
2. Project name and description
3. Provider selection (anthropic, openai, google, ollama, bedrock, agentcore)
4. Model selection (region-aware for AgentCore with Bedrock model catalog)
5. Agent instructions
6. **Tool Wizard** — pick pre-built tools or scaffold custom `@tool` stubs
7. **MCP Wizard** — pick pre-built MCP servers or scaffold custom `@mcp.tool()` stubs
8. Feature toggles (memory, guards, structured output, eval, deploy)
9. Credential check (AgentCore only)
10. Summary and confirmation
11. Project generation
12. Optional "Deploy now?" prompt (AgentCore only)
13. **Testing mode** — launch the browser UI dashboard or the interactive CLI

#### Multi-Agent Projects

When you select `multi` at the project type prompt, the wizard guides you through:

- **Shared configuration** — after naming the project, you're asked whether to use the same provider/model and tools for all agents. If yes, these are collected once upfront and applied to every agent, dramatically reducing setup time for teams where all agents share infrastructure
- **Agent count** (minimum 2) with per-agent configuration (name, description, instructions — plus provider/model/tools if not shared)
- **Agent name sanitization** — names like "Molly Mikes" or "Cash Carter" are automatically converted to valid Python identifiers (`molly_mikes`, `cash_carter`) for filenames and code, with the original name preserved in docstrings and display
- **Orchestration pattern selection** with descriptions:
  - **Pipeline** — linear sequential chaining, each agent receives the previous agent's output
  - **Graph** — directed graph with conditional edges, branching, and loops
  - **AgentTeam** — orchestrator routes tasks to specialized agents (auto or parallel strategy)
  - **Human-in-the-Loop** — graph with pause/resume checkpoints for human review
- Pattern-specific configuration (execution order, edges, strategy, pause nodes, etc.)
- Feature selection, summary, and project generation

Generated multi-agent project structure:

```
my-project/
├── agent_molly_mikes.py   # Individual agent files (sanitized names)
├── agent_rex_routes.py
├── main.py                # Orchestration wiring (Pipeline/Graph/Team/HITL)
├── tools_molly_mikes.py   # Per-agent tool files (if configured)
├── README.md
├── synth.toml
└── ui/                    # Testing dashboard (if UI mode selected)
    ├── server.py
    └── static/
```

#### Single-Agent Projects

Generated project structure:

```
my-agent/
├── agent.py           # Your agent with selected provider, tools, and features
├── README.md          # Project-specific docs with run instructions
├── synth.toml         # Project configuration
├── tools.py           # Custom tool stubs (if tools selected)
├── mcp_server.py      # MCP server stubs (if MCP selected)
├── eval_dataset.json  # Evaluation cases (if eval selected)
├── eval_config.json   # AgentCore Evaluations config (AgentCore + eval only)
├── agentcore.yaml     # AWS config (AgentCore projects only)
└── .env.template      # Environment variable template (AgentCore only)
```

The testing UI (if selected) is scaffolded at the workspace root, shared across all agents:

```
workspace/
├── my-agent/          # Agent project
├── another-agent/     # Another agent project
└── ui/                # Shared testing dashboard
    ├── server.py
    └── static/
```

For AgentCore projects, `synth init` also:
- Auto-detects AWS credentials (env vars → `~/.aws/credentials` → AWS Toolkit profiles)
- Prompts for target AWS region (default: `us-east-1`)
- Shows Bedrock models available in that region
- Writes `aws_region`, `model_id`, `cris_enabled`, and `aws_profile` to `agentcore.yaml`

Common patterns:

```bash
synth init                          # Full interactive wizard
synth init && cd my-agent && synth dev agent.py   # Init + start developing
```

### `synth dev`

Rich terminal UI for interactive development:

```bash
synth dev my_agent.py
```

When run without a file argument, `synth dev` scans the workspace for agent files and presents an interactive picker. For agents with an `agentcore.yaml`, it checks live deployment status against the AWS account and shows color-coded badges (active, creating, failed). If the selected agent isn't deployed yet, you'll be prompted to deploy before opening the REPL.

Features: streaming token-by-token output, tool call visualization, slash commands (`/tools`, `/reload`, `/trace`, `/export`, `/clear`, `/cost`, `/quit`), markdown rendering, status bar with live cost/token tracking.

### `synth ui`

Launch the browser-based testing UI for any agent file:

```bash
synth ui my_agent.py
```

When run without a file argument, `synth ui` uses the same agent discovery logic as `synth dev`. The command launches the UI server as a subprocess using the SDK's own Python interpreter, so it works correctly even when installed via pipx. The agent file path is passed via the `SYNTH_AGENT_FILE` environment variable.

### `synth create ui`

Scaffold a full-featured browser-based testing dashboard:

```bash
synth create ui my-dashboard
cd my-dashboard
pip install uvicorn fastapi
python server.py
# Open http://localhost:8420
```

The dashboard includes:

- **Streaming chat** with SSE, thinking block support, and markdown rendering
- **Real-time flow visualization** — the Flow tab renders a live node graph as the agent executes, showing the full path from prompt → agent → tool calls → output. Each node is clickable to inspect trace data, arguments, results, token usage, and cost in a slide-in detail panel. Supports multi-agent delegation chains for Team, Pipeline, and Graph orchestration
- **Multi-agent collaboration view** — for AgentTeam, Pipeline, and Graph projects, the UI shows real-time delegation cards as each agent runs, with tool calls, output previews, latency, and cost per agent. A swimlane panel on the final response shows all agent contributions at a glance. The server auto-detects `team`, `pipeline`, or `graph` exports from your `main.py`
- **Conversation management** with persistence, multiple threads, and export
- **Telemetry panel** with per-response and session-level tokens, cost, latency, and cost-per-turn sparkline
- **Tool playground** to test individual tools with custom arguments
- **Prompt library** with versioning, notes, and variable injection (`{{variable}}` syntax)
- **A/B testing** to compare two prompt variants side-by-side with diff view
- **Eval runner** with keyword scoring, LLM judge, golden baselines, and regression detection
- **Session replay** with timeline view, token usage heatmap, and anomaly detection (slow, expensive, or short responses)
- **Scenario builder** for scripted multi-turn conversations
- **AgentCore Evaluations** panel showing evaluator scores, config status, and on-demand evaluation (when configured)
- **Hot-reload** to pick up agent changes without restarting the server

The UI is also scaffolded automatically when you choose `ui` as the testing mode during `synth init`. The UI is created once at the workspace root and shared across all agents — subsequent `synth init` runs detect the existing UI and reuse it. If the server is already running, you'll just see the URL. UI dependencies (`uvicorn`, `fastapi`) are auto-installed if missing.

### `synth deploy`

Guided deployment wizard with versioning and rollback:

```bash
synth deploy --target agentcore my_agent.py
synth deploy --target agentcore --dry-run my_agent.py  # Validate only
synth deploy --target agentcore --version 1.2.0        # Explicit version
synth deploy --target agentcore --rollback              # Revert to previous version
synth deploy --target agentcore -y                      # Skip confirmation prompts
```

Stages: credential validation → dependency check → file validation → manifest generation → Dockerfile validation → artifact packaging → deployment readiness → AgentCore API submission. Each prints `[  OK  ]` or `[FAIL]` with a corrective suggestion on failure.

The readiness stage reports on auth method, memory backend, guards, tools, search API keys, and target region/model — with warnings for any missing components. Deployments are tracked with semantic versioning in the agent registry, enabling rollback to any previous version.

### `synth edit agent`

Interactively modify an existing agent without editing files manually:

```bash
synth edit agent agent.py
```

Menu options: (a) instructions, (b) model, (c) tools, (d) MCP servers. Shows a diff before writing. Uses atomic temp-file rename to prevent corruption.

### `synth doctor`

```bash
synth doctor
```

Checks: Python version, core dependencies, provider API keys, `SYNTH_TRACE_ENDPOINT` format, optional provider packages, and (when `agentcore.yaml` is present) AgentCore config fields (`aws_region`, `model_id`, `cris_enabled`, `aws_profile`).

### `synth bench`

```bash
synth bench my_agent.py "Hello" --runs 20 --warmup 2
synth bench my_agent.py "Hello" --compare --models "claude-sonnet-4-5,gpt-4o,claude-haiku-3-5"
```

Reports p50/p95/p99 latency, average tokens, cost per run, and success rate. The `--compare` flag benchmarks multiple models side-by-side with a comparison table.

### `synth agents`

Manage deployed agents:

```bash
synth agents list                    # Table with live AgentCore status
synth agents status my-agent         # Detailed agent info
synth agents start my-agent          # Start a deployed agent
synth agents stop my-agent           # Stop a deployed agent
synth agents destroy my-agent        # Tear down and remove from registry
```

The `list` command queries AgentCore for live status and merges with the local registry and `.bedrock_agentcore/` metadata. Agents deployed from other machines or by teammates are visible.

---

## Trace Exporters

Ship traces to external observability platforms with pluggable exporters:

```python
from synth.tracing.exporters import LangfuseExporter, DatadogExporter, HoneycombExporter

agent = Agent(
    model="claude-sonnet-4-5",
    trace_exporter=[
        LangfuseExporter(),           # Reads LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY
        DatadogExporter(),             # Reads DD_AGENT_HOST, DD_TRACE_AGENT_PORT
        HoneycombExporter(),           # Reads HONEYCOMB_API_KEY, HONEYCOMB_DATASET
    ],
)
```

Exporters run after each `arun()`/`astream()` completion. If an exporter raises, the error is logged and the next exporter runs — agent operation is never interrupted.

Write custom exporters by subclassing `BaseTraceExporter`:

```python
from synth.tracing.exporters import BaseTraceExporter

class MyExporter(BaseTraceExporter):
    async def export(self, trace):
        # Send trace data to your platform
        ...
```

Install extras: `pip install synth-agent-sdk[langfuse]`, `synth-agent-sdk[datadog]`, or `synth-agent-sdk[honeycomb]`.

---

## Eval Suites

Pre-built evaluation suites for common agent quality dimensions:

```python
from synth.eval.suites import get_suite

suite = get_suite("tool-selection")
report = suite.run(agent=my_agent)
print(f"Score: {report.overall_score}")
```

| Suite | What It Tests |
|-------|--------------|
| `"tool-selection"` | Agent picks the correct tool on the first call |
| `"hallucination"` | Tool arguments are grounded in conversation context |
| `"grounding"` | Response claims are supported by tool results |
| `"guard-compliance"` | No guard violations across test cases |
| `"multi-step"` | Tool call sequence matches expected order |

Run from the CLI:

```bash
synth eval my_agent.py --suite tool-selection
```

---

## Agent-to-Agent (A2A) Protocol

Communicate with external agents using Google's A2A protocol:

```python
from synth.tools.a2a import A2AClientTool, A2AServerAdapter, A2AAgentCard

# Client mode — use a remote A2A agent as a tool
client = A2AClientTool("https://remote-agent.example.com")
card = await client.discover()
agent = Agent(model="claude-sonnet-4-5", tools=[client.as_tool()])

# Server mode — expose your agent as an A2A endpoint
card = A2AAgentCard(name="my-agent", description="Helpful assistant", url="https://my-agent.example.com")
adapter = A2AServerAdapter(agent=my_agent, card=card)
response = await adapter.handle_task_send(task_payload)
```

---

## synth-tools — Pre-Built Tool Integrations

A companion package with 50+ tools across 12 categories:

```bash
pip install synth-tools[sql,web,github]   # Install specific extras
pip install synth-tools[all]               # Install everything
```

```python
from synth_tools import sql_toolkit, github_toolkit, web_toolkit

db_tools = sql_toolkit("sqlite:///mydb.db", read_only=True)
gh_tools = github_toolkit()  # Uses GITHUB_TOKEN env var
web_tools = web_toolkit()

agent = Agent(model="claude-sonnet-4-5", tools=[db_tools, gh_tools, web_tools])
```

| Category | Factory | Key Tools |
|----------|---------|-----------|
| SQL | `sql_toolkit(conn)` | query, execute, list_tables, describe_table |
| Vector | `vector_toolkit(provider)` | search, upsert, delete (Pinecone/Weaviate/ChromaDB) |
| Web | `web_toolkit()` | http_fetch, html_extract_text, html_select, extract_links |
| Slack | `slack_toolkit(token)` | send_message, read_channel, list_channels, send_dm |
| Email | `email_toolkit(smtp)` | email_send, email_send_html |
| S3 | `s3_toolkit(bucket)` | read/write/list/delete objects, presigned URLs |
| GCS | `gcs_toolkit(bucket)` | read/write/list/delete objects, signed URLs |
| REST | `rest_toolkit(url, auth)` | get, post, put, patch, delete (bearer/api_key/basic) |
| Code | `code_toolkit()` | python_eval, python_exec (sandboxed) |
| Docs | `docs_toolkit()` | pdf_extract_text, csv_parse, json_parse |
| Cache | `redis_toolkit(url)` | get, set, delete, list_keys, incr |
| Search | `google_search_toolkit()` | web_search, news_search |
| GitHub | `github_toolkit()` | search_repos, get_file, list_issues, create_issue, list_prs |
| Utility | `utility_toolkit()` | current_datetime, date_diff, math_eval, json_format |

See the [synth-tools README](../synth-tools/README.md) for full documentation.

---

## Benchmarking

Measure agent latency, cost, and reliability:

```bash
synth bench my_agent.py "Hello" --runs 20 --warmup 2
```

Compare models side-by-side:

```bash
synth bench my_agent.py "Summarize this" --compare --models "gpt-4o,claude-haiku-3-5"
```

Reports p50/p95/p99 latency, average tokens, cost per run, and success rate.

---

## Testing Dashboard

Scaffold a browser-based testing dashboard:

```bash
synth create ui my-dashboard
synth ui my_agent.py              # Or launch directly for any agent
```

The dashboard at `http://localhost:8420` includes streaming chat, real-time flow visualization (live node graph during execution with clickable trace details), telemetry panel (tokens, cost, latency per turn), tool playground, prompt library with A/B testing, eval runner, session replay with anomaly detection, scenario builder for multi-turn conversations, and configuration panel for runtime model/instruction swapping.

For multi-agent projects, the dashboard auto-detects your orchestration pattern (Pipeline, Graph, AgentTeam) and shows real-time delegation cards with per-agent tool calls, output, and cost.

---

# Part 2 — Platform Management: Agents at Scale in the Enterprise

This section covers deploying, managing, governing, and observing agents in production AWS environments. Manage the full lifecycle of AgentCore resources — gateways, runtimes, memory, and agent deployments — across accounts with policy enforcement, drift detection, and centralized state tracking. If you're building agents locally, everything you need is in Part 1 above.

---

## Deploying to AWS AgentCore

### Prerequisites

Install the AgentCore extra:

```bash
pip install synth-agent-sdk[agentcore]
# Or for full AWS support (includes browser tools, auth, and gateway client):
pip install synth-agent-sdk[aws]
```

You also need working AWS credentials on your machine. Set them up using one of these methods:

**Option A — AWS CLI (recommended for most users):**

```bash
# Install the AWS CLI
# macOS
brew install awscli

# Windows
winget install Amazon.AWSCLI

# Linux
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip && sudo ./aws/install

# Then configure your credentials
aws configure
# Enter your Access Key ID, Secret Access Key, default region, and output format
```

**Option B — AWS IAM Identity Center (SSO):**

```bash
aws configure sso
# Follow the prompts to set up SSO with your organization's identity provider
aws sso login --profile your-profile
```

**Option C — AWS Toolkit for VS Code / JetBrains:**

If you use an IDE with the [AWS Toolkit](https://aws.amazon.com/developer/tools/#IDE_and_IDE_Toolkits) extension, it manages credentials through its own auth flow (Builder ID or IAM Identity Center). Synth picks up these credentials automatically via the shared AWS credential chain.

**Verify your credentials:**

```bash
aws sts get-caller-identity
# Should print your account ID, user ARN, and user ID

synth doctor
# Checks AWS credentials and AgentCore config
```

> For AgentCore deployments, your IAM role needs permissions for Bedrock model invocation and AgentCore API access. Check with your AWS administrator if `synth deploy` fails with access denied errors.

### Wrapping Your Agent

```python
from synth import Agent
from synth.deploy.agentcore import agentcore_handler

agent = Agent(
    model="bedrock/claude-sonnet-4-5",
    instructions="You are a customer support agent.",
    tools=[lookup_order, check_inventory],
)

app = agentcore_handler(agent)
```

### Deploy

```bash
synth deploy --target agentcore --dry-run   # Validate first
synth deploy --target agentcore             # Deploy
```

Deploying new code (initial deploy or code changes) must be done from the terminal — the CLI needs access to your source code, entrypoint, and build configuration to package and push the artifact.

Once deployed, agents can be managed from the Platform Space UI (`synth platform ui`) without any local configuration. The UI queries the AgentCore control-plane API directly, so any authorised user with AWS credentials can:

- View all agents and their live status (Ready, Creating, Failed, etc.)
- Redeploy an agent (restart the runtime with the existing artifact)
- Connect to an agent and send messages
- Destroy an agent and its associated resources

Redeploying from the UI restarts the runtime with the same code that was last deployed. To push new code changes, run `synth deploy --target agentcore` from a terminal with the source code.

The packager automatically excludes `.env` files, credential files, and `.synth/checkpoints/` from the artifact. It also scans `agentcore.yaml` for accidental credential patterns and aborts if any are found.

### Environment Variables in the Container

`synth deploy` reads the `environment:` section of `agentcore.yaml` and passes each entry to the container via `agentcore launch --env KEY=VALUE`. This is the right place for **non-sensitive** config like feature flags or log levels.

```yaml
# agentcore.yaml
environment:
  SYNTH_NO_BANNER: "1"
  LOG_LEVEL: "INFO"
```

**API keys and secrets must not go in `agentcore.yaml`.** The deploy wizard filters out any key whose name contains `key`, `secret`, `token`, `password`, or similar patterns — they are never passed via `--env` to avoid exposure in process listings.

Instead, store secrets in AWS Secrets Manager or SSM Parameter Store and fetch them at agent startup:

```python
from synth.deploy.agentcore import get_ssm_parameter

# In your agent file — fetched at runtime inside the container
TAVILY_API_KEY = get_ssm_parameter("/myapp/prod/TAVILY_API_KEY", decrypt=True)

agent = Agent(
    model="bedrock/claude-sonnet-4-5",
    tools=[web_search],
)
```

The readiness stage (`synth deploy`) will warn you if a search API key is found only in your local `.env` and remind you to move it to Secrets Manager before the container can use it.

### Registry Publishing

When you deploy to AgentCore, Synth publishes an AWS AgentCore Agent Registry record for each deployed agent, each `@tool`-decorated callable, and each MCP server configured in the project. These records give the Dev Space UI and `synth agents list` a single place to answer "where is this agent?", "which agent hosts this tool?", and "which MCP endpoints are reachable?" without hand-maintaining a catalogue.

#### `registry:` in `agentcore.yaml`

You can pin the target registry per project by adding a `registry:` block to `agentcore.yaml`. The block is optional — when absent, Synth resolves the registry through the non-interactive default chain (see below) or by prompting interactively.

```yaml
# agentcore.yaml
registry:
  name: synth-agents       # Target registry for publishing. Default:
                           #   "synth-agents" for single-agent projects,
                           #   "synth-teams"  for team projects
                           #   (projects with a top-level `team:` section).
  tags:                    # Optional. Applied when Synth auto-creates the
    Environment: prod      # registry via CreateRegistry. Ignored when the
    Owner: platform-team   # registry already exists.
```

- **`registry.name`** — optional string. Names the registry that Agent, Tool, and MCP records are published into. If no registry with this name exists in the target region, Synth creates it via `bedrock-agentcore-control:CreateRegistry`. When the key is omitted, Synth falls back to `synth-agents` for single-agent projects and to `synth-teams` for projects that declare a `team:` section, preserving back-compat with teams deployed under v2.0.x.
- **`registry.tags`** — optional map of `string: string`. Applied as AWS tags on the registry only when Synth creates it. If the registry already exists, the tags are ignored (Synth does not retag existing registries).

#### `[deploy]` in `synth.toml`

To opt the project out of registry publishing without touching the CLI, set the opt-out key in `synth.toml`:

```toml
# synth.toml
[deploy]
publish_to_registry = true   # Default. Publish Agent, Tool, and MCP records
                             # on every `synth deploy`.
# publish_to_registry = false  # Skip every registry call for this project.
```

- **`[deploy] publish_to_registry`** — bool, default `true`. When `false`, Synth skips `ListRegistries`, `CreateRegistry`, and every `CreateRegistryRecord` call for every deploy of this project, and the Dev Space UI renders `Not published` in the registry column.
- **Interaction with `--no-registry`.** The CLI flag is per-invocation and the TOML key is per-project. Either one causes the deploy to skip publishing — the CLI wins when both are set. `--no-registry` and `--registry-name` cannot be combined on the same invocation; passing both raises `SynthConfigError`.
- **Environment variable equivalent.** `SYNTH_REGISTRY_NAME=<name>` is the env-var step of the same resolution chain and has the same effect as adding `registry.name: <name>` to `agentcore.yaml` for one shell session. The precedence is: `--registry-name` flag → `SYNTH_REGISTRY_NAME` env var → `agentcore.yaml` → team-deployment resolution (team projects only) → default (`synth-teams` / `synth-agents`).

#### Interactive walkthrough

When `synth deploy` or `synth init --target agentcore` (with "deploy now?" accepted) runs in a TTY and neither `--registry-name` nor `--no-registry` was passed, the wizard asks three prompts in sequence. The prompts never appear under `CI=true`, with `SYNTH_NO_PROMPT=1`, or with `--yes`/`-y` — those runs fall through to the default chain instead.

Single-agent project — default name `synth-agents`:

```
? Publish agents, tools, and MCP servers to an AgentCore registry? [Y/n] ▸ y
? Which registry?
  1) synth-agents  (us-east-1)  ab12cd34
  2) my-playground (us-east-1)  ef56gh78
  [+] Create new registry…
  ▸ 1
? Remember this registry for future deploys of this project? [Y/n] ▸ y

  ✓ Selected registry: synth-agents
  ✓ Wrote registry.name: synth-agents to agentcore.yaml
```

Team project — default name `synth-teams`:

```
? Publish agents, tools, and MCP servers to an AgentCore registry? [Y/n] ▸ y
? Which registry?
  (no registries found in us-east-1)
  [+] Create new registry… (name: synth-teams) ▸ (enter to accept default)
? Remember this registry for future deploys of this project? [Y/n] ▸ n

  ✓ Created registry: synth-teams  (reg-abcd1234efgh)
  ✓ Selection applies to this run only
```

Each prompt has a 120-second input timeout. If the user does not answer within that window, Synth treats the run as if the first prompt had been answered `n` and proceeds with legacy env-var wiring — the deploy still completes, but nothing is published.

The "Remember this registry?" prompt writes `registry.name: <selection>` into the project's `agentcore.yaml` only on `y`. Answering `n` keeps the selection scoped to the current run so a one-off override does not mutate committed config.

### Secure User Identity

```python
from synth.deploy.agentcore import extract_user_id

user_id = extract_user_id(context)  # Extracts from signed JWT in RequestContext
```

### Gateway MCP Client

```python
from synth.deploy.agentcore import create_gateway_client

client = create_gateway_client(
    gateway_url="https://my-gateway.example.com",
    client_id_param="/myapp/gateway/client_id",
    client_secret_param="/myapp/gateway/client_secret",
)
mcp_client = client.as_mcp_client()
```

### Code Interpreter

```python
from synth.deploy.agentcore import CodeInterpreterTools

ci = CodeInterpreterTools()
result = ci.execute_python("import math; print(math.sqrt(144))")
print(result)  # "12.0"
```

### Browser Tool

Search the web and navigate pages using AgentCore's managed Chrome browser — no third-party API keys needed:

```python
from synth.deploy.agentcore import BrowserTools
from synth import tool

browser = BrowserTools(region="us-west-2")

@tool
def search_web(query: str) -> str:
    """Search the web for information."""
    return browser.search(query)

@tool
def browse_page(url: str) -> str:
    """Navigate to a URL and extract its content."""
    return browser.navigate(url)

agent = Agent(model="bedrock/claude-sonnet-4-5", tools=[search_web, browse_page])
```

> **Note:** `search_web` uses lightweight HTTP requests (no browser needed). `browse_page` tries HTTP first and falls back to Playwright for JavaScript-heavy pages. Playwright is installed with `pip install synth-agent-sdk[aws]`, but you also need browser binaries: `playwright install chromium`.

### Built-in Web Search (API-based)

For lighter-weight search without a browser session, use the built-in `web_search` tool with a search API key:

```python
from synth.tools import web_search

agent = Agent(model="claude-sonnet-4-5", tools=[web_search])
```

Supports `BRAVE_API_KEY`, `SERPAPI_API_KEY`, or `TAVILY_API_KEY` — auto-detects whichever is set.

For AgentCore deployments, store the key in AWS Secrets Manager or SSM and fetch it at startup (see [Environment Variables in the Container](#environment-variables-in-the-container)).

### AgentCore Memory

Memory is automatically configured when deploying to AgentCore. The adapter wraps your agent with `AgentCoreMemory`, which stores and retrieves conversation history via the AgentCore events API. No manual setup required — just ensure `AGENTCORE_MEMORY_ENDPOINT` and `AGENTCORE_MEMORY_ID` are set in your deployment environment.

```python
# Memory works automatically in AgentCore deployments.
# For explicit configuration:
from synth.deploy.agentcore import AgentCoreMemory

agent = Agent(
    model="bedrock/claude-sonnet-4-5",
    memory=AgentCoreMemory(memory_id="mem-abc123"),
)
```

### SSM Config

```python
from synth.deploy.agentcore import get_ssm_parameter

db_url = get_ssm_parameter("/myapp/prod/db_url")
api_key = get_ssm_parameter("/myapp/prod/api_key", decrypt=True)
```

---

### Deploying Multi-Agent Teams

Deploy an `AgentTeam` to AgentCore with a single command. Synth deploys each child agent as an independent AgentCore runtime, then deploys the orchestrator with the child ARNs injected as environment variables.

**Project structure:**

```
my-team/
├── main.py                  # Team orchestrator (imports child agents)
├── agent_researcher.py      # Child agent with tools, memory, guards
├── agent_analyst.py
├── agent_writer.py
├── tools_researcher.py      # Tools for each agent
├── tools_analyst.py
├── tools_writer.py
├── agentcore.yaml           # Team deployment configuration
└── requirements.txt
```

**Configure `agentcore.yaml`:**

```yaml
agent_name: My Research Team
aws_region: us-east-1
model_id: bedrock/us.anthropic.claude-sonnet-4-6

team:
  orchestrator_name: team_orchestrator
  collaboration_style: a2a_collaborative
  deployment_order:
    - researcher
    - analyst
    - writer
    - team_orchestrator
  agents:
    - name: researcher
      role: researcher
      file: agent_researcher.py
      model_id: bedrock/us.anthropic.claude-sonnet-4-6
    - name: analyst
      role: analyst
      file: agent_analyst.py
      model_id: bedrock/us.anthropic.claude-sonnet-4-6
    - name: writer
      role: writer
      file: agent_writer.py
      model_id: bedrock/us.anthropic.claude-sonnet-4-6
```

**Deploy:**

```bash
synth deploy --target agentcore main.py
```

The deploy wizard runs shared preflight checks once, then shows a per-agent progress table:

```
  Deploying team (3 child agent(s) + orchestrator)...
  [  OK  ] Credential validation: Account ****6940 via aws_toolkit
  [  OK  ] Dependency check: synth[agentcore] is installed.
  [  OK  ] Agent file validation: Agent loaded

  ⠹ researcher      launching    ━━━━━━━━━━━━━━━━━━━╺  92%
  ⠹ analyst          pending      ━━━━━━━━━━━━━━━━━━━━   0%
  ⠹ writer           pending      ━━━━━━━━━━━━━━━━━━━━   0%
  ⠹ team_orchestrator pending     ━━━━━━━━━━━━━━━━━━━━   0%
```

Each child agent's ARN is captured and passed to the orchestrator as `SYNTH_AGENT_ARN_{ROLE_UPPER}` environment variables. At runtime, the orchestrator resolves these ARNs and invokes child agents via the AgentCore `InvokeAgentRuntime` API.

**Rollback the entire team:**

```bash
synth deploy --target agentcore main.py --rollback
```

---

## AgentCore Observability

When deployed to AWS AgentCore, Synth integrates with AgentCore's native observability capabilities. No additional configuration is required — the `agentcore_handler` adapter handles trace and metric forwarding automatically.

The Synth testing dashboard's AgentCore tab provides:
- Live health status and agent state monitoring
- Invocation logs with sorting, filtering, and auto-refresh
- Memory inspector for conversation history across sessions
- Cost and usage dashboards with time-series charts
- Remote invocation for side-by-side comparison with local execution
- Evaluation scores and on-demand evaluation triggers

For custom metrics beyond what AgentCore provides natively, Synth's `ObservabilityCollector` and `CloudWatchPublisher` (in `synth/observability/`) can publish agent execution metrics (latency, token usage, cost, error rates) directly to AWS CloudWatch.

The three pluggable trace exporters (Langfuse, Datadog, Honeycomb) work alongside AgentCore's native observability — you can ship traces to multiple destinations simultaneously. See [Trace Exporters](#trace-exporters) in Part 1 for configuration.

---

## AgentCore Evaluations

Synth integrates with AgentCore's Evaluations service for continuous agent quality monitoring. When you run `synth init` with the AgentCore provider and enable the "eval" feature, the wizard generates everything you need.

### What Gets Generated

- `eval_config.json` — Online evaluation configuration with three built-in evaluators (Helpfulness, Correctness, GoalSuccessRate) at a 1.0 sampling rate
- `agentcore.yaml` — Updated with an `evaluations` section and the required IAM permissions
- `eval_dataset.json` — Local evaluation dataset (also available for non-AgentCore providers)
- Agent code comment referencing the eval config

### Built-in Evaluators

| Evaluator | Level | What It Measures |
|-----------|-------|-----------------|
| `Builtin.Helpfulness` | TRACE | Whether the agent's response is helpful and relevant |
| `Builtin.Correctness` | TRACE | Factual accuracy of the agent's response |
| `Builtin.GoalSuccessRate` | SESSION | Whether the agent achieved the user's goal |

### Dashboard Integration

When evaluations are configured, the Dashboard's AgentCore tab shows an Evaluations sub-section with:

- Summary table of most recent evaluator scores (scores below 0.5 are flagged)
- Online evaluation config status (active/disabled, sampling rate, evaluator list)
- "Run Evaluation" button for on-demand evaluation against the most recent session

### API Endpoints

| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/agentcore/evaluations` | GET | Fetch evaluation scores |
| `/api/agentcore/evaluations/run` | POST | Trigger on-demand evaluation |
| `/api/agentcore/evaluations/config` | GET | Get evaluation config status |

All evaluation endpoints apply credential scrubbing to response data.

---

## Platform Governance

Synth's platform module provides centralized management of AgentCore resources — gateways, runtimes, and memory — across multiple AWS accounts. This goes beyond deployment: you manage the full lifecycle of gateway targets, provision runtime and memory resources, enforce policies, and track state across environments.

```bash
pip install synth-agent-sdk[platform]
```

### Resource Management

Publish and manage three resource types on AgentCore gateways:

```bash
# Register tool targets on gateways (Lambda, OpenAPI, Smithy, MCP Server)
synth platform publish tool-manifest.yaml --env production

# Provision runtime resources (agent runtimes with VPC, session config)
synth platform publish runtime-manifest.yaml --env staging --resource-type runtime

# Provision memory resources (conversation memory with encryption, strategies)
synth platform publish memory-manifest.yaml --env production --resource-type memory
```

Tool publishing uses idempotent create-or-update semantics — existing targets with the same name are replaced. Cross-account gateways are handled automatically via STS AssumeRole.

### Governance and Policy

```bash
synth platform plan --env staging              # Show pending changes (desired vs current)
synth platform status --env production         # Current deployment state across accounts
synth platform doctor --resources --env prod   # Detect drift between state and live AWS
synth platform discover --env production       # List tools available on configured gateways
synth platform bootstrap --account 123456789012 --region us-east-1  # Cross-account IAM setup
synth platform apply --plan plan.json          # Execute a previously generated plan
synth platform ui                              # Launch Platform Space dashboard
```

Key capabilities:
- Declarative resource manifests (`ToolManifest`, `RuntimeManifest`, `MemoryManifest`) with schema validation
- Gateway target management — register, update, and delete MCP gateway targets across accounts
- Runtime provisioning — create agent runtimes with VPC configuration, session TTL, and protocol settings
- Memory provisioning — create managed memory with encryption, expiry policies, and retrieval strategies
- Policy-as-code validation with Cedar authorization policies deployed after target registration
- Role-based access control with config file integrity verification (hash-based tamper detection)
- Infrastructure drift detection comparing recorded state against live AWS resources
- Deployment planning with desired-vs-current state diffs and pipeline mode (webhook notifications, plan artifacts)
- Pre-flight verification — three-gate chain (RBAC → policy validation → AWS permission check) before any publish
- Cross-account IAM role bootstrapping for multi-account deployments
- Semantic versioning with rollback support (`synth deploy --rollback`)
- State tracking across environments (local JSON + S3/DynamoDB backends)

The Platform Space browser dashboard (`synth platform ui`) provides observability panels, cost analytics, agent version comparison across environments, and a dedicated Agents view for managing deployed AgentCore agents.

### Platform Space — Agents View

The Agents tab in the Platform Space UI lists all agents deployed to AgentCore in your AWS account, queried live from the control-plane API. No local configuration files are required — any authorised user with AWS credentials can manage agents from any machine.

Each agent card shows:
- Live status from AgentCore (Ready, Creating, Updating, Failed, Deleting)
- Runtime version, region, and description
- Action buttons: Connect, Redeploy, Destroy, Refresh

| Action | What It Does | Requires Local Config? |
|--------|-------------|----------------------|
| Connect | Opens an inline chat to send messages to the agent | No |
| Redeploy | Restarts the runtime with the existing deployed artifact | No |
| Destroy | Deletes the agent runtime, endpoint, and associated resources | No |
| Deploy new code | Packages and pushes new source code to AgentCore | Yes (terminal only) |

The Connect button is disabled when the agent status is not `Ready`. Redeploy uses the `UpdateAgentRuntime` API to re-trigger deployment of the current artifact — it does not require source code or the AgentCore CLI config. To deploy new code changes, use `synth deploy --target agentcore` from a terminal with the source.

---

## Agent Registry and Lifecycle

Synth maintains a persistent registry of deployed agents at `~/.synth/registry.json` (owner-only permissions) for tracking and managing agent deployments.

```bash
synth agents list                    # Table with live AgentCore status
synth agents status my-agent         # Detailed agent info
synth agents start my-agent          # Start a deployed agent
synth agents stop my-agent           # Stop a deployed agent
synth agents destroy my-agent        # Tear down and remove from registry
```

The `list` command queries AgentCore for live status and merges with the local registry and `.bedrock_agentcore/` metadata. Agents deployed from other machines or by teammates are visible.

### UI vs Terminal

| Operation | Platform Space UI | Terminal (CLI) |
|-----------|------------------|----------------|
| List agents with live status | ✓ (Agents tab) | `synth agents list` |
| View agent details | ✓ | `synth agents status <name>` |
| Connect / invoke | ✓ (inline chat) | `agentcore invoke` |
| Redeploy (restart existing artifact) | ✓ | `agentcore launch --auto-update-on-conflict` |
| Deploy new code | — | `synth deploy --target agentcore` |
| Destroy | ✓ | `synth agents destroy <name>` |

The UI operations use the AgentCore control-plane API directly and do not require local configuration files or the AgentCore CLI. Deploying new code changes is the only operation that requires terminal access with the source code.

---

## CDK Infrastructure Constructs

Provision supporting AWS resources alongside AgentCore with composable CDK constructs:

```bash
pip install synth-agent-sdk[cdk]
```

```python
from synth.infra import SynthAgentStack

app = cdk.App()
SynthAgentStack(app, "my-agent-stack", agent_name="my-agent")
```

Individual constructs for fine-grained control:

| Construct | Resource | Key Config |
|-----------|----------|------------|
| `SynthStateTable` | DynamoDB | PAY_PER_REQUEST, PITR enabled, configurable PK/SK |
| `SynthArtifactBucket` | S3 | Encryption, SSL enforced, no public access |
| `SynthTaskQueue` | SQS + DLQ | Configurable visibility timeout, retention |
| `SynthCheckpointStore` | ElastiCache Redis | Durable graph/pipeline checkpointing |
| `SynthAgentGateway` | API Gateway + Cognito | HTTP front-door with auth |
| `SynthJournalTable` | DynamoDB | Durable execution journal storage |
| `SynthCacheRedis` | ElastiCache Redis | Semantic/prompt cache backend |
| `SynthCacheTable` | DynamoDB | Cache backend (serverless alternative to Redis) |
| `SynthKnowledgeSearch` | OpenSearch Serverless | Vector index for RAG knowledge retrieval |
| `SynthDocumentBucket` | S3 | Source document storage for knowledge base |
| `SynthTenantTable` | DynamoDB | Per-tenant metadata and quota tracking |
| `SynthTenantBucket` | S3 | Tenant-scoped artifact and data storage |
| `SynthAuditTable` | DynamoDB | Immutable audit log records with hash chain |
| `SynthAuditBucket` | S3 | Long-term audit archive storage |
| `SynthAuditLogGroup` | CloudWatch Logs | Real-time audit log streaming |
| `SynthWebhookTrigger` | API Gateway | Webhook endpoint for agent triggers |
| `SynthScheduleTrigger` | EventBridge Scheduler | Cron/rate-based agent invocation |
| `SynthEventTrigger` | EventBridge Rule | Event-pattern-based agent invocation |
| `SynthSQSTrigger` | SQS + Lambda | Queue-driven agent invocation |
| `SynthCanaryDeployment` | Lambda + CloudWatch | Weighted traffic splitting with rollback alarms |

`SynthEnterpriseStack` composes all enterprise constructs with boolean enable flags:

```python
from synth.infra import SynthEnterpriseStack

SynthEnterpriseStack(app, "enterprise",
    agent_name="my-agent",
    enable_durable=True,
    enable_cache=True,
    enable_knowledge=True,
    enable_tenancy=True,
    enable_audit=True,
    enable_triggers=True,
    enable_canary=True,
)
```

All constructs export SSM parameters for runtime discovery and default to `RemovalPolicy.RETAIN`. Each exposes `grant_*()` methods for least-privilege IAM wiring.

---

# Reference

---

## Error Handling

All Synth errors inherit from `SynthError` and include `component` and `suggestion` fields.

| Error | When |
|-------|------|
| `SynthConfigError` | Missing API key, invalid model, missing provider package |
| `ToolDefinitionError` | `@tool` missing type annotations or docstring |
| `ToolExecutionError` | Tool function raised an exception |
| `GuardViolationError` | A guard check failed |
| `CostLimitError` | Cost guard limit exceeded |
| `RateLimitViolationError` | Rate limit guard threshold exceeded |
| `SynthParseError` | Structured output couldn't be parsed after retries |
| `GraphRoutingError` | No edge condition matched at a graph node |
| `GraphLoopError` | Graph exceeded `max_iterations` |
| `RunNotFoundError` | No checkpoint found for the given `run_id` |
| `PipelineError` | A pipeline stage failed |
| `MCPConnectionError` | Failed to connect to an MCP server |
| `MCPToolError` | An MCP tool invocation failed |
| `VCRMismatchError` | VCR replay diverged from recorded conversation |
| `A2AProtocolError` | A2A protocol communication or payload error |
| `SecretResolutionError` | Failed to resolve secret from Secrets Manager/SSM |
| `RegistryError` | Agent registry file I/O or validation failure |
| `DurableExecutionError` | Durable journal write/replay failure |
| `CacheError` | Cache read/write or similarity computation failure |
| `KnowledgeRetrievalError` | Knowledge retriever failed to fetch or rank documents |
| `TenantIsolationError` | Tenant context missing or cross-tenant access violation |
| `TriggerConfigError` | Invalid trigger configuration or event source setup |
| `AuditLogError` | Audit record write failure or hash chain integrity violation |

```python
from synth.errors import SynthConfigError, ToolExecutionError, GuardViolationError

try:
    result = agent.run("Do something risky.")
except GuardViolationError as e:
    print(f"Guard '{e.guard_name}' blocked: {e.remediation}")
except ToolExecutionError as e:
    print(f"Tool '{e.tool_name}' failed: {e.original_error}")
except SynthConfigError as e:
    print(f"Config issue in {e.component}: {e.suggestion}")
```

---

## Environment Variables

| Variable | Purpose | Required? |
|----------|---------|-----------|
| `ANTHROPIC_API_KEY` | Anthropic Claude API key | Only for `claude-*` models |
| `OPENAI_API_KEY` | OpenAI GPT API key | Only for `gpt-*` models |
| `GOOGLE_API_KEY` | Google Gemini API key | Only for `gemini-*` models |
| `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` | AWS credentials for Bedrock/AgentCore | Only for `bedrock/*` (or use IAM) |
| `AWS_PROFILE` | Named AWS profile for credential resolution | No (auto-detected) |
| `SYNTH_TRACE_ENDPOINT` | HTTPS URL of an OTel collector | No |
| `AGENTCORE_MEMORY_ENDPOINT` | AgentCore managed memory API URL | Auto-set in AgentCore runtime |
| `AGENTCORE_MEMORY_ID` | AgentCore memory instance ID | Auto-set in AgentCore runtime |
| `LANGFUSE_PUBLIC_KEY` / `LANGFUSE_SECRET_KEY` | Langfuse trace exporter credentials | Only with `LangfuseExporter` |
| `DD_AGENT_HOST` / `DD_TRACE_AGENT_PORT` | Datadog agent connection | Only with `DatadogExporter` |
| `HONEYCOMB_API_KEY` / `HONEYCOMB_DATASET` | Honeycomb trace exporter | Only with `HoneycombExporter` |
| `GITHUB_TOKEN` | GitHub API token for `github_toolkit` | Only with synth-tools GitHub |
| `SYNTH_NO_BANNER` | Set to `1` to skip the boot sequence | No |
| `SYNTH_AGENT_FILE` | Agent file path for UI server | Auto-set by `synth ui` |
| `NO_COLOR` | Disable colored terminal output | No |

---

## FAQ

**Do I need an API key?**
Yes, for cloud models. Ollama runs locally and needs no key.

**Can I use Synth in Jupyter?**
Yes. Synth detects an existing event loop and handles it automatically.

**How do I switch models?**
Change the `model` string. Install the matching extra and set the API key.

**What if the provider is down?**
Synth retries on HTTP 429 and 5xx with exponential backoff. Configure with `max_retries` and `retry_backoff`. For automatic failover, use `fallback=["gpt-4o", "claude-haiku-3-5"]` to try alternative models.

**Can I use multiple models in one app?**
Yes. Each `Agent` has its own model. Use `synth init` with the `multi` project type to scaffold a multi-agent project with orchestration built in. Use `AgentTool` to compose agents hierarchically.

**How do I test my agent without API keys?**
Use `TestModel` for deterministic unit tests, `FunctionModel` for custom test logic, or `VCRRecorder` to replay recorded interactions. All available from `synth.testing`.

**How do I connect to MCP servers?**
Use `MCPClient` with a URL or command: `mcp = MCPClient("https://mcp.example.com"); await mcp.connect(); agent = Agent(tools=[mcp])`. Install with `pip install synth-agent-sdk[mcp]`.

**How do I run graph nodes in parallel?**
Add multiple unconditional edges from the same source node. Synth auto-detects the fan-out and runs targets concurrently with isolated state copies.

**How do I test my agent in a browser?**
Run `synth create ui my-dashboard` or choose `ui` as the testing mode during `synth init`. This gives you a full dashboard with streaming chat, real-time flow visualization, telemetry, prompt library, A/B testing, evals, session replay, and scenario builder at `http://localhost:8420`. For multi-agent projects, the dashboard auto-detects your orchestration pattern and shows real-time agent delegation with per-agent tool calls, output, and cost.

**How do I debug what my agent is doing?**
Use `result.trace.show()` for a visual timeline, or `synth dev my_agent.py` for an interactive terminal UI with `/trace` command.

**Is my data secure?**
Synth never logs or serializes API keys. Guards run before side-effecting operations. Checkpoints use JSON only. All provider calls use HTTPS. Deployment artifacts are scanned for credential patterns before packaging.

**What are the core dependencies?**
`pydantic`, `httpx`, `click`, `typing-extensions`, `rich`, `prompt-toolkit`. Provider SDKs are optional extras.

**How do I send traces to Langfuse/Datadog/Honeycomb?**
Use `trace_exporter` on Agent: `Agent(model="claude-sonnet-4-5", trace_exporter=LangfuseExporter())`. Install the matching extra (`synth-agent-sdk[langfuse]`, etc.) and set the required env vars.

**What is synth-tools?**
A companion package with 50+ pre-built tools for SQL, vector stores, web scraping, GitHub, Slack, cloud storage, and more. Install with `pip install synth-tools[all]` or `pip install synth-agent-sdk[toolkit]`.

**How do I communicate with other A2A agents?**
Use `A2AClientTool` to consume remote A2A agents as tools, or `A2AServerAdapter` to expose your agent as an A2A endpoint.

**What is Platform Governance?**
The `synth platform` CLI group manages AgentCore resources across AWS accounts — publishing tools to gateways, validating Cedar policies, detecting drift, and planning deployments. Install with `pip install synth-agent-sdk[platform]`.

**How do I manage deployed agents?**
Use `synth agents list` to see all deployed agents with live status, `synth agents start/stop` for lifecycle control, and `synth deploy --rollback` to revert to a previous version. The Platform Space UI (`synth platform ui`) provides the same management capabilities — view status, redeploy, connect, and destroy — without needing local configuration files. Deploying new code is the only operation that requires terminal access.

**How do I benchmark different models?**
Use `synth bench my_agent.py "prompt" --compare --models "claude-sonnet-4-5,gpt-4o"` to compare latency, cost, and token usage across models.

---

## License

MIT
