| name | aevatar-workflow-yaml |
| description | Write and edit Aevatar workflow YAML definitions. Covers canonical schema, closed_world_mode, formal roles config (provider/model/limits/event modules/routes/connectors), all 26 primitives, branching, retry/error policies, and validation constraints. |
Aevatar Workflow YAML Authoring
Use this skill when creating, editing, reviewing, or debugging workflow yaml files.
Canonical Schema
All keys use snake_case (UnderscoredNamingConvention).
name: my_workflow
description: |
What this workflow does.
configuration:
closed_world_mode: false
roles:
- id: analyst
name: Analyst
system_prompt: |
You are a systems analyst.
provider: openai
model: gpt-5.4
temperature: 0.2
max_tokens: 512
max_tool_rounds: 4
max_history_messages: 50
stream_buffer_capacity: 128
event_modules: "mod1,mod2"
event_routes: |
event.type == ChatRequestEvent -> mod1
connectors:
- api_connector
extensions:
event_modules: "legacy_mod"
event_routes: "event.type == LegacyEvent -> legacy_mod"
steps:
- id: step1
type: llm_call
target_role: analyst
parameters:
prompt_prefix: "Analyze:"
agent_type: RoleGAgent
agent_id: role:analyst
next: step2
children: []
branches:
true: next_a
false: next_b
_default: fallback
retry:
max_attempts: 3
backoff: exponential
delay_ms: 1000
on_error:
strategy: fail
fallback_step: step_x
default_output: ""
timeout_ms: 30000
Critical Rules
type defaults to llm_call.
target_role and role are aliases; target_role wins.
- Role
id and name fallback: if one is missing, the other is used for both.
parameters is Dict<string,string>; use string values in authoring.
- Step flow precedence: branch routing ->
next -> list-order fallback.
children is recursive and can nest arbitrarily.
_default is the reserved fallback branch key.
- Dynamic parameter keys are used by some modules, e.g.
branch.{key}, sub_param_{key}, vote_param_{key}.
- Workflow roles and standalone role YAML share the same normalization semantics.
event_modules / event_routes precedence: top-level fields > extensions.*.
- Ergonomic aliases are normalized at parse-time to canonical primitives:
http_get/http_post/http_put/http_delete/mcp_call/cli_call -> connector_call
foreach_llm -> foreach
map_reduce_llm -> map_reduce
parameters.agent_type is supported for llm_call / evaluate / reflect and can directly target a GAgent type.
- When
parameters.agent_type is present, target_role can be omitted and is not required for target resolution.
parameters.agent_id is optional; if omitted, runtime generates a stable actor id from workflow actor + step + agent type.
- In agent-type dispatch mode, step
parameters are forwarded as chat metadata except agent_type and agent_id.
Validation Constraints
conditional should define both branches.true and branches.false.
switch should define _default in branches.
while should provide at least one of:
condition
- positive
max_iterations
workflow_call should include parameters.workflow.
- Step IDs should be unique.
Closed World Mode
When configuration.closed_world_mode: true, the following step types are blocked:
llm_call
tool_call
connector_call / bridge_call
http_get / http_post / http_put / http_delete / mcp_call / cli_call
evaluate / judge
reflect
human_input
human_approval
wait_signal / wait
emit / publish
parallel / parallel_fanout / fan_out
race / select
map_reduce / mapreduce
map_reduce_llm
vote_consensus / vote
foreach / for_each / foreach_llm
Primitive Catalog (26 Total)
| Category | Type | Aliases | Purpose |
|---|
| data | transform | transform | Pure text ops (uppercase, count, split, etc.) |
| data | assign | assign | Set a workflow variable |
| data | retrieve_facts | retrieve_facts | Keyword search over input lines |
| data | cache | cache | Cache child step results by key |
| control | guard | guard, assert | Input validation gate |
| control | conditional | conditional | Binary branching |
| control | switch | switch | Multi-way branching |
| control | while | while, loop | Repetition loop |
| control | delay | delay, sleep | Pause execution |
| control | wait_signal | wait_signal, wait | Wait for external signal |
| control | checkpoint | checkpoint | Save execution point |
| ai | llm_call | llm_call | Send prompt to role LLM |
| ai | tool_call | tool_call | Invoke registered tool |
| ai | evaluate | evaluate, judge | LLM-as-judge scoring |
| ai | reflect | reflect | Self-critique and improve |
| composition | foreach | foreach, for_each, foreach_llm | Iterate by delimiter |
| composition | parallel | parallel_fanout, parallel, fan_out | Fan-out to multiple workers |
| composition | race | race, select | First-response-wins |
| composition | map_reduce | map_reduce, mapreduce, map_reduce_llm | Split -> map -> reduce |
| composition | workflow_call | workflow_call, sub_workflow | Invoke sub-workflow |
| composition | vote_consensus | vote_consensus, vote | Consensus aggregation |
| integration | connector_call | connector_call, bridge_call, cli_call, mcp_call, http_get, http_post, http_put, http_delete | Call external connector |
| integration | emit | emit, publish | Publish external event |
| human | human_input | human_input | Wait for human text input |
| human | human_approval | human_approval | Wait for human approval |
| internal | workflow_loop | workflow_loop | Runtime orchestrator (do not hand-author in normal YAML) |
Common Patterns
Role Formalization (Full Role Config)
configuration:
closed_world_mode: false
roles:
- id: planner
name: Planner
system_prompt: "You plan robust workflows."
provider: openai
model: gpt-5.4
temperature: 0.2
max_tokens: 512
max_tool_rounds: 4
max_history_messages: 50
stream_buffer_capacity: 128
event_modules: "llm_handler,tool_handler"
event_routes: |
event.type == ChatRequestEvent -> llm_handler
connectors: [search_api, issue_tracker]
extensions:
event_modules: "legacy_module"
event_routes: "event.type == LegacyEvent -> legacy_module"
In this example, runtime uses top-level event_modules/event_routes rather than extensions.*.
Linear Pipeline
steps:
- id: validate
type: guard
parameters: { check: "not_empty" }
next: process
- id: process
type: transform
parameters: { op: "uppercase" }
next: output
- id: output
type: assign
parameters: { target: "result", value: "$input" }
When no next is specified, list order is used.
Multi-role LLM Chain
roles:
- id: analyst
system_prompt: "Identify the top 3 problems."
- id: advisor
system_prompt: "Propose solutions for each problem."
steps:
- id: analyze
type: llm_call
role: analyst
next: propose
- id: propose
type: llm_call
role: advisor
Direct GAgent Type Dispatch (No YAML role)
Use when a step should call a concrete GAgent directly:
steps:
- id: call_specialist_agent
type: llm_call
parameters:
agent_type: RoleGAgent
agent_id: role:repo-analyst
prompt_prefix: "Summarize the repository architecture."
The same agent_type pattern also works for evaluate and reflect.
External Messaging via NyxID Relay
Workflow-local Telegram bridge actors are retired. When a workflow is triggered from a channel message, keep the channel traffic on the NyxID relay path: NyxID forwards the inbound platform message to Aevatar's /api/webhooks/nyxid-relay callback, the workflow processes normalized relay context, and replies go back through NyxID channel relay APIs instead of a workflow-owned send/wait-reply actor.
steps:
- id: compose_relay_reply
type: llm_call
role: advisor
parameters:
prompt_prefix: |
Please answer the inbound channel request.
Message: ${relay.message.text}
next: send_relay_reply
- id: send_relay_reply
type: connector_call
parameters:
connector: nyxid_channel_relay
operation: /api/v1/channel-relay/reply
message_id: "${relay.message_id}"
text: "${compose_relay_reply}"
timeout_ms: "30000"
If the work needs an external agent such as OpenClaw, model that as a normal relay conversation owned by NyxID and resume the workflow from the next inbound relay callback or a persisted continuation. Do not add workflow-local polling steps for platform chat history.
steps:
- id: request_external_research
type: connector_call
parameters:
connector: nyxid_channel_relay
operation: /api/v1/channel-relay/reply
message_id: "${relay.message_id}"
text: |
@${relay.external_agent_username}
Please research this repository and summarize the architecture.
Repo URL: ${collect_repo_url}
Please include final architecture details in your reply.
timeout_ms: "30000"
next: mark_external_research_pending
- id: mark_external_research_pending
type: assign
parameters:
target: "external_research_status"
value: "pending_relay_callback"
- id: process_openclaw_result
type: assign
parameters:
target: "architecture_summary"
value: "${relay.message.text}"
- id: timeout_fallback
type: assign
parameters:
target: relay_continuation_timeout
value: "Relay continuation timeout"
Key Points for Relay Delegation:
- Inbound ownership: NyxID owns the platform webhook and forwards normalized channel messages to Aevatar.
- Outbound ownership: Aevatar sends replies through NyxID channel relay APIs, usually
/api/v1/channel-relay/reply.
- Continuation: Long-running external work should resume from a later relay callback or persisted workflow continuation.
- No workflow polling: Do not poll platform chat history or wait for replies inside a workflow step.
Prompt Composition for External Agents
When a workflow asks an external agent to continue work through the relay, prompt quality matters more than strict format contracts.
Use this structure:
- Who + objective (one short line)
- Concrete task list (3-6 numbered items)
- Resolved runtime parameters (single final values only)
- Minimal output hint (soft preference, not hard protocol)
Key rules:
- Resolve workflow decisions first, then send only final facts.
- Good:
report_output_directory: /Users/me/Report
- Bad:
if user says yes then use path A else path B
- Do not forward raw control signals (
yes, no, true, false) without context.
- Convert them into explicit business meaning before sending.
- Prefer soft wording for external agents:
please include ... if possible / 尽量包含
- Avoid brittle
must return exact JSON unless the target is known to obey it.
- Keep bridge prompts short and actionable; avoid policy/debug text irrelevant to the target.
- When sending user-provided paths/URLs (e.g.
~/Report, REPORT_PATH), prefer plain text transport.
- Avoid
parse_mode: Markdown unless you fully escape Markdown symbols.
- Otherwise
~, _, *, [], () may alter visible text.
Anti-pattern (bad):
prompt_prefix: |
Default dir: ~/Report
Human decision: ${collect_report_directory_decision}
If human says yes:/path then use that path else default.
Better (good):
steps:
- id: route_report_directory
type: conditional
parameters:
condition: "/"
branches:
true: set_custom_report_directory
false: set_default_report_directory
- id: set_default_report_directory
type: assign
parameters:
target: "report_output_directory"
value: "~/Report"
- id: set_custom_report_directory
type: assign
parameters:
target: "report_output_directory"
value: "$input"
- id: send_to_openclaw
type: connector_call
parameters:
connector: nyxid_channel_relay
operation: /api/v1/channel-relay/reply
message_id: "${relay.message_id}"
text: |
@${relay.external_agent_username}
Please research this repository and write a report.
Repo URL: ${collect_repo_url}
Report output directory: ${report_output_directory}
Please include final REPORT_PATH if possible.
Runtime Defaults From config.json
You can inject shared runtime values via WorkflowRuntimeDefaults in host config.json; they become run metadata variables and can be referenced as ${...} in workflow YAML. Channel callback payload such as relay.message_id and relay.message.text comes from the NyxID relay ingress rather than static defaults.
{
"WorkflowRuntimeDefaults": {
"relay.external_agent_username": "openclaw_bot"
}
}
Request metadata with the same key overrides configured defaults.
Switch Branching
steps:
- id: route
type: switch
parameters:
branch.bug: handle_bug
branch.feature: handle_feature
branch._default: handle_other
branches:
bug: handle_bug
feature: handle_feature
_default: handle_other
Both parameters.branch.* and branches are expected.
Closed-world Deterministic Loop
configuration:
closed_world_mode: true
steps:
- id: init
type: assign
parameters: { target: "i", value: "0" }
next: loop
- id: loop
type: while
parameters:
condition: "${lt(i, 5)}"
step: assign
sub_param_target: "i"
sub_param_value: "${add(i, 1)}"
Retry and Error Handling
steps:
- id: risky_step
type: connector_call
parameters:
connector: "external_api"
timeout_ms: "10000"
retry:
max_attempts: 3
backoff: exponential
delay_ms: 2000
on_error:
strategy: fallback
fallback_step: safe_default
Connector Ergonomic Aliases
steps:
- id: read_health
type: http_get
parameters:
connector: "internal_http"
path: "/healthz"
- id: run_cli
type: cli_call
parameters:
connector: "demo_cli_dotnet"
- id: invoke_mcp
type: mcp_call
parameters:
connector: "demo_mcp"
tool: "list_tools"
References