#4902 DSL: AI Agent Orchestration
#4902 DSL: AI Agent Orchestration
#4902 Describe a domain specific language (DSL) for AI agent orchestration. All available AI agents are defined in a folder/text repository and can be referenced directly from the DSL
A Domain-Specific Language (DSL) for AI agent orchestration acts as a declarative coordination layer that separates agent capabilities (defined externally) from workflow execution logic (orchestration, routing, control flow, and safety gates).
Core Architecture & Mental Model
┌───────────────────────────┐ ┌───────────────────────────┐ │ Agent Repository │ │ DSL Orchestration File │ │ (Directory / Markdown) │ │ (.flow / YAML / HCL) │ │ │ │ │ │ agents/ │ │ workflow IncidentTriage: │ │ ├── triage_agent.md │◄─────┼── pipeline: │
│ ├── db_specialist.md │ │ - step: classify │ │ └── security_lead.md │ │ call: triage_agent │ └───────────────────────────┘ └─────────────┬─────────────┘ │ ▼ ┌───────────────────────────┐ │ Execution Engine │ │ - Context / State bus │ │ - Concurrency & Routing │ │ - Tool / Guardrail gates │ └───────────────────────────┘
Agent Repository: Each agent lives as an individual text artifact (typically
.agent.md,.yaml, or front-matter Markdown) specifying its role, persona, model tier, attached tools, and output schema.DSL Runtime: Discovers available agents via path or package reference (e.g.,
@agents/db_specialist), loads their interface contracts, and enforces step transitions, state propagation, retries, and validations.
DSL Syntax Design: AgentFlow
Below is a declarative, human-readable DSL syntax designed specifically for direct repository linkage.
# workflow: automated_incident_response.flow
name: AutomatedIncidentResponse
version: "1.0"
# Explicit agent imports mapped to the repository
agents:
triage: import "agents/triage_agent.md"
db_expert: import "agents/db_specialist.md"
sec_analyst: import "agents/security_lead.md"
summarizer: import "agents/reporter.md"
# Global state schema passed across steps
state:
incident_raw: string
severity: enum [P1, P2, P3, P4] = P3
findings: list[string] = []
resolution_report: string = ""
# Workflow execution definition
pipeline:
# 1. Sequential classification step
- step: TriageIncident
agent: triage
input:
description: state.incident_raw
output:
severity: $.severity
suspected_domains: $.tags
timeout: 30s
# 2. Branching & Dynamic Fan-Out
- step: DeepInvestigation
branch:
when: state.severity in [P1, P2]
parallel:
- step: InvestigateDatabase
if: "'database' in state.suspected_domains"
agent: db_expert
input:
logs: context.events
retry:
max_attempts: 3
backoff: exponential
- step: InvestigateSecurity
if: "'auth' in state.suspected_domains or state.severity == P1"
agent: sec_analyst
input:
audit_trail: context.security_logs
guardrail:
reject_if: "$.confidence < 0.70"
# 3. Consensus / Deliberation Pattern
- step: DebateActionPlan
mode: consensus
rounds: 2
participants: [db_expert, sec_analyst]
topic: "Agree on immediate containment measures without dropping prod connections."
output:
agreed_plan: $.consensus_statement
# 4. Final Aggregation
- step: GenerateReport
agent: summarizer
input:
triage: state.severity
investigations: [InvestigateDatabase.output, InvestigateSecurity.output]
plan: DebateActionPlan.agreed_plan
output:
resolution_report: $.markdown
Primitives & Key Language Constructs
| Language Construct | Purpose | Example |
| Agent Binding | Links identifiers directly to repository paths or specs. | agent: triage import "agents/triage.md" |
| Execution Topologies | Supports sequential, fan-out/fan-in (parallel), and collaborative turns. | mode: consensus, parallel: [...] |
| State & Scope | Context bus with explicit schema definitions preventing prompt bleed. | state.incident_raw, $.output_field |
| Guardrails & Filters | Assertion layers checking output structures, confidence, or regex constraints. | guardrail: reject_if: "$.score < 0.8" |
| Fault Resilience | Declarative retry policies, fallbacks, model degradations, and timeouts. | fallback: call agents/fallback_agent.md |
| Human-in-the-Loop | Native pause hooks awaiting external approval tokens or webhook calls. | await_approval: role == "lead_sre" |
Agent Definition Contract in the Repository
For the DSL to bind to agents unambiguously, agents in the text repository define clear input/output contracts alongside prompts:
<!-- agents/triage_agent.md -->
---
name: triage_agent
model: gpt-4o-mini
tools: [query_datadog, query_pagerduty]
temperature: 0.1
schema:
input:
description: string
output:
severity: string (P1|P2|P3|P4)
tags: array[string]
---
You are an automated triage SRE. Categorize incoming alerts by severity and domain tags.
Compilation & Runtime Pipeline
Static Analysis & Link Checking: The compiler parses the DSL file and scans the designated folder repository to confirm every referenced agent file exists and that declared step inputs match the agent's schema.
DAG Construction: Control flow statements (
step,parallel,branch,if) are transformed into a directed acyclic graph (DAG) or a state machine.Context Isolation: When a node executes, only its declared
inputkeys are injected into the agent's prompt context, avoiding context window exhaustion.Telemetry & Tracing: Emits OpenTelemetry events for step transitions, token usage, latency, and agent handoffs.
How can this work along with Pydantic classes for each agent?
Integrating Pydantic with the DSL provides compile-time structural verification, runtime JSON-schema enforcement, and automatic model validation at agent boundaries.
In this architecture, each agent in the repository defines its interface using Pydantic models (either directly via Python modules or via Pydantic-compatible JSON schemas inside Markdown frontmatter). The DSL runtime binds to these models to validate inputs before invoking an LLM and parse outputs into typed objects.
Architectural Layout
agents/
├── triage/
│ ├── schemas.py # Pydantic Input/Output models
│ └── prompt.md # System prompt & persona
└── db_expert/
├── schemas.py
└── prompt.md
workflows/
└── incident_flow.yaml # DSL referencing the agent definitions
Step 1: Defining Agent Contracts with Pydantic
Each agent exports two core schemas: Input and Output.
# agents/triage/schemas.py
from enum import Enum
from pydantic import BaseModel, Field
class SeverityEnum(str, Enum):
P1 = "P1"
P2 = "P2"
P3 = "P3"
P4 = "P4"
class TriageInput(BaseModel):
alert_raw: str = Field(..., description="Raw incoming alert or stack trace")
service: str = Field(..., description="Source service reporting the alert")
class TriageOutput(BaseModel):
severity: SeverityEnum
domains: list[str] = Field(default_factory=list, description="Affected subsystems")
summary: str
confidence: float = Field(ge=0.0, le=1.0)
The accompanying prompt references these contracts:
<!-- agents/triage/prompt.md -->
---
name: triage_agent
schemas: agents.triage.schemas
input_model: TriageInput
output_model: TriageOutput
model: gpt-4o-mini
---
You are an SRE incident classifier. Given an incoming alert, extract the severity, affected domains, and provide an initial summary.
Step 2: Referencing Typed Agents in the DSL
The DSL maps execution steps to agent paths, automatically inheriting the Pydantic type definitions for validation:
# workflows/incident_flow.yaml
name: TriagePipeline
agents:
triage: import "agents/triage"
db_expert: import "agents/db_expert"
state:
raw_alert: string
origin_service: string
pipeline:
- step: TriageAlert
agent: triage
# The DSL engine validates these keys against TriageInput at parse time
input:
alert_raw: state.raw_alert
service: state.origin_service
- step: InvestigateDB
agent: db_expert
if: "'database' in TriageAlert.output.domains"
# Validated against DBExpertInput
input:
context: TriageAlert.output.summary
severity: TriageAlert.output.severity
Step 3: Runtime Execution & Validation Lifecycle
When the orchestration engine processes a step, Pydantic sits at both boundaries:
[DSL Step Inputs] │ ▼ 1. Validate & Pack ──► `TriageInput(**step_inputs)` (Raises DSLBindingError if mismatched) │ ▼ 2. LLM Call ─────────► Injects `TriageOutput.model_json_schema()` as structured output schema │ ▼ 3. Validate & Cast ──► `TriageOutput.model_validate_json(raw_llm_response)` │ ▼ [State Bus Updated] ──► Available to downstream steps with dot-notation access
Python Runtime Implementation Hook
Here is how the DSL interpreter dynamically resolves the Pydantic classes and enforces contracts:
import importlib
from pathlib import Path
from pydantic import BaseModel, ValidationError
class AgentExecutor:
def __init__(self, agent_dir: str):
self.path = Path(agent_dir)
# Dynamically load the agent's schema module
module_path = str(self.path / "schemas.py").replace("/", ".").rstrip(".py")
self.schemas = importlib.import_module(f"agents.{self.path.name}.schemas")
self.input_model: type[BaseModel] = getattr(self.schemas, f"{self.path.name.capitalize()}Input")
self.output_model: type[BaseModel] = getattr(self.schemas, f"{self.path.name.capitalize()}Output")
def execute_step(self, raw_inputs: dict, llm_client) -> BaseModel:
# 1. Pre-execution validation
try:
validated_input = self.input_model(**raw_inputs)
except ValidationError as e:
raise RuntimeError(f"DSL input binding failed schema check: {e}")
# 2. Structured LLM Call using JSON Schema generated by Pydantic
response = llm_client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": (self.path / "prompt.md").read_text()},
{"role": "user", "content": validated_input.model_dump_json()}
],
response_format={
"type": "json_schema",
"json_schema": {
"name": self.output_model.__name__,
"schema": self.output_model.model_json_schema()
}
}
)
# 3. Post-execution response casting
return self.output_model.model_validate_json(response.choices[0].message.content)
Key Advantages of Combining DSLs with Pydantic
| Feature | Without Pydantic | With Pydantic Integration |
| Static Verification | Errors caught only when LLM responds unpredictably. | Syntax/type mismatches caught at DSL load time before sending API calls. |
| Prompt Engineering | Manual JSON formatting instructions written inside prompts. | Uses model_json_schema() to leverage provider-native structured output engines. |
| State Propagation | Unstructured JSON parsing (res["data"]["id"]). | Strongly-typed field access (StepName.output.field) with autocomplete/IDE support. |
| Recovery / Retries | Blanket retries on generic errors. | Targeted prompt retries providing Pydantic's ValidationError.errors() directly back to the model. |
From <https://gemini.google.com/app/1f0a7b7c230a83ea> Google Gemini (3.8 Flash)
Comments
Post a Comment