create-leafmesh 2.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- create_leafmesh/__init__.py +3 -0
- create_leafmesh/cli.py +252 -0
- create_leafmesh/create.py +106 -0
- create_leafmesh/templates/Dockerfile +21 -0
- create_leafmesh/templates/README.md +309 -0
- create_leafmesh/templates/agency/__init__.py +0 -0
- create_leafmesh/templates/agency/advisor_agent.py +151 -0
- create_leafmesh/templates/agency/external_agents.py +278 -0
- create_leafmesh/templates/agency/fallback_researcher_agent.py +80 -0
- create_leafmesh/templates/agency/greeter_agent.py +79 -0
- create_leafmesh/templates/agency/processor_agent.py +90 -0
- create_leafmesh/templates/agency/researcher_agent.py +99 -0
- create_leafmesh/templates/agency/scheduler_agent.py +67 -0
- create_leafmesh/templates/agency/tools.py +123 -0
- create_leafmesh/templates/claude_skills/leafmesh/SKILL.md +2049 -0
- create_leafmesh/templates/claude_skills/leafmesh/agent-config-fields.md +1309 -0
- create_leafmesh/templates/claude_skills/leafmesh/examples.md +537 -0
- create_leafmesh/templates/claude_skills/leafmesh/reference.md +492 -0
- create_leafmesh/templates/configs/config.yaml +1028 -0
- create_leafmesh/templates/docker-compose.yml +28 -0
- create_leafmesh/templates/dockerignore +17 -0
- create_leafmesh/templates/env +109 -0
- create_leafmesh/templates/gitignore +33 -0
- create_leafmesh/templates/hitl_stub_receiver.py +149 -0
- create_leafmesh/templates/main.py +105 -0
- create_leafmesh/templates/requirements.txt +10 -0
- create_leafmesh-2.1.0.dist-info/METADATA +6 -0
- create_leafmesh-2.1.0.dist-info/RECORD +31 -0
- create_leafmesh-2.1.0.dist-info/WHEEL +5 -0
- create_leafmesh-2.1.0.dist-info/entry_points.txt +2 -0
- create_leafmesh-2.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,2049 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: leafmesh
|
|
3
|
+
description: Wire multi-agent meshes, HITL webhooks, can_call routing, decorators, and YAML config for the LeafMesh SDK. Use when adding agents, configuring flows, or debugging mesh issues.
|
|
4
|
+
allowed-tools: Read, Grep, Glob, Edit, Write, Bash
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# LeafMesh SDK Development Skill
|
|
8
|
+
|
|
9
|
+
You are an expert at building multi-agent orchestration systems with the LeafMesh SDK.
|
|
10
|
+
|
|
11
|
+
## When the user asks you to... do this:
|
|
12
|
+
|
|
13
|
+
| User says | Action |
|
|
14
|
+
|-----------|--------|
|
|
15
|
+
| "Add a new LLM agent" | 1. Add YAML block in `configs/config.yaml` 2. Create `agency/<name>_agent.py` (optional -- pure YAML works) 3. Wire `can_call` from upstream agents 4. Add entry point if needed |
|
|
16
|
+
| "Add a programmatic agent" | 1. Add YAML block with `agent_type: "programmatic"` 2. If connector-only: add `integration` + `connector_config` (no Python needed) 3. If Python logic: create `agency/<name>_agent.py` 4. Wire `can_call` |
|
|
17
|
+
| "Add an external agent" | 1. Add YAML block with `agent_type: "external"`, `framework`, and `connector_config` (no Python needed) 2. Optionally add `agency/<name>_agent.py` to post-process connector result 3. Wire `can_call` |
|
|
18
|
+
| "Integrate with Zapier/n8n/Composio/MCP" | Programmatic: `integration: "zapier"` + `connector_config`. External: `framework: "n8n"` + `connector_config`. Pre-compose helper: `@pre_compose(context_processor=zapier(...))`. See reference.md for all connector fields. |
|
|
19
|
+
| "Set up HITL / human review" | Pick **one** `human_interface`: `default` (inbox), `webhook` (channels/HTTP), or `api` (Python callback). Each one accepts a *different* set of config fields — see HITL section below. Mixing them (e.g. `default` + `webhook_config`) is rejected at YAML load. |
|
|
20
|
+
| "Connect agents" / "wire routing" | Add `can_call` entries with conditions. Use `calling_agent_response.field` in conditions. |
|
|
21
|
+
| "Add a tool" | Create `@global_tool` in `agency/tools.py`, add tool name to agent's `tools:` list in YAML |
|
|
22
|
+
| "Fan-out / fan-in" | Add multiple agents in `can_call` (fan-out), add `wait_for` expression on aggregator (fan-in) |
|
|
23
|
+
| "Schedule an agent" | Add `wake_up: "cron expression"` to agent YAML |
|
|
24
|
+
| "Debug why agent X isn't called" | Check `can_call` conditions, verify `calling_agent_response` fields match, check `communication_type` |
|
|
25
|
+
| "Validate my config" | POST the config to `/api/yaml/validate` or read `configs/config.yaml` and check structure |
|
|
26
|
+
|
|
27
|
+
## Current project config
|
|
28
|
+
!`cat configs/config.yaml 2>/dev/null | head -30`
|
|
29
|
+
|
|
30
|
+
## How This Project Works
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
configs/config.yaml <- Agent definitions, mesh topology, entry points, HITL config
|
|
34
|
+
agency/*_agent.py <- Agent logic (auto-discovered by filename match)
|
|
35
|
+
agency/tools.py <- Custom tools (@global_tool, @tool)
|
|
36
|
+
main.py <- Entry point: loads config, starts mesh + API server
|
|
37
|
+
hitl_stub_receiver.py <- Webhook stub for testing HITL locally
|
|
38
|
+
.env <- API keys, Redis, license key
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Auto-discovery**: The SDK matches Python function names to YAML agent names. `greeter_agent()` in `agency/greeter_agent.py` binds to the `greeter_agent:` block in `config.yaml`.
|
|
42
|
+
|
|
43
|
+
**Execution flow**: Entry point -> agent function runs -> SDK handles mesh communication (can_call), session state, Redis persistence, and observability automatically.
|
|
44
|
+
|
|
45
|
+
## Core SDK Pattern
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from leafmesh import LeafMesh
|
|
49
|
+
|
|
50
|
+
sdk = LeafMesh.from_yaml("configs/config.yaml")
|
|
51
|
+
await sdk.start() # Starts Redis, agent registry, API server, scheduler
|
|
52
|
+
result = await sdk.mesh_call("entry_point_name", input_data={"message": "Hello"}, session_id="optional")
|
|
53
|
+
await sdk.stop()
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## User-Facing APIs
|
|
57
|
+
|
|
58
|
+
The SDK auto-starts a FastAPI server. These are the APIs users call to trigger workflows and interact with the mesh:
|
|
59
|
+
|
|
60
|
+
| Method | Endpoint | Purpose |
|
|
61
|
+
|--------|----------|---------|
|
|
62
|
+
| POST | `/api/mesh/request` | Trigger a workflow via entry point |
|
|
63
|
+
| POST | `/api/mesh/stream` | SSE stream of LLM response via entry point |
|
|
64
|
+
| POST | `/webhook/{entry_point}` | Webhook: new task OR HITL human response |
|
|
65
|
+
| POST | `/callback/{agent_name}` | Connector callback: async response from external system (n8n, Zapier, etc.) |
|
|
66
|
+
| GET | `/api/mesh/entry_points` | List available entry points |
|
|
67
|
+
| GET | `/api/webhook/secret` | Get HMAC signing secret for webhooks |
|
|
68
|
+
| POST | `/api/yaml/validate` | Validate a full config (for frontend editors) |
|
|
69
|
+
| POST | `/api/sessions/{session_id}/agents/{agent_name}/rerun` | Re-run an agent in an existing session, with optional feedback / new input |
|
|
70
|
+
| GET | `/health` | Health check |
|
|
71
|
+
| GET | `/docs` | Interactive API docs (ReDoc) |
|
|
72
|
+
|
|
73
|
+
### Triggering a workflow
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
# Via API
|
|
77
|
+
curl -X POST http://127.0.0.1:18820/api/mesh/request \
|
|
78
|
+
-H "Content-Type: application/json" \
|
|
79
|
+
-d '{"entry_point": "greet_user", "data": {"message": "Hello"}}'
|
|
80
|
+
|
|
81
|
+
# Via webhook (external systems: Slack, Zapier, n8n, etc.)
|
|
82
|
+
curl -X POST http://127.0.0.1:18820/webhook/greet_user \
|
|
83
|
+
-H "Content-Type: application/json" \
|
|
84
|
+
-H "X-LeafMesh-Signature: sha256=<hmac>" \
|
|
85
|
+
-d '{"message": "Hello"}'
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Webhook smart routing
|
|
89
|
+
|
|
90
|
+
The webhook endpoint routes automatically based on the payload:
|
|
91
|
+
|
|
92
|
+
| Payload | Behavior |
|
|
93
|
+
|---------|----------|
|
|
94
|
+
| No `session_id` | **New task** -- routes to the entry point's target agent |
|
|
95
|
+
| `session_id` + agent is paused (HITL) | **Resume** -- delivers human response to waiting agent |
|
|
96
|
+
| `session_id` + agent is busy (mid-chain) | **Rejected** -- returns `status: "busy"` |
|
|
97
|
+
| `session_id` + agent is idle | **New task on same session** -- preserves conversation history |
|
|
98
|
+
|
|
99
|
+
### HMAC webhook signing
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
# Get secret
|
|
103
|
+
SECRET=$(curl -s http://127.0.0.1:18820/api/webhook/secret | jq -r .secret)
|
|
104
|
+
|
|
105
|
+
# Sign a payload
|
|
106
|
+
BODY='{"session_id": "sess1", "decision": "approved", "message": "Looks good"}'
|
|
107
|
+
SIG=$(echo -n "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $2}')
|
|
108
|
+
|
|
109
|
+
# Send signed webhook
|
|
110
|
+
curl -X POST http://127.0.0.1:18820/webhook/greet_user \
|
|
111
|
+
-H "Content-Type: application/json" \
|
|
112
|
+
-H "X-LeafMesh-Signature: sha256=$SIG" \
|
|
113
|
+
-d "$BODY"
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Rerunning an agent
|
|
117
|
+
|
|
118
|
+
Re-run a single agent inside an existing session, optionally with feedback so it can self-correct (`leafmesh >= 1.0.299`). Use this for a "Rerun" button in your UI, custom retry rules outside Manager analysis, or debugging from a script.
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
# Python — same input as last time, no feedback
|
|
122
|
+
result = await sdk.rerun_agent(
|
|
123
|
+
agent_name="advisor_agent",
|
|
124
|
+
session_id="sess-123",
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
# Python — with feedback (caller spotted a bad shape)
|
|
128
|
+
result = await sdk.rerun_agent(
|
|
129
|
+
agent_name="advisor_agent",
|
|
130
|
+
session_id="sess-123",
|
|
131
|
+
feedback={"error": "missing action_items", "expected_shape": {"action_items": "list"}},
|
|
132
|
+
reason="schema_mismatch",
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# Python — deliberately steer with new input
|
|
136
|
+
result = await sdk.rerun_agent(
|
|
137
|
+
agent_name="processor_agent",
|
|
138
|
+
session_id="sess-123",
|
|
139
|
+
new_input={"message": "Now check refunds instead", "request_type": "refund"},
|
|
140
|
+
)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
# HTTP — same primitive, for non-Python clients (e.g. ADK Studio rerun button)
|
|
145
|
+
curl -X POST http://127.0.0.1:18820/api/sessions/sess-123/agents/advisor_agent/rerun \
|
|
146
|
+
-H "Content-Type: application/json" \
|
|
147
|
+
-d '{"feedback": {"error": "lacks specifics"}, "reason": "user_request"}'
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
`feedback` is rendered per agent type — LLM agents see a correction note in their prompt, human agents see it in their inbox/channel UI, external connectors receive `data._rerun_context`, programmatic agents receive `input_data._rerun_context`. Both Python and HTTP forms route through `Manager.execute_state` — same conductor as strict yields enforcement (`enforce_yields: true`).
|
|
151
|
+
|
|
152
|
+
Returns dispatch metadata (the agent runs asynchronously — subscribe to events on `session_id` for the result):
|
|
153
|
+
|
|
154
|
+
```json
|
|
155
|
+
{"status": "dispatched", "agent": "advisor_agent", "session_id": "sess-123", "input_source": "stored_original", "reason": "user_request"}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
When `new_input` is omitted, the SDK pulls the agent's most recent stored input from `auto_store_agent_input`. If neither exists, the call raises `LeafMeshError`.
|
|
159
|
+
|
|
160
|
+
## Agent Types
|
|
161
|
+
|
|
162
|
+
| Type | Use When | LLM? | Pure YAML? | Example |
|
|
163
|
+
|------|----------|------|------------|---------|
|
|
164
|
+
| `llm` | Need AI reasoning, generation, analysis | Yes | Yes | Conversation, research, advisory |
|
|
165
|
+
| `human` | Need human decisions, approvals, HITL review | No | Yes | Approval gates, chat interfaces |
|
|
166
|
+
| `programmatic` | Deterministic logic, API calls, data transforms | No | Yes (with connector) | Data processing, Zapier/n8n actions |
|
|
167
|
+
| `external` | Wrap existing framework (CrewAI, LangGraph, n8n, etc.) | Varies | Yes (with connector) | Framework integration |
|
|
168
|
+
|
|
169
|
+
All agent types work from pure YAML. For programmatic and external agents, a connector (`integration` or `framework` + `connector_config`) can be the entire execution engine -- no Python code needed. The connector response is returned as-is. Optionally add `@sdk.intelligence()` to post-process the connector result.
|
|
170
|
+
|
|
171
|
+
## STRICT — fields by `agent_type`
|
|
172
|
+
|
|
173
|
+
YAML load rejects fields that don't apply to the declared `agent_type`. Set only the fields that match.
|
|
174
|
+
|
|
175
|
+
| `agent_type` | Allowed type-specific fields |
|
|
176
|
+
|---|---|
|
|
177
|
+
| `llm` | `model`, `prompt`, `temperature`, `max_tokens`, `max_completion_tokens`, `reasoning`, `thinking`, `thinking_budget`, `enable_prompt_caching`, `response_format`, `optimization_strategy`, `context_parts`, `tools`, `tool_choice`, `max_tool_calls_per_message`, `tool_call_timeout`, `allow_parallel_tool_calls`, `tool_categories` |
|
|
178
|
+
| `human` | `human_interface`, `human_timeout_seconds`, `human_context_template`, `human_prompt_template`, `fallback_on_timeout`, `fallback_response`, `require_human_confirmation`, `human_escalation_triggers`, `operator_ids`, `webhook_config`, `channels` |
|
|
179
|
+
| `external` | `framework` (**required**), `connector_config` |
|
|
180
|
+
| `programmatic` | `integration`; `connector_config` allowed only when `integration` is set |
|
|
181
|
+
|
|
182
|
+
**Universal fields** (any type): `name`, `description`, `agent_type`, `communication_type`, `parallel`, `max_concurrent`, `wake_up`, `yields`, `inputs`, `can_call`, `narration`, `wait_for`, `wait_for_timeout`, `auto_store_response`, `auto_store_yields`, `enforce_yields`, `enforce_yields_retry`, `memory`, `knowledge`.
|
|
183
|
+
|
|
184
|
+
**Do not set `is_human_powered` manually** — it's auto-derived from `agent_type` and is silently overwritten by the validator.
|
|
185
|
+
|
|
186
|
+
## STRICT — `human_interface` rules
|
|
187
|
+
|
|
188
|
+
A human agent picks **exactly one** interface. The fields below depend on which one.
|
|
189
|
+
|
|
190
|
+
| `human_interface` | Path | Required fields | Forbidden together |
|
|
191
|
+
|---|---|---|---|
|
|
192
|
+
| `default` | ADK-Frontend HITL inbox (hosted only) | none | do NOT set `webhook_config` or `channels` — they're ignored at runtime |
|
|
193
|
+
| `webhook` | Outbound HTTP / channel adapters | `webhook_config.outbound_url` OR `channels` (one is enough) | — |
|
|
194
|
+
| `api` / `custom` | Python callback registered via `sdk.register_human_handler()` | none | do NOT set `webhook_config` or `channels` |
|
|
195
|
+
|
|
196
|
+
`channels` only fires when `human_interface: webhook`. Setting `channels` with `default` or `api` is silently ignored at runtime — don't do it.
|
|
197
|
+
|
|
198
|
+
## Human-in-the-Loop (HITL)
|
|
199
|
+
|
|
200
|
+
The human agent is a full mesh node -- not just an approval step. It participates in the agent chain like any other agent, with `can_call` conditions that route based on context.
|
|
201
|
+
|
|
202
|
+
### HITL YAML Config — pick **one** of these three blocks
|
|
203
|
+
|
|
204
|
+
> Don't mix interfaces. The validator rejects fields that don't apply to the chosen `human_interface`.
|
|
205
|
+
|
|
206
|
+
#### Option A — `human_interface: webhook` (outbound webhook OR channel adapter)
|
|
207
|
+
|
|
208
|
+
```yaml
|
|
209
|
+
agents:
|
|
210
|
+
client:
|
|
211
|
+
agent_type: "human"
|
|
212
|
+
human_interface: "webhook" # outbound HTTP / channels
|
|
213
|
+
communication_type: "dual" # respond + wait for inbound response
|
|
214
|
+
human_timeout_seconds: 300
|
|
215
|
+
# operator_ids: ["alice@co.com"] # restrict who sees this in inbox (empty = all)
|
|
216
|
+
|
|
217
|
+
webhook_config:
|
|
218
|
+
outbound_url: "http://127.0.0.1:9999/human-notify"
|
|
219
|
+
outbound_headers: {Content-Type: "application/json"}
|
|
220
|
+
outbound_timeout: 30
|
|
221
|
+
max_retries: 1
|
|
222
|
+
retry_delay: 2
|
|
223
|
+
# inbound_endpoint is auto-derived from entry_points
|
|
224
|
+
|
|
225
|
+
# OR (instead of webhook_config) use a native channel adapter:
|
|
226
|
+
# channels:
|
|
227
|
+
# slack:
|
|
228
|
+
# bot_token: "${SLACK_BOT_TOKEN}"
|
|
229
|
+
# signing_secret: "${SLACK_SIGNING_SECRET}"
|
|
230
|
+
# listen_channels: ["${SLACK_CHANNEL_ID}"]
|
|
231
|
+
# post_channel: "${SLACK_POST_CHANNEL}"
|
|
232
|
+
|
|
233
|
+
can_call:
|
|
234
|
+
- agent: "greeter_agent"
|
|
235
|
+
condition: "not calling_agent_response.from_agent"
|
|
236
|
+
- agent: "processor_agent"
|
|
237
|
+
condition: "calling_agent_response.from_agent == 'greeter_agent'"
|
|
238
|
+
|
|
239
|
+
yields: {request_data: "object"}
|
|
240
|
+
inputs: {user_message: "string"}
|
|
241
|
+
|
|
242
|
+
entry_points:
|
|
243
|
+
- name: "greet_user"
|
|
244
|
+
target: "greeter_agent"
|
|
245
|
+
- name: "human_contact"
|
|
246
|
+
target: "client"
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
#### Option B — `human_interface: default` (ADK-Frontend HITL inbox, hosted only)
|
|
250
|
+
|
|
251
|
+
```yaml
|
|
252
|
+
agents:
|
|
253
|
+
client:
|
|
254
|
+
agent_type: "human"
|
|
255
|
+
human_interface: "default" # writes to Redis inbox + stream — that's it
|
|
256
|
+
communication_type: "dual"
|
|
257
|
+
human_timeout_seconds: 300
|
|
258
|
+
# operator_ids: ["alice@co.com"]
|
|
259
|
+
# NO webhook_config, NO channels — they're ignored on this interface
|
|
260
|
+
can_call:
|
|
261
|
+
- agent: "greeter_agent"
|
|
262
|
+
condition: "not calling_agent_response.from_agent"
|
|
263
|
+
- agent: "processor_agent"
|
|
264
|
+
condition: "calling_agent_response.from_agent == 'greeter_agent'"
|
|
265
|
+
yields: {request_data: "object"}
|
|
266
|
+
inputs: {user_message: "string"}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
#### Option C — `human_interface: api` (Python callback)
|
|
270
|
+
|
|
271
|
+
```yaml
|
|
272
|
+
agents:
|
|
273
|
+
client:
|
|
274
|
+
agent_type: "human"
|
|
275
|
+
human_interface: "api" # call into Python — no HTTP, no inbox
|
|
276
|
+
communication_type: "dual"
|
|
277
|
+
human_timeout_seconds: 300
|
|
278
|
+
# NO webhook_config, NO channels
|
|
279
|
+
can_call:
|
|
280
|
+
- agent: "greeter_agent"
|
|
281
|
+
condition: "not calling_agent_response.from_agent"
|
|
282
|
+
yields: {request_data: "object"}
|
|
283
|
+
inputs: {user_message: "string"}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
```python
|
|
287
|
+
# Register the Python handler for human_interface: api
|
|
288
|
+
async def my_human_handler(context, session_id, timeout):
|
|
289
|
+
return {"human_decision": "approved", "human_message": "Looks good"}
|
|
290
|
+
sdk.register_human_handler("client", my_human_handler)
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### HITL Scenarios
|
|
294
|
+
|
|
295
|
+
**Scenario 1 (System-initiated):** System triggers workflow, human reviews mid-flow
|
|
296
|
+
```
|
|
297
|
+
POST /api/mesh/request {"entry_point": "greet_user", "data": {"message": "..."}}
|
|
298
|
+
-> greeter_agent (LLM) -> client (HITL, outbound webhook sent)
|
|
299
|
+
-> [human reviews, responds via webhook]
|
|
300
|
+
-> from_agent == "greeter_agent" -> processor_agent -> researcher + fallback -> advisor
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
**Scenario 2 (Human-initiated):** Human contacts mesh first via webhook
|
|
304
|
+
```
|
|
305
|
+
POST /webhook/human_contact {"message": "I need help with..."}
|
|
306
|
+
-> client (no from_agent -> routes to greeter)
|
|
307
|
+
-> greeter_agent (LLM, dual callback -> client)
|
|
308
|
+
-> client (HITL, outbound webhook sent)
|
|
309
|
+
-> [human reviews, responds via webhook]
|
|
310
|
+
-> from_agent == "greeter_agent" -> processor_agent -> researcher + fallback -> advisor
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
**Scenario 3 (Same session, new message):** Human sends another message after workflow completes
|
|
314
|
+
```
|
|
315
|
+
POST /webhook/human_contact {"session_id": "existing-session", "message": "Now check my refund"}
|
|
316
|
+
-> Session not paused -> treated as new request on same session
|
|
317
|
+
-> Conversation history preserved from previous interaction
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### How from_agent Routing Works
|
|
321
|
+
|
|
322
|
+
When an agent calls the human agent, the SDK stores `called_by` in Redis. When the human responds via webhook, the SDK includes `from_agent` in the output data so `can_call` conditions can route based on who called.
|
|
323
|
+
|
|
324
|
+
```yaml
|
|
325
|
+
# In output_data available to can_call conditions:
|
|
326
|
+
calling_agent_response.from_agent # Who called the human ("greeter_agent" or "")
|
|
327
|
+
calling_agent_response.human_message # What the human said
|
|
328
|
+
calling_agent_response.human_decision # Human's decision field
|
|
329
|
+
calling_agent_response.human_data # Any data from the human
|
|
330
|
+
calling_agent_response.human_initiated # true (always for human output)
|
|
331
|
+
calling_agent_response.source_agent # The human agent name ("client")
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### Channel Adapters (Slack, Telegram, etc.)
|
|
335
|
+
|
|
336
|
+
`channels` only fires when `human_interface: webhook`. Other interfaces silently ignore it. To make a channel actually deliver messages, the human agent must declare the webhook interface:
|
|
337
|
+
|
|
338
|
+
```yaml
|
|
339
|
+
agents:
|
|
340
|
+
client:
|
|
341
|
+
agent_type: "human"
|
|
342
|
+
human_interface: "webhook" # REQUIRED for channels to fire
|
|
343
|
+
communication_type: "dual"
|
|
344
|
+
channels:
|
|
345
|
+
slack:
|
|
346
|
+
bot_token: "${SLACK_BOT_TOKEN}"
|
|
347
|
+
signing_secret: "${SLACK_SIGNING_SECRET}"
|
|
348
|
+
listen_channels: ["${SLACK_LISTEN_CHANNEL}"]
|
|
349
|
+
post_channel: "${SLACK_POST_CHANNEL}"
|
|
350
|
+
telegram:
|
|
351
|
+
bot_token: "${TELEGRAM_BOT_TOKEN}"
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
The HITL flow works identically across all channels -- the SDK handles transport, the agent handles routing. With multiple channels configured, the SDK tries each in order; if all fail, it falls back to `webhook_config.outbound_url` (when set).
|
|
355
|
+
|
|
356
|
+
## YAML Agent Config (All Fields)
|
|
357
|
+
|
|
358
|
+
```yaml
|
|
359
|
+
agents:
|
|
360
|
+
# ── LLM Agent ──
|
|
361
|
+
my_agent:
|
|
362
|
+
name: my_agent
|
|
363
|
+
agent_type: "llm" # llm | human | programmatic | external
|
|
364
|
+
description: "What this agent does"
|
|
365
|
+
model: "gpt-4o-mini" # Any supported model
|
|
366
|
+
prompt: "You are a helpful..." # System prompt
|
|
367
|
+
temperature: 0.1
|
|
368
|
+
max_tokens: 800
|
|
369
|
+
max_completion_tokens: 1000 # For o1/o3/gpt-5.x reasoning models
|
|
370
|
+
communication_type: "dual" # dual | chain | execute
|
|
371
|
+
|
|
372
|
+
# Mesh routing
|
|
373
|
+
can_call:
|
|
374
|
+
- agent: "next_agent"
|
|
375
|
+
condition: "calling_agent_response.status == 'ready'"
|
|
376
|
+
wait_for: "agent_a AND (agent_b OR agent_c)"
|
|
377
|
+
wait_for_timeout: 60
|
|
378
|
+
|
|
379
|
+
# Tools
|
|
380
|
+
tools: ["word_count", "timestamp"]
|
|
381
|
+
tool_categories: ["data", "utility"]
|
|
382
|
+
tool_choice: "auto"
|
|
383
|
+
max_tool_calls_per_message: 5
|
|
384
|
+
allow_parallel_tool_calls: true
|
|
385
|
+
tool_call_timeout: 30
|
|
386
|
+
|
|
387
|
+
# Features
|
|
388
|
+
reasoning: true # SDK chain-of-thought tool injection (any model)
|
|
389
|
+
thinking: true # Native model thinking (Anthropic/OpenAI o-series/Gemini 2.5+)
|
|
390
|
+
thinking_budget: 8192 # Max thinking tokens (1024-32768)
|
|
391
|
+
enable_prompt_caching: true # Provider-native prompt caching (~90% savings on Anthropic)
|
|
392
|
+
parallel: true # Parallel execution
|
|
393
|
+
max_concurrent: 3 # Max concurrent invocations
|
|
394
|
+
wake_up: "0 9 * * *" # Cron schedule
|
|
395
|
+
optimization_strategy: "performance" # performance | cost | speed
|
|
396
|
+
|
|
397
|
+
# Structured output — force LLM to respond with valid JSON schema
|
|
398
|
+
# response_format:
|
|
399
|
+
# type: "object"
|
|
400
|
+
# properties:
|
|
401
|
+
# summary: { type: "string" }
|
|
402
|
+
# score: { type: "number" }
|
|
403
|
+
# required: ["summary"]
|
|
404
|
+
|
|
405
|
+
# Smart memory
|
|
406
|
+
memory:
|
|
407
|
+
strategy: "hybrid" # recency | relevance | hybrid
|
|
408
|
+
limit: 10
|
|
409
|
+
cross_session: true
|
|
410
|
+
cross_session_limit: 50
|
|
411
|
+
relevance_weight: 0.6
|
|
412
|
+
recency_weight: 0.4
|
|
413
|
+
decay_hours: 24
|
|
414
|
+
|
|
415
|
+
# Yields & inputs
|
|
416
|
+
yields: {summary: string, score: number}
|
|
417
|
+
inputs: {query: string, context: object}
|
|
418
|
+
|
|
419
|
+
# Context parts (shape LLM tone)
|
|
420
|
+
context_parts:
|
|
421
|
+
care: "Be empathetic and patient"
|
|
422
|
+
sentiment_analysis: "Detect user frustration"
|
|
423
|
+
guardrails: "Never discuss competitors"
|
|
424
|
+
|
|
425
|
+
# auto_store_response: true # Auto-store responses in Redis (default: true)
|
|
426
|
+
# auto_store_yields: true # Auto-store yields in Redis (default: true)
|
|
427
|
+
|
|
428
|
+
# ── Programmatic Agent (connector-only, no Python needed) ──
|
|
429
|
+
zapier_agent:
|
|
430
|
+
agent_type: "programmatic"
|
|
431
|
+
integration: "zapier" # zapier | composio | n8n | mcp
|
|
432
|
+
connector_config:
|
|
433
|
+
connection: "google_sheets"
|
|
434
|
+
action: "create_spreadsheet_row"
|
|
435
|
+
api_key: "${ZAPIER_API_KEY}"
|
|
436
|
+
# mode: "callback" # For async workflows
|
|
437
|
+
# callback_timeout: 120
|
|
438
|
+
parallel: true # Parallel execution
|
|
439
|
+
max_concurrent: 3 # Max concurrent invocations
|
|
440
|
+
yields: {status: string}
|
|
441
|
+
inputs: {data: object}
|
|
442
|
+
|
|
443
|
+
# ── External Agent (connector-only, no Python needed) ──
|
|
444
|
+
crew_agent:
|
|
445
|
+
agent_type: "external"
|
|
446
|
+
framework: "crewai" # crewai | langgraph | autogen | a2a | mcp | n8n | zapier | composio | custom
|
|
447
|
+
connector_config:
|
|
448
|
+
endpoint: "http://localhost:9000"
|
|
449
|
+
api_key: "${CREWAI_API_KEY}" # Bearer Token
|
|
450
|
+
# user_api_key: "${CREWAI_USER_API_KEY}" # User Bearer Token (preferred over api_key)
|
|
451
|
+
yields: {result: object}
|
|
452
|
+
inputs: {task: string}
|
|
453
|
+
|
|
454
|
+
# ── Human Agent — webhook interface (with optional channels) ──
|
|
455
|
+
reviewer:
|
|
456
|
+
agent_type: "human"
|
|
457
|
+
human_interface: "webhook" # default | webhook | api | custom
|
|
458
|
+
communication_type: "dual"
|
|
459
|
+
human_timeout_seconds: 300
|
|
460
|
+
# operator_ids: ["alice@co.com"] # restrict inbox (empty = all)
|
|
461
|
+
# fallback_on_timeout: true
|
|
462
|
+
# fallback_response: {decision: "timeout_default", message: "Request timed out"}
|
|
463
|
+
# require_human_confirmation: false
|
|
464
|
+
# human_escalation_triggers: ["urgent", "high_value"]
|
|
465
|
+
|
|
466
|
+
# webhook_config — required when human_interface: webhook AND no channels
|
|
467
|
+
webhook_config:
|
|
468
|
+
outbound_url: "http://127.0.0.1:9999/human-notify"
|
|
469
|
+
outbound_headers: {Content-Type: "application/json"}
|
|
470
|
+
outbound_timeout: 30
|
|
471
|
+
# inbound_endpoint: "/webhook/review" # auto-derived from entry_points
|
|
472
|
+
# inbound_auth_token: "${WEBHOOK_AUTH_TOKEN}"
|
|
473
|
+
max_retries: 1
|
|
474
|
+
retry_delay: 2
|
|
475
|
+
# response_mapping: {user_reply: "response"}
|
|
476
|
+
|
|
477
|
+
# channels — only valid when human_interface: webhook
|
|
478
|
+
channels:
|
|
479
|
+
slack:
|
|
480
|
+
bot_token: "${SLACK_BOT_TOKEN}"
|
|
481
|
+
signing_secret: "${SLACK_SIGNING_SECRET}"
|
|
482
|
+
listen_channels: ["${SLACK_CHANNEL_ID}"]
|
|
483
|
+
post_channel: "${SLACK_POST_CHANNEL}"
|
|
484
|
+
|
|
485
|
+
can_call:
|
|
486
|
+
- agent: "publisher"
|
|
487
|
+
condition: "calling_agent_response.human_decision == 'approved'"
|
|
488
|
+
yields: {decision: string}
|
|
489
|
+
inputs: {request: object}
|
|
490
|
+
|
|
491
|
+
# ── Human Agent — default (inbox) interface — hosted only ──
|
|
492
|
+
# reviewer_inbox:
|
|
493
|
+
# agent_type: "human"
|
|
494
|
+
# human_interface: "default" # writes only to ADK-Frontend HITL inbox
|
|
495
|
+
# communication_type: "dual"
|
|
496
|
+
# human_timeout_seconds: 300
|
|
497
|
+
# # NO webhook_config, NO channels — runtime ignores them
|
|
498
|
+
# can_call: [...]
|
|
499
|
+
# yields: {decision: string}
|
|
500
|
+
# inputs: {request: object}
|
|
501
|
+
|
|
502
|
+
# ── Human Agent — api interface (Python callback) ──
|
|
503
|
+
# reviewer_api:
|
|
504
|
+
# agent_type: "human"
|
|
505
|
+
# human_interface: "api" # routed to a registered Python handler
|
|
506
|
+
# communication_type: "dual"
|
|
507
|
+
# human_timeout_seconds: 300
|
|
508
|
+
# # NO webhook_config, NO channels
|
|
509
|
+
# can_call: [...]
|
|
510
|
+
# yields: {decision: string}
|
|
511
|
+
# inputs: {request: object}
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
## Condition Syntax (can_call conditions)
|
|
515
|
+
|
|
516
|
+
Conditions evaluate agent output data:
|
|
517
|
+
|
|
518
|
+
```yaml
|
|
519
|
+
can_call:
|
|
520
|
+
- agent: "specialist"
|
|
521
|
+
condition: "calling_agent_response.status == 'needs_specialist'"
|
|
522
|
+
- agent: "escalation"
|
|
523
|
+
condition: "calling_agent_response.priority == 'high'"
|
|
524
|
+
- agent: "greeter"
|
|
525
|
+
condition: "not calling_agent_response.from_agent" # Falsy check
|
|
526
|
+
- agent: "processor"
|
|
527
|
+
condition: "calling_agent_response.from_agent == 'greeter_agent'"
|
|
528
|
+
- agent: "default"
|
|
529
|
+
condition: "true" # Always matches
|
|
530
|
+
- agent: "urgent"
|
|
531
|
+
condition: "calling_agent_response.item_count > 0" # Numeric comparison
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
**Operators**: `==`, `!=`, `>`, `<`, `>=`, `<=`, `and`, `or`, `not`
|
|
535
|
+
**Access**: `calling_agent_response.field_name` for the upstream agent's output
|
|
536
|
+
|
|
537
|
+
## Decorators -- The 5 Pipelines
|
|
538
|
+
|
|
539
|
+
### @pre_compose -- Prepare inputs BEFORE the LLM
|
|
540
|
+
```python
|
|
541
|
+
from leafmesh import pre_compose
|
|
542
|
+
|
|
543
|
+
@pre_compose(
|
|
544
|
+
context_processor=enrich_context, # -> context["prepared_data"]["business_context"]
|
|
545
|
+
input_processor=clean_input, # -> context["prepared_data"]["clean_user_input"]
|
|
546
|
+
others_processor=load_extras, # -> context["prepared_data"]["others"]
|
|
547
|
+
)
|
|
548
|
+
async def my_agent(llm_response, input_data, context):
|
|
549
|
+
prepared = context.get("prepared_data", {})
|
|
550
|
+
return {"result": llm_response}
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
### @chain -- Sequential post-processing AFTER the LLM
|
|
554
|
+
```python
|
|
555
|
+
from leafmesh import chain
|
|
556
|
+
|
|
557
|
+
@chain(validate, format_output)
|
|
558
|
+
async def my_agent(llm_response, input_data, context):
|
|
559
|
+
return {"recommendations": llm_response}
|
|
560
|
+
# Runs: agent() -> validate() -> format_output()
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
### @chain_with_results -- Chain that collects intermediate results
|
|
564
|
+
```python
|
|
565
|
+
from leafmesh import chain_with_results
|
|
566
|
+
|
|
567
|
+
@chain_with_results(step1, step2, step3)
|
|
568
|
+
async def my_agent(llm_response, input_data, context):
|
|
569
|
+
return {"main": llm_response}
|
|
570
|
+
# Returns: {"main_result": ..., "chain_results": [step1_result, step2_result, step3_result]}
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
### @conditional_chain -- Chain only if condition is met
|
|
574
|
+
```python
|
|
575
|
+
from leafmesh import conditional_chain
|
|
576
|
+
|
|
577
|
+
@conditional_chain(
|
|
578
|
+
lambda result, ctx: result.get("needs_review"),
|
|
579
|
+
review_step, approval_step
|
|
580
|
+
)
|
|
581
|
+
async def my_agent(llm_response, input_data, context):
|
|
582
|
+
return {"needs_review": True, "data": llm_response}
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
### @compose -- Shape output per downstream agent
|
|
586
|
+
```python
|
|
587
|
+
from leafmesh import compose
|
|
588
|
+
|
|
589
|
+
@compose(
|
|
590
|
+
billing_agent=lambda result, ctx: {"invoice": result["invoice_id"]},
|
|
591
|
+
support_agent=lambda result, ctx: {"ticket": result["summary"]},
|
|
592
|
+
)
|
|
593
|
+
async def my_agent(llm_response, input_data, context):
|
|
594
|
+
return {"invoice_id": "INV-123", "summary": "Issue resolved"}
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
**Combining decorators** (order matters -- bottom-up execution):
|
|
598
|
+
```python
|
|
599
|
+
@chain(validate, score) # 3. Post-process
|
|
600
|
+
@compose(report=shape_report) # 2. Shape per-target
|
|
601
|
+
async def advisor(llm_response, input_data, context):
|
|
602
|
+
return {...} # 1. Agent logic
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
## Communication Types
|
|
606
|
+
|
|
607
|
+
| Type | Behavior |
|
|
608
|
+
|------|----------|
|
|
609
|
+
| `dual` | Agent responds immediately, then calls downstream agents asynchronously |
|
|
610
|
+
| `chain` | Routes to downstream agent, waits for its result, returns combined |
|
|
611
|
+
| `execute` | Calls downstream, uses result inline, continues processing |
|
|
612
|
+
|
|
613
|
+
## Fan-In Patterns (wait_for)
|
|
614
|
+
|
|
615
|
+
```yaml
|
|
616
|
+
wait_for: "A AND B" # Wait for both
|
|
617
|
+
wait_for: "A OR B" # First one wins (race)
|
|
618
|
+
wait_for: "A AND B?" # A required, B optional
|
|
619
|
+
wait_for: "A AND (B OR C)" # A required + race between B and C
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
Access upstream yields in agent function:
|
|
623
|
+
```python
|
|
624
|
+
upstream_yields = input_data.get("upstream_yields", {})
|
|
625
|
+
agent_a_data = upstream_yields.get("agent_a", {})
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
## Tools
|
|
629
|
+
|
|
630
|
+
```python
|
|
631
|
+
from leafmesh import global_tool, tool
|
|
632
|
+
|
|
633
|
+
@global_tool(name="lookup", description="Look up a record", category="data",
|
|
634
|
+
allowed_agents=["researcher_agent"], requires_confirmation=True)
|
|
635
|
+
def lookup(record_id: str) -> dict:
|
|
636
|
+
return {"id": record_id, "data": "..."}
|
|
637
|
+
|
|
638
|
+
@tool(name="format_md", description="Format as markdown")
|
|
639
|
+
def format_md(items: list) -> str:
|
|
640
|
+
return "\n".join(f"- {item}" for item in items)
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
## Manager (Coordination + Escalation)
|
|
644
|
+
|
|
645
|
+
```yaml
|
|
646
|
+
manager:
|
|
647
|
+
enabled: true
|
|
648
|
+
model: "gpt-4o-mini" # Summarizer model
|
|
649
|
+
domain: "generic" # generic | ecommerce | data_analysis
|
|
650
|
+
routing:
|
|
651
|
+
mode: "learning" # static | learning (adaptive routing)
|
|
652
|
+
confidence_threshold: 0.7
|
|
653
|
+
fallback: "all"
|
|
654
|
+
escalation:
|
|
655
|
+
targets:
|
|
656
|
+
- type: "human_agent"
|
|
657
|
+
agent: "client"
|
|
658
|
+
# - type: "webhook"
|
|
659
|
+
# url: "${ESCALATION_WEBHOOK_URL}"
|
|
660
|
+
# - type: "channel"
|
|
661
|
+
# provider: "slack"
|
|
662
|
+
# channel_id: "${ESCALATION_SLACK_CHANNEL}"
|
|
663
|
+
auto_escalate:
|
|
664
|
+
max_retries: 3
|
|
665
|
+
max_errors_per_session: 5
|
|
666
|
+
timeout_threshold: 2
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
## LLM Providers
|
|
670
|
+
|
|
671
|
+
| Provider | Model Prefix | Config |
|
|
672
|
+
|----------|-------------|--------|
|
|
673
|
+
| OpenAI | `gpt-`, `o1-`, `o3-`, `o4-` | `OPENAI_API_KEY` env var |
|
|
674
|
+
| Anthropic | `claude-` | `ANTHROPIC_API_KEY` env var |
|
|
675
|
+
| Google | `gemini-` | `GOOGLE_API_KEY` env var |
|
|
676
|
+
| DeepSeek | `deepseek-` | `DEEPSEEK_API_KEY` env var |
|
|
677
|
+
| AWS Bedrock | `bedrock/model-name` | `mesh.bedrock.region` in YAML |
|
|
678
|
+
| Google Vertex | `vertex/model-name` | `mesh.vertex.project` + `location` |
|
|
679
|
+
| Azure Foundry | `foundry/model-name` | `mesh.foundry.endpoint` in YAML |
|
|
680
|
+
| Local (vLLM, Ollama, etc.) | any name | `mesh.local.endpoint` + `server_type` in YAML |
|
|
681
|
+
|
|
682
|
+
## Building New Agents -- Step by Step
|
|
683
|
+
|
|
684
|
+
1. **Add YAML config** in `configs/config.yaml` under `agents:`
|
|
685
|
+
2. **For connector-only agents** (programmatic with `integration` or external with `framework`): done -- no Python needed
|
|
686
|
+
3. **For agents with custom logic**: create `agency/<name>_agent.py` -- function name must match agent name
|
|
687
|
+
4. **Add to can_call** of upstream agents that should route to it
|
|
688
|
+
5. **Add entry point** if it should be directly invocable
|
|
689
|
+
6. **Restart** the mesh (`python main.py`)
|
|
690
|
+
|
|
691
|
+
### Common Patterns
|
|
692
|
+
|
|
693
|
+
**Hub-and-spoke**: One router agent that calls specialists based on conditions
|
|
694
|
+
```yaml
|
|
695
|
+
router_agent:
|
|
696
|
+
can_call:
|
|
697
|
+
- agent: "sales_agent"
|
|
698
|
+
condition: "calling_agent_response.intent == 'sales'"
|
|
699
|
+
- agent: "support_agent"
|
|
700
|
+
condition: "calling_agent_response.intent == 'support'"
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
**Pipeline**: Linear A -> B -> C chain
|
|
704
|
+
```yaml
|
|
705
|
+
intake_agent:
|
|
706
|
+
can_call: [{agent: "analyzer_agent"}]
|
|
707
|
+
analyzer_agent:
|
|
708
|
+
can_call: [{agent: "responder_agent"}]
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
**Fan-out/fan-in**: Parallel processing with aggregation
|
|
712
|
+
```yaml
|
|
713
|
+
splitter_agent:
|
|
714
|
+
can_call:
|
|
715
|
+
- {agent: "worker_a"}
|
|
716
|
+
- {agent: "worker_b"}
|
|
717
|
+
aggregator_agent:
|
|
718
|
+
wait_for: "worker_a AND worker_b"
|
|
719
|
+
```
|
|
720
|
+
|
|
721
|
+
**Race pattern**: Multiple approaches, first wins
|
|
722
|
+
```yaml
|
|
723
|
+
consumer_agent:
|
|
724
|
+
wait_for: "fast_agent OR slow_agent"
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
**HITL approval gate**: Agent -> human review -> continue or revise
|
|
728
|
+
```yaml
|
|
729
|
+
draft_agent:
|
|
730
|
+
can_call: [{agent: "reviewer"}]
|
|
731
|
+
reviewer:
|
|
732
|
+
agent_type: "human"
|
|
733
|
+
communication_type: "dual"
|
|
734
|
+
can_call:
|
|
735
|
+
- agent: "publisher"
|
|
736
|
+
condition: "calling_agent_response.human_decision == 'approved'"
|
|
737
|
+
- agent: "draft_agent"
|
|
738
|
+
condition: "calling_agent_response.human_decision == 'revision_needed'"
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
## Session & Upstream Yields
|
|
742
|
+
|
|
743
|
+
```python
|
|
744
|
+
async def my_agent(llm_response, input_data, context):
|
|
745
|
+
upstream = input_data.get("upstream_yields", {})
|
|
746
|
+
caller_data = upstream.get("caller_agent_name", {})
|
|
747
|
+
session_id = context.get("session_id")
|
|
748
|
+
memory_posts = context.get("memory_posts", [])
|
|
749
|
+
prepared = context.get("prepared_data", {})
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
## Additional Resources
|
|
753
|
+
|
|
754
|
+
- **[agent-config-fields.md](agent-config-fields.md)** — **Authoritative field reference for every YAML field**. Lists every option for every agent type (`llm`, `human`, `external`, `programmatic`), plus `WebhookConfig`, `ChannelConfig`, `Memory`, `EscalationConfig`, `EscalationTarget`, `LeafMeshConfig`, `ManagerConfig`, `MeshConfig` (Bedrock / Vertex / Foundry / Local), `RedisConfig`, `EvolutionConfig`, `DataStructure`, `Entry Points`, all per-framework `connector_config` schemas, and the **Field Applicability by Agent Type** matrix. Read this file when in doubt about any field, accepted values, default, or what's allowed where.
|
|
755
|
+
- **[reference.md](reference.md)** — SDK Python API (`sdk.start()`, `sdk.mesh_call()`, `@global_tool`, decorators, error classes, env vars, etc.).
|
|
756
|
+
- **[examples.md](examples.md)** — copy-paste agent patterns (HITL, fan-out, hub-and-spoke, race, etc.).
|
|
757
|
+
|
|
758
|
+
> When the user asks "what fields can I put on a programmatic agent?" / "what's the default for `temperature`?" / "what does `wait_for: A AND B?` mean?" / "how do I configure n8n callback mode?" / "what fields does `EscalationTarget` accept?" — the answer lives in **agent-config-fields.md**. Don't guess; quote the file.
|
|
759
|
+
|
|
760
|
+
|
|
761
|
+
---
|
|
762
|
+
|
|
763
|
+
# Complete Agent Config Field Reference
|
|
764
|
+
|
|
765
|
+
_Inlined from `agent-config-fields.md` — every field, every default, every allowed value._
|
|
766
|
+
|
|
767
|
+
This document lists every configuration field, its type, default value, and accepted values. Use this to build frontend forms, dropdowns, and validation.
|
|
768
|
+
|
|
769
|
+
---
|
|
770
|
+
|
|
771
|
+
|
|
772
|
+
## Agent Types
|
|
773
|
+
|
|
774
|
+
| Value | Description |
|
|
775
|
+
|-------|-------------|
|
|
776
|
+
| `llm` | LLM-powered agent (default). Executes via OpenAI, Claude, Bedrock, Vertex, or Foundry. |
|
|
777
|
+
| `human` | Human operator agent. Routes to a person via API, webhook, or channel. |
|
|
778
|
+
| `programmatic` | Python function with business logic. No LLM calls. |
|
|
779
|
+
| `external` | Delegates to an external framework (CrewAI, LangGraph, AutoGen, etc.). |
|
|
780
|
+
|
|
781
|
+
---
|
|
782
|
+
|
|
783
|
+
## AgentConfig — Core Fields (All Types)
|
|
784
|
+
|
|
785
|
+
These fields apply to every agent regardless of `agent_type`.
|
|
786
|
+
|
|
787
|
+
| Field | Type | Default | Accepted Values | Required | Description |
|
|
788
|
+
|-------|------|---------|-----------------|----------|-------------|
|
|
789
|
+
| `name` | string | — | any string | **yes** | Unique agent name within the mesh |
|
|
790
|
+
| `description` | string | `null` | any string | no | Agent description and purpose |
|
|
791
|
+
| `agent_type` | string | `"llm"` | `llm`, `human`, `programmatic`, `external` | no | Agent execution type (see note below) |
|
|
792
|
+
| `communication_type` | string | `"dual"` | `dual`, `chain`, `execute` | no | How agent communicates with the mesh |
|
|
793
|
+
| `parallel` | bool | `false` | `true`, `false` | no | Enable parallel processing |
|
|
794
|
+
| `max_concurrent` | int | `null` | 1 – unlimited | no | Max concurrent calls when `parallel: true` (null = unlimited) |
|
|
795
|
+
| `wake_up` | string | `null` | cron expression (e.g. `"0 9 * * *"`) | no | Schedule for periodic wake-up |
|
|
796
|
+
| `yields` | dict | `{}` | key: field name, value: type string or nested object | no | Output schema — what agent produces |
|
|
797
|
+
| `inputs` | dict | `{}` | key: field name, value: type string or nested object | no | Input schema — what agent expects |
|
|
798
|
+
| `can_call` | list | `[]` | list of `{"agent": "name"}` or `{"agent": "name", "condition": "expr"}` | no | Agents this agent can invoke |
|
|
799
|
+
| `narration` | string | `null` | any string (multiline supported) | no | Plain-English routing hints for the Manager — evaluated by the Summarizer when conditions don't cover everything (see [Narration Routing](#narration-routing)) |
|
|
800
|
+
| `wait_for` | string or list | `[]` | agent names or expression string | no | Fan-in/join condition |
|
|
801
|
+
| `wait_for_timeout` | int | `60` | 1 – unlimited (seconds) | no | Hard timeout for fan-in |
|
|
802
|
+
| `auto_store_response` | bool | `true` | `true`, `false` | no | Auto-store responses in Redis |
|
|
803
|
+
| `auto_store_yields` | bool | `true` | `true`, `false` | no | Auto-store yields in Redis |
|
|
804
|
+
| `memory` | bool or dict | `false` | `true`, `false`, or memory config dict | no | Agent memory — see [Memory Config](#memory-config) |
|
|
805
|
+
| `memory_limit` | int | `10` | 1 – 100 | no | Legacy: max recent feed posts (use `memory.limit` instead) |
|
|
806
|
+
| `knowledge` | bool or dict | `false` | `false`, or `{serviceName, enabled, groupName}` | no | Knowledge/RAG — see [Knowledge Config](#knowledge-config) |
|
|
807
|
+
| `enforce_yields` | bool | `false` | `true`, `false` | no | Strictly validate this agent's output against the declared `yields:` schema. `false` (default) fills missing keys with type defaults and logs warnings; `true` triggers a Manager-driven retry up to `enforce_yields_retry` times, then escalates. See [Yields Enforcement](#yields-enforcement). |
|
|
808
|
+
| `enforce_yields_retry` | int | `0` | 0 – unlimited | no | Maximum self-correction attempts when `enforce_yields: true`. `0` fails on first contract violation. Each retry passes the previous output + validation errors as feedback so LLM/external/human/programmatic agents can self-correct. Honored by every agent type. |
|
|
809
|
+
|
|
810
|
+
### Yields Enforcement
|
|
811
|
+
|
|
812
|
+
`enforce_yields` and `enforce_yields_retry` work together to make `yields:` an enforceable contract on the producer side, without coupling agents to each other's shapes.
|
|
813
|
+
|
|
814
|
+
**Default behavior (`enforce_yields: false`)** — lenient mode, backwards-compatible:
|
|
815
|
+
|
|
816
|
+
- Missing yield keys → filled with type defaults (`""`, `0`, `[]`, `{}`, `false`).
|
|
817
|
+
- Type mismatches → kept verbatim, WARNING logged.
|
|
818
|
+
- `can_call` conditions evaluate on a known shape (no more silent skips on undefined keys).
|
|
819
|
+
|
|
820
|
+
**Strict mode (`enforce_yields: true`)** — for production-critical agents:
|
|
821
|
+
|
|
822
|
+
- On contract violation, the SDK fires a Manager-driven retry through `Manager.execute_state(...)`.
|
|
823
|
+
- Up to `enforce_yields_retry` attempts. Each retry sees the previous (wrong) output + validation errors as `_rerun_context`:
|
|
824
|
+
- **LLM agents** — prompt builder appends a correction note.
|
|
825
|
+
- **Human agents** — outbound payload exposes `_rerun_context`; inbox/channel UI surfaces what's needed.
|
|
826
|
+
- **External connectors** — `data._rerun_context` is added to the workflow payload.
|
|
827
|
+
- **Programmatic agents** — `input_data._rerun_context` is available alongside the (possibly Summarizer-corrected) inputs.
|
|
828
|
+
- After the retry budget is exhausted, fires `AGENT_ERROR` with `error_type="YieldContractFailure"` + `retry_exhausted=true`. Routes through `manager.escalation:` if configured.
|
|
829
|
+
|
|
830
|
+
**Example:**
|
|
831
|
+
|
|
832
|
+
```yaml
|
|
833
|
+
agents:
|
|
834
|
+
client:
|
|
835
|
+
agent_type: human
|
|
836
|
+
human_interface: webhook
|
|
837
|
+
yields:
|
|
838
|
+
request_data: object
|
|
839
|
+
decision: string
|
|
840
|
+
enforce_yields: true # strict
|
|
841
|
+
enforce_yields_retry: 3 # 3 self-correction attempts before escalating
|
|
842
|
+
```
|
|
843
|
+
|
|
844
|
+
**Programmatic agents are retryable too** — the Summarizer can inspect the failure and produce a `corrected_input` (e.g. fix `"ORGANIZATION"` → `"Organization"`). The retry runs the same deterministic function with the corrected input and produces a different result. See [Manager — Rerun Flow](../core-concepts/manager#rerun) for the full path.
|
|
845
|
+
|
|
846
|
+
### Changing `agent_type`
|
|
847
|
+
|
|
848
|
+
You can change an agent's type via `PATCH /api/yaml/agents/{name}`. When `agent_type` changes, the ADK automatically **removes fields that don't belong to the new type**:
|
|
849
|
+
|
|
850
|
+
| Switching away from... | Fields removed |
|
|
851
|
+
|------------------------|----------------|
|
|
852
|
+
| `llm` | `model`, `prompt`, `temperature`, `max_tokens`, `max_completion_tokens`, `reasoning`, `thinking`, `thinking_budget`, `tools`, `tool_categories`, `context_parts`, `tool_choice`, `response_format` |
|
|
853
|
+
| `human` | `webhook_config`, `human_interface`, `human_timeout_seconds`, `channels`, `is_human_powered` |
|
|
854
|
+
| `external` | `framework`, `connector_config` |
|
|
855
|
+
| `programmatic` | `integration` |
|
|
856
|
+
|
|
857
|
+
This prevents stale configuration from the old type interfering with the new type's execution.
|
|
858
|
+
|
|
859
|
+
### `yields` and `inputs` Type Strings
|
|
860
|
+
|
|
861
|
+
Values can be a simple type string (flat field) or a nested object definition.
|
|
862
|
+
|
|
863
|
+
**Flat fields** — value is a type string:
|
|
864
|
+
|
|
865
|
+
| Type String | Description |
|
|
866
|
+
|-------------|-------------|
|
|
867
|
+
| `"string"` | Text value |
|
|
868
|
+
| `"number"` | Numeric value |
|
|
869
|
+
| `"boolean"` | True/false |
|
|
870
|
+
| `"list"` | Array/list |
|
|
871
|
+
| `"object"` | Dictionary/object (unstructured) |
|
|
872
|
+
|
|
873
|
+
**Nested fields** — value is an object with `type` and `fields`:
|
|
874
|
+
|
|
875
|
+
```yaml
|
|
876
|
+
inputs:
|
|
877
|
+
# Flat fields
|
|
878
|
+
name: string
|
|
879
|
+
email: string
|
|
880
|
+
|
|
881
|
+
# Nested object with defined sub-fields
|
|
882
|
+
arguments:
|
|
883
|
+
type: object
|
|
884
|
+
fields:
|
|
885
|
+
summary: string
|
|
886
|
+
start_datetime: string
|
|
887
|
+
end_datetime: string
|
|
888
|
+
timezone: string
|
|
889
|
+
|
|
890
|
+
yields:
|
|
891
|
+
# Flat
|
|
892
|
+
status: string
|
|
893
|
+
|
|
894
|
+
# Nested
|
|
895
|
+
data:
|
|
896
|
+
type: object
|
|
897
|
+
fields:
|
|
898
|
+
result: string
|
|
899
|
+
metadata: object
|
|
900
|
+
```
|
|
901
|
+
|
|
902
|
+
When a field uses the nested format, the frontend renders individual sub-field editors instead of a single text input. This is useful for external framework integrations (Composio, Zapier, etc.) where the request body has a known structure.
|
|
903
|
+
|
|
904
|
+
### `wait_for` Expression Syntax
|
|
905
|
+
|
|
906
|
+
```yaml
|
|
907
|
+
# Simple list — all required (AND)
|
|
908
|
+
wait_for:
|
|
909
|
+
- agent_a
|
|
910
|
+
- agent_b
|
|
911
|
+
|
|
912
|
+
# Expression strings
|
|
913
|
+
wait_for: "agent_a AND agent_b" # Wait for both
|
|
914
|
+
wait_for: "agent_a OR agent_b" # Wait for first
|
|
915
|
+
wait_for: "agent_a AND agent_b?" # agent_b is optional (? suffix)
|
|
916
|
+
wait_for: "agent_a AND (agent_b OR agent_c)" # Nested logic
|
|
917
|
+
wait_for: "agent_a AND (agent_b OR agent_c) AND agent_d?" # Complex expression
|
|
918
|
+
```
|
|
919
|
+
|
|
920
|
+
### `can_call` Format
|
|
921
|
+
|
|
922
|
+
```yaml
|
|
923
|
+
# Simple
|
|
924
|
+
can_call:
|
|
925
|
+
- agent: "next_agent"
|
|
926
|
+
|
|
927
|
+
# With condition
|
|
928
|
+
can_call:
|
|
929
|
+
- agent: "next_agent"
|
|
930
|
+
- agent: "conditional_agent"
|
|
931
|
+
condition: "calling_agent_response.status == 'ready'"
|
|
932
|
+
```
|
|
933
|
+
|
|
934
|
+
### Narration Routing
|
|
935
|
+
|
|
936
|
+
`narration` is an agent-level field for routing hints you **can't express as conditions**. Conditions handle definitive routes ("if category is billing, call billing_agent"). Narration handles non-definitive routes ("if the customer sounds frustrated and mentions cancelling, maybe call retention_agent").
|
|
937
|
+
|
|
938
|
+
```yaml
|
|
939
|
+
agents:
|
|
940
|
+
triage:
|
|
941
|
+
yields:
|
|
942
|
+
category: "string"
|
|
943
|
+
urgency: "number"
|
|
944
|
+
can_call:
|
|
945
|
+
- agent: "billing_agent"
|
|
946
|
+
condition: "category == 'billing'"
|
|
947
|
+
- agent: "technical_agent"
|
|
948
|
+
condition: "category == 'technical'"
|
|
949
|
+
narration: >
|
|
950
|
+
If the customer mentions cancelling their subscription, route to retention_agent.
|
|
951
|
+
If the customer mentions a competitor by name, route to win_back_agent.
|
|
952
|
+
If the customer asks about enterprise plans, route to sales_agent.
|
|
953
|
+
```
|
|
954
|
+
|
|
955
|
+
**How it works:**
|
|
956
|
+
|
|
957
|
+
1. Conditions are evaluated first by the control plane (AST, instant, deterministic)
|
|
958
|
+
2. Condition targets are dispatched immediately
|
|
959
|
+
3. The Summarizer — which already analyzes every agent output via LLM — sees the narration in its prompt context
|
|
960
|
+
4. The Summarizer's `next_agents` recommendation now reflects both condition-routed agents and narration-suggested agents
|
|
961
|
+
5. The Manager compares `next_agents` against what conditions already dispatched, and calls the difference
|
|
962
|
+
|
|
963
|
+
**Key rules:**
|
|
964
|
+
|
|
965
|
+
- Conditions are the authority — narration never overrides a condition result
|
|
966
|
+
- Narration targets are **additive** — they add to condition targets, never remove
|
|
967
|
+
- Narration can reference **any agent** in the mesh, not just those in `can_call`
|
|
968
|
+
- No narration = zero overhead (the Summarizer's prompt is unchanged)
|
|
969
|
+
- If the Manager is disabled, narrations are ignored
|
|
970
|
+
|
|
971
|
+
See **[Manager — Narration Routing](../core-concepts/manager#narration-routing)** and **[Message Routing](../messages/routing#narration-routing)** for the full flow.
|
|
972
|
+
|
|
973
|
+
### Knowledge Config
|
|
974
|
+
|
|
975
|
+
`knowledge` enables RAG-powered context injection from a vector database. The agent gets both pre-call injection (automatic) and a `query_knowledge` tool (on-demand) — same dual pattern as memory.
|
|
976
|
+
|
|
977
|
+
```yaml
|
|
978
|
+
agents:
|
|
979
|
+
support_agent:
|
|
980
|
+
knowledge:
|
|
981
|
+
serviceName: "mongo_main"
|
|
982
|
+
enabled: true
|
|
983
|
+
groupName: "product_docs"
|
|
984
|
+
```
|
|
985
|
+
|
|
986
|
+
| Field | Type | Required | Default | Description |
|
|
987
|
+
|-------|------|----------|---------|-------------|
|
|
988
|
+
| `serviceName` | string | yes | — | Provider name (configured via Knowledge API, stored in Redis) |
|
|
989
|
+
| `enabled` | bool | no | `true` | Enable/disable knowledge for this agent |
|
|
990
|
+
| `groupName` | string | no | `null` | Query a specific group. Omit to query all groups. |
|
|
991
|
+
|
|
992
|
+
**Key points:**
|
|
993
|
+
|
|
994
|
+
- Provider connection details (connection strings, API keys, embedding model) are configured via the Knowledge API, not in YAML
|
|
995
|
+
- When enabled, both retrieval paths are active automatically — no mode flag
|
|
996
|
+
- `query_knowledge` tool is stripped from agents without knowledge enabled
|
|
997
|
+
- The Manager can also have knowledge for SOP awareness (configured under `manager.knowledge`)
|
|
998
|
+
|
|
999
|
+
See **[Manager — Narration Routing](../core-concepts/manager#narration-routing)** for how knowledge integrates with the Summarizer.
|
|
1000
|
+
|
|
1001
|
+
---
|
|
1002
|
+
|
|
1003
|
+
## LLM Agent Fields (`agent_type: "llm"`)
|
|
1004
|
+
|
|
1005
|
+
These fields are used when `agent_type` is `"llm"`. Ignored for other types.
|
|
1006
|
+
|
|
1007
|
+
| Field | Type | Default | Accepted Values | Description |
|
|
1008
|
+
|-------|------|---------|-----------------|-------------|
|
|
1009
|
+
| `model` | string | `"gpt-4o-mini"` | see [Model List](#model-list) below | LLM model name |
|
|
1010
|
+
| `prompt` | string | `null` | any string (multiline supported) | System prompt |
|
|
1011
|
+
| `temperature` | float | `0.1` | `0.0` – `2.0` | LLM temperature (creativity/randomness) |
|
|
1012
|
+
| `max_tokens` | int | `800` | 1 – model max | Max output tokens (legacy models) |
|
|
1013
|
+
| `max_completion_tokens` | int | `null` | 1 – model max | Max completion tokens (o1, gpt-5.x models) |
|
|
1014
|
+
| `reasoning` | bool | `false` | `true`, `false` | Enable ADK-level chain-of-thought reasoning (tool injection — works with any model) |
|
|
1015
|
+
| `thinking` | bool | `false` | `true`, `false` | Enable native model-level extended thinking (requires model support — see below) |
|
|
1016
|
+
| `thinking_budget` | int | `null` | 1024 – 32768 (tokens) | Max thinking tokens. Provider defaults apply when omitted. |
|
|
1017
|
+
| `enable_prompt_caching` | bool | `false` | `true`, `false` | Enable provider-native prompt caching for cost reduction (see below) |
|
|
1018
|
+
| `response_format` | dict | `null` | JSON Schema object | Structured output — forces LLM to respond with valid JSON matching this schema |
|
|
1019
|
+
| `optimization_strategy` | string | `null` | `performance`, `cost`, `speed` | Per-agent model selection strategy |
|
|
1020
|
+
| `context_parts` | dict | `null` | see below | Optional context parts |
|
|
1021
|
+
| `tools` | list | `[]` | tool name strings | Available tools |
|
|
1022
|
+
| `tool_choice` | string | `"auto"` | `auto`, `none`, or specific tool name | Tool selection strategy |
|
|
1023
|
+
| `max_tool_calls_per_message` | int | `5` | 0 – 20 | Max tool calls per LLM message |
|
|
1024
|
+
| `tool_call_timeout` | float | `30.0` | 0.1 – 300 (seconds) | Tool execution timeout |
|
|
1025
|
+
| `allow_parallel_tool_calls` | bool | `true` | `true`, `false` | Allow parallel tool execution |
|
|
1026
|
+
| `tool_categories` | list | `[]` | category name strings | Tool categories agent can access |
|
|
1027
|
+
|
|
1028
|
+
### `context_parts` Keys
|
|
1029
|
+
|
|
1030
|
+
Each key is injected as a separate system message with a bracketed label, in the order below. Custom keys are also supported — they receive an auto-generated label from their name (`MY_KEY` → `[MY KEY]`).
|
|
1031
|
+
|
|
1032
|
+
| Key | Label injected | Description |
|
|
1033
|
+
|-----|---------------|-------------|
|
|
1034
|
+
| `care` | `[EMPATHY & TONE]` | Warmth/empathy instructions — shapes how the agent expresses itself |
|
|
1035
|
+
| `sentiment_analysis` | `[SENTIMENT ANALYSIS]` | Tone detection instructions — tells the agent to read user mood |
|
|
1036
|
+
| `guardrails` | `[SAFETY GUARDRAILS]` | Safety and compliance rules — what the agent must never do |
|
|
1037
|
+
| `flows` | `[FLOW INSTRUCTIONS]` | **Per-caller routing behaviour** — what the agent should do differently depending on who called it and where in the mesh it is |
|
|
1038
|
+
|
|
1039
|
+
Values are free text strings. All keys are optional — use any combination.
|
|
1040
|
+
|
|
1041
|
+
```yaml
|
|
1042
|
+
context_parts:
|
|
1043
|
+
care: |
|
|
1044
|
+
Always respond with empathy. Acknowledge frustration before solving.
|
|
1045
|
+
guardrails: |
|
|
1046
|
+
Never share internal system details. No PII disclosure.
|
|
1047
|
+
flows: |
|
|
1048
|
+
When called from the entry point (no from_agent):
|
|
1049
|
+
- This is a new user. Greet warmly and gather requirements.
|
|
1050
|
+
When called from client (human agent):
|
|
1051
|
+
- The human has already responded. Don't re-greet. Summarise and proceed.
|
|
1052
|
+
When called from scheduler_agent:
|
|
1053
|
+
- This is a scheduled run. Skip greeting, produce a structured summary.
|
|
1054
|
+
```
|
|
1055
|
+
|
|
1056
|
+
### Model List
|
|
1057
|
+
|
|
1058
|
+
**OpenAI:**
|
|
1059
|
+
- `gpt-4o-mini`, `gpt-4o`
|
|
1060
|
+
- `gpt-4.1`, `gpt-4.1-mini`, `gpt-4.1-nano`
|
|
1061
|
+
- `gpt-5.1`, `gpt-5.2`
|
|
1062
|
+
- `o1`, `o1-mini`, `o3`, `o3-mini`, `o4-mini`
|
|
1063
|
+
|
|
1064
|
+
**Anthropic:**
|
|
1065
|
+
- `claude-opus-4-6`, `claude-sonnet-4-6`
|
|
1066
|
+
- `claude-haiku-4-5-20251001`
|
|
1067
|
+
|
|
1068
|
+
**Google:**
|
|
1069
|
+
- `gemini-2.0-flash`, `gemini-2.5-pro`
|
|
1070
|
+
|
|
1071
|
+
**DeepSeek:**
|
|
1072
|
+
- `deepseek-chat`, `deepseek-reasoner`
|
|
1073
|
+
|
|
1074
|
+
**AWS Bedrock:**
|
|
1075
|
+
- Any Bedrock model ID (e.g. `anthropic.claude-3-sonnet-20240229-v1:0`, `amazon.titan-text-premier-v1:0`)
|
|
1076
|
+
- Requires `mesh.bedrock` config
|
|
1077
|
+
|
|
1078
|
+
**Google Vertex AI:**
|
|
1079
|
+
- Any Vertex model ID (e.g. `gemini-1.5-pro`)
|
|
1080
|
+
- Requires `mesh.vertex` config
|
|
1081
|
+
|
|
1082
|
+
**Azure Foundry:**
|
|
1083
|
+
- Any Azure deployment name
|
|
1084
|
+
- Requires `mesh.foundry` config
|
|
1085
|
+
|
|
1086
|
+
**Local Models (vLLM, SGLang, Ollama, llama.cpp, etc.):**
|
|
1087
|
+
- Any model name supported by your local server
|
|
1088
|
+
- Requires `mesh.local` config (or `LOCAL_MODEL_ENDPOINT` env var)
|
|
1089
|
+
- See [LocalModelConfig](#localmodelconfig) for server setup
|
|
1090
|
+
|
|
1091
|
+
### `reasoning` vs `thinking`
|
|
1092
|
+
|
|
1093
|
+
These are two **separate** features:
|
|
1094
|
+
|
|
1095
|
+
| Feature | `reasoning: true` | `thinking: true` |
|
|
1096
|
+
|---------|-------------------|-------------------|
|
|
1097
|
+
| **What it does** | ADK injects chain-of-thought tool calls into the prompt | Enables provider-native extended thinking/reasoning |
|
|
1098
|
+
| **Works with** | Any model (tool injection) | Only models that support native thinking |
|
|
1099
|
+
| **Token cost** | Tool call overhead | Dedicated thinking tokens (billed as output) |
|
|
1100
|
+
| **Quality** | Good for structured reasoning | Best quality — model's internal reasoning |
|
|
1101
|
+
|
|
1102
|
+
You can use both together — `reasoning` adds ADK tools while `thinking` enables native model thinking.
|
|
1103
|
+
|
|
1104
|
+
### Native Thinking — Provider Support
|
|
1105
|
+
|
|
1106
|
+
| Provider | Model Requirement | Behavior |
|
|
1107
|
+
|----------|------------------|----------|
|
|
1108
|
+
| **Anthropic** | Claude Sonnet 4.6, Opus 4.6 | Adaptive/extended thinking with `budget_tokens` |
|
|
1109
|
+
| **OpenAI** | o1, o3, o3-mini, o4-mini | `reasoning.effort` parameter (low/medium/high) |
|
|
1110
|
+
| **Google** | Gemini 2.5+, Gemini 3.x | `thinkingConfig` with `thinkingBudget` |
|
|
1111
|
+
| **DeepSeek** | DeepSeek-R1 | Native chain-of-thought (auto-enabled for R1 models) |
|
|
1112
|
+
| **Bedrock** | Claude models on Bedrock | Extended thinking with `budgetTokens` |
|
|
1113
|
+
| **Vertex** | Claude + Gemini on Vertex | Both thinking APIs supported |
|
|
1114
|
+
| **Foundry** | Azure o-series models | `reasoning.effort` parameter |
|
|
1115
|
+
| **Local** | Depends on model/server | Passthrough if server supports it |
|
|
1116
|
+
|
|
1117
|
+
### Prompt Caching — Provider Support
|
|
1118
|
+
|
|
1119
|
+
| Provider | How it works | Savings |
|
|
1120
|
+
|----------|-------------|---------|
|
|
1121
|
+
| **Anthropic** | `cache_control: ephemeral` on system prompt + tools | ~90% on cached reads |
|
|
1122
|
+
| **Bedrock** | `promptCaching` parameter | ~90% on cached reads |
|
|
1123
|
+
| **Vertex (Claude)** | `cache_control: ephemeral` on system prompt | ~90% on cached reads |
|
|
1124
|
+
| **OpenAI** | Automatic — no config needed (stats in response) | ~50% on cached |
|
|
1125
|
+
| **Google** | Context caching API (requires separate setup) | Varies |
|
|
1126
|
+
|
|
1127
|
+
### `response_format` — Structured Output
|
|
1128
|
+
|
|
1129
|
+
Forces the LLM to respond with valid JSON matching a JSON Schema. Supported across all providers — each provider translates the schema to its native structured output API.
|
|
1130
|
+
|
|
1131
|
+
```yaml
|
|
1132
|
+
agents:
|
|
1133
|
+
data_extractor:
|
|
1134
|
+
agent_type: llm
|
|
1135
|
+
model: gpt-4o
|
|
1136
|
+
prompt: "Extract structured data from the user's message."
|
|
1137
|
+
response_format:
|
|
1138
|
+
type: json_schema
|
|
1139
|
+
json_schema:
|
|
1140
|
+
name: extracted_data
|
|
1141
|
+
strict: true
|
|
1142
|
+
schema:
|
|
1143
|
+
type: object
|
|
1144
|
+
properties:
|
|
1145
|
+
name:
|
|
1146
|
+
type: string
|
|
1147
|
+
email:
|
|
1148
|
+
type: string
|
|
1149
|
+
priority:
|
|
1150
|
+
type: string
|
|
1151
|
+
enum: [low, medium, high]
|
|
1152
|
+
required: [name, email, priority]
|
|
1153
|
+
additionalProperties: false
|
|
1154
|
+
```
|
|
1155
|
+
|
|
1156
|
+
| Provider | Native API used |
|
|
1157
|
+
|----------|----------------|
|
|
1158
|
+
| **OpenAI** | `response_format` parameter (structured outputs) |
|
|
1159
|
+
| **Anthropic** | Tool-based JSON extraction with schema |
|
|
1160
|
+
| **Google** | `response_schema` in generation config |
|
|
1161
|
+
| **DeepSeek** | `response_format` parameter |
|
|
1162
|
+
| **Bedrock** | Schema injected into system prompt |
|
|
1163
|
+
| **Foundry** | `response_format` parameter |
|
|
1164
|
+
| **Local** | `response_format` passthrough |
|
|
1165
|
+
|
|
1166
|
+
### Example
|
|
1167
|
+
|
|
1168
|
+
```yaml
|
|
1169
|
+
agents:
|
|
1170
|
+
analyst:
|
|
1171
|
+
agent_type: llm
|
|
1172
|
+
model: claude-sonnet-4-6
|
|
1173
|
+
thinking: true # native model thinking
|
|
1174
|
+
thinking_budget: 8192 # max 8K thinking tokens
|
|
1175
|
+
reasoning: true # also inject ADK chain-of-thought tools
|
|
1176
|
+
enable_prompt_caching: true # cache system prompt + tools
|
|
1177
|
+
prompt: |
|
|
1178
|
+
You are a data analyst. Analyze the provided data thoroughly.
|
|
1179
|
+
```
|
|
1180
|
+
|
|
1181
|
+
---
|
|
1182
|
+
|
|
1183
|
+
## Human Agent Fields (`agent_type: "human"`)
|
|
1184
|
+
|
|
1185
|
+
These fields are used when `agent_type` is `"human"`. `is_human_powered` is auto-set to `true`.
|
|
1186
|
+
|
|
1187
|
+
| Field | Type | Default | Accepted Values | Description |
|
|
1188
|
+
|-------|------|---------|-----------------|-------------|
|
|
1189
|
+
| `is_human_powered` | bool | `false` | `true`, `false` | Auto-synced to `true` when agent_type="human" |
|
|
1190
|
+
| `human_interface` | string | `"api"` | `default`, `api`, `webhook`, `custom` | How human receives/submits input (see below) |
|
|
1191
|
+
| `human_timeout_seconds` | int | `300` | 1 – 3600 (seconds) | Human response timeout |
|
|
1192
|
+
| `human_context_template` | string | `null` | any string | Template for presenting context to human |
|
|
1193
|
+
| `human_prompt_template` | string | `null` | any string | Template for human prompts |
|
|
1194
|
+
| `fallback_on_timeout` | bool | `true` | `true`, `false` | Use fallback response when human doesn't respond |
|
|
1195
|
+
| `fallback_response` | dict | `null` | arbitrary JSON | Default response on human timeout |
|
|
1196
|
+
| `require_human_confirmation` | bool | `false` | `true`, `false` | Require approval before proceeding |
|
|
1197
|
+
| `human_escalation_triggers` | list | `[]` | free text strings | Conditions triggering human escalation |
|
|
1198
|
+
| `operator_ids` | list | `[]` | list of strings (email or ID) | Operators who can see this agent's HITL requests. Empty = broadcast (all operators see it). |
|
|
1199
|
+
| `webhook_config` | object | `null` | see [WebhookConfig](#webhookconfig) | Webhook settings (required for `webhook` interface) |
|
|
1200
|
+
| `channels` | dict | `{}` | see [ChannelConfig](#channelconfig) | Native channel adapters (Slack, Telegram, etc.) |
|
|
1201
|
+
|
|
1202
|
+
### `human_interface` — Interface Types
|
|
1203
|
+
|
|
1204
|
+
| Value | Description | How it works |
|
|
1205
|
+
|-------|-------------|-------------|
|
|
1206
|
+
| `default` | **ADK-Frontend HITL Inbox** (recommended) | Writes request to Redis, emits stream event. ADK-Frontend renders an inbox with conversation thread. Human replies via the UI. Supports parallel requests per session. |
|
|
1207
|
+
| `webhook` | **External webhook** | POSTs request to `webhook_config.outbound_url`. Human responds via inbound webhook endpoint. Also supports native channel adapters (Slack, Telegram, etc.). |
|
|
1208
|
+
| `api` | **Python callback** | Calls a Python handler registered via `sdk.register_human_handler()`. No outbound HTTP. Used for custom integrations and testing. |
|
|
1209
|
+
| `custom` | **Custom handler** | Same as `api` — uses the registered `human_interface_handler` callback. |
|
|
1210
|
+
|
|
1211
|
+
> **Note:** `default` is only available on the LeafMesh hosted platform. For self-hosted deployments, use `webhook` with your own `outbound_url`, or `api` with a Python callback.
|
|
1212
|
+
|
|
1213
|
+
### Example — Default (ADK-Frontend Inbox)
|
|
1214
|
+
|
|
1215
|
+
```yaml
|
|
1216
|
+
agents:
|
|
1217
|
+
support_human:
|
|
1218
|
+
agent_type: human
|
|
1219
|
+
human_interface: default # ADK-Frontend inbox
|
|
1220
|
+
human_timeout_seconds: 300
|
|
1221
|
+
yields:
|
|
1222
|
+
resolution: string
|
|
1223
|
+
action_taken: string
|
|
1224
|
+
```
|
|
1225
|
+
|
|
1226
|
+
### Example — Webhook with Channel Adapter
|
|
1227
|
+
|
|
1228
|
+
```yaml
|
|
1229
|
+
agents:
|
|
1230
|
+
support_human:
|
|
1231
|
+
agent_type: human
|
|
1232
|
+
human_interface: webhook
|
|
1233
|
+
human_timeout_seconds: 600
|
|
1234
|
+
webhook_config:
|
|
1235
|
+
outbound_url: "https://my-app.com/api/human-requests"
|
|
1236
|
+
channels:
|
|
1237
|
+
slack:
|
|
1238
|
+
bot_token: "${SLACK_BOT_TOKEN:}"
|
|
1239
|
+
signing_secret: "${SLACK_SIGNING_SECRET:}"
|
|
1240
|
+
post_channel: "C123456"
|
|
1241
|
+
```
|
|
1242
|
+
|
|
1243
|
+
### Two Scenarios for Human Agent Sessions
|
|
1244
|
+
|
|
1245
|
+
Human agents support two interaction patterns via the same webhook endpoint:
|
|
1246
|
+
|
|
1247
|
+
**Scenario 1 — Resume (session_id present):** When a POST to `/webhook/{entry_point}` includes a `session_id` that matches a pending HITL request, the ADK resumes that session. The operator's response is routed via `can_call` to the next agent.
|
|
1248
|
+
|
|
1249
|
+
**Scenario 2 — New (no session_id or not found):** When a POST has no `session_id` or the session has no pending expectation, the ADK creates a new workflow. If the human agent has no upstream caller, the operator's message is immediately routed via `can_call` — no HITL pending step is created.
|
|
1250
|
+
|
|
1251
|
+
---
|
|
1252
|
+
|
|
1253
|
+
## External Agent Fields (`agent_type: "external"`)
|
|
1254
|
+
|
|
1255
|
+
These fields are used when `agent_type` is `"external"`.
|
|
1256
|
+
|
|
1257
|
+
| Field | Type | Default | Accepted Values | Required | Description |
|
|
1258
|
+
|-------|------|---------|-----------------|----------|-------------|
|
|
1259
|
+
| `framework` | string | `null` | `crewai`, `langgraph`, `autogen`, `a2a`, `mcp`, `zapier`, `composio`, `n8n`, `custom` | **yes** | External framework name |
|
|
1260
|
+
| `connector_config` | dict | `{}` | framework-specific key-values | no | Connection configuration — passed as `**kwargs` to the connector |
|
|
1261
|
+
|
|
1262
|
+
> **One agent = one action/workflow.** Each agent targets one specific endpoint, graph, tool, or action. Create multiple agents with different `connector_config` values to call different workflows.
|
|
1263
|
+
>
|
|
1264
|
+
> All `connector_config` fields can also be overridden per-call via `request.connector_config` at runtime.
|
|
1265
|
+
|
|
1266
|
+
### Common connector_config fields (all frameworks)
|
|
1267
|
+
|
|
1268
|
+
These fields are available on **every** connector type:
|
|
1269
|
+
|
|
1270
|
+
| Field | Type | Default | Description |
|
|
1271
|
+
|-------|------|---------|-------------|
|
|
1272
|
+
| `mode` | string | `"sync"` | Execution mode: `"sync"` (wait for HTTP response) or `"callback"` (fire request, wait for external system to POST back) |
|
|
1273
|
+
| `callback_timeout` | float | `120.0` | Seconds to wait for a callback response before timing out (only used when `mode: "callback"`) |
|
|
1274
|
+
|
|
1275
|
+
#### Sync vs Callback Mode
|
|
1276
|
+
|
|
1277
|
+
**Sync mode** (default): The connector POSTs to the external system and holds the HTTP connection open until the response arrives. This works when the external system returns the actual result in the same HTTP response.
|
|
1278
|
+
|
|
1279
|
+
**Callback mode**: The connector POSTs to the external system with a `_leafmesh_callback_url` and `_leafmesh_session_id` injected into the payload. The connector then blocks internally until the external system POSTs the result back to `/callback/{agent_name}`. Use this when:
|
|
1280
|
+
- The external workflow takes longer than the HTTP timeout
|
|
1281
|
+
- The external system uses "fire and forget" (e.g., n8n's "Respond Immediately" mode)
|
|
1282
|
+
- You need the external system to process asynchronously and deliver results later
|
|
1283
|
+
|
|
1284
|
+
**How external systems use callbacks:**
|
|
1285
|
+
|
|
1286
|
+
The LeafMesh connector injects these fields into the outbound payload:
|
|
1287
|
+
- `_leafmesh_callback_url` — the URL to POST the result back to
|
|
1288
|
+
- `_leafmesh_session_id` — the session ID to include in the callback
|
|
1289
|
+
|
|
1290
|
+
The external system should POST to `_leafmesh_callback_url` with a JSON body containing:
|
|
1291
|
+
```json
|
|
1292
|
+
{
|
|
1293
|
+
"session_id": "<the _leafmesh_session_id value>",
|
|
1294
|
+
"result": { ... }
|
|
1295
|
+
}
|
|
1296
|
+
```
|
|
1297
|
+
|
|
1298
|
+
### connector_config fields per framework
|
|
1299
|
+
|
|
1300
|
+
#### crewai
|
|
1301
|
+
|
|
1302
|
+
| Field | Type | Default | Required | Env Fallback | Description |
|
|
1303
|
+
|-------|------|---------|----------|-------------|-------------|
|
|
1304
|
+
| `endpoint` | string | `""` | **yes** | `CREWAI_ENDPOINT` | HTTP endpoint for deployed CrewAI crew |
|
|
1305
|
+
| `api_key` | string | `""` | no | `CREWAI_API_KEY` | Bearer Token for authentication |
|
|
1306
|
+
| `user_api_key` | string | `""` | no | `CREWAI_USER_API_KEY` | User Bearer Token (preferred over `api_key` when both are set) |
|
|
1307
|
+
| `poll_interval` | float | `2.0` | no | — | Seconds between status polls |
|
|
1308
|
+
| `max_poll_seconds` | float | `300.0` | no | — | Max total polling time (seconds) |
|
|
1309
|
+
| `http_timeout` | float | `30.0` | no | — | HTTP request timeout (seconds) |
|
|
1310
|
+
|
|
1311
|
+
#### langgraph
|
|
1312
|
+
|
|
1313
|
+
| Field | Type | Default | Required | Env Fallback | Description |
|
|
1314
|
+
|-------|------|---------|----------|-------------|-------------|
|
|
1315
|
+
| `endpoint` | string | `""` | **yes** | `LANGGRAPH_ENDPOINT`, `LANGCHAIN_ENDPOINT` | LangGraph Platform deployment URL |
|
|
1316
|
+
| `api_key` | string | `""` | no | `LANGCHAIN_API_KEY`, `LANGGRAPH_API_KEY` | API key |
|
|
1317
|
+
| `graph_id` | string | `"agent"` | no | — | **Which graph to run** — this is the workflow selector |
|
|
1318
|
+
| `poll_interval` | float | `1.0` | no | — | Seconds between status polls |
|
|
1319
|
+
| `max_poll_seconds` | float | `300.0` | no | — | Max total polling time (seconds) |
|
|
1320
|
+
| `http_timeout` | float | `30.0` | no | — | HTTP request timeout (seconds) |
|
|
1321
|
+
|
|
1322
|
+
#### autogen
|
|
1323
|
+
|
|
1324
|
+
Connects to an external AutoGen Studio or custom AutoGen API service via HTTP.
|
|
1325
|
+
|
|
1326
|
+
| Field | Type | Default | Required | Env Fallback | Description |
|
|
1327
|
+
|-------|------|---------|----------|-------------|-------------|
|
|
1328
|
+
| `endpoint` | string | `""` | **yes** | `AUTOGEN_ENDPOINT` | AutoGen service base URL (e.g. `http://localhost:8081`) |
|
|
1329
|
+
| `api_key` | string | `""` | no | `AUTOGEN_API_KEY` | Bearer token for authentication |
|
|
1330
|
+
| `workflow_id` | string | `""` | no | — | Workflow/agent ID to execute on the AutoGen service |
|
|
1331
|
+
| `timeout` | float | `120.0` | no | — | HTTP request timeout (seconds) |
|
|
1332
|
+
| `poll_interval` | float | `2.0` | no | — | Seconds between status poll requests |
|
|
1333
|
+
| `max_poll_seconds` | float | `300.0` | no | — | Max total polling time (seconds) |
|
|
1334
|
+
|
|
1335
|
+
#### a2a
|
|
1336
|
+
|
|
1337
|
+
| Field | Type | Default | Required | Env Fallback | Description |
|
|
1338
|
+
|-------|------|---------|----------|-------------|-------------|
|
|
1339
|
+
| `url` | string | `""` | **yes** | `A2A_AGENT_URL` | A2A-compatible agent server base URL |
|
|
1340
|
+
| `auth_token` | string | `""` | no | `A2A_AUTH_TOKEN` | Bearer token for authentication |
|
|
1341
|
+
| `auth_scheme` | string | `"Bearer"` | no | — | Authorization header scheme |
|
|
1342
|
+
| `poll_interval` | float | `2.0` | no | — | Seconds between task status polls |
|
|
1343
|
+
| `max_poll_seconds` | float | `300.0` | no | — | Max total polling time (seconds) |
|
|
1344
|
+
| `http_timeout` | float | `30.0` | no | — | HTTP request timeout (seconds) |
|
|
1345
|
+
|
|
1346
|
+
#### mcp
|
|
1347
|
+
|
|
1348
|
+
MCP supports two transport modes. `tool_name` is always required.
|
|
1349
|
+
|
|
1350
|
+
**Common fields (both transports):**
|
|
1351
|
+
|
|
1352
|
+
| Field | Type | Default | Required | Env Fallback | Description |
|
|
1353
|
+
|-------|------|---------|----------|-------------|-------------|
|
|
1354
|
+
| `tool_name` | string | `""` | **yes** | — | **Which MCP tool to call** — the workflow selector |
|
|
1355
|
+
| `transport` | string | `"stdio"` | no | — | Transport mode: `"stdio"` or `"http"` |
|
|
1356
|
+
| `timeout` | float | `60.0` | no | — | Request timeout (seconds) |
|
|
1357
|
+
|
|
1358
|
+
**stdio transport fields:**
|
|
1359
|
+
|
|
1360
|
+
| Field | Type | Default | Required | Description |
|
|
1361
|
+
|-------|------|---------|----------|-------------|
|
|
1362
|
+
| `command` | string | `""` | **yes** (stdio) | Executable to launch (e.g. `"npx"`) |
|
|
1363
|
+
| `args` | list | `[]` | no | Command arguments (e.g. `["-y", "@mcp/server-npm"]`) |
|
|
1364
|
+
| `env` | dict | `null` | no | Environment variables for the subprocess |
|
|
1365
|
+
|
|
1366
|
+
**http transport fields:**
|
|
1367
|
+
|
|
1368
|
+
| Field | Type | Default | Required | Description |
|
|
1369
|
+
|-------|------|---------|----------|-------------|
|
|
1370
|
+
| `url` | string | `""` | **yes** (http) | MCP server HTTP/SSE endpoint |
|
|
1371
|
+
| `auth_token` | string | `""` | no | Bearer token |
|
|
1372
|
+
|
|
1373
|
+
#### zapier
|
|
1374
|
+
|
|
1375
|
+
Tool name is built as `{connection}_{action}` (e.g. `google_sheets_create_row`).
|
|
1376
|
+
|
|
1377
|
+
| Field | Type | Default | Required | Env Fallback | Description |
|
|
1378
|
+
|-------|------|---------|----------|-------------|-------------|
|
|
1379
|
+
| `connection` | string | `""` | yes* | — | Zapier app name (e.g. `"google_sheets"`, `"slack"`, `"gmail"`) |
|
|
1380
|
+
| `action` | string | `""` | yes* | — | Action name (e.g. `"create_row"`, `"send_message"`) |
|
|
1381
|
+
| `mcp_key` | string | `""` | yes† | `ZAPIER_MCP_KEY` | Zapier MCP key — used first if `prefer_mcp=true` |
|
|
1382
|
+
| `api_key` | string | `""` | yes† | `ZAPIER_API_KEY` | Zapier REST API key — used as fallback |
|
|
1383
|
+
| `prefer_mcp` | bool | `true` | no | — | Try MCP path first; fall back to REST on failure |
|
|
1384
|
+
| `instructions` | string | `""` | no | — | Optional natural language instructions (REST path only) |
|
|
1385
|
+
| `timeout` | float | `60.0` | no | — | HTTP request timeout (seconds) |
|
|
1386
|
+
|
|
1387
|
+
*At least one of `connection` or `action` required for the tool name. †At least one of `mcp_key` or `api_key` required.
|
|
1388
|
+
|
|
1389
|
+
#### composio
|
|
1390
|
+
|
|
1391
|
+
| Field | Type | Default | Required | Env Fallback | Description |
|
|
1392
|
+
|-------|------|---------|----------|-------------|-------------|
|
|
1393
|
+
| `action` | string | `""` | **yes** | — | **Composio action enum** (e.g. `"GITHUB_STAR_A_REPOSITORY"`) — the workflow selector |
|
|
1394
|
+
| `entity_id` | string | `"default"` | no | `COMPOSIO_ENTITY_ID` | User/entity context for managed auth |
|
|
1395
|
+
| `api_key` | string | `""` | no | `COMPOSIO_API_KEY` | Composio API key |
|
|
1396
|
+
| `timeout` | float | `60.0` | no | — | Execution timeout (seconds) |
|
|
1397
|
+
|
|
1398
|
+
#### n8n
|
|
1399
|
+
|
|
1400
|
+
| Field | Type | Default | Required | Env Fallback | Description |
|
|
1401
|
+
|-------|------|---------|----------|-------------|-------------|
|
|
1402
|
+
| `webhook_url` | string | `""` | **yes** | `N8N_WEBHOOK_URL` | Full webhook trigger URL — **one URL per n8n workflow** |
|
|
1403
|
+
| `auth_token` | string | `""` | no | `N8N_AUTH_TOKEN` | Bearer token |
|
|
1404
|
+
| `timeout` | float | `60.0` | no | — | HTTP request timeout (seconds, sync mode only) |
|
|
1405
|
+
|
|
1406
|
+
**n8n webhook URL types:**
|
|
1407
|
+
- **Production:** `https://your-instance.app.n8n.cloud/webhook/<id>` — works when workflow is **activated** (toggle ON)
|
|
1408
|
+
- **Test:** `https://your-instance.app.n8n.cloud/webhook-test/<id>` — only works while n8n editor has "Listen for Test Event" active (one-shot, for development only)
|
|
1409
|
+
|
|
1410
|
+
**n8n + callback mode:**
|
|
1411
|
+
|
|
1412
|
+
When `mode: "callback"`, the n8n workflow should:
|
|
1413
|
+
1. Start with a Webhook trigger node (receives the payload including `_leafmesh_callback_url`)
|
|
1414
|
+
2. Configure the Webhook node to "Respond Immediately" (optional — sync mode works too)
|
|
1415
|
+
3. Process the workflow
|
|
1416
|
+
4. End with an HTTP Request node that POSTs back to `{{ $json._leafmesh_callback_url }}` with body:
|
|
1417
|
+
```json
|
|
1418
|
+
{ "session_id": "{{ $json._leafmesh_session_id }}", "result": { ... } }
|
|
1419
|
+
```
|
|
1420
|
+
|
|
1421
|
+
---
|
|
1422
|
+
|
|
1423
|
+
## Programmatic Agent Fields (`agent_type: "programmatic"`)
|
|
1424
|
+
|
|
1425
|
+
These fields are used when `agent_type` is `"programmatic"`.
|
|
1426
|
+
|
|
1427
|
+
| Field | Type | Default | Accepted Values | Description |
|
|
1428
|
+
|-------|------|---------|-----------------|-------------|
|
|
1429
|
+
| `integration` | string | `null` | `zapier`, `composio`, `n8n`, `mcp` | Integration connector (optional) |
|
|
1430
|
+
| `connector_config` | dict | `{}` | integration-specific key-values | Same fields as the matching external framework connector — see tables above |
|
|
1431
|
+
|
|
1432
|
+
**Validation rule:** `integration` is only valid when `agent_type` is `"programmatic"`.
|
|
1433
|
+
|
|
1434
|
+
When `integration` is set, `connector_config` is passed as `**kwargs` to the connector's `__init__`. Use the same fields as the matching framework in the tables above (zapier → zapier fields, mcp → mcp fields, etc.).
|
|
1435
|
+
|
|
1436
|
+
The common fields (`mode`, `callback_timeout`) are also available here — programmatic agents with connectors support the same sync/callback modes as external agents.
|
|
1437
|
+
|
|
1438
|
+
---
|
|
1439
|
+
|
|
1440
|
+
## WebhookConfig
|
|
1441
|
+
|
|
1442
|
+
Used inside `webhook_config` for human agents.
|
|
1443
|
+
|
|
1444
|
+
| Field | Type | Default | Description |
|
|
1445
|
+
|-------|------|---------|-------------|
|
|
1446
|
+
| `outbound_url` | string | `null` | URL to POST responses to external system |
|
|
1447
|
+
| `outbound_headers` | dict | `{}` | Headers for outbound webhook (key-value string pairs) |
|
|
1448
|
+
| `outbound_timeout` | int | `30` | Timeout for outbound calls (seconds) |
|
|
1449
|
+
| `inbound_endpoint` | string | `null` | Endpoint path for inbound responses (e.g. `"/webhook/human_contact"`). If not set, derived from entry points. |
|
|
1450
|
+
| `inbound_auth_token` | string | `null` | Auth token for validating inbound requests |
|
|
1451
|
+
| `response_mapping` | dict | `{}` | Field mapping for webhook response transformation |
|
|
1452
|
+
| `max_retries` | int | `3` | Max retry attempts for failed outbound webhooks |
|
|
1453
|
+
| `retry_delay` | int | `5` | Delay between retries (seconds) |
|
|
1454
|
+
|
|
1455
|
+
---
|
|
1456
|
+
|
|
1457
|
+
## ChannelConfig
|
|
1458
|
+
|
|
1459
|
+
Used inside `channels` dict for human agents. Keys are provider names.
|
|
1460
|
+
|
|
1461
|
+
### Supported Provider Keys
|
|
1462
|
+
|
|
1463
|
+
| Key | Provider |
|
|
1464
|
+
|-----|----------|
|
|
1465
|
+
| `slack` | Slack Bot API |
|
|
1466
|
+
| `telegram` | Telegram Bot API |
|
|
1467
|
+
| `discord` | Discord Bot API |
|
|
1468
|
+
| `whatsapp` | WhatsApp Business API (Meta Cloud API) |
|
|
1469
|
+
| `teams` | Microsoft Teams Bot Framework |
|
|
1470
|
+
|
|
1471
|
+
### Fields Per Channel
|
|
1472
|
+
|
|
1473
|
+
| Field | Type | Default | Description |
|
|
1474
|
+
|-------|------|---------|-------------|
|
|
1475
|
+
| `bot_token` | string | `null` | Bot/API token for the provider (see per-provider notes below) |
|
|
1476
|
+
| `signing_secret` | string | `null` | Request verification secret (see per-provider notes below) |
|
|
1477
|
+
| `listen_channels` | list | `[]` | Channel/chat IDs to accept inbound messages from (empty = all) |
|
|
1478
|
+
| `post_channel` | string | `null` | Default channel/chat ID for outbound messages (see per-provider notes below) |
|
|
1479
|
+
| `verify_token` | string | `null` | Webhook verification token — **WhatsApp only** (`hub.verify_token`) |
|
|
1480
|
+
|
|
1481
|
+
**Note:** `ChannelConfig` allows extra fields (`extra="allow"`) for any provider-specific config.
|
|
1482
|
+
|
|
1483
|
+
### Per-Provider Field Semantics
|
|
1484
|
+
|
|
1485
|
+
| Provider | `bot_token` | `signing_secret` | `post_channel` | `verify_token` |
|
|
1486
|
+
|----------|------------|------------------|----------------|----------------|
|
|
1487
|
+
| `slack` | Bot OAuth token (`xoxb-…`) | Slack signing secret (HMAC-SHA256) | Channel ID (e.g. `C123456`) | — |
|
|
1488
|
+
| `telegram` | Bot token from @BotFather | Secret token set when registering webhook | Chat ID | — |
|
|
1489
|
+
| `discord` | Bot token (without `Bot ` prefix) | App public key (Ed25519 — requires `pynacl`) | Channel ID | — |
|
|
1490
|
+
| `whatsapp` | Meta Graph API access token | Meta app secret (HMAC-SHA256) | Phone number ID | `hub.verify_token` for webhook registration |
|
|
1491
|
+
| `teams` | Bot Framework App ID | Bot Framework App password | Conversation ID | — |
|
|
1492
|
+
|
|
1493
|
+
### Inbound Route Registered Per Provider
|
|
1494
|
+
|
|
1495
|
+
| Provider | Route |
|
|
1496
|
+
|----------|-------|
|
|
1497
|
+
| `slack` | `POST /channels/slack/{agent_name}/events` |
|
|
1498
|
+
| `telegram` | `POST /channels/telegram/{agent_name}/webhook` |
|
|
1499
|
+
| `discord` | `POST /channels/discord/{agent_name}/interactions` |
|
|
1500
|
+
| `whatsapp` | `GET /channels/whatsapp/{agent_name}/webhook` (verification) + `POST` (messages) |
|
|
1501
|
+
| `teams` | `POST /channels/teams/{agent_name}/messages` |
|
|
1502
|
+
|
|
1503
|
+
### Example
|
|
1504
|
+
|
|
1505
|
+
```yaml
|
|
1506
|
+
channels:
|
|
1507
|
+
slack:
|
|
1508
|
+
bot_token: "${SLACK_BOT_TOKEN:}"
|
|
1509
|
+
signing_secret: "${SLACK_SIGNING_SECRET:}"
|
|
1510
|
+
listen_channels: ["C123456", "C789012"]
|
|
1511
|
+
post_channel: "C123456"
|
|
1512
|
+
|
|
1513
|
+
telegram:
|
|
1514
|
+
bot_token: "${TELEGRAM_BOT_TOKEN:}"
|
|
1515
|
+
signing_secret: "${TELEGRAM_SECRET_TOKEN:}" # optional
|
|
1516
|
+
listen_channels: [] # empty = all chats
|
|
1517
|
+
post_channel: ""
|
|
1518
|
+
|
|
1519
|
+
discord:
|
|
1520
|
+
bot_token: "${DISCORD_BOT_TOKEN:}"
|
|
1521
|
+
signing_secret: "${DISCORD_PUBLIC_KEY:}" # Ed25519 public key
|
|
1522
|
+
listen_channels: ["987654321098765432"]
|
|
1523
|
+
post_channel: "987654321098765432"
|
|
1524
|
+
|
|
1525
|
+
whatsapp:
|
|
1526
|
+
bot_token: "${WHATSAPP_ACCESS_TOKEN:}"
|
|
1527
|
+
signing_secret: "${META_APP_SECRET:}"
|
|
1528
|
+
post_channel: "${WHATSAPP_PHONE_NUMBER_ID:}" # phone number ID, not a phone number
|
|
1529
|
+
verify_token: "my_verify_token" # set same value in Meta dashboard
|
|
1530
|
+
|
|
1531
|
+
teams:
|
|
1532
|
+
bot_token: "${TEAMS_APP_ID:}"
|
|
1533
|
+
signing_secret: "${TEAMS_APP_PASSWORD:}"
|
|
1534
|
+
post_channel: "" # set at runtime from inbound activity
|
|
1535
|
+
```
|
|
1536
|
+
|
|
1537
|
+
---
|
|
1538
|
+
|
|
1539
|
+
## Memory Config
|
|
1540
|
+
|
|
1541
|
+
Field: `memory` — accepts `bool` or `dict`.
|
|
1542
|
+
|
|
1543
|
+
### Simple Mode
|
|
1544
|
+
|
|
1545
|
+
```yaml
|
|
1546
|
+
memory: false # Disabled (default)
|
|
1547
|
+
memory: true # Enabled with defaults
|
|
1548
|
+
```
|
|
1549
|
+
|
|
1550
|
+
### Advanced Mode (Dict)
|
|
1551
|
+
|
|
1552
|
+
| Field | Type | Default | Accepted Values | Description |
|
|
1553
|
+
|-------|------|---------|-----------------|-------------|
|
|
1554
|
+
| `strategy` | string | `"recency"` | `recency`, `relevance`, `hybrid` | Memory retrieval strategy |
|
|
1555
|
+
| `limit` | int | `10` | 1 – 100 | Max feed posts per invocation |
|
|
1556
|
+
| `cross_session` | bool | `false` | `true`, `false` | Persist memory across sessions |
|
|
1557
|
+
| `cross_session_limit` | int | `50` | 1 – 500 | Max cross-session posts to retain |
|
|
1558
|
+
| `relevance_weight` | float | `0.6` | 0.0 – 1.0 | Weight for relevance scoring |
|
|
1559
|
+
| `recency_weight` | float | `0.4` | 0.0 – 1.0 | Weight for recency scoring |
|
|
1560
|
+
| `decay_hours` | int | `24` | 1 – unlimited | Hours before entries decay |
|
|
1561
|
+
|
|
1562
|
+
### Example
|
|
1563
|
+
|
|
1564
|
+
```yaml
|
|
1565
|
+
memory:
|
|
1566
|
+
strategy: "hybrid"
|
|
1567
|
+
limit: 10
|
|
1568
|
+
cross_session: true
|
|
1569
|
+
cross_session_limit: 50
|
|
1570
|
+
relevance_weight: 0.6
|
|
1571
|
+
recency_weight: 0.4
|
|
1572
|
+
decay_hours: 24
|
|
1573
|
+
```
|
|
1574
|
+
|
|
1575
|
+
---
|
|
1576
|
+
|
|
1577
|
+
## EscalationConfig
|
|
1578
|
+
|
|
1579
|
+
Used inside `manager.escalation`.
|
|
1580
|
+
|
|
1581
|
+
| Field | Type | Default | Description |
|
|
1582
|
+
|-------|------|---------|-------------|
|
|
1583
|
+
| `targets` | list of [EscalationTarget](#escalationtarget) | `[]` | Escalation targets — all fire in parallel |
|
|
1584
|
+
| `auto_escalate` | dict | see below | Auto-escalation rules |
|
|
1585
|
+
|
|
1586
|
+
### `auto_escalate` Fields
|
|
1587
|
+
|
|
1588
|
+
| Field | Type | Default | Description |
|
|
1589
|
+
|-------|------|---------|-------------|
|
|
1590
|
+
| `max_retries` | int | `3` | Max retry attempts before escalating |
|
|
1591
|
+
| `max_errors_per_session` | int | `5` | Error count threshold per session |
|
|
1592
|
+
| `timeout_threshold` | int | `2` | Consecutive timeouts before escalating |
|
|
1593
|
+
|
|
1594
|
+
---
|
|
1595
|
+
|
|
1596
|
+
## EscalationTarget
|
|
1597
|
+
|
|
1598
|
+
Each target in the `escalation.targets` list.
|
|
1599
|
+
|
|
1600
|
+
| Field | Type | Default | Accepted Values | Applies To |
|
|
1601
|
+
|-------|------|---------|-----------------|------------|
|
|
1602
|
+
| `type` | string | **required** | `human_agent`, `webhook`, `channel` | all |
|
|
1603
|
+
| `agent` | string | `null` | agent name | `human_agent` |
|
|
1604
|
+
| `entry_point` | string | `null` | entry point name | `human_agent` |
|
|
1605
|
+
| `url` | string | `null` | URL | `webhook` |
|
|
1606
|
+
| `method` | string | `"POST"` | `POST`, `PUT`, `PATCH` | `webhook` |
|
|
1607
|
+
| `headers` | dict | `{}` | key-value string pairs | `webhook` |
|
|
1608
|
+
| `payload_template` | dict | `null` | JSON with `{{var}}` placeholders | `webhook` |
|
|
1609
|
+
| `provider` | string | `null` | `slack`, `telegram`, `discord`, `whatsapp`, `teams` | `channel` |
|
|
1610
|
+
| `channel_id` | string | `null` | channel ID | `channel` |
|
|
1611
|
+
| `message_template` | string | `null` | text with `{{var}}` placeholders | `channel` |
|
|
1612
|
+
|
|
1613
|
+
### Example
|
|
1614
|
+
|
|
1615
|
+
```yaml
|
|
1616
|
+
escalation:
|
|
1617
|
+
targets:
|
|
1618
|
+
- type: "human_agent"
|
|
1619
|
+
agent: "customer_support_team"
|
|
1620
|
+
|
|
1621
|
+
- type: "webhook"
|
|
1622
|
+
url: "https://incident.example.com/api/escalate"
|
|
1623
|
+
method: "POST"
|
|
1624
|
+
headers:
|
|
1625
|
+
Authorization: "Bearer ${ESCALATION_TOKEN:}"
|
|
1626
|
+
payload_template:
|
|
1627
|
+
incident_id: "{{session_id}}"
|
|
1628
|
+
severity: "{{severity_level}}"
|
|
1629
|
+
message: "{{error_message}}"
|
|
1630
|
+
|
|
1631
|
+
- type: "channel"
|
|
1632
|
+
provider: "slack"
|
|
1633
|
+
channel_id: "#critical-incidents"
|
|
1634
|
+
message_template: "Escalation: {{message}} (session: {{session_id}})"
|
|
1635
|
+
|
|
1636
|
+
auto_escalate:
|
|
1637
|
+
max_retries: 3
|
|
1638
|
+
max_errors_per_session: 5
|
|
1639
|
+
timeout_threshold: 2
|
|
1640
|
+
```
|
|
1641
|
+
|
|
1642
|
+
---
|
|
1643
|
+
|
|
1644
|
+
## Top-Level Config (LeafMeshConfig)
|
|
1645
|
+
|
|
1646
|
+
| Field | Type | Default | Accepted Values | Description |
|
|
1647
|
+
|-------|------|---------|-----------------|-------------|
|
|
1648
|
+
| `name` | string | `"default_mesh"` | any string | Mesh name |
|
|
1649
|
+
| `version` | string | `"1.0.0"` | any string | Configuration version |
|
|
1650
|
+
| `architecture` | string | `"managed_mesh"` | `managed_mesh` | Architecture type (only one supported) |
|
|
1651
|
+
| `debug` | bool | `false` | `true`, `false` | Enable debug mode |
|
|
1652
|
+
| `log_level` | string | `"INFO"` | `DEBUG`, `INFO`, `WARNING`, `ERROR` | Logging level |
|
|
1653
|
+
| `environment` | string | `"development"` | `development`, `production` | Environment |
|
|
1654
|
+
| `redis` | object | see [RedisConfig](#redisconfig) | — | Redis connection |
|
|
1655
|
+
| `manager` | object | see [ManagerConfig](#managerconfig) | — | Manager coordination + analysis |
|
|
1656
|
+
| `mesh` | object | see [MeshConfig](#meshconfig--cloud-providers) | — | Mesh network + cloud providers |
|
|
1657
|
+
| `agents` | dict | `{}` | agent name → AgentConfig | Agent configurations |
|
|
1658
|
+
| `entry_points` | list | `[{"name": "default_entry", "target": "summarizer", "condition": "always"}]` | see [Entry Points](#entry-points) | Named portals into mesh |
|
|
1659
|
+
| `data_structures` | dict | `{}` | name → DataStructure | Custom data type definitions |
|
|
1660
|
+
| `auto_discover` | dict | `null` | `{"directory": "path", "pattern": "*.py", "recursive": true}` | Auto-discover agent files |
|
|
1661
|
+
| `evolution` | object | see [EvolutionConfig](#evolutionconfig) | — | Evolutionary optimization |
|
|
1662
|
+
|
|
1663
|
+
**Note:** `LeafMeshConfig` has `extra="forbid"` — unknown top-level keys will raise a validation error.
|
|
1664
|
+
|
|
1665
|
+
---
|
|
1666
|
+
|
|
1667
|
+
## ManagerConfig
|
|
1668
|
+
|
|
1669
|
+
| Field | Type | Default | Accepted Values | Description |
|
|
1670
|
+
|-------|------|---------|-----------------|-------------|
|
|
1671
|
+
| `enabled` | bool | `true` | `true`, `false` | Enable manager + summarizer |
|
|
1672
|
+
| `model` | string | `"gpt-4o-mini"` | same as [Model List](#model-list) | LLM model for Summarizer analysis |
|
|
1673
|
+
| `domain` | string | `"generic"` | `generic`, `ecommerce`, `data_analysis` | Summarizer domain specialization |
|
|
1674
|
+
| `prompt` | string | `null` | any string (multiline supported) | **Evaluation criteria** — tell the Manager what success looks like, what to escalate on, and what patterns to watch. Injected into every Summarizer analysis call as an `EVALUATION CRITERIA` section, alongside the domain prompt. |
|
|
1675
|
+
| `can_intervene` | bool | `true` | `true`, `false` | Allow manager interventions (false = read-only) |
|
|
1676
|
+
| `coordination_rules` | dict | `{}` | arbitrary key-values | User-defined business rules |
|
|
1677
|
+
| `chain_completion_timeout` | float | `60.0` | seconds | Wait time before checking chain completeness |
|
|
1678
|
+
| `health_check_interval` | int | `60` | seconds | Seconds between health checks |
|
|
1679
|
+
| `agent_timeout_threshold` | int | `180` | seconds | Seconds before agent is timed out |
|
|
1680
|
+
| `escalation` | object | `null` | see [EscalationConfig](#escalationconfig) | Escalation targets and rules |
|
|
1681
|
+
| `routing` | dict | see below | — | Manager routing configuration |
|
|
1682
|
+
|
|
1683
|
+
### `manager.prompt` — Evaluation Criteria
|
|
1684
|
+
|
|
1685
|
+
Gives the Manager direct context about your mesh's specific purpose, success criteria, and escalation triggers. The Summarizer reads this on every agent turn alongside its domain template.
|
|
1686
|
+
|
|
1687
|
+
```yaml
|
|
1688
|
+
manager:
|
|
1689
|
+
model: "gpt-4o-mini"
|
|
1690
|
+
domain: "generic"
|
|
1691
|
+
prompt: |
|
|
1692
|
+
This mesh handles customer support tickets.
|
|
1693
|
+
A successful flow means:
|
|
1694
|
+
- greeter identifies the issue category correctly
|
|
1695
|
+
- processor routes to the right specialist agent
|
|
1696
|
+
- the customer receives a clear resolution within 5 minutes
|
|
1697
|
+
|
|
1698
|
+
Escalate if:
|
|
1699
|
+
- the same issue loops more than twice
|
|
1700
|
+
- sentiment is negative AND no resolution has been proposed
|
|
1701
|
+
- the human agent times out without responding
|
|
1702
|
+
|
|
1703
|
+
Watch for:
|
|
1704
|
+
- advisor_agent confidence scores below 0.6
|
|
1705
|
+
- processor_agent routing to fallback more than 50% of the time
|
|
1706
|
+
```
|
|
1707
|
+
|
|
1708
|
+
### `routing` Fields
|
|
1709
|
+
|
|
1710
|
+
| Field | Type | Default | Accepted Values | Description |
|
|
1711
|
+
|-------|------|---------|-----------------|-------------|
|
|
1712
|
+
| `mode` | string | `"static"` | `static`, `learning` | Routing mode (static=YAML only, learning=adaptive) |
|
|
1713
|
+
| `memory_size` | int | `100` | 1 – 1000 | Max routing decisions to remember |
|
|
1714
|
+
| `confidence_threshold` | float | `0.7` | 0.0 – 1.0 | Min confidence to accept learned route |
|
|
1715
|
+
| `fallback` | string | `"all"` | `all` | Fallback when confidence too low |
|
|
1716
|
+
| `decay_days` | int | `30` | 1 – 365 | Days before old routing memory decays |
|
|
1717
|
+
|
|
1718
|
+
### `human_input_rules` (Defaults)
|
|
1719
|
+
|
|
1720
|
+
| Field | Type | Default |
|
|
1721
|
+
|-------|------|---------|
|
|
1722
|
+
| `max_concurrent_requests` | int | `3` |
|
|
1723
|
+
| `max_agent_requests` | int | `5` |
|
|
1724
|
+
| `enable_request_queuing` | bool | `true` |
|
|
1725
|
+
|
|
1726
|
+
### `timeout_rules` (Defaults)
|
|
1727
|
+
|
|
1728
|
+
| Field | Type | Default |
|
|
1729
|
+
|-------|------|---------|
|
|
1730
|
+
| `max_timeouts_before_escalation` | int | `2` |
|
|
1731
|
+
| `timeout_escalation_enabled` | bool | `true` |
|
|
1732
|
+
| `escalation_notify_managers` | bool | `true` |
|
|
1733
|
+
|
|
1734
|
+
### `workflow_pause_rules` (Defaults)
|
|
1735
|
+
|
|
1736
|
+
| Field | Type | Default |
|
|
1737
|
+
|-------|------|---------|
|
|
1738
|
+
| `max_pause_duration_minutes` | int | `30` |
|
|
1739
|
+
| `max_concurrent_paused_workflows` | int | `2` |
|
|
1740
|
+
| `pause_monitoring_enabled` | bool | `true` |
|
|
1741
|
+
|
|
1742
|
+
### `human_response_rules` (Defaults)
|
|
1743
|
+
|
|
1744
|
+
| Scenario | `requires_manager_review` | `auto_escalate` |
|
|
1745
|
+
|----------|--------------------------|-----------------|
|
|
1746
|
+
| `approval` | `false` | `false` |
|
|
1747
|
+
| `escalation` | `true` | `true` |
|
|
1748
|
+
| `timeout` | `true` | `false` |
|
|
1749
|
+
|
|
1750
|
+
---
|
|
1751
|
+
|
|
1752
|
+
## MeshConfig & Cloud Providers
|
|
1753
|
+
|
|
1754
|
+
### MeshConfig
|
|
1755
|
+
|
|
1756
|
+
| Field | Type | Default | Description |
|
|
1757
|
+
|-------|------|---------|-------------|
|
|
1758
|
+
| `call_timeout` | int | `30` | Call timeout in seconds |
|
|
1759
|
+
| `bedrock` | object | `null` | AWS Bedrock config |
|
|
1760
|
+
| `vertex` | object | `null` | Google Vertex AI config |
|
|
1761
|
+
| `foundry` | object | `null` | Microsoft Foundry/Azure AI config |
|
|
1762
|
+
| `local` | object | `null` | Local model server config (vLLM, SGLang, Ollama, etc.) |
|
|
1763
|
+
|
|
1764
|
+
### BedrockConfig (AWS)
|
|
1765
|
+
|
|
1766
|
+
| Field | Type | Default | Description |
|
|
1767
|
+
|-------|------|---------|-------------|
|
|
1768
|
+
| `region` | string | `"us-east-1"` | AWS region |
|
|
1769
|
+
| `profile` | string | `null` | AWS profile from `~/.aws/credentials` |
|
|
1770
|
+
| `endpoint_url` | string | `null` | Custom Bedrock endpoint URL |
|
|
1771
|
+
|
|
1772
|
+
Auth: `AWS_ACCESS_KEY_ID` + `AWS_SECRET_ACCESS_KEY` env vars, IAM role, or `~/.aws/credentials`
|
|
1773
|
+
|
|
1774
|
+
### VertexConfig (Google Cloud)
|
|
1775
|
+
|
|
1776
|
+
| Field | Type | Default | Required | Description |
|
|
1777
|
+
|-------|------|---------|----------|-------------|
|
|
1778
|
+
| `project` | string | — | **yes** | GCP project ID |
|
|
1779
|
+
| `location` | string | `"us-central1"` | no | GCP region |
|
|
1780
|
+
|
|
1781
|
+
Auth: `GOOGLE_APPLICATION_CREDENTIALS` env var or `gcloud auth`
|
|
1782
|
+
|
|
1783
|
+
### FoundryConfig (Azure)
|
|
1784
|
+
|
|
1785
|
+
| Field | Type | Default | Required | Description |
|
|
1786
|
+
|-------|------|---------|----------|-------------|
|
|
1787
|
+
| `endpoint` | string | — | **yes** | Foundry endpoint URL (e.g. `https://<resource>.openai.azure.com`) |
|
|
1788
|
+
| `api_version` | string | `null` | no | Azure API version (e.g. `2024-10-21`). Omit for v1 endpoint. |
|
|
1789
|
+
|
|
1790
|
+
Auth: `AZURE_FOUNDRY_API_KEY` or `AZURE_FOUNDRY_TOKEN` env vars
|
|
1791
|
+
|
|
1792
|
+
### LocalModelConfig
|
|
1793
|
+
|
|
1794
|
+
| Field | Type | Default | Required | Description |
|
|
1795
|
+
|-------|------|---------|----------|-------------|
|
|
1796
|
+
| `endpoint` | string | `"http://localhost:11434/api/generate"` | no | Model server URL |
|
|
1797
|
+
| `server_type` | string | `null` (auto-detect) | no | Server type: `openai`, `ollama`, `textgen`, `huggingface` |
|
|
1798
|
+
| `timeout` | float | `120.0` | no | Request timeout in seconds |
|
|
1799
|
+
| `headers` | dict | `{}` | no | Custom HTTP headers (e.g. for auth) |
|
|
1800
|
+
|
|
1801
|
+
Env fallbacks: `LOCAL_MODEL_ENDPOINT`, `LOCAL_MODEL_SERVER_TYPE`, `LOCAL_MODEL_TIMEOUT`
|
|
1802
|
+
|
|
1803
|
+
**Supported servers and their `endpoint` + `server_type`:**
|
|
1804
|
+
|
|
1805
|
+
| Server | `server_type` | Example `endpoint` |
|
|
1806
|
+
|--------|--------------|-------------------|
|
|
1807
|
+
| vLLM | `openai` | `http://localhost:8000/v1/chat/completions` |
|
|
1808
|
+
| SGLang | `openai` | `http://localhost:30000/v1/chat/completions` |
|
|
1809
|
+
| llama.cpp server | `openai` | `http://localhost:8080/v1/chat/completions` |
|
|
1810
|
+
| LM Studio | `openai` | `http://localhost:1234/v1/chat/completions` |
|
|
1811
|
+
| LocalAI | `openai` | `http://localhost:8080/v1/chat/completions` |
|
|
1812
|
+
| TGI (--api openai) | `openai` | `http://localhost:8080/v1/chat/completions` |
|
|
1813
|
+
| TensorRT-LLM | `openai` | `http://localhost:8000/v1/chat/completions` |
|
|
1814
|
+
| Triton + vLLM | `openai` | `http://localhost:8000/v1/chat/completions` |
|
|
1815
|
+
| Ollama | `ollama` | `http://localhost:11434/api/generate` |
|
|
1816
|
+
| Text Gen Web UI | `textgen` | `http://localhost:5000/api/v1/generate` |
|
|
1817
|
+
| HF Inference | `huggingface` | `https://api-inference.huggingface.co/models/...` |
|
|
1818
|
+
|
|
1819
|
+
> **Tip:** Most production servers (vLLM, SGLang, TGI, llama.cpp) expose OpenAI-compatible endpoints. Use `server_type: openai` for all of them. If `server_type` is omitted, it's auto-detected from the URL.
|
|
1820
|
+
|
|
1821
|
+
```yaml
|
|
1822
|
+
mesh:
|
|
1823
|
+
local:
|
|
1824
|
+
endpoint: http://localhost:8000/v1/chat/completions
|
|
1825
|
+
server_type: openai
|
|
1826
|
+
timeout: 120
|
|
1827
|
+
headers:
|
|
1828
|
+
Authorization: "Bearer ${VLLM_API_KEY:}"
|
|
1829
|
+
```
|
|
1830
|
+
|
|
1831
|
+
---
|
|
1832
|
+
|
|
1833
|
+
## RedisConfig
|
|
1834
|
+
|
|
1835
|
+
| Field | Type | Default | Description |
|
|
1836
|
+
|-------|------|---------|-------------|
|
|
1837
|
+
| `host` | string | `"localhost"` | Redis server host |
|
|
1838
|
+
| `port` | int | `6379` | Redis server port |
|
|
1839
|
+
| `db` | int | `0` | Redis database number |
|
|
1840
|
+
| `password` | string | `null` | Redis password |
|
|
1841
|
+
| `decode_responses` | bool | `true` | Decode Redis responses |
|
|
1842
|
+
| `auto_storage` | bool | `true` | Enable automatic data storage |
|
|
1843
|
+
| `default_ttl` | int | `3600` | Default TTL (seconds) |
|
|
1844
|
+
| `session_ttl` | int | `7200` | Session TTL (seconds) |
|
|
1845
|
+
| `cluster_mode` | bool | `false` | Use Redis cluster |
|
|
1846
|
+
| `cluster_nodes` | list | `[]` | Cluster node addresses (strings) |
|
|
1847
|
+
| `ssl` | bool | `false` | Enable TLS for Redis connection |
|
|
1848
|
+
| `ssl_cert_reqs` | string | `"required"` | TLS verification mode: `required` \| `optional` \| `none` |
|
|
1849
|
+
| `ssl_ca_certs` | string | `null` | Path to CA bundle for verifying Redis server cert |
|
|
1850
|
+
| `ssl_certfile` | string | `null` | Client TLS cert path (mutual TLS) |
|
|
1851
|
+
| `ssl_keyfile` | string | `null` | Client TLS private key path (mutual TLS) |
|
|
1852
|
+
| `ssl_check_hostname` | bool | `true` | Verify server hostname against certificate |
|
|
1853
|
+
|
|
1854
|
+
---
|
|
1855
|
+
|
|
1856
|
+
## APIConfig
|
|
1857
|
+
|
|
1858
|
+
Top-level `api:` block Configures the ADK's HTTP server.
|
|
1859
|
+
|
|
1860
|
+
| Field | Type | Default | Description |
|
|
1861
|
+
|-------|------|---------|-------------|
|
|
1862
|
+
| `cors_origins` | list[string] | `[]` | Additional CORS origins, appended to the ADK's built-in defaults (`https://platform.leafcraft.ai` + localhost dev ports). Each entry must be a full origin (`scheme://host[:port]`). |
|
|
1863
|
+
|
|
1864
|
+
---
|
|
1865
|
+
|
|
1866
|
+
## EvolutionConfig
|
|
1867
|
+
|
|
1868
|
+
| Field | Type | Default | Accepted Values | Description |
|
|
1869
|
+
|-------|------|---------|-----------------|-------------|
|
|
1870
|
+
| `enabled` | bool | `false` | `true`, `false` | Enable evolutionary optimization |
|
|
1871
|
+
| `strategy` | string | `"genetic"` | free text | Evolution strategy |
|
|
1872
|
+
| `population_size` | int | `20` | 1+ | Population size per generation |
|
|
1873
|
+
| `generations` | int | `50` | 1+ | Maximum generations |
|
|
1874
|
+
| `mutation_rate` | float | `0.1` | 0.0 – 1.0 | Mutation probability |
|
|
1875
|
+
| `crossover_rate` | float | `0.7` | 0.0 – 1.0 | Crossover probability |
|
|
1876
|
+
| `elite_size` | int | `2` | 1+ | Elite genomes to preserve per generation |
|
|
1877
|
+
| `mutation_types` | list | `["prompt_variation", "temperature_adjustment", "tool_selection"]` | `prompt_variation`, `temperature_adjustment`, `tool_selection` | Mutation types |
|
|
1878
|
+
| `fitness_function` | string | `"task_completion_rate"` | free text | Fitness evaluation function |
|
|
1879
|
+
| `selection_method` | string | `"tournament"` | free text | Selection method |
|
|
1880
|
+
| `test_scenarios` | list | `[]` | list of scenario dicts — see below | Weighted test scenarios for fitness evaluation |
|
|
1881
|
+
|
|
1882
|
+
### `test_scenarios` — Scenario Dict Fields
|
|
1883
|
+
|
|
1884
|
+
Each scenario in `test_scenarios` is a dict with these fields:
|
|
1885
|
+
|
|
1886
|
+
| Field | Type | Default | Description |
|
|
1887
|
+
|-------|------|---------|-------------|
|
|
1888
|
+
| `name` | string | `"scenario_N"` | Human-readable label — shown in logs and Studio |
|
|
1889
|
+
| `entry_point` | string | `"test"` | Which mesh entry point to call |
|
|
1890
|
+
| `input` | any | `"Test scenario input"` | Input data passed to the mesh call |
|
|
1891
|
+
| `expected_outcome` | dict | `{}` | Key/value pairs the response must contain. All keys checked — partial matches return a partial score. |
|
|
1892
|
+
| `weight` | float | `1.0` | How much this scenario counts toward the overall fitness score. Higher = matters more. |
|
|
1893
|
+
| `timeout` | float | `30.0` | Per-scenario timeout in seconds |
|
|
1894
|
+
|
|
1895
|
+
**Fitness formula:**
|
|
1896
|
+
```
|
|
1897
|
+
fitness = Σ (weight × scenario_score) / Σ (weights)
|
|
1898
|
+
```
|
|
1899
|
+
where `scenario_score = outcome_match_ratio × (1 − latency_penalty)`.
|
|
1900
|
+
|
|
1901
|
+
A scenario with `weight: 2.0` counts twice as much as one with `weight: 1.0`. Timed-out scenarios score 0 and still count toward the denominator.
|
|
1902
|
+
|
|
1903
|
+
### Example
|
|
1904
|
+
|
|
1905
|
+
```yaml
|
|
1906
|
+
evolution:
|
|
1907
|
+
enabled: true
|
|
1908
|
+
population_size: 20
|
|
1909
|
+
generations: 50
|
|
1910
|
+
elite_size: 2
|
|
1911
|
+
test_scenarios:
|
|
1912
|
+
- name: "happy_path"
|
|
1913
|
+
entry_point: "greet_user"
|
|
1914
|
+
input: { "message": "I need help with my order" }
|
|
1915
|
+
expected_outcome:
|
|
1916
|
+
status: "success"
|
|
1917
|
+
weight: 1.0
|
|
1918
|
+
|
|
1919
|
+
- name: "escalation_path"
|
|
1920
|
+
entry_point: "greet_user"
|
|
1921
|
+
input: { "message": "This is urgent and completely broken" }
|
|
1922
|
+
expected_outcome:
|
|
1923
|
+
escalated: true
|
|
1924
|
+
weight: 2.0 # weighted heavier — escalation correctness matters more
|
|
1925
|
+
|
|
1926
|
+
- name: "hitl_flow"
|
|
1927
|
+
entry_point: "human_contact"
|
|
1928
|
+
input: { "user_message": "I want a refund" }
|
|
1929
|
+
expected_outcome:
|
|
1930
|
+
human_involved: true
|
|
1931
|
+
weight: 1.5
|
|
1932
|
+
```
|
|
1933
|
+
|
|
1934
|
+
---
|
|
1935
|
+
|
|
1936
|
+
## DataStructure
|
|
1937
|
+
|
|
1938
|
+
| Field | Type | Default | Description |
|
|
1939
|
+
|-------|------|---------|-------------|
|
|
1940
|
+
| `type` | string | **required** | `object`, `string`, `number`, `boolean`, `list` |
|
|
1941
|
+
| `properties` | dict | `null` | Object properties (for type=object) |
|
|
1942
|
+
| `required` | list | `[]` | Required field names |
|
|
1943
|
+
| `validation_rules` | dict | `{}` | Validation rules (key-value strings) |
|
|
1944
|
+
|
|
1945
|
+
---
|
|
1946
|
+
|
|
1947
|
+
## Entry Points
|
|
1948
|
+
|
|
1949
|
+
Each entry point is a named portal into the mesh.
|
|
1950
|
+
|
|
1951
|
+
| Field | Type | Default | Description |
|
|
1952
|
+
|-------|------|---------|-------------|
|
|
1953
|
+
| `name` | string | **required** | Entry point name |
|
|
1954
|
+
| `target` | string | **required** | Target agent name |
|
|
1955
|
+
| `description` | string | optional | Description |
|
|
1956
|
+
| `condition` | string | `"always"` | Trigger condition |
|
|
1957
|
+
|
|
1958
|
+
### Example
|
|
1959
|
+
|
|
1960
|
+
```yaml
|
|
1961
|
+
entry_points:
|
|
1962
|
+
- name: "greet_user"
|
|
1963
|
+
target: "greeter_agent"
|
|
1964
|
+
description: "Main entry — greets user and starts the agent chain"
|
|
1965
|
+
- name: "direct_research"
|
|
1966
|
+
target: "researcher_agent"
|
|
1967
|
+
description: "Skip greeting, go straight to research"
|
|
1968
|
+
```
|
|
1969
|
+
|
|
1970
|
+
---
|
|
1971
|
+
|
|
1972
|
+
## Validation Rules
|
|
1973
|
+
|
|
1974
|
+
These are enforced by Pydantic validators in the ADK:
|
|
1975
|
+
|
|
1976
|
+
| Rule | Constraint |
|
|
1977
|
+
|------|-----------|
|
|
1978
|
+
| `agent_type` | Must be `llm`, `human`, `programmatic`, or `external` |
|
|
1979
|
+
| `communication_type` | Must be `dual`, `chain`, or `execute` |
|
|
1980
|
+
| `optimization_strategy` | Must be `performance`, `cost`, or `speed` (or null) |
|
|
1981
|
+
| `max_tool_calls_per_message` | Must be 0–20 |
|
|
1982
|
+
| `tool_call_timeout` | Must be > 0 and <= 300 seconds |
|
|
1983
|
+
| `tool_choice` | Must be a non-empty string (`auto`, `none`, or tool name) |
|
|
1984
|
+
| `framework` | Must be `crewai`, `langgraph`, `autogen`, `a2a`, `mcp`, `zapier`, `composio`, `n8n`, `custom` (or null) |
|
|
1985
|
+
| `framework` required | When `agent_type` is `external`, `framework` must be set |
|
|
1986
|
+
| `integration` | Must be `zapier`, `composio`, `n8n`, `mcp` (or null) |
|
|
1987
|
+
| `integration` restricted | Only valid when `agent_type` is `programmatic` |
|
|
1988
|
+
| `is_human_powered` sync | Auto-set to `true` when `agent_type="human"`, forced to `false` when `agent_type="llm"` |
|
|
1989
|
+
| `AgentConfig` extras | Allows arbitrary extra fields (`extra="allow"`) |
|
|
1990
|
+
| `LeafMeshConfig` extras | Rejects unknown top-level keys (`extra="forbid"`) |
|
|
1991
|
+
|
|
1992
|
+
---
|
|
1993
|
+
|
|
1994
|
+
## Field Applicability by Agent Type
|
|
1995
|
+
|
|
1996
|
+
Shows which fields are **used** (U), **ignored** (—), or **required** (R) for each agent type.
|
|
1997
|
+
|
|
1998
|
+
| Field | `llm` | `human` | `programmatic` | `external` |
|
|
1999
|
+
|-------|-------|---------|----------------|------------|
|
|
2000
|
+
| `name` | R | R | R | R |
|
|
2001
|
+
| `description` | U | U | U | U |
|
|
2002
|
+
| `model` | U | — | — | — |
|
|
2003
|
+
| `prompt` | U | — | — | — |
|
|
2004
|
+
| `temperature` | U | — | — | — |
|
|
2005
|
+
| `max_tokens` | U | — | — | — |
|
|
2006
|
+
| `max_completion_tokens` | U | — | — | — |
|
|
2007
|
+
| `reasoning` | U | — | — | — |
|
|
2008
|
+
| `thinking` | U | — | — | — |
|
|
2009
|
+
| `thinking_budget` | U | — | — | — |
|
|
2010
|
+
| `enable_prompt_caching` | U | — | — | — |
|
|
2011
|
+
| `response_format` | U | — | — | — |
|
|
2012
|
+
| `optimization_strategy` | U | — | — | — |
|
|
2013
|
+
| `context_parts` | U | — | — | — |
|
|
2014
|
+
| `tools` | U | — | — | — |
|
|
2015
|
+
| `tool_choice` | U | — | — | — |
|
|
2016
|
+
| `max_tool_calls_per_message` | U | — | — | — |
|
|
2017
|
+
| `tool_call_timeout` | U | — | — | — |
|
|
2018
|
+
| `allow_parallel_tool_calls` | U | — | — | — |
|
|
2019
|
+
| `tool_categories` | U | — | — | — |
|
|
2020
|
+
| `is_human_powered` | — | auto | — | — |
|
|
2021
|
+
| `human_interface` | — | U | — | — |
|
|
2022
|
+
| `human_timeout_seconds` | — | U | — | — |
|
|
2023
|
+
| `human_context_template` | — | U | — | — |
|
|
2024
|
+
| `human_prompt_template` | — | U | — | — |
|
|
2025
|
+
| `fallback_on_timeout` | — | U | — | — |
|
|
2026
|
+
| `fallback_response` | — | U | — | — |
|
|
2027
|
+
| `require_human_confirmation` | — | U | — | — |
|
|
2028
|
+
| `human_escalation_triggers` | — | U | — | — |
|
|
2029
|
+
| `webhook_config` | — | U | — | — |
|
|
2030
|
+
| `channels` | — | U | — | — |
|
|
2031
|
+
| `framework` | — | — | — | R |
|
|
2032
|
+
| `connector_config` | — | — | — | U |
|
|
2033
|
+
| `integration` | — | — | U | — |
|
|
2034
|
+
| `communication_type` | U | U | U | U |
|
|
2035
|
+
| `parallel` | U | U | U | U |
|
|
2036
|
+
| `max_concurrent` | U | U | U | U |
|
|
2037
|
+
| `wake_up` | U | U | U | U |
|
|
2038
|
+
| `yields` | U | U | U | U |
|
|
2039
|
+
| `inputs` | U | U | U | U |
|
|
2040
|
+
| `can_call` | U | U | U | U |
|
|
2041
|
+
| `narration` | U | U | U | U |
|
|
2042
|
+
| `knowledge` | U | U | U | U |
|
|
2043
|
+
| `wait_for` | U | U | U | U |
|
|
2044
|
+
| `wait_for_timeout` | U | U | U | U |
|
|
2045
|
+
| `auto_store_response` | U | U | U | U |
|
|
2046
|
+
| `auto_store_yields` | U | U | U | U |
|
|
2047
|
+
| `memory` | U | U | U | U |
|
|
2048
|
+
|
|
2049
|
+
**Legend:** R = required, U = used, — = ignored, auto = auto-set
|