Skip to content

Commit 500145f

Browse files
feat: universal Studio adapter architecture for any agent framework
- Add StudioAdapter ABC with capabilities, graph, streaming, state, and history methods - Add LangGraphAdapter with full graph topology, SSE streaming, checkpointer, and breakpoints - Add GenericAdapter with trace-based SSE, synthetic graphs, and in-memory state/history - Add @studio_graph, @studio_state, @studio_stream decorators for incremental opt-in - Add adapter factory (resolve_adapter) with automatic framework detection - Refactor router.py and run_tracker.py to delegate all framework-specific logic to adapters - Update studio __init__.py with new public API exports - Rewrite docs/guide/studio.md with full adapter documentation and decorator examples
1 parent dc89aa2 commit 500145f

9 files changed

Lines changed: 1318 additions & 332 deletions

File tree

docs/guide/studio.md

Lines changed: 134 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,167 @@
11
# Agentomatic Studio 🎨
22

3-
Agentomatic provides a built-in visual development environment specifically designed to help you debug, inspect, and trace the execution of your agents in real-time. It completely removes the guesswork from debugging multi-agent graphs and provides native support for features like Time-Travel and Conditional Breakpoints.
3+
Agentomatic provides a built-in visual development environment specifically designed to help you debug, inspect, and trace the execution of your agents in real-time. It works with **any agent framework** — LangGraph, LangChain, CrewAI, AutoGen, or raw Python — via a universal adapter system.
44

55
## Quick Start
66

7-
You don't need to configure a separate repository or run separate containers. The studio is bundled directly into the `agentomatic` pip package.
8-
9-
To install the optional studio dependencies and launch the server:
7+
The studio is bundled directly into the `agentomatic` pip package. No separate setup required.
108

119
```bash
1210
pip install "agentomatic[studio]"
1311
agentomatic run --studio
1412
```
1513

16-
The unified platform will start serving both your API endpoints at `http://localhost:8000` and the Studio UI at `http://localhost:8000/studio/ui/`.
14+
The unified platform starts serving your API endpoints at `http://localhost:8000` and the Studio UI at `http://localhost:8000/studio/ui/`.
15+
16+
---
17+
18+
## Framework Support
19+
20+
Agentomatic Studio uses a **universal adapter system** to provide the best possible debugging experience for every agent:
21+
22+
| Capability | LangGraph | LangChain / Custom | With Decorators |
23+
|---|:---:|:---:|:---:|
24+
| Graph Topology | ✅ Real graph | ✅ Synthetic linear | ✅ Custom graph |
25+
| SSE Node Streaming |`astream_events` | ✅ Trace-based | ✅ Custom stream |
26+
| Time-Travel History | ✅ Checkpointer | ✅ In-memory traces | ✅ In-memory traces |
27+
| State Inspection | ✅ Checkpointer | ✅ Last I/O capture | ✅ Custom provider |
28+
| State Mutation |`aupdate_state` | ⚠️ In-memory only | ⚠️ In-memory only |
29+
| Breakpoints |`interrupt_before` |||
30+
| HITL Support | ✅ Native |||
1731

1832
---
1933

2034
## Key Features
2135

2236
### 1. Live Node Streaming
23-
When you execute an agent query via the chat interface, the **Graph View** maps directly to your agent's underlying LangGraph topology. As the execution progresses, nodes will visually pulse and light up.
2437

25-
- Server-Sent Events (SSE) stream the node transitions directly from the backend `astream_events` API.
26-
- You can follow complex conditional edges and loops perfectly.
38+
When you execute an agent query, the **Graph View** maps directly to your agent's topology. As the execution progresses, nodes pulse and light up in real-time.
39+
40+
- **LangGraph agents**: Server-Sent Events stream node transitions directly from `astream_events`.
41+
- **Other agents**: The generic adapter wraps execution with trace events that capture timing, input/output payloads, and exceptions.
2742

2843
### 2. Time-Travel Debugging
29-
Debugging deeply nested loops or non-deterministic agent flows can be difficult. Agentomatic natively records every step using the checkpointer.
3044

31-
- **History View**: Inside the Debug Console, the **Time Travel** tab lists all past checkpoints for the active thread.
32-
- **Replay**: Click **"Replay from here"** on any historical snapshot to branch your thread. The execution will perfectly resume from that prior state, allowing you to try a different input or test a different edge path.
45+
Agentomatic records every execution step for historical replay.
46+
47+
- **History View**: The **Time Travel** tab lists all past checkpoints (LangGraph) or execution traces (other frameworks).
48+
- **Replay**: Click **"Replay from here"** on any snapshot to branch your thread and resume from that state.
3349

3450
### 3. Conditional Breakpoints
35-
You can freeze execution right before an agent performs a critical action (like calling an external API or making a final decision).
3651

37-
- **Setting Breakpoints**: Right-click any node in the Graph View and select **"Add Breakpoint"**.
38-
- **Execution**: When the graph reaches that node, it will pause. The node will pulse, and the backend checkpointer will suspend the thread.
39-
- **Resuming**: You can either resume execution, or edit the state before continuing.
52+
Freeze execution before a critical node (LangGraph only).
53+
54+
- **Setting Breakpoints**: Right-click any node in the Graph View → **"Add Breakpoint"**.
55+
- **Execution**: The graph pauses before the target node. The node pulses, and the thread is suspended.
56+
- **Resuming**: Resume execution or edit the state before continuing.
4057

4158
### 4. Live State Editing
42-
While the graph is suspended (either due to a breakpoint or a `Human-in-the-loop` interrupt), you can manually mutate the internal LangGraph state payload.
4359

44-
- **State View**: In the Debug Console, navigate to the **State** tab to see your `StateGraph` variables in real-time.
45-
- **Editing**: Click **"Edit State"**, modify the JSON directly, and click **"Save"**.
46-
- **Backend Mutate**: The studio fires an update command which natively triggers `graph.aupdate_state(config, values)`. When you resume execution, the agent will use your injected state!
60+
During a breakpoint pause or HITL interrupt, you can mutate the graph state.
61+
62+
- **State View**: Navigate to the **State** tab in the Debug Console.
63+
- **Editing**: Click **"Edit State"**, modify the JSON, and click **"Save"**.
64+
- **LangGraph**: Changes are persisted via `graph.aupdate_state()`.
65+
- **Other frameworks**: Changes are stored in the in-memory trace store.
66+
67+
---
68+
69+
## Studio Decorators
70+
71+
For non-LangGraph agents, you can incrementally opt-in to richer Studio capabilities using decorators. These let you provide custom graph topologies, state providers, and stream functions.
72+
73+
### `@studio_graph`
74+
75+
Register a custom graph topology for your agent:
76+
77+
```python
78+
from agentomatic.studio import studio_graph
79+
80+
@studio_graph
81+
def my_topology():
82+
return {
83+
"nodes": [
84+
{"id": "__start__", "name": "Start", "type": "start"},
85+
{"id": "fetch_data", "name": "Fetch Data", "type": "tool"},
86+
{"id": "process", "name": "Process", "type": "agent"},
87+
{"id": "validate", "name": "Validate", "type": "condition"},
88+
{"id": "__end__", "name": "End", "type": "end"},
89+
],
90+
"edges": [
91+
{"source": "__start__", "target": "fetch_data"},
92+
{"source": "fetch_data", "target": "process"},
93+
{"source": "process", "target": "validate"},
94+
{"source": "validate", "target": "__end__", "condition": "valid"},
95+
{"source": "validate", "target": "process", "condition": "retry"},
96+
]
97+
}
98+
```
99+
100+
### `@studio_state`
101+
102+
Register a custom state provider:
103+
104+
```python
105+
from agentomatic.studio import studio_state
106+
107+
@studio_state
108+
async def get_my_state(thread_id: str) -> dict:
109+
"""Return the current state for a thread."""
110+
return await my_database.get_thread_state(thread_id)
111+
```
112+
113+
### `@studio_stream`
114+
115+
Register a custom SSE event stream:
116+
117+
```python
118+
from agentomatic.studio import studio_stream
119+
from agentomatic.studio.models import StudioRunEvent
120+
121+
@studio_stream
122+
async def my_streamer(state, config, breakpoints):
123+
yield StudioRunEvent(event="node_start", run_id="", timestamp="...", node="my_node")
124+
result = await my_agent.process(state)
125+
yield StudioRunEvent(event="node_end", run_id="", timestamp="...", node="my_node", data={"output": result})
126+
```
47127

48128
---
49129

50-
## How It Works Under The Hood
130+
## Architecture
51131

52-
The Agentomatic Studio is built on React and communicates via standard Agentomatic APIs mounted under the `/studio/` router.
132+
The Studio uses a layered adapter architecture:
53133

54-
- **Topology Extraction**: `GET /studio/agents/{name}/graph` extracts your node mapping via `graph.get_graph().to_json()`.
55-
- **SSE Tracking**: `POST /studio/agents/{name}/runs/stream` wraps your execution config with any active breakpoints (`interrupt_before_nodes`). It streams all `on_chat_model_stream`, `on_tool_start`, and custom node events back to the UI.
56-
- **State Patching**: `POST /studio/agents/{name}/threads/{tid}/state` patches the checkpoint database dynamically.
134+
```
135+
┌──────────────────────────────────────────────────────┐
136+
│ Studio Router │
137+
│ (FastAPI endpoints at /studio/) │
138+
└──────────────────┬───────────────────────────────────┘
139+
│ resolve_adapter(agent)
140+
┌──────────────┼──────────────────┐
141+
│ │ │
142+
▼ ▼ ▼
143+
┌────────┐ ┌──────────┐ ┌──────────────┐
144+
│LangGraph│ │ Generic │ │ Custom │
145+
│Adapter │ │ Adapter │ │ Adapter │
146+
│ (full) │ │ (traces) │ │(user-defined)│
147+
└────────┘ └──────────┘ └──────────────┘
148+
```
57149

58-
There is no "mock" data — the studio gives you 100% accurate insights into how your code operates in production.
150+
- **Studio Router**: Framework-agnostic FastAPI endpoints.
151+
- **Adapter Factory**: Automatically selects the best adapter based on the agent's configuration.
152+
- **LangGraphAdapter**: Full-featured — uses `CompiledGraph` APIs natively.
153+
- **GenericAdapter**: Trace-based — wraps `node_fn()` with timing and I/O capture.
154+
- **Custom Adapter**: User-registered via decorators or direct assignment.
155+
156+
### API Endpoints
157+
158+
| Endpoint | Method | Description |
159+
|---|---|---|
160+
| `/studio/info` | GET | Platform metadata and capabilities |
161+
| `/studio/agents` | GET | List agents with capabilities |
162+
| `/studio/agents/{name}/graph` | GET | Graph topology (real or synthetic) |
163+
| `/studio/agents/{name}/schemas` | GET | Input/output JSON schemas |
164+
| `/studio/agents/{name}/runs/stream` | POST | SSE-streamed execution |
165+
| `/studio/agents/{name}/threads/{tid}/state` | GET | Thread state snapshot |
166+
| `/studio/agents/{name}/threads/{tid}/state` | POST | Update thread state |
167+
| `/studio/agents/{name}/threads/{tid}/history` | GET | Checkpoint/trace history |

src/agentomatic/studio/__init__.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,37 @@
1-
"""Agentomatic Studio — Debug & inspection API for frontend tooling.
1+
"""Agentomatic Studio — Universal Debug & Inspection API.
22
33
Provides REST + SSE endpoints for graph visualization, agent execution
4-
tracing, state inspection, and checkpoint browsing.
4+
tracing, state inspection, and checkpoint browsing. Works with **any**
5+
agent framework via the adapter system.
56
67
Usage::
78
8-
from agentomatic.studio import GraphInspector, RunTracker
9+
from agentomatic.studio import StudioAdapter, studio_graph, studio_state
910
from agentomatic.studio.router import create_studio_router
11+
from agentomatic.studio.adapters import resolve_adapter
1012
1113
router = create_studio_router(registry=registry, store=store)
1214
"""
1315

1416
from __future__ import annotations
1517

18+
from agentomatic.studio.adapter import StudioAdapter
19+
from agentomatic.studio.decorators import (
20+
register_studio_hooks,
21+
studio_graph,
22+
studio_state,
23+
studio_stream,
24+
)
1625
from agentomatic.studio.graph_inspector import GraphInspector
1726
from agentomatic.studio.run_tracker import RunTracker
1827

1928
__all__ = [
2029
"GraphInspector",
2130
"RunTracker",
31+
"StudioAdapter",
32+
"register_studio_hooks",
33+
"studio_graph",
34+
"studio_state",
35+
"studio_stream",
2236
]
37+

src/agentomatic/studio/adapter.py

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
"""Universal adapter protocol for Agentomatic Studio.
2+
3+
Any agent framework can implement this protocol to unlock the full
4+
Studio debugging experience. See :class:`LangGraphAdapter` for the
5+
reference implementation and :class:`GenericAdapter` for the fallback.
6+
"""
7+
8+
from __future__ import annotations
9+
10+
from abc import ABC, abstractmethod
11+
from collections.abc import AsyncGenerator
12+
from typing import TYPE_CHECKING, Any
13+
14+
if TYPE_CHECKING:
15+
from agentomatic.studio.models import (
16+
StudioCheckpoint,
17+
StudioGraphTopology,
18+
StudioRunEvent,
19+
StudioStateSnapshot,
20+
)
21+
22+
23+
class StudioAdapter(ABC):
24+
"""Abstract base class defining the universal Studio interface.
25+
26+
Every adapter must implement these methods. Methods that are not
27+
supported by the underlying framework should return sensible
28+
defaults (empty lists, ``None``, etc.) rather than raising.
29+
30+
Attributes:
31+
agent_name: The machine name of the agent this adapter wraps.
32+
"""
33+
34+
def __init__(self, agent_name: str) -> None:
35+
self.agent_name = agent_name
36+
37+
# ------------------------------------------------------------------
38+
# Capabilities
39+
# ------------------------------------------------------------------
40+
41+
@property
42+
@abstractmethod
43+
def capabilities(self) -> list[str]:
44+
"""Return the list of Studio capabilities this adapter supports.
45+
46+
Possible values: ``'graph'``, ``'streaming'``, ``'checkpoints'``,
47+
``'state'``, ``'breakpoints'``, ``'hitl'``, ``'traces'``.
48+
"""
49+
...
50+
51+
@property
52+
def supports_graph(self) -> bool:
53+
"""Whether this adapter can provide a real execution graph."""
54+
return "graph" in self.capabilities
55+
56+
@property
57+
def supports_checkpoints(self) -> bool:
58+
"""Whether this adapter supports checkpoint-based time-travel."""
59+
return "checkpoints" in self.capabilities
60+
61+
@property
62+
def supports_state_mutation(self) -> bool:
63+
"""Whether this adapter supports live state editing."""
64+
return "state" in self.capabilities
65+
66+
@property
67+
def supports_breakpoints(self) -> bool:
68+
"""Whether this adapter supports conditional breakpoints."""
69+
return "breakpoints" in self.capabilities
70+
71+
# ------------------------------------------------------------------
72+
# Graph topology
73+
# ------------------------------------------------------------------
74+
75+
@abstractmethod
76+
async def get_graph(self) -> StudioGraphTopology:
77+
"""Return the agent's execution graph topology.
78+
79+
Returns:
80+
A :class:`StudioGraphTopology` describing the nodes and edges.
81+
For adapters that don't support real graphs, return a synthetic
82+
linear topology.
83+
"""
84+
...
85+
86+
# ------------------------------------------------------------------
87+
# Execution streaming
88+
# ------------------------------------------------------------------
89+
90+
@abstractmethod
91+
async def stream_execution(
92+
self,
93+
state: dict[str, Any],
94+
config: dict[str, Any] | None = None,
95+
breakpoints: list[str] | None = None,
96+
checkpoint_id: str | None = None,
97+
) -> AsyncGenerator[StudioRunEvent, None]:
98+
"""Execute the agent and yield events as they occur.
99+
100+
Args:
101+
state: Initial state dict to pass to the agent.
102+
config: Optional execution configuration (e.g. thread_id).
103+
breakpoints: Optional list of node names to pause before.
104+
checkpoint_id: Optional checkpoint to resume from.
105+
106+
Yields:
107+
:class:`StudioRunEvent` instances for each notable step.
108+
"""
109+
...
110+
111+
# ------------------------------------------------------------------
112+
# State inspection
113+
# ------------------------------------------------------------------
114+
115+
@abstractmethod
116+
async def get_state(self, thread_id: str) -> StudioStateSnapshot | None:
117+
"""Retrieve the latest state for a thread.
118+
119+
Args:
120+
thread_id: The conversation thread identifier.
121+
122+
Returns:
123+
A :class:`StudioStateSnapshot`, or ``None`` if state
124+
inspection is not supported.
125+
"""
126+
...
127+
128+
@abstractmethod
129+
async def update_state(
130+
self,
131+
thread_id: str,
132+
updates: dict[str, Any],
133+
) -> StudioStateSnapshot | None:
134+
"""Apply a partial state update to a thread.
135+
136+
Args:
137+
thread_id: The conversation thread identifier.
138+
updates: Key-value pairs to merge into the current state.
139+
140+
Returns:
141+
The updated :class:`StudioStateSnapshot`, or ``None`` if
142+
state mutation is not supported.
143+
"""
144+
...
145+
146+
# ------------------------------------------------------------------
147+
# Checkpoint history
148+
# ------------------------------------------------------------------
149+
150+
@abstractmethod
151+
async def get_history(self, thread_id: str) -> list[StudioCheckpoint]:
152+
"""Return the checkpoint history for a thread.
153+
154+
Args:
155+
thread_id: The conversation thread identifier.
156+
157+
Returns:
158+
A list of :class:`StudioCheckpoint` objects, newest first.
159+
Returns an empty list if checkpoints are not supported.
160+
"""
161+
...

0 commit comments

Comments
 (0)