Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 formatTemplate Specification and Validation Rules

Terminology

TermDefinition
Workflow TemplateA portable, reusable blueprint for an entire workflow — aggregates agent, task, tool, and MCP templates. Exported/imported as ZIP files.
Tool TemplateA Python package (tool.py + requirements.txt) that agents can invoke at runtime. Defines configuration parameters and invocation arguments via Pydantic models.
Agent TemplateA reusable agent definition with role, backstory, goal, and references to its tools and MCP servers. Maps to a CrewAI Agent.
Task TemplateA 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 TemplateA Model Context Protocol server configuration — type (Python or Node), startup arguments, and required environment variables.
CollatedInputThe runtime data structure that fully describes a workflow for execution — language models, agents, tasks, tools, MCP servers, and the workflow itself.
Deployment ArtifactA tar.gz archive containing a workflow.yaml, collated_input.json, and all required tool code. Consumed by the workflow engine.
Workflow EngineA 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

  1. Proto-first API: All backend operations are defined in protobuf. The gRPC service is the single source of truth for capabilities.
  2. Thin service layer: service.py is a router — business logic lives in domain modules. This pattern is easy to replicate.
  3. Dual storage: SQLite for metadata, filesystem for code artifacts. Template ZIPs bridge both by bundling the manifest JSON and the studio-data/ subtree.
  4. Isolated execution: The workflow engine is a separate package with its own virtualenv. This ensures tool dependencies don’t conflict with the studio itself.
  5. 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 TypeVisualPurpose
taskLight greenA CrewAI task — displays truncated description. Connected left-to-right in execution order.
agentWhite/light blueA CrewAI agent — displays name and optional icon. Pulsing animation when active during execution.
toolDark grayA tool instance attached to an agent — displays name and optional icon.
mcpDark grayAn 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:

  1. Sum total width: each agent contributes 220 * max(0, num_tools + num_mcps - 1) + 220
  2. Start offset: -0.5 * totalWidth + 110
  3. Each tool/MCP shifts the offset by +220

Edge Types

All edges use MarkerType.Arrow with 20x20 size.

EdgeSource HandleTarget HandleCondition
Task → TaskrightleftSequential (adjacent tasks)
Task → AgentbottomtopSequential mode
Task → Managerbottom(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:

  1. Events fetched from /api/workflow/events?trace_id={id}
  2. processEvents() extracts active node IDs and event metadata
  3. Active nodes receive: active: true (triggers CSS pulse animation), info (event text), infoType (event category)

Event info types displayed on nodes:

  • TaskStart, LLMCall, ToolInput, ToolOutput
  • Delegate, EndDelegate, AskCoworker, EndAskCoworker
  • Completion, FailedCompletion

Key Takeaway for Harness Builders

To build an equivalent canvas:

  1. Parse the workflow_template.json or CollatedInput to extract the entity graph
  2. Apply the layout algorithm above (or use XYFlow’s auto-layout plugins)
  3. Subscribe to the event stream to overlay live execution state
  4. Use the DiagramStateInput interface 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:

FrameworkInstrumentorCaptures
CrewAIopeninference.instrumentation.crewai.CrewAIInstrumentorCrew, Agent, Task execution spans
LiteLLMopeninference.instrumentation.litellm.LiteLLMInstrumentorLLM API call spans
LangChainopeninference.instrumentation.langchain.LangChainInstrumentorLangGraph 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:

CategoryEvents
Crew lifecycleCrewKickoffStarted, CrewKickoffCompleted, CrewKickoffFailed
Agent executionAgentExecutionStarted, AgentExecutionCompleted, AgentExecutionError
Task executionTaskStarted, TaskCompleted, TaskFailed
Tool usageToolUsageStarted, ToolUsageFinished, ToolUsageError
LLM callsLLMCallStarted, 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 time
  • type — event class name
  • agent_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:

  1. Deploy Phoenix (or any OTel-compatible collector) as your trace backend
  2. Register a TracerProvider using phoenix.otel.register() pointed at your collector
  3. Instrument your framework with the appropriate OpenInference instrumentor
  4. Implement an event stream — POST structured events keyed by trace ID to your collector
  5. Poll events from the frontend — the canvas subscribes to GET /events?trace_id={id} and overlays them onto nodes
  6. 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.

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:

DomainKey RPCsCount
ModelsListModels, AddModel, TestModel, SetStudioDefaultModel8
Tool TemplatesListToolTemplates, AddToolTemplate, UpdateToolTemplate5
Tool InstancesListToolInstances, CreateToolInstance, TestToolInstance5
MCP TemplatesListMcpTemplates, AddMcpTemplate, UpdateMcpTemplate5
MCP InstancesListMcpInstances, CreateMcpInstance, UpdateMcpInstance5
AgentsListAgents, AddAgent, UpdateAgent5
TasksListTasks, AddTask, UpdateTask5
WorkflowsListWorkflows, AddWorkflow, CloneWorkflow, TestWorkflow, DeployWorkflow12
Workflow TemplatesListWorkflowTemplates, ExportWorkflowTemplate, ImportWorkflowTemplate6
Agent/Task TemplatesCRUD for reusable agent and task blueprints10+
Deployed WorkflowsListDeployedWorkflows, UndeployWorkflow, Suspend/Resume5
UtilityHealthCheck, FileUpload/Download, GetAssetData9

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 package
  • ImportWorkflowTemplate(file_path) → Imports a ZIP, regenerates UUIDs, copies assets, creates DB records
  • DeployWorkflow(workflow_id, config) → Packages and deploys a workflow to a target endpoint
  • TestWorkflow(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

  1. Define your own proto starting from OIP trace definitions. Add CRUD RPCs as needed for your entity model.
  2. Keep the service layer thin — route to domain modules. This makes the service testable and extensible.
  3. Expose a REST proxy if your frontend doesn’t support gRPC natively. The slug-based pattern is simple and effective.
  4. Use the Import/Export ZIP format as your interchange format with Agent Studio. The Template Specification chapters document this format exhaustively.
  5. 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:

  1. On export: All IDs are regenerated. The exported ZIP contains fresh UUIDs that differ from the database.
  2. 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_id is null. Available to all workflows.
  • Scoped: workflow_template_id is 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:

ModeTask AssignmentManager Agent
sequentialEach task has an assigned_agent_template_id. Tasks execute in array order.Not used.
hierarchicalManager 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 UserParameters and ToolParameters instances
  • 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

FieldTypeRequiredDefaultDescription
idstring (UUID)YesUnique identifier
workflow_template_idstring (UUID)NonullScopes the template to a workflow template. Set in exported ZIPs.
namestringYesHuman-readable agent name
descriptionstringNonullDescription of the agent’s purpose
rolestringNonullCrewAI role — what the agent does (e.g., “Senior Data Analyst”)
backstorystringNonullCrewAI backstory — context that shapes agent behavior
goalstringNonullCrewAI goal — what the agent is trying to achieve
allow_delegationbooleanNotrueWhether this agent can delegate tasks to other agents
verbosebooleanNotrueWhether to log detailed execution info
cachebooleanNotrueWhether to cache LLM responses
temperaturefloatNo0.7LLM sampling temperature
max_iterintegerNo10Maximum reasoning iterations before the agent must produce output
tool_template_idsarray of UUIDsNo[]References to tool templates this agent can use
mcp_template_idsarray of UUIDsNo[]References to MCP templates this agent can use
pre_packagedbooleanNofalseWhether shipped as part of the studio. Always false in exports.
agent_image_pathstringNo“”Relative path to the agent’s icon within the ZIP

CrewAI Mapping

When instantiated, agent template fields map to CrewAI’s Agent constructor:

Template FieldCrewAI Parameter
rolerole
backstorybackstory
goalgoal
allow_delegationallow_delegation
verboseverbose
cachecache
temperaturetemperature
max_itermax_iter

Cross-References

  • Every UUID in tool_template_ids must correspond to an entry in the ZIP’s tool_templates array.
  • Every UUID in mcp_template_ids must correspond to an entry in the ZIP’s mcp_templates array.
  • The agent’s own id may be referenced by:
    • workflow_template.agent_template_ids
    • workflow_template.manager_agent_template_id
    • task_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

FieldTypeRequiredDefaultDescription
idstring (UUID)YesUnique identifier
workflow_template_idstring (UUID)NonullScopes the template to a workflow template
namestringNonullHuman-readable task name
descriptionstringNonullWhat the agent should do. This is the primary instruction given to the assigned agent.
expected_outputstringNonullDescription of the expected result format. Guides the agent on what to produce.
assigned_agent_template_idstring (UUID)NonullThe 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_id set, 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_id or use_default_manager) orchestrates execution
  • The manager decides which worker agent handles each task
  • assigned_agent_template_id is optional — the manager may override assignments

Cross-References

  • assigned_agent_template_id, if set, must correspond to an entry in the ZIP’s agent_templates array
  • The task’s id must be listed in workflow_template.task_template_ids
  • Task order in task_template_ids defines 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

FieldTypeRequiredDefaultDescription
idstring (UUID)YesUnique identifier
workflow_template_idstring (UUID)NonullScopes the template to a workflow template
namestringYesHuman-readable server name
typestringYesServer type: "PYTHON" or "NODE"
argsarray of stringsYesCommand-line arguments for starting the server
env_namesarray of stringsYesEnvironment variable names the server requires
toolsobject or nullNonullMCP-exposed tool definitions. Set to null on export. Populated during import validation.
statusstringNo“”Validation status. Set to empty string on export.
mcp_image_pathstringNo“”Relative path to the server’s icon within the ZIP

Server Types

TypeRuntimeExample
PYTHONExecuted via Pythonpython -m my_mcp_server
NODEExecuted via Node.jsnpx @modelcontextprotocol/server-filesystem

Export/Import Behavior

Two fields receive special treatment during the export/import cycle:

  • tools: Set to null on export. During import, Agent Studio starts the MCP server, queries it for available tools, and populates this field. SDK validators should accept null here.
  • 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 id referenced in an agent template’s mcp_template_ids must correspond to an entry in the ZIP’s mcp_templates array
  • The mcp_templates array 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 underscores
  • random_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 TypePath Convention
Toolstudio-data/dynamic_assets/tool_template_icons/{slug}_{random}_icon.{ext}
Agentstudio-data/dynamic_assets/agent_template_icons/{uuid}_icon.{ext}
MCPstudio-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:

  1. UUID regeneration: All IDs in the manifest are remapped to fresh UUIDs. Cross-references are updated to match.
  2. Directory renaming: Tool template directories are renamed with new slugs and random suffixes.
  3. Icon renaming: Icon files are renamed to match the new directory names or UUIDs.
  4. Path updates: All source_folder_path, tool_image_path, agent_image_path, and mcp_image_path fields are updated to reflect the new names.
  5. File copy: The studio-data/ subtree is merged into the project’s existing studio-data/ directory.
  6. Database insert: All templates are inserted into the SQLite database with new IDs.
  7. MCP validation: Background jobs start MCP servers and populate their tools fields.

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):

  1. Generate UUIDv4 strings for all template IDs
  2. Ensure all cross-references are consistent
  3. Place tool code under studio-data/tool_templates/{slug}_{random}/
  4. Place icons under the appropriate studio-data/dynamic_assets/ subdirectory
  5. Write workflow_template.json at the ZIP root with correct path references
  6. Create the ZIP archive with standard deflate compression

The ZIP should not contain:

  • .venv/ directories
  • __pycache__/ directories
  • .requirements_hash.txt files
  • Any files outside of workflow_template.json and studio-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": [ ... ]
}
FieldTypeRequiredDescription
template_versionstringYesSchema version. Currently "0.0.1".
workflow_templateobjectYesThe workflow template definition.
agent_templatesarrayYesAll agent templates referenced by the workflow.
tool_templatesarrayYesAll tool templates referenced by agents.
mcp_templatesarrayYesAll MCP templates referenced by agents. May be empty.
task_templatesarrayYesAll 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
}
FieldTypeRequiredDefaultDescription
idstring (UUID)YesUnique identifier
namestringYesWorkflow name
descriptionstringNonullWorkflow description
processstringNonull"sequential" or "hierarchical"
agent_template_idsarray of UUIDsNonullOrdered list of agent template IDs
task_template_idsarray of UUIDsNonullOrdered list of task template IDs (defines execution order)
manager_agent_template_idstring (UUID)NonullManager agent for hierarchical mode
use_default_managerbooleanNofalseUse a default manager instead of a custom one
is_conversationalbooleanNofalseWhether the workflow supports multi-turn conversation
pre_packagedbooleanNofalseAlways 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
}
FieldTypeRequiredDefaultDescription
idstring (UUID)YesUnique identifier
workflow_template_idstring (UUID)NonullScoping
namestringYesTool name (must match ^[a-zA-Z0-9 ]+$)
python_code_file_namestringYesAlways "tool.py"
python_requirements_file_namestringYesAlways "requirements.txt"
source_folder_pathstringYesPath to tool directory within the ZIP
pre_builtbooleanNofalseAlways false in exports
tool_image_pathstringNo“”Path to icon within the ZIP
is_venv_toolbooleanNotrueWhether 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:

  1. Every UUID in workflow_template.agent_template_ids must exist in agent_templates[].id
  2. Every UUID in workflow_template.task_template_ids must exist in task_templates[].id
  3. If workflow_template.manager_agent_template_id is set, it must exist in agent_templates[].id
  4. Every UUID in each agent_templates[].tool_template_ids must exist in tool_templates[].id
  5. Every UUID in each agent_templates[].mcp_template_ids must exist in mcp_templates[].id
  6. If task_templates[].assigned_agent_template_id is set, it must exist in agent_templates[].id
  7. Every tool_templates[].source_folder_path must reference a directory that exists in the ZIP
  8. Every non-empty *_image_path field 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

FormatExtensions
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 TypeIcon 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 TypeJSON FieldExample Value
Tooltool_image_path"studio-data/dynamic_assets/tool_template_icons/json_reader_abc123_icon.png"
Agentagent_image_path"studio-data/dynamic_assets/agent_template_icons/a1b2c3d4-..._icon.png"
MCPmcp_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:

  1. If the path field is non-empty, the file must exist at that path within the ZIP
  2. The file extension (lowercased) must be one of: .png, .jpg, .jpeg
  3. The path must be under the appropriate studio-data/dynamic_assets/ subdirectory
  4. 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/, and deployable_workflows/ are excluded
  • Within tool directories, the following are excluded:
    • .venv/
    • .next/
    • node_modules/
    • .nvm/
    • .requirements_hash.txt

How the Engine Consumes Artifacts

  1. The engine extracts the tar.gz to a temporary directory
  2. Reads workflow.yaml to determine the artifact type
  3. Loads collated_input.json and parses it into the CollatedInput Pydantic model
  4. Resolves tool paths relative to the extracted directory
  5. Creates virtual environments for each tool from their requirements.txt
  6. 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
      }
    }
  ]
}
FieldTypeDescription
default_language_model_idstringID of the model to use when an agent doesn’t specify one
language_models[].model_idstringUnique identifier
language_models[].model_namestringHuman-readable name
language_models[].generation_configobjectLLM 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
    }
  ]
}
FieldTypeDescription
idstringUnique tool instance identifier
namestringTool name
python_code_file_namestringAlways "tool.py"
python_requirements_file_namestringAlways "requirements.txt"
source_folder_pathstringPath to tool code directory within the artifact
tool_metadatastringJSON-encoded metadata including user parameter definitions
tool_image_uristring or nullRelative path to icon
is_venv_toolbooleanWhether 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
    }
  ]
}
FieldTypeDescription
idstringUnique MCP instance identifier
namestringServer name
typestring"PYTHON" or "NODE"
argsarray of stringsServer startup arguments
env_namesarray of stringsRequired environment variable names
toolsarray of strings or nullAvailable tool names (populated at runtime)
mcp_image_uristring or nullRelative 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": ""
    }
  ]
}
FieldTypeDescription
idstringUnique agent identifier
namestringAgent name
llm_provider_model_idstring or nullOverride LLM for this agent (uses default if null)
crew_ai_rolestringAgent’s role description
crew_ai_backstorystringAgent’s background context
crew_ai_goalstringAgent’s primary objective
crew_ai_allow_delegationbooleanCan delegate to other agents
crew_ai_verbosebooleanDetailed logging
crew_ai_cachebooleanCache LLM responses
crew_ai_temperaturefloat or nullSampling temperature
crew_ai_max_iterinteger or nullMaximum reasoning iterations
tool_instance_idsarray of stringsIDs of tools this agent can use
mcp_instance_idsarray of stringsIDs of MCP servers this agent can use
agent_image_uristringRelative 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"
    }
  ]
}
FieldTypeDescription
idstringUnique task identifier
descriptionstringTask instructions for the agent
expected_outputstringWhat the agent should produce
assigned_agent_idstring or nullAgent 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"
  }
}
FieldTypeDescription
idstringUnique workflow identifier
namestringWorkflow name
descriptionstring or nullWorkflow description
crew_ai_processstring"sequential" or "hierarchical"
agent_idsarray of stringsParticipating agent IDs
task_idsarray of stringsTask IDs in execution order
manager_agent_idstring or nullManager agent for hierarchical mode
llm_provider_model_idstring or nullDefault LLM override for the workflow
is_conversationalbooleanMulti-turn conversation support
created_atstring (ISO 8601) or nullCreation 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 = {}
FieldTypeDescription
generation_configobjectDefault LLM generation parameters applied to all language models
tool_configobjectTool-specific configuration keyed by tool instance ID
mcp_configobjectMCP server secrets keyed by MCP instance ID
llm_configobjectLLM API keys and endpoint overrides
environmentobjectAdditional 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

TypeDescriptionRequired Fields
OPENAIOpenAI APIapi_key
OPENAI_COMPATIBLEOpenAI-compatible endpointsapi_base, api_key
AZURE_OPENAIAzure OpenAI Serviceapi_base, api_key
GEMINIGoogle Geminiapi_key
ANTHROPICAnthropic Claudeapi_key
CAIICloudera AI Inferenceapi_base, api_key
BEDROCKAWS Bedrockaws_region_name, aws_access_key_id, aws_secret_access_key

Deployment Targets

Agent Studio supports multiple deployment target types:

TargetDescription
workbench_modelCML Model endpoint — the primary deployment target
langgraph_serverLangGraph Server deployment
ai_inferenceCloudera AI Inference (planned)
model_registryModel Registry deployment (planned)

Workflow Source Types

Workflows can be deployed from multiple sources:

SourceDescription
workflowAn existing workflow in the current Agent Studio instance
workflow_templateA workflow template (instantiates first, then deploys)
workflow_artifactA pre-packaged tar.gz artifact
githubA GitHub repository URL (cloned and packaged by Agent Studio)

Environment Variables at Runtime

Deployed workflows receive these environment variables:

VariableDescription
AGENT_STUDIO_OPS_ENDPOINTPhoenix observability endpoint for trace export
AGENT_STUDIO_WORKFLOW_ARTIFACTPath to the extracted deployment artifact
AGENT_STUDIO_WORKFLOW_DEPLOYMENT_CONFIGJSON-encoded DeploymentConfig
AGENT_STUDIO_MODEL_EXECUTION_DIRWorking directory for model execution
CDSW_APIV2_KEYCML API v2 authentication key
CDSW_PROJECT_IDCML project identifier
CREWAI_DISABLE_TELEMETRYSet 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)

RuleDescription
S-001ZIP must contain workflow_template.json at the root level (not nested in a subdirectory)
S-002workflow_template.json must be valid JSON
S-003If any tool templates exist, the studio-data/tool_templates/ directory must exist in the ZIP
S-004If any icons are referenced, the studio-data/dynamic_assets/ directory must exist in the ZIP

Manifest Rules (ERROR)

RuleDescription
M-001template_version field must be present (currently "0.0.1")
M-002workflow_template field must be present and be an object
M-003agent_templates field must be present and be an array
M-004tool_templates field must be present and be an array
M-005task_templates field must be present and be an array
M-006mcp_templates field must be present and be an array (may be empty)
M-007workflow_template.id must be a non-empty string
M-008workflow_template.name must be a non-empty string
M-009Every element in agent_templates, tool_templates, mcp_templates, task_templates must have an id field

Cross-Reference Integrity Rules (ERROR)

RuleDescription
X-001Every UUID in workflow_template.agent_template_ids must match an agent_templates[].id
X-002Every UUID in workflow_template.task_template_ids must match a task_templates[].id
X-003If workflow_template.manager_agent_template_id is set, it must match an agent_templates[].id
X-004Every UUID in each agent_templates[].tool_template_ids must match a tool_templates[].id
X-005Every UUID in each agent_templates[].mcp_template_ids must match a mcp_templates[].id
X-006If task_templates[].assigned_agent_template_id is set, it must match an agent_templates[].id
X-007No duplicate IDs across all entity arrays (all IDs must be unique within the manifest)

Tool Validation Rules (ERROR)

RuleDescription
T-001Every tool_templates[].source_folder_path must reference a directory that exists in the ZIP
T-002Each tool directory must contain a file matching python_code_file_name (typically tool.py)
T-003Each tool directory must contain a file matching python_requirements_file_name (typically requirements.txt)
T-004tool.py must parse as valid Python (no syntax errors)
T-005tool.py must contain a class named UserParameters
T-006tool.py must contain a class named ToolParameters
T-007tool.py must contain a function named run_tool

Tool Validation Rules (WARNING)

RuleDescription
T-W01tool.py should define OUTPUT_KEY at module level
T-W02tool.py should contain an if __name__ == "__main__": block
T-W03requirements.txt should list pydantic as a dependency
T-W04UserParameters should inherit from BaseModel
T-W05ToolParameters should inherit from BaseModel

Name Validation Rules (ERROR)

RuleDescription
N-001Every tool_templates[].name must match the regex ^[a-zA-Z0-9 ]+$ (alphanumeric and spaces only)
N-002Tool template names must be unique within the manifest

Icon Validation Rules (ERROR)

RuleDescription
I-001If tool_image_path is non-empty, the file must exist in the ZIP
I-002If agent_image_path is non-empty, the file must exist in the ZIP
I-003If mcp_image_path is non-empty, the file must exist in the ZIP
I-004Icon file extensions (lowercased) must be one of: .png, .jpg, .jpeg

Process Mode Rules (WARNING)

RuleDescription
P-W01If process is "hierarchical", either manager_agent_template_id should be set or use_default_manager should be true
P-W02If process is "sequential", all task templates should have assigned_agent_template_id set

ID Format Rules (WARNING)

RuleDescription
F-W01All 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.

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 ImportWorkflowTemplate gRPC 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