Introduction
Agent Studio is a full-stack platform for building, testing, and deploying AI agent workflows. It provides a visual workflow builder backed by a gRPC API, a workflow engine supporting CrewAI and LangGraph frameworks, and an embedded observability layer powered by Arize Phoenix. Workflows are composed of agents, tasks, tools, and MCP servers — packaged as portable templates that can be imported, exported, and deployed as production endpoints.
This guide serves two audiences:
| If you are… | Start here |
|---|---|
| Building an agent harness for rapid template development (visual canvas, telemetry, gRPC) | Architecture Reference |
| Building a validation SDK to ensure template ZIPs conform to the expected format | Template Specification and Validation Rules |
Terminology
| Term | Definition |
|---|---|
| Workflow Template | A portable, reusable blueprint for an entire workflow — aggregates agent, task, tool, and MCP templates. Exported/imported as ZIP files. |
| Tool Template | A Python package (tool.py + requirements.txt) that agents can invoke at runtime. Defines configuration parameters and invocation arguments via Pydantic models. |
| Agent Template | A reusable agent definition with role, backstory, goal, and references to its tools and MCP servers. Maps to a CrewAI Agent. |
| Task Template | A unit of work assigned to an agent, with a description and expected output. Execution order is defined by position in the workflow’s task list. |
| MCP Template | A Model Context Protocol server configuration — type (Python or Node), startup arguments, and required environment variables. |
| CollatedInput | The runtime data structure that fully describes a workflow for execution — language models, agents, tasks, tools, MCP servers, and the workflow itself. |
| Deployment Artifact | A tar.gz archive containing a workflow.yaml, collated_input.json, and all required tool code. Consumed by the workflow engine. |
| Workflow Engine | A separate Python package (FastAPI/Uvicorn) that executes workflows. Runs as sidecar processes on ports 51000+. |
Template Lifecycle
The upstream SDK’s responsibility ends at validation — ensuring the ZIP conforms to the structure documented in the Template Specification chapters. Agent Studio handles instantiation, testing, and deployment.
System Overview
Agent Studio is composed of four primary subsystems that communicate over well-defined boundaries. Understanding this topology is essential for building an external harness that mirrors the same development experience.
Component Topology
Entity Hierarchy
The core data model has two parallel hierarchies — instances (live workflow state) and templates (portable blueprints):
Templates are the packaging unit. When a workflow template is imported, Agent Studio creates instances from the templates. When exported, instances are snapshot back into templates.
Data Flow
File-Based Storage: studio-data/
Alongside the SQLite database, Agent Studio uses a filesystem tree for code and assets:
studio-data/
├── tool_templates/ Tool code packages (tool.py + requirements.txt)
│ ├── sample_tool/
│ ├── json_reader_abc123/
│ └── calculator_def456/
├── dynamic_assets/ Icons for templates and instances
│ ├── tool_template_icons/
│ ├── tool_instance_icons/
│ ├── agent_template_icons/
│ ├── agent_icons/
│ ├── mcp_template_icons/
│ └── mcp_instance_icons/
├── workflows/ Per-workflow working directories
├── deployable_workflows/ Staged deployment artifacts
├── deployable_applications/ Staged application artifacts
└── temp_files/ Transient upload/export scratch space
The studio-data/ path is a hard-coded constant (consts.ALL_STUDIO_DATA_LOCATION = "studio-data"). All file references stored in the database (e.g., source_folder_path, tool_image_path) are relative to the project root and rooted under this directory.
Design Principles for Harness Builders
- Proto-first API: All backend operations are defined in protobuf. The gRPC service is the single source of truth for capabilities.
- Thin service layer:
service.pyis a router — business logic lives in domain modules. This pattern is easy to replicate. - Dual storage: SQLite for metadata, filesystem for code artifacts. Template ZIPs bridge both by bundling the manifest JSON and the
studio-data/subtree. - Isolated execution: The workflow engine is a separate package with its own virtualenv. This ensures tool dependencies don’t conflict with the studio itself.
- Observable by default: Every workflow execution produces OTel traces and a structured event stream, enabling the visual canvas to show real-time progress.
Visual Canvas (XYFlow)
Agent Studio renders workflows as interactive directed graphs using XYFlow (@xyflow/react, formerly ReactFlow). An external harness that aims to provide a similar visual development experience should implement a compatible node/edge model.
Node Types
The canvas uses four node types, each with distinct visual styling and behavior:
| Node Type | Visual | Purpose |
|---|---|---|
task | Light green | A CrewAI task — displays truncated description. Connected left-to-right in execution order. |
agent | White/light blue | A CrewAI agent — displays name and optional icon. Pulsing animation when active during execution. |
tool | Dark gray | A tool instance attached to an agent — displays name and optional icon. |
mcp | Dark gray | An MCP server instance attached to an agent — displays name, icon, and tool list. |
Data Model
The diagram state is defined as:
interface DiagramState {
nodes: Node[]; // XYFlow Node objects with type, position, data
edges: Edge[]; // XYFlow Edge objects with source, target, handles
hasCustomPositions?: boolean; // Preserves user drag-to-reposition
}
Each node’s data field carries entity-specific metadata:
// Task node data
{ label, name, taskId, taskData, isConversational }
// Agent node data
{ label, name, iconData, agentId, agentData, manager?, isDefaultManager? }
// Tool node data
{ label, name, iconData, workflowId, toolInstanceId, agentId, agentTools }
// MCP node data
{ name, iconData, active, toolList, activeTool, mcpInstanceId, agentId, workflowId }
Layout Algorithm
The layout is deterministic and derived from workflow structure:
Sequential mode: Tasks connect directly down to their assigned agents.
Hierarchical mode: Tasks connect to a manager agent node, which connects down to worker agents.
Agent x-positions are calculated to center the group:
- Sum total width: each agent contributes
220 * max(0, num_tools + num_mcps - 1) + 220 - Start offset:
-0.5 * totalWidth + 110 - Each tool/MCP shifts the offset by
+220
Edge Types
All edges use MarkerType.Arrow with 20x20 size.
| Edge | Source Handle | Target Handle | Condition |
|---|---|---|---|
| Task → Task | right | left | Sequential (adjacent tasks) |
| Task → Agent | bottom | top | Sequential mode |
| Task → Manager | bottom | (default) | Hierarchical mode |
| Manager → Agent | (default) | (default) | Hierarchical mode |
| Agent → Tool | (default) | (default) | Always |
| Agent → MCP | (default) | (default) | Always |
Live Event Overlay
During workflow execution, the canvas receives events from the telemetry pipeline and overlays them onto nodes:
- Events fetched from
/api/workflow/events?trace_id={id} processEvents()extracts active node IDs and event metadata- Active nodes receive:
active: true(triggers CSS pulse animation),info(event text),infoType(event category)
Event info types displayed on nodes:
TaskStart,LLMCall,ToolInput,ToolOutputDelegate,EndDelegate,AskCoworker,EndAskCoworkerCompletion,FailedCompletion
Key Takeaway for Harness Builders
To build an equivalent canvas:
- Parse the
workflow_template.jsonorCollatedInputto extract the entity graph - Apply the layout algorithm above (or use XYFlow’s auto-layout plugins)
- Subscribe to the event stream to overlay live execution state
- Use the
DiagramStateInputinterface as your data contract — it requires: workflow state, icon data map, tasks, tool instances, MCP instances, tool templates, and agents
Telemetry Pipeline (OTel + Phoenix)
Agent Studio uses OpenTelemetry with OpenInference semantic conventions for workflow observability. Traces are collected by an embedded Arize Phoenix instance. An external harness should implement a compatible telemetry pipeline to enable visual execution monitoring.
Architecture
Trace Export
Each workflow execution creates a root span and exports traces to Phoenix:
from phoenix.otel import register
tracer_provider = register(
project_name=workflow_name,
endpoint=f"{ops_endpoint}/v1/traces",
headers={"Authorization": f"Bearer {api_key}"},
)
The trace ID is extracted from the root span as a 32-character hex string:
tracer = tracer_provider.get_tracer("opentelemetry.agentstudio.workflow.model")
with tracer.start_as_current_span(f"Workflow Run: {time}") as span:
trace_id = f"{span.get_span_context().trace_id:032x}"
Framework Instrumentors
Agent Studio uses OpenInference instrumentors to automatically capture framework-level spans:
| Framework | Instrumentor | Captures |
|---|---|---|
| CrewAI | openinference.instrumentation.crewai.CrewAIInstrumentor | Crew, Agent, Task execution spans |
| LiteLLM | openinference.instrumentation.litellm.LiteLLMInstrumentor | LLM API call spans |
| LangChain | openinference.instrumentation.langchain.LangChainInstrumentor | LangGraph node execution spans |
Instrumentation is applied once per runner process:
CrewAIInstrumentor().instrument(tracer_provider=tracer_provider)
LiteLLMInstrumentor().instrument(tracer_provider=tracer_provider)
Structured Event Pipeline
In addition to OTel traces, Agent Studio captures a structured event stream from CrewAI’s event bus. This powers the real-time canvas overlay.
Event Registration
Global handlers are registered on the CrewAI event bus singleton:
from crewai.utilities.events import crewai_event_bus
for event_cls in EVENT_PROCESSORS:
crewai_event_bus.on(event_cls)(post_event)
Event Types
The following events are captured and processed:
| Category | Events |
|---|---|
| Crew lifecycle | CrewKickoffStarted, CrewKickoffCompleted, CrewKickoffFailed |
| Agent execution | AgentExecutionStarted, AgentExecutionCompleted, AgentExecutionError |
| Task execution | TaskStarted, TaskCompleted, TaskFailed |
| Tool usage | ToolUsageStarted, ToolUsageFinished, ToolUsageError |
| LLM calls | LLMCallStarted, LLMCallCompleted, LLMCallFailed |
Each event is processed by a type-specific extractor that selects relevant fields (e.g., tool name, agent ID, error message) and enriched with:
timestamp— event timetype— event class nameagent_studio_id— maps the event to a specific tool/agent instance in the canvas
Event Posting
Events are POSTed as JSON to the Phoenix event broker:
POST {ops_endpoint}/events
Authorization: Bearer {api_key}
Content-Type: application/json
{
"trace_id": "a1b2c3d4...",
"event": {
"timestamp": "2025-01-15T10:30:00",
"type": "tool_usage_started",
"agent_studio_id": "tool-instance-uuid",
"tool_name": "json_reader",
"tool_args": "{\"filepath\": \"data.json\"}"
}
}
Trace Context Propagation
Each async workflow task maintains its own trace context via Python contextvars. This allows the global event handlers (shared across all concurrent workflows on a single runner) to route events to the correct trace:
from contextvars import ContextVar
_trace_id_var: ContextVar[str] = ContextVar("trace_id")
def get_trace_id() -> str:
return _trace_id_var.get()
Harness Implementation Guide
To replicate this telemetry stack in an external harness:
- Deploy Phoenix (or any OTel-compatible collector) as your trace backend
- Register a TracerProvider using
phoenix.otel.register()pointed at your collector - Instrument your framework with the appropriate OpenInference instrumentor
- Implement an event stream — POST structured events keyed by trace ID to your collector
- Poll events from the frontend — the canvas subscribes to
GET /events?trace_id={id}and overlays them onto nodes - Propagate trace context through async task boundaries using contextvars
gRPC Service Design
Agent Studio exposes its backend via a gRPC service defined in Protocol Buffers. For external harnesses, we recommend starting from the Open Inference Protocol (OIP) as your protobuf foundation and evolving gRPC interfaces to support your harness’s specific needs. Agent Studio’s own proto (agent_studio.proto) is an implementation detail — it is tightly coupled to the Cloudera AI platform and should not be adopted directly.
Recommended Starting Point: Open Inference Protocol
OIP defines standard protobuf messages for AI inference traces, spans, and evaluations. It aligns with OpenTelemetry semantic conventions and is already used by Agent Studio’s telemetry layer (via the openinference-instrumentation-* packages).
For a harness that needs to:
- Execute and observe agent workflows → extend OIP trace/span definitions
- Manage templates and deployments → define your own CRUD service RPCs
- Proxy between a frontend and backend → follow the REST-to-gRPC proxy pattern described below
Agent Studio’s Service Architecture (Reference Only)
Agent Studio’s gRPC service is organized into domain-specific RPC groups:
| Domain | Key RPCs | Count |
|---|---|---|
| Models | ListModels, AddModel, TestModel, SetStudioDefaultModel | 8 |
| Tool Templates | ListToolTemplates, AddToolTemplate, UpdateToolTemplate | 5 |
| Tool Instances | ListToolInstances, CreateToolInstance, TestToolInstance | 5 |
| MCP Templates | ListMcpTemplates, AddMcpTemplate, UpdateMcpTemplate | 5 |
| MCP Instances | ListMcpInstances, CreateMcpInstance, UpdateMcpInstance | 5 |
| Agents | ListAgents, AddAgent, UpdateAgent | 5 |
| Tasks | ListTasks, AddTask, UpdateTask | 5 |
| Workflows | ListWorkflows, AddWorkflow, CloneWorkflow, TestWorkflow, DeployWorkflow | 12 |
| Workflow Templates | ListWorkflowTemplates, ExportWorkflowTemplate, ImportWorkflowTemplate | 6 |
| Agent/Task Templates | CRUD for reusable agent and task blueprints | 10+ |
| Deployed Workflows | ListDeployedWorkflows, UndeployWorkflow, Suspend/Resume | 5 |
| Utility | HealthCheck, FileUpload/Download, GetAssetData | 9 |
Total: 125+ RPCs.
Service Pattern
The gRPC service (studio/service.py) acts as a thin router:
class AgentStudioApp(AgentStudioServicer):
def __init__(self):
self.dao = AgentStudioDao()
def ListWorkflows(self, request, context):
return list_workflows(request, cml=self.cml, dao=self.dao)
def ExportWorkflowTemplate(self, request, context):
return export_workflow_template(request, cml=self.cml, dao=self.dao)
Each RPC delegates to a domain function that receives (request, cml, dao). Business logic lives entirely in the domain modules (studio/agents/, studio/tools/, studio/workflow/, etc.).
Key Programmatic Entry Points
For template management, the most important RPCs are:
ExportWorkflowTemplate(id)→ Returns a ZIP file path containing the complete template packageImportWorkflowTemplate(file_path)→ Imports a ZIP, regenerates UUIDs, copies assets, creates DB recordsDeployWorkflow(workflow_id, config)→ Packages and deploys a workflow to a target endpointTestWorkflow(workflow_id, inputs)→ Executes a workflow on an in-studio runner with full telemetry
Frontend Proxy Pattern
The Next.js frontend does not speak gRPC directly. Instead, API routes translate HTTP requests:
The proxy uses slug-based routing — the URL path segment maps to the gRPC method name. This pattern means any REST client can interact with the backend without a gRPC client library.
Additionally, Phoenix traffic is proxied via Next.js rewrites:
/api/ops/* → http://localhost:50052/*
Recommendations for External Harnesses
- Define your own proto starting from OIP trace definitions. Add CRUD RPCs as needed for your entity model.
- Keep the service layer thin — route to domain modules. This makes the service testable and extensible.
- Expose a REST proxy if your frontend doesn’t support gRPC natively. The slug-based pattern is simple and effective.
- Use the Import/Export ZIP format as your interchange format with Agent Studio. The Template Specification chapters document this format exhaustively.
- Don’t depend on
agent_studio.proto— it is versioned for internal use and subject to change without notice.
Template System Concepts
Templates vs Instances
Agent Studio separates templates (reusable blueprints) from instances (live copies within a workflow):
- A tool template is a code package in the template catalog. A tool instance is a copy of that template added to a specific workflow, with its own virtualenv and configuration.
- An agent template defines role/backstory/goal and tool references. An agent instance (just called “agent”) is a copy within a workflow, bound to specific tool instances and an LLM model.
- Task templates and task instances follow the same pattern.
Templates are the unit of portability. When you export a workflow template as a ZIP, the export captures all nested agent, tool, task, and MCP templates. When you import that ZIP into another Agent Studio instance, new templates are created, and a workflow can be instantiated from them.
Entity Graph
A workflow template aggregates all other template types through ID references:
All references are by UUID. The workflow_template.json manifest in a ZIP must maintain referential integrity — every ID referenced must correspond to an entry in the appropriate array.
ID Management
All IDs are UUIDv4 strings. Two critical rules:
- On export: All IDs are regenerated. The exported ZIP contains fresh UUIDs that differ from the database.
- On import: All IDs are regenerated again. The UUIDs in the ZIP are never used as-is in the target database.
This means IDs in a template ZIP are ephemeral — they exist only to establish cross-references between entities within the ZIP. An SDK validator must check that these cross-references are consistent, but the actual UUID values are arbitrary.
Scoping
In the Agent Studio database, templates can be either:
- Global:
workflow_template_idis null. Available to all workflows. - Scoped:
workflow_template_idis set. Tied to a specific workflow template.
In exported ZIPs, all templates are scoped — every tool, agent, task, and MCP template has its workflow_template_id set to the workflow template’s ID. This ensures the import creates a self-contained set of templates.
Process Modes
Workflows support two execution modes, reflected in the process field:
| Mode | Task Assignment | Manager Agent |
|---|---|---|
sequential | Each task has an assigned_agent_template_id. Tasks execute in array order. | Not used. |
hierarchical | Manager agent decides which worker agent handles each task. | Required — either manager_agent_template_id is set, or use_default_manager is true. |
Tool Template Specification
Tool templates are the most complex template type. Each tool is a self-contained Python package that agents invoke at runtime. This chapter is the authoritative reference for building conformant tool templates.
Required Files
Every tool template directory must contain:
{tool_name}/
├── tool.py # REQUIRED: Main entrypoint
└── requirements.txt # REQUIRED: Python dependencies
Optional additional files are permitted:
{tool_name}/
├── tool.py
├── requirements.txt
├── README.md # Documentation
├── helper_module.py # Additional Python modules
└── data/ # Data files the tool uses
└── config.json
tool.py Specification
The entrypoint file must contain all of the following components. Agent Studio validates their presence via Python AST parsing.
1. Module Docstring
"""
Description of what this tool does.
This text is extracted as the tool's description and shown to agents.
"""
The module-level docstring is extracted via ast.get_docstring() and used as the tool’s description in the UI and in agent prompts.
2. UserParameters Class
from pydantic import BaseModel
from typing import Optional
class UserParameters(BaseModel):
"""Configuration parameters set once when the tool is added to a workflow."""
api_key: str # Required parameter
endpoint: Optional[str] = None # Optional parameter with default
- Must inherit from
pydantic.BaseModel - Must be named exactly
UserParameters - Defines configuration that is set once per tool instance (API keys, database URLs, etc.)
- Can be empty (
pass) if no configuration is needed - Agent Studio extracts parameter metadata via AST:
Optional[T]→ marked as not required- Fields with default values → marked as not required
- All other fields → marked as required
3. ToolParameters Class
from pydantic import BaseModel, Field
class ToolParameters(BaseModel):
"""Arguments passed by an agent each time it invokes this tool."""
query: str = Field(description="The search query to execute")
max_results: int = Field(default=10, description="Maximum number of results")
- Must inherit from
pydantic.BaseModel - Must be named exactly
ToolParameters Field(description=...)annotations are passed to the LLM agent as parameter descriptions — these help the agent decide what values to pass- Supported types:
str,int,float,bool,List[T],Optional[T],Literal["val1", "val2"]
4. run_tool Function
from typing import Any
def run_tool(config: UserParameters, args: ToolParameters) -> Any:
"""Main tool logic. Return value is sent back to the calling agent."""
result = do_work(config.api_key, args.query)
return result
- Must be named exactly
run_tool - Receives validated
UserParametersandToolParametersinstances - Return value should be JSON-serializable for the agent to parse
5. OUTPUT_KEY Constant
OUTPUT_KEY = "tool_output"
- Module-level string constant
- When present, only stdout content printed after this key is returned to the agent
- Allows debug/log output before the key without polluting the agent’s response
- Recommended but not strictly required for import — however, without it, all stdout is returned to the agent
6. CLI Entrypoint
import json
import argparse
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--user-params", required=True, help="JSON string of UserParameters")
parser.add_argument("--tool-params", required=True, help="JSON string of ToolParameters")
args = parser.parse_args()
config = UserParameters(**json.loads(args.user_params))
params = ToolParameters(**json.loads(args.tool_params))
output = run_tool(config, params)
print(OUTPUT_KEY, output)
Agent Studio invokes tools as subprocesses:
python tool.py --user-params '{"api_key": "..."}' --tool-params '{"query": "..."}'
Complete Example
"""
A simple JSON reader tool that reads JSON files from a given path
relative to the tool's directory.
"""
from pydantic import BaseModel, Field
from typing import Any
import json
import argparse
from pathlib import Path
import sys
import os
ROOT_DIR = Path(__file__).parent
sys.path.append(str(ROOT_DIR))
os.chdir(ROOT_DIR)
class UserParameters(BaseModel):
pass
class ToolParameters(BaseModel):
filepath: str = Field(
description="The local path to the JSON file to read, relative to the tool's directory."
)
def run_tool(config: UserParameters, args: ToolParameters) -> Any:
filepath = args.filepath
data = json.load(open(filepath))
return json.dumps(data, indent=2)
OUTPUT_KEY = "tool_output"
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--user-params", required=True)
parser.add_argument("--tool-params", required=True)
args = parser.parse_args()
config = UserParameters(**json.loads(args.user_params))
params = ToolParameters(**json.loads(args.tool_params))
output = run_tool(config, params)
print(OUTPUT_KEY, output)
requirements.txt
Standard pip format. pydantic is always required (it’s the validation framework):
pydantic
requests>=2.28.0
numpy==1.24.0
Name Validation
Tool template names must match the regex:
^[a-zA-Z0-9 ]+$
Only alphanumeric characters and spaces are allowed. No special characters, underscores, or hyphens.
Names must be unique within their scope (global or within a specific workflow template).
Directory Naming Convention
When packaged in a ZIP, tool template directories follow this naming pattern:
{slugified_name}_{random_6_chars}
Where slugified_name is the tool name lowercased with spaces replaced by underscores, and the random suffix is a 6-character alphanumeric string. Examples:
json_reader_abc123/
my_api_tool_xk9f2m/
calculator_def456/
Virtual Environment (Runtime)
At runtime, Agent Studio creates a .venv/ inside each tool instance directory and installs dependencies from requirements.txt. The following paths are excluded from template exports:
.venv/__pycache__/.requirements_hash.txt
Testing Locally
Tools can be tested outside Agent Studio:
cd my_tool/
pip install -r requirements.txt
python tool.py \
--user-params '{}' \
--tool-params '{"filepath": "data/sample.json"}'
Expected output:
tool_output {"key": "value", ...}
Agent Template Specification
An agent template defines a reusable AI agent with a persona (role, backstory, goal) and references to the tools and MCP servers it can use. At runtime, agent templates map to CrewAI Agent instances.
Fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
id | string (UUID) | Yes | — | Unique identifier |
workflow_template_id | string (UUID) | No | null | Scopes the template to a workflow template. Set in exported ZIPs. |
name | string | Yes | — | Human-readable agent name |
description | string | No | null | Description of the agent’s purpose |
role | string | No | null | CrewAI role — what the agent does (e.g., “Senior Data Analyst”) |
backstory | string | No | null | CrewAI backstory — context that shapes agent behavior |
goal | string | No | null | CrewAI goal — what the agent is trying to achieve |
allow_delegation | boolean | No | true | Whether this agent can delegate tasks to other agents |
verbose | boolean | No | true | Whether to log detailed execution info |
cache | boolean | No | true | Whether to cache LLM responses |
temperature | float | No | 0.7 | LLM sampling temperature |
max_iter | integer | No | 10 | Maximum reasoning iterations before the agent must produce output |
tool_template_ids | array of UUIDs | No | [] | References to tool templates this agent can use |
mcp_template_ids | array of UUIDs | No | [] | References to MCP templates this agent can use |
pre_packaged | boolean | No | false | Whether shipped as part of the studio. Always false in exports. |
agent_image_path | string | No | “” | Relative path to the agent’s icon within the ZIP |
CrewAI Mapping
When instantiated, agent template fields map to CrewAI’s Agent constructor:
| Template Field | CrewAI Parameter |
|---|---|
role | role |
backstory | backstory |
goal | goal |
allow_delegation | allow_delegation |
verbose | verbose |
cache | cache |
temperature | temperature |
max_iter | max_iter |
Cross-References
- Every UUID in
tool_template_idsmust correspond to an entry in the ZIP’stool_templatesarray. - Every UUID in
mcp_template_idsmust correspond to an entry in the ZIP’smcp_templatesarray. - The agent’s own
idmay be referenced by:workflow_template.agent_template_idsworkflow_template.manager_agent_template_idtask_template.assigned_agent_template_id
JSON Example
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"workflow_template_id": "w9x8y7z6-5432-1098-fedc-ba0987654321",
"name": "Research Analyst",
"description": "Analyzes data and produces research reports",
"role": "Senior Research Analyst",
"backstory": "You are an experienced analyst with expertise in data interpretation.",
"goal": "Produce thorough, accurate research reports based on available data.",
"allow_delegation": true,
"verbose": true,
"cache": true,
"temperature": 0.7,
"max_iter": 10,
"tool_template_ids": ["t1234567-..."],
"mcp_template_ids": [],
"pre_packaged": false,
"agent_image_path": "studio-data/dynamic_assets/agent_template_icons/a1b2c3d4-e5f6-7890-abcd-ef1234567890_icon.png"
}
Task Template Specification
A task template defines a unit of work to be executed by an agent. Tasks are the fundamental building blocks of workflow execution — they define what needs to be done and what output is expected.
Fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
id | string (UUID) | Yes | — | Unique identifier |
workflow_template_id | string (UUID) | No | null | Scopes the template to a workflow template |
name | string | No | null | Human-readable task name |
description | string | No | null | What the agent should do. This is the primary instruction given to the assigned agent. |
expected_output | string | No | null | Description of the expected result format. Guides the agent on what to produce. |
assigned_agent_template_id | string (UUID) | No | null | The agent template responsible for executing this task |
Execution Semantics
Sequential Mode
When workflow_template.process is "sequential":
- Tasks execute in the order they appear in
workflow_template.task_template_ids - Each task should have
assigned_agent_template_idset, pointing to the agent that will execute it - Output from one task may be available as context for subsequent tasks
Hierarchical Mode
When workflow_template.process is "hierarchical":
- A manager agent (specified by
manager_agent_template_idoruse_default_manager) orchestrates execution - The manager decides which worker agent handles each task
assigned_agent_template_idis optional — the manager may override assignments
Cross-References
assigned_agent_template_id, if set, must correspond to an entry in the ZIP’sagent_templatesarray- The task’s
idmust be listed inworkflow_template.task_template_ids - Task order in
task_template_idsdefines execution order for sequential workflows
JSON Example
{
"id": "t1234567-89ab-cdef-0123-456789abcdef",
"workflow_template_id": "w9x8y7z6-5432-1098-fedc-ba0987654321",
"name": "Analyze Sales Data",
"description": "Review the quarterly sales data and identify trends, anomalies, and key insights.",
"expected_output": "A structured report with sections for trends, anomalies, and actionable recommendations.",
"assigned_agent_template_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
MCP Template Specification
An MCP (Model Context Protocol) template defines a configuration for an MCP server that agents can use to access external tools and resources. Agent Studio supports running MCP servers as child processes during workflow execution.
Fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
id | string (UUID) | Yes | — | Unique identifier |
workflow_template_id | string (UUID) | No | null | Scopes the template to a workflow template |
name | string | Yes | — | Human-readable server name |
type | string | Yes | — | Server type: "PYTHON" or "NODE" |
args | array of strings | Yes | — | Command-line arguments for starting the server |
env_names | array of strings | Yes | — | Environment variable names the server requires |
tools | object or null | No | null | MCP-exposed tool definitions. Set to null on export. Populated during import validation. |
status | string | No | “” | Validation status. Set to empty string on export. |
mcp_image_path | string | No | “” | Relative path to the server’s icon within the ZIP |
Server Types
| Type | Runtime | Example |
|---|---|---|
PYTHON | Executed via Python | python -m my_mcp_server |
NODE | Executed via Node.js | npx @modelcontextprotocol/server-filesystem |
Export/Import Behavior
Two fields receive special treatment during the export/import cycle:
tools: Set tonullon export. During import, Agent Studio starts the MCP server, queries it for available tools, and populates this field. SDK validators should acceptnullhere.status: Set to empty string""on export. During import, it transitions through"VALIDATING"→"VALID"or"VALIDATION_FAILED". SDK validators should accept empty string or any status value.
Cross-References
- Every MCP template
idreferenced in an agent template’smcp_template_idsmust correspond to an entry in the ZIP’smcp_templatesarray - The
mcp_templatesarray in the manifest may be empty or absent in older exports
JSON Example
{
"id": "m1234567-89ab-cdef-0123-456789abcdef",
"workflow_template_id": "w9x8y7z6-5432-1098-fedc-ba0987654321",
"name": "Filesystem Server",
"type": "NODE",
"args": ["npx", "@modelcontextprotocol/server-filesystem", "/workspace"],
"env_names": [],
"tools": null,
"status": "",
"mcp_image_path": "studio-data/dynamic_assets/mcp_template_icons/filesystem_server_abc123_icon.png"
}
Workflow Template ZIP Format
This chapter specifies the exact structure of a workflow template ZIP file — the portable interchange format for Agent Studio templates. SDK builders must validate that generated ZIPs conform to this structure.
ZIP Structure
workflow_template.zip
├── workflow_template.json # REQUIRED: manifest
└── studio-data/ # REQUIRED if tools or icons exist
├── tool_templates/ # Tool code packages
│ ├── {slug}_{random}/ # One directory per tool template
│ │ ├── tool.py # REQUIRED per tool
│ │ ├── requirements.txt # REQUIRED per tool
│ │ └── [additional files...] # Optional supporting files
│ └── ...
└── dynamic_assets/ # Icon images
├── tool_template_icons/
│ └── {slug}_{random}_icon.{png|jpg|jpeg}
├── agent_template_icons/
│ └── {uuid}_icon.{png|jpg|jpeg}
└── mcp_template_icons/
└── {slug}_{random}_icon.{png|jpg|jpeg}
Path Requirements
Root Manifest
The file workflow_template.json must be at the ZIP root (not inside a subdirectory).
studio-data/ Directory
The path studio-data/ is a hard-coded constant in Agent Studio. All file paths stored in the manifest JSON are relative to the ZIP root and rooted under studio-data/. During import, the entire studio-data/ subtree is copied into the project’s root studio-data/ directory.
Tool Template Directories
Each tool template directory must be located at:
studio-data/tool_templates/{directory_name}/
The directory_name follows the convention {slugified_name}_{random_string}:
slugified_name: tool name lowercased, spaces replaced with underscoresrandom_string: 6-character alphanumeric string for uniqueness
Examples: json_reader_abc123, my_api_tool_xk9f2m
The source_folder_path field in the tool template JSON must match the actual directory path within the ZIP.
Icon Files
Icon paths in the manifest must point to files that actually exist within the ZIP:
| Template Type | Path Convention |
|---|---|
| Tool | studio-data/dynamic_assets/tool_template_icons/{slug}_{random}_icon.{ext} |
| Agent | studio-data/dynamic_assets/agent_template_icons/{uuid}_icon.{ext} |
| MCP | studio-data/dynamic_assets/mcp_template_icons/{slug}_{random}_icon.{ext} |
If a template has no icon, the corresponding path field should be an empty string "".
Import Behavior
When Agent Studio imports a ZIP, the following transformations occur:
- UUID regeneration: All IDs in the manifest are remapped to fresh UUIDs. Cross-references are updated to match.
- Directory renaming: Tool template directories are renamed with new slugs and random suffixes.
- Icon renaming: Icon files are renamed to match the new directory names or UUIDs.
- Path updates: All
source_folder_path,tool_image_path,agent_image_path, andmcp_image_pathfields are updated to reflect the new names. - File copy: The
studio-data/subtree is merged into the project’s existingstudio-data/directory. - Database insert: All templates are inserted into the SQLite database with new IDs.
- MCP validation: Background jobs start MCP servers and populate their
toolsfields.
Because of step 2-4, the specific directory names and icon filenames in your ZIP are not preserved. They only need to be valid and internally consistent at the time of import.
Generating ZIPs Programmatically
When building a ZIP outside of Agent Studio (e.g., in a CI pipeline):
- Generate UUIDv4 strings for all template IDs
- Ensure all cross-references are consistent
- Place tool code under
studio-data/tool_templates/{slug}_{random}/ - Place icons under the appropriate
studio-data/dynamic_assets/subdirectory - Write
workflow_template.jsonat the ZIP root with correct path references - Create the ZIP archive with standard deflate compression
The ZIP should not contain:
.venv/directories__pycache__/directories.requirements_hash.txtfiles- Any files outside of
workflow_template.jsonandstudio-data/
workflow_template.json Schema
The manifest file is the heart of a workflow template ZIP. It contains the complete definition of every template entity and their cross-references. This chapter documents every field.
Top-Level Structure
{
"template_version": "0.0.1",
"workflow_template": { ... },
"agent_templates": [ ... ],
"tool_templates": [ ... ],
"mcp_templates": [ ... ],
"task_templates": [ ... ]
}
| Field | Type | Required | Description |
|---|---|---|---|
template_version | string | Yes | Schema version. Currently "0.0.1". |
workflow_template | object | Yes | The workflow template definition. |
agent_templates | array | Yes | All agent templates referenced by the workflow. |
tool_templates | array | Yes | All tool templates referenced by agents. |
mcp_templates | array | Yes | All MCP templates referenced by agents. May be empty. |
task_templates | array | Yes | All task templates referenced by the workflow. |
workflow_template Object
{
"id": "uuid",
"name": "My Workflow",
"description": "A workflow that does X",
"process": "sequential",
"agent_template_ids": ["uuid-1", "uuid-2"],
"task_template_ids": ["uuid-3", "uuid-4"],
"manager_agent_template_id": null,
"use_default_manager": false,
"is_conversational": false,
"pre_packaged": false
}
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
id | string (UUID) | Yes | — | Unique identifier |
name | string | Yes | — | Workflow name |
description | string | No | null | Workflow description |
process | string | No | null | "sequential" or "hierarchical" |
agent_template_ids | array of UUIDs | No | null | Ordered list of agent template IDs |
task_template_ids | array of UUIDs | No | null | Ordered list of task template IDs (defines execution order) |
manager_agent_template_id | string (UUID) | No | null | Manager agent for hierarchical mode |
use_default_manager | boolean | No | false | Use a default manager instead of a custom one |
is_conversational | boolean | No | false | Whether the workflow supports multi-turn conversation |
pre_packaged | boolean | No | false | Always false in exports |
agent_templates Array
Each element:
{
"id": "uuid",
"workflow_template_id": "uuid",
"name": "Agent Name",
"description": "What this agent does",
"role": "Senior Analyst",
"backstory": "Background context...",
"goal": "Produce accurate analysis",
"allow_delegation": true,
"verbose": true,
"cache": true,
"temperature": 0.7,
"max_iter": 10,
"tool_template_ids": ["uuid-5"],
"mcp_template_ids": [],
"pre_packaged": false,
"agent_image_path": "studio-data/dynamic_assets/agent_template_icons/uuid_icon.png"
}
See Agent Template Specification for full field documentation.
tool_templates Array
Each element:
{
"id": "uuid",
"workflow_template_id": "uuid",
"name": "JSON Reader",
"python_code_file_name": "tool.py",
"python_requirements_file_name": "requirements.txt",
"source_folder_path": "studio-data/tool_templates/json_reader_abc123",
"pre_built": false,
"tool_image_path": "studio-data/dynamic_assets/tool_template_icons/json_reader_abc123_icon.png",
"is_venv_tool": true
}
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
id | string (UUID) | Yes | — | Unique identifier |
workflow_template_id | string (UUID) | No | null | Scoping |
name | string | Yes | — | Tool name (must match ^[a-zA-Z0-9 ]+$) |
python_code_file_name | string | Yes | — | Always "tool.py" |
python_requirements_file_name | string | Yes | — | Always "requirements.txt" |
source_folder_path | string | Yes | — | Path to tool directory within the ZIP |
pre_built | boolean | No | false | Always false in exports |
tool_image_path | string | No | “” | Path to icon within the ZIP |
is_venv_tool | boolean | No | true | Whether the tool uses a virtual environment. Always true for new tools. |
mcp_templates Array
Each element:
{
"id": "uuid",
"workflow_template_id": "uuid",
"name": "Filesystem Server",
"type": "NODE",
"args": ["npx", "@modelcontextprotocol/server-filesystem", "/workspace"],
"env_names": [],
"tools": null,
"status": "",
"mcp_image_path": ""
}
See MCP Template Specification for full field documentation.
task_templates Array
Each element:
{
"id": "uuid",
"workflow_template_id": "uuid",
"name": "Analyze Data",
"description": "Review the dataset and identify key trends.",
"expected_output": "A structured report with findings.",
"assigned_agent_template_id": "uuid"
}
See Task Template Specification for full field documentation.
Cross-Reference Integrity Rules
An SDK validator must enforce these rules:
- Every UUID in
workflow_template.agent_template_idsmust exist inagent_templates[].id - Every UUID in
workflow_template.task_template_idsmust exist intask_templates[].id - If
workflow_template.manager_agent_template_idis set, it must exist inagent_templates[].id - Every UUID in each
agent_templates[].tool_template_idsmust exist intool_templates[].id - Every UUID in each
agent_templates[].mcp_template_idsmust exist inmcp_templates[].id - If
task_templates[].assigned_agent_template_idis set, it must exist inagent_templates[].id - Every
tool_templates[].source_folder_pathmust reference a directory that exists in the ZIP - Every non-empty
*_image_pathfield must reference a file that exists in the ZIP
Dynamic Assets and Icons
Icons are optional visual assets for tool, agent, and MCP templates. They are displayed in the Agent Studio UI and on the XYFlow visual canvas.
Supported Formats
| Format | Extensions |
|---|---|
| PNG | .png |
| JPEG | .jpg, .jpeg |
Extensions are validated case-insensitively (e.g., .PNG and .png are both accepted, but stored lowercased).
No other image formats are supported. SVG, GIF, WebP, and BMP will be rejected.
Storage Paths
Icons are stored under studio-data/dynamic_assets/ with type-specific subdirectories:
studio-data/dynamic_assets/
├── tool_template_icons/
│ └── {slug}_{random}_icon.{ext} e.g., json_reader_abc123_icon.png
├── agent_template_icons/
│ └── {uuid}_icon.{ext} e.g., a1b2c3d4-..._icon.png
└── mcp_template_icons/
└── {slug}_{random}_icon.{ext} e.g., filesystem_server_xyz789_icon.jpg
Naming Conventions
| Template Type | Icon Filename Pattern |
|---|---|
| Tool | {tool_directory_basename}_icon.{ext} — matches the tool directory name |
| Agent | {agent_template_uuid}_icon.{ext} — uses the agent template ID |
| MCP | {unique_slug}_{random}_icon.{ext} — generated slug similar to tools |
Manifest References
Each template type has a path field that references its icon:
| Template Type | JSON Field | Example Value |
|---|---|---|
| Tool | tool_image_path | "studio-data/dynamic_assets/tool_template_icons/json_reader_abc123_icon.png" |
| Agent | agent_image_path | "studio-data/dynamic_assets/agent_template_icons/a1b2c3d4-..._icon.png" |
| MCP | mcp_image_path | "studio-data/dynamic_assets/mcp_template_icons/fs_server_xyz789_icon.png" |
If a template has no icon, the path field should be an empty string "". Do not use null — use "".
Validation Rules
For SDK validators:
- If the path field is non-empty, the file must exist at that path within the ZIP
- The file extension (lowercased) must be one of:
.png,.jpg,.jpeg - The path must be under the appropriate
studio-data/dynamic_assets/subdirectory - File content is not validated beyond existence — no dimension, size, or format header checks
Deployment Artifact Format (tar.gz)
When a workflow is deployed, Agent Studio packages it into a tar.gz archive that the workflow engine can execute independently. This format differs from the template ZIP — it contains runtime-resolved data rather than template blueprints.
Archive Structure
artifact.tar.gz
├── workflow.yaml # REQUIRED: artifact type descriptor
├── collated_input.json # REQUIRED: complete runtime definition
└── studio-data/ # Tool code and workflow files
└── workflows/
└── {workflow_directory_name}/
└── tools/
└── {tool_slug}/
├── tool.py
├── requirements.txt
└── [additional files]
workflow.yaml
A simple descriptor that tells the workflow engine how to load the artifact:
type: collated_input
input: collated_input.json
Currently, collated_input is the only supported artifact type. The input field points to the JSON file containing the full workflow definition.
collated_input.json
The complete runtime representation of the workflow. See CollatedInput Schema for the full specification.
studio-data/ Subtree
The studio-data/ directory is a filtered copy of the project’s studio-data/:
- Only the specific workflow’s directory under
studio-data/workflows/is included tool_templates/,temp_files/, anddeployable_workflows/are excluded- Within tool directories, the following are excluded:
.venv/.next/node_modules/.nvm/.requirements_hash.txt
How the Engine Consumes Artifacts
- The engine extracts the tar.gz to a temporary directory
- Reads
workflow.yamlto determine the artifact type - Loads
collated_input.jsonand parses it into theCollatedInputPydantic model - Resolves tool paths relative to the extracted directory
- Creates virtual environments for each tool from their
requirements.txt - Executes the workflow using CrewAI or LangGraph based on the artifact contents
LangGraph Artifact Alternative
LangGraph workflows use a different artifact structure, detected by the presence of langgraph.json:
langgraph_artifact/
├── langgraph.json # LangGraph graph definition
├── pyproject.toml # Dependencies (installed via pip)
├── .env # Optional environment variables
└── src/
└── graph.py # Graph implementation
The langgraph.json schema:
{
"graphs": {
"graph_name": "src/graph.py:graph_symbol"
}
}
Currently limited to a single graph per artifact. Dependencies are installed via pip install . from the artifact root.
CollatedInput Schema
The CollatedInput is the universal runtime contract between Agent Studio and the workflow engine. It fully describes a workflow’s execution environment — language models, agents, tasks, tools, and MCP servers.
Top-Level Structure
class CollatedInput(BaseModel):
default_language_model_id: str
language_models: List[Input__LanguageModel]
tool_instances: List[Input__ToolInstance]
mcp_instances: List[Input__MCPInstance]
agents: List[Input__Agent]
tasks: List[Input__Task]
workflow: Input__Workflow
Language Models
{
"default_language_model_id": "model-uuid",
"language_models": [
{
"model_id": "model-uuid",
"model_name": "gpt-4o",
"generation_config": {
"do_sample": true,
"temperature": 0.1,
"max_new_tokens": 4096,
"top_p": 1,
"top_k": 50,
"num_beams": 1,
"max_length": null
}
}
]
}
| Field | Type | Description |
|---|---|---|
default_language_model_id | string | ID of the model to use when an agent doesn’t specify one |
language_models[].model_id | string | Unique identifier |
language_models[].model_name | string | Human-readable name |
language_models[].generation_config | object | LLM generation parameters |
Tool Instances
{
"tool_instances": [
{
"id": "tool-uuid",
"name": "JSON Reader",
"python_code_file_name": "tool.py",
"python_requirements_file_name": "requirements.txt",
"source_folder_path": "studio-data/workflows/my_workflow/tools/json_reader_abc123",
"tool_metadata": "{\"user_params\": [\"api_key\"], \"user_params_metadata\": {\"api_key\": {\"required\": true}}}",
"tool_image_uri": "tool_template_icons/json_reader_abc123_icon.png",
"is_venv_tool": true
}
]
}
| Field | Type | Description |
|---|---|---|
id | string | Unique tool instance identifier |
name | string | Tool name |
python_code_file_name | string | Always "tool.py" |
python_requirements_file_name | string | Always "requirements.txt" |
source_folder_path | string | Path to tool code directory within the artifact |
tool_metadata | string | JSON-encoded metadata including user parameter definitions |
tool_image_uri | string or null | Relative path to icon |
is_venv_tool | boolean | Whether the tool uses a virtual environment |
MCP Instances
{
"mcp_instances": [
{
"id": "mcp-uuid",
"name": "Filesystem Server",
"type": "NODE",
"args": ["npx", "@modelcontextprotocol/server-filesystem", "/workspace"],
"env_names": [],
"tools": ["read_file", "write_file", "list_directory"],
"mcp_image_uri": null
}
]
}
| Field | Type | Description |
|---|---|---|
id | string | Unique MCP instance identifier |
name | string | Server name |
type | string | "PYTHON" or "NODE" |
args | array of strings | Server startup arguments |
env_names | array of strings | Required environment variable names |
tools | array of strings or null | Available tool names (populated at runtime) |
mcp_image_uri | string or null | Relative path to icon |
Agents
{
"agents": [
{
"id": "agent-uuid",
"name": "Research Analyst",
"llm_provider_model_id": "model-uuid",
"crew_ai_role": "Senior Research Analyst",
"crew_ai_backstory": "You are an experienced analyst...",
"crew_ai_goal": "Produce accurate research reports",
"crew_ai_allow_delegation": true,
"crew_ai_verbose": true,
"crew_ai_cache": true,
"crew_ai_temperature": 0.7,
"crew_ai_max_iter": 10,
"tool_instance_ids": ["tool-uuid"],
"mcp_instance_ids": [],
"agent_image_uri": ""
}
]
}
| Field | Type | Description |
|---|---|---|
id | string | Unique agent identifier |
name | string | Agent name |
llm_provider_model_id | string or null | Override LLM for this agent (uses default if null) |
crew_ai_role | string | Agent’s role description |
crew_ai_backstory | string | Agent’s background context |
crew_ai_goal | string | Agent’s primary objective |
crew_ai_allow_delegation | boolean | Can delegate to other agents |
crew_ai_verbose | boolean | Detailed logging |
crew_ai_cache | boolean | Cache LLM responses |
crew_ai_temperature | float or null | Sampling temperature |
crew_ai_max_iter | integer or null | Maximum reasoning iterations |
tool_instance_ids | array of strings | IDs of tools this agent can use |
mcp_instance_ids | array of strings | IDs of MCP servers this agent can use |
agent_image_uri | string | Relative path to icon |
Tasks
{
"tasks": [
{
"id": "task-uuid",
"description": "Analyze the quarterly sales data and identify trends.",
"expected_output": "A structured report with trends and recommendations.",
"assigned_agent_id": "agent-uuid"
}
]
}
| Field | Type | Description |
|---|---|---|
id | string | Unique task identifier |
description | string | Task instructions for the agent |
expected_output | string | What the agent should produce |
assigned_agent_id | string or null | Agent assigned to this task (sequential mode) |
Workflow
{
"workflow": {
"id": "workflow-uuid",
"name": "Sales Analysis Workflow",
"description": "Analyzes quarterly sales data",
"crew_ai_process": "sequential",
"agent_ids": ["agent-uuid"],
"task_ids": ["task-uuid"],
"manager_agent_id": null,
"llm_provider_model_id": null,
"is_conversational": false,
"created_at": "2025-01-15T10:30:00"
}
}
| Field | Type | Description |
|---|---|---|
id | string | Unique workflow identifier |
name | string | Workflow name |
description | string or null | Workflow description |
crew_ai_process | string | "sequential" or "hierarchical" |
agent_ids | array of strings | Participating agent IDs |
task_ids | array of strings | Task IDs in execution order |
manager_agent_id | string or null | Manager agent for hierarchical mode |
llm_provider_model_id | string or null | Default LLM override for the workflow |
is_conversational | boolean | Multi-turn conversation support |
created_at | string (ISO 8601) or null | Creation timestamp |
Deployment Configuration
When deploying a workflow, Agent Studio applies a DeploymentConfig that controls runtime behavior — LLM generation parameters, tool configuration, MCP server secrets, and environment variables.
DeploymentConfig
class DeploymentConfig(BaseModel):
generation_config: Dict = {}
tool_config: Dict[str, Dict[str, str]] = {}
mcp_config: Dict[str, Dict[str, str]] = {}
llm_config: Dict = {}
environment: Dict = {}
| Field | Type | Description |
|---|---|---|
generation_config | object | Default LLM generation parameters applied to all language models |
tool_config | object | Tool-specific configuration keyed by tool instance ID |
mcp_config | object | MCP server secrets keyed by MCP instance ID |
llm_config | object | LLM API keys and endpoint overrides |
environment | object | Additional environment variables passed to the deployment |
Default Generation Config
When no overrides are provided, Agent Studio uses these defaults:
{
"do_sample": true,
"temperature": 0.1,
"max_new_tokens": 4096,
"top_p": 1,
"top_k": 50,
"num_beams": 1,
"max_length": null
}
Language Model Configuration
At deployment time, each language model receives connection details:
class Input__LanguageModelConfig(BaseModel):
provider_model: str # Provider-specific model identifier (e.g., "gpt-4o")
model_type: SupportedModelTypes # Provider type enum
api_base: Optional[str] # Custom API endpoint
api_key: Optional[str] # Authentication key
extra_headers: Optional[Dict[str, str]] # Additional HTTP headers
# AWS Bedrock specific
aws_region_name: Optional[str]
aws_access_key_id: Optional[str]
aws_secret_access_key: Optional[str]
aws_session_token: Optional[str]
Supported Model Types
| Type | Description | Required Fields |
|---|---|---|
OPENAI | OpenAI API | api_key |
OPENAI_COMPATIBLE | OpenAI-compatible endpoints | api_base, api_key |
AZURE_OPENAI | Azure OpenAI Service | api_base, api_key |
GEMINI | Google Gemini | api_key |
ANTHROPIC | Anthropic Claude | api_key |
CAII | Cloudera AI Inference | api_base, api_key |
BEDROCK | AWS Bedrock | aws_region_name, aws_access_key_id, aws_secret_access_key |
Deployment Targets
Agent Studio supports multiple deployment target types:
| Target | Description |
|---|---|
workbench_model | CML Model endpoint — the primary deployment target |
langgraph_server | LangGraph Server deployment |
ai_inference | Cloudera AI Inference (planned) |
model_registry | Model Registry deployment (planned) |
Workflow Source Types
Workflows can be deployed from multiple sources:
| Source | Description |
|---|---|
workflow | An existing workflow in the current Agent Studio instance |
workflow_template | A workflow template (instantiates first, then deploys) |
workflow_artifact | A pre-packaged tar.gz artifact |
github | A GitHub repository URL (cloned and packaged by Agent Studio) |
Environment Variables at Runtime
Deployed workflows receive these environment variables:
| Variable | Description |
|---|---|
AGENT_STUDIO_OPS_ENDPOINT | Phoenix observability endpoint for trace export |
AGENT_STUDIO_WORKFLOW_ARTIFACT | Path to the extracted deployment artifact |
AGENT_STUDIO_WORKFLOW_DEPLOYMENT_CONFIG | JSON-encoded DeploymentConfig |
AGENT_STUDIO_MODEL_EXECUTION_DIR | Working directory for model execution |
CDSW_APIV2_KEY | CML API v2 authentication key |
CDSW_PROJECT_ID | CML project identifier |
CREWAI_DISABLE_TELEMETRY | Set to disable CrewAI’s built-in telemetry |
Validation Rules Reference
This chapter is an exhaustive checklist of rules that a validation SDK must enforce when checking a workflow template ZIP. Rules are categorized by severity.
ERROR rules block import — Agent Studio will reject the ZIP if these fail.
WARNING rules indicate issues that won’t prevent import but may cause problems at runtime.
Structural Rules (ERROR)
| Rule | Description |
|---|---|
S-001 | ZIP must contain workflow_template.json at the root level (not nested in a subdirectory) |
S-002 | workflow_template.json must be valid JSON |
S-003 | If any tool templates exist, the studio-data/tool_templates/ directory must exist in the ZIP |
S-004 | If any icons are referenced, the studio-data/dynamic_assets/ directory must exist in the ZIP |
Manifest Rules (ERROR)
| Rule | Description |
|---|---|
M-001 | template_version field must be present (currently "0.0.1") |
M-002 | workflow_template field must be present and be an object |
M-003 | agent_templates field must be present and be an array |
M-004 | tool_templates field must be present and be an array |
M-005 | task_templates field must be present and be an array |
M-006 | mcp_templates field must be present and be an array (may be empty) |
M-007 | workflow_template.id must be a non-empty string |
M-008 | workflow_template.name must be a non-empty string |
M-009 | Every element in agent_templates, tool_templates, mcp_templates, task_templates must have an id field |
Cross-Reference Integrity Rules (ERROR)
| Rule | Description |
|---|---|
X-001 | Every UUID in workflow_template.agent_template_ids must match an agent_templates[].id |
X-002 | Every UUID in workflow_template.task_template_ids must match a task_templates[].id |
X-003 | If workflow_template.manager_agent_template_id is set, it must match an agent_templates[].id |
X-004 | Every UUID in each agent_templates[].tool_template_ids must match a tool_templates[].id |
X-005 | Every UUID in each agent_templates[].mcp_template_ids must match a mcp_templates[].id |
X-006 | If task_templates[].assigned_agent_template_id is set, it must match an agent_templates[].id |
X-007 | No duplicate IDs across all entity arrays (all IDs must be unique within the manifest) |
Tool Validation Rules (ERROR)
| Rule | Description |
|---|---|
T-001 | Every tool_templates[].source_folder_path must reference a directory that exists in the ZIP |
T-002 | Each tool directory must contain a file matching python_code_file_name (typically tool.py) |
T-003 | Each tool directory must contain a file matching python_requirements_file_name (typically requirements.txt) |
T-004 | tool.py must parse as valid Python (no syntax errors) |
T-005 | tool.py must contain a class named UserParameters |
T-006 | tool.py must contain a class named ToolParameters |
T-007 | tool.py must contain a function named run_tool |
Tool Validation Rules (WARNING)
| Rule | Description |
|---|---|
T-W01 | tool.py should define OUTPUT_KEY at module level |
T-W02 | tool.py should contain an if __name__ == "__main__": block |
T-W03 | requirements.txt should list pydantic as a dependency |
T-W04 | UserParameters should inherit from BaseModel |
T-W05 | ToolParameters should inherit from BaseModel |
Name Validation Rules (ERROR)
| Rule | Description |
|---|---|
N-001 | Every tool_templates[].name must match the regex ^[a-zA-Z0-9 ]+$ (alphanumeric and spaces only) |
N-002 | Tool template names must be unique within the manifest |
Icon Validation Rules (ERROR)
| Rule | Description |
|---|---|
I-001 | If tool_image_path is non-empty, the file must exist in the ZIP |
I-002 | If agent_image_path is non-empty, the file must exist in the ZIP |
I-003 | If mcp_image_path is non-empty, the file must exist in the ZIP |
I-004 | Icon file extensions (lowercased) must be one of: .png, .jpg, .jpeg |
Process Mode Rules (WARNING)
| Rule | Description |
|---|---|
P-W01 | If process is "hierarchical", either manager_agent_template_id should be set or use_default_manager should be true |
P-W02 | If process is "sequential", all task templates should have assigned_agent_template_id set |
ID Format Rules (WARNING)
| Rule | Description |
|---|---|
F-W01 | All id fields should be valid UUID format (8-4-4-4-12 hex pattern) |
Building a Validation SDK
This chapter provides practical guidance for building a validation library that checks workflow template ZIPs against the Validation Rules. The SDK is intended to be used in CI/CD pipelines (e.g., as a GitHub Action) to catch errors before templates reach Agent Studio.
Recommended Architecture
from dataclasses import dataclass
from enum import Enum
from typing import List
class Severity(Enum):
ERROR = "error"
WARNING = "warning"
@dataclass
class ValidationIssue:
rule: str # Rule code (e.g., "T-004")
severity: Severity
message: str # Human-readable description
path: str # File or field path within the ZIP
@dataclass
class ValidationResult:
valid: bool # True if no ERROR-level issues
issues: List[ValidationIssue]
def validate_template_zip(zip_path: str) -> ValidationResult:
"""Main entry point for SDK consumers."""
...
Validation Pipeline
Step 1: Open ZIP and Check Structure
import zipfile
import json
def validate_structure(zf: zipfile.ZipFile) -> List[ValidationIssue]:
issues = []
names = zf.namelist()
# S-001: workflow_template.json at root
if "workflow_template.json" not in names:
issues.append(ValidationIssue(
rule="S-001", severity=Severity.ERROR,
message="workflow_template.json not found at ZIP root",
path="/"
))
return issues
Step 2: Parse Manifest and Validate Top-Level Keys
def validate_manifest(data: dict) -> List[ValidationIssue]:
issues = []
required_keys = {
"template_version": "M-001",
"workflow_template": "M-002",
"agent_templates": "M-003",
"tool_templates": "M-004",
"task_templates": "M-005",
}
for key, rule in required_keys.items():
if key not in data:
issues.append(ValidationIssue(
rule=rule, severity=Severity.ERROR,
message=f"Required key '{key}' missing from manifest",
path="workflow_template.json"
))
# M-006: mcp_templates (may be absent in older exports)
if "mcp_templates" not in data:
data["mcp_templates"] = [] # Normalize
return issues
Step 3: Build ID Index and Validate Cross-References
def validate_cross_references(data: dict) -> List[ValidationIssue]:
issues = []
# Build ID sets
agent_ids = {a["id"] for a in data.get("agent_templates", [])}
tool_ids = {t["id"] for t in data.get("tool_templates", [])}
mcp_ids = {m["id"] for m in data.get("mcp_templates", [])}
task_ids = {t["id"] for t in data.get("task_templates", [])}
wt = data.get("workflow_template", {})
# X-001: agent_template_ids
for aid in wt.get("agent_template_ids", []):
if aid not in agent_ids:
issues.append(ValidationIssue(
rule="X-001", severity=Severity.ERROR,
message=f"workflow_template.agent_template_ids references unknown agent '{aid}'",
path="workflow_template.json"
))
# X-002: task_template_ids
for tid in wt.get("task_template_ids", []):
if tid not in task_ids:
issues.append(ValidationIssue(
rule="X-002", severity=Severity.ERROR,
message=f"workflow_template.task_template_ids references unknown task '{tid}'",
path="workflow_template.json"
))
# X-003: manager_agent_template_id
mgr = wt.get("manager_agent_template_id")
if mgr and mgr not in agent_ids:
issues.append(ValidationIssue(
rule="X-003", severity=Severity.ERROR,
message=f"manager_agent_template_id references unknown agent '{mgr}'",
path="workflow_template.json"
))
# X-004, X-005: agent -> tool/mcp references
for agent in data.get("agent_templates", []):
for tid in agent.get("tool_template_ids", []):
if tid not in tool_ids:
issues.append(ValidationIssue(
rule="X-004", severity=Severity.ERROR,
message=f"Agent '{agent['id']}' references unknown tool '{tid}'",
path="workflow_template.json"
))
for mid in agent.get("mcp_template_ids", []):
if mid not in mcp_ids:
issues.append(ValidationIssue(
rule="X-005", severity=Severity.ERROR,
message=f"Agent '{agent['id']}' references unknown MCP '{mid}'",
path="workflow_template.json"
))
# X-006: task -> agent references
for task in data.get("task_templates", []):
assigned = task.get("assigned_agent_template_id")
if assigned and assigned not in agent_ids:
issues.append(ValidationIssue(
rule="X-006", severity=Severity.ERROR,
message=f"Task '{task['id']}' references unknown agent '{assigned}'",
path="workflow_template.json"
))
return issues
Step 4: Validate Tool Templates via AST
This is the most critical validation — it ensures that tool.py files contain the required components. The logic mirrors Agent Studio’s own AST-based extraction.
import ast
def validate_tool_py(source_code: str, tool_path: str) -> List[ValidationIssue]:
issues = []
# T-004: Must parse as valid Python
try:
tree = ast.parse(source_code)
except SyntaxError as e:
issues.append(ValidationIssue(
rule="T-004", severity=Severity.ERROR,
message=f"Python syntax error: {e}",
path=tool_path
))
return issues # Can't validate further
# Collect top-level names
class_names = set()
function_names = set()
assignments = set()
has_main_block = False
for node in ast.walk(tree):
if isinstance(node, ast.ClassDef):
class_names.add(node.name)
elif isinstance(node, ast.FunctionDef):
function_names.add(node.name)
elif isinstance(node, ast.Assign):
for target in node.targets:
if isinstance(target, ast.Name):
assignments.add(target.id)
# Check for if __name__ == "__main__"
for node in ast.walk(tree):
if isinstance(node, ast.If):
test = node.test
if (isinstance(test, ast.Compare) and
isinstance(test.left, ast.Name) and
test.left.id == "__name__"):
has_main_block = True
# T-005: UserParameters class
if "UserParameters" not in class_names:
issues.append(ValidationIssue(
rule="T-005", severity=Severity.ERROR,
message="Missing 'class UserParameters(BaseModel)'",
path=tool_path
))
# T-006: ToolParameters class
if "ToolParameters" not in class_names:
issues.append(ValidationIssue(
rule="T-006", severity=Severity.ERROR,
message="Missing 'class ToolParameters(BaseModel)'",
path=tool_path
))
# T-007: run_tool function
if "run_tool" not in function_names:
issues.append(ValidationIssue(
rule="T-007", severity=Severity.ERROR,
message="Missing 'def run_tool(...)' function",
path=tool_path
))
# T-W01: OUTPUT_KEY (warning)
if "OUTPUT_KEY" not in assignments:
issues.append(ValidationIssue(
rule="T-W01", severity=Severity.WARNING,
message="Missing 'OUTPUT_KEY' module-level assignment",
path=tool_path
))
# T-W02: __main__ block (warning)
if not has_main_block:
issues.append(ValidationIssue(
rule="T-W02", severity=Severity.WARNING,
message="Missing 'if __name__ == \"__main__\":' block",
path=tool_path
))
return issues
Step 5: Validate Icons
import os
VALID_EXTENSIONS = {".png", ".jpg", ".jpeg"}
def validate_icon(path: str, zip_names: set, rule: str) -> List[ValidationIssue]:
issues = []
if not path: # Empty string = no icon
return issues
if path not in zip_names:
issues.append(ValidationIssue(
rule=rule, severity=Severity.ERROR,
message=f"Icon file not found in ZIP: {path}",
path=path
))
else:
ext = os.path.splitext(path)[1].lower()
if ext not in VALID_EXTENSIONS:
issues.append(ValidationIssue(
rule="I-004", severity=Severity.ERROR,
message=f"Invalid icon extension '{ext}' (must be .png, .jpg, or .jpeg)",
path=path
))
return issues
Step 6: Assemble Results
def validate_template_zip(zip_path: str) -> ValidationResult:
issues = []
with zipfile.ZipFile(zip_path, "r") as zf:
names = set(zf.namelist())
# Step 1: Structure
issues.extend(validate_structure(zf))
if any(i.rule == "S-001" for i in issues):
return ValidationResult(valid=False, issues=issues)
# Step 2: Parse manifest
data = json.loads(zf.read("workflow_template.json"))
issues.extend(validate_manifest(data))
if any(i.severity == Severity.ERROR for i in issues):
return ValidationResult(valid=False, issues=issues)
# Step 3: Cross-references
issues.extend(validate_cross_references(data))
# Step 4: Tool templates
for tool in data.get("tool_templates", []):
src_path = tool.get("source_folder_path", "")
tool_py_path = f"{src_path}/tool.py"
req_path = f"{src_path}/requirements.txt"
# T-001: Directory exists
dir_entries = [n for n in names if n.startswith(src_path + "/")]
if not dir_entries:
issues.append(ValidationIssue(
rule="T-001", severity=Severity.ERROR,
message=f"Tool directory not found: {src_path}",
path=src_path
))
continue
# T-002: tool.py exists
if tool_py_path not in names:
issues.append(ValidationIssue(
rule="T-002", severity=Severity.ERROR,
message=f"tool.py not found in {src_path}",
path=tool_py_path
))
else:
source = zf.read(tool_py_path).decode("utf-8")
issues.extend(validate_tool_py(source, tool_py_path))
# T-003: requirements.txt exists
if req_path not in names:
issues.append(ValidationIssue(
rule="T-003", severity=Severity.ERROR,
message=f"requirements.txt not found in {src_path}",
path=req_path
))
# N-001: Name validation
import re
name = tool.get("name", "")
if not re.match(r"^[a-zA-Z0-9 ]+$", name):
issues.append(ValidationIssue(
rule="N-001", severity=Severity.ERROR,
message=f"Tool name '{name}' contains invalid characters",
path="workflow_template.json"
))
# Icons
issues.extend(validate_icon(
tool.get("tool_image_path", ""), names, "I-001"))
# Step 5: Agent and MCP icons
for agent in data.get("agent_templates", []):
issues.extend(validate_icon(
agent.get("agent_image_path", ""), names, "I-002"))
for mcp in data.get("mcp_templates", []):
issues.extend(validate_icon(
mcp.get("mcp_image_path", ""), names, "I-003"))
errors = [i for i in issues if i.severity == Severity.ERROR]
return ValidationResult(valid=len(errors) == 0, issues=issues)
CLI Interface
A simple CLI wrapper for use in scripts and CI:
import sys
if __name__ == "__main__":
result = validate_template_zip(sys.argv[1])
for issue in result.issues:
prefix = "ERROR" if issue.severity == Severity.ERROR else "WARN"
print(f"[{prefix}] {issue.rule}: {issue.message} ({issue.path})")
sys.exit(0 if result.valid else 1)
Usage:
python validate.py my_template.zip
# Exit code 0 = valid, 1 = errors found
GitHub Actions Integration
This chapter shows how to integrate template validation into a CI/CD pipeline using GitHub Actions.
Example Workflow
name: Validate Agent Studio Templates
on:
pull_request:
paths:
- 'templates/**'
push:
branches: [main]
paths:
- 'templates/**'
jobs:
validate:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.10'
- name: Install validation SDK
run: pip install as-template-validator # Your published SDK package
- name: Build template ZIP
run: |
cd templates/my-workflow
zip -r ../../my-workflow.zip workflow_template.json studio-data/
- name: Validate template
run: |
as-validate my-workflow.zip
# Exit code 0 = valid, non-zero = errors found
- name: Post results to PR
if: failure() && github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const { execSync } = require('child_process');
const output = execSync('as-validate my-workflow.zip 2>&1 || true').toString();
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `## Template Validation Results\n\`\`\`\n${output}\n\`\`\``
});
Repository Structure
A recommended repository layout for template development:
my-agent-templates/
├── .github/
│ └── workflows/
│ └── validate.yml # CI workflow above
├── templates/
│ └── my-workflow/
│ ├── workflow_template.json # Manifest
│ └── studio-data/
│ ├── tool_templates/
│ │ └── my_tool_abc123/
│ │ ├── tool.py
│ │ └── requirements.txt
│ └── dynamic_assets/
│ └── tool_template_icons/
│ └── my_tool_abc123_icon.png
├── scripts/
│ └── build-zip.sh # ZIP packaging script
└── README.md
Build Script
A simple script to package a template directory into a ZIP:
#!/bin/bash
# scripts/build-zip.sh
# Usage: ./scripts/build-zip.sh templates/my-workflow output.zip
set -euo pipefail
TEMPLATE_DIR="$1"
OUTPUT_ZIP="$2"
if [ ! -f "$TEMPLATE_DIR/workflow_template.json" ]; then
echo "ERROR: workflow_template.json not found in $TEMPLATE_DIR"
exit 1
fi
cd "$TEMPLATE_DIR"
zip -r "$(realpath "$OUTPUT_ZIP")" \
workflow_template.json \
studio-data/ \
-x "**/.venv/*" \
-x "**/__pycache__/*" \
-x "**/.requirements_hash.txt"
echo "Created $OUTPUT_ZIP"
Optional: Deploy to Agent Studio
If your CI pipeline has access to an Agent Studio instance, you can deploy validated templates automatically.
Agent Studio supports deployment directly from GitHub URLs using the github workflow source type. The platform clones the repository, packages the workflow, and deploys it.
For programmatic deployment, use the gRPC API:
# Upload the ZIP via the Agent Studio API
# (requires CDSW_APIV2_KEY and CDSW_DOMAIN as GitHub secrets)
curl -X POST "https://${CDSW_DOMAIN}/api/v1/workflow-templates/import" \
-H "Authorization: Bearer ${CDSW_APIV2_KEY}" \
-F "file=@my-workflow.zip"
Note: The exact HTTP endpoint depends on your Agent Studio deployment configuration. The canonical interface is the
ImportWorkflowTemplategRPC RPC, proxied through the Next.js API layer.
Multi-Template Validation
For repositories containing multiple templates:
- name: Validate all templates
run: |
EXIT_CODE=0
for dir in templates/*/; do
ZIP="/tmp/$(basename $dir).zip"
(cd "$dir" && zip -r "$ZIP" workflow_template.json studio-data/)
echo "Validating $ZIP..."
if ! as-validate "$ZIP"; then
EXIT_CODE=1
fi
done
exit $EXIT_CODE