A planner → executor → reviewer multi-agent orchestrator running as a single Python app on Embr. Three Foundry agents collaborate to handle one user task, with a feedback loop until the Reviewer ships an answer or max iterations is hit.
This sample is the "bounded 2–5 agent orchestrator" shape — a small, scoped pattern that's currently the only multi-agent topology with reliable production results (per Microsoft strategy/research docs). It deliberately avoids open-ended swarms, hand-offs, and dynamic agent allocation.
| Agent | Foundry name | Tools | Role |
|---|---|---|---|
| Planner | embr-multi-agent-planner |
none | Decompose task into 1–5 steps, return JSON |
| Executor | embr-multi-agent-executor |
lookup_fact, do_math |
Run each step, optionally calling a tool |
| Reviewer | embr-multi-agent-reviewer |
none | Decide ship-or-revise, synthesize final answer |
The orchestration loop lives in app/orchestrator.py:
for i in range(max_iterations):
plan = planner(task, prior_feedback)
step_results = [executor(step) for step in plan.steps] # tools resolved here
review = reviewer(task, plan, step_results)
if review.verdict == "ship": return review.final_answer
feedback = review.reason
return last_review.final_answer # max-iterations
Each agent is created lazily on first use and reused-by-name across cold
starts. All three share the same AgentsClient and the same service
principal credential.
| Var | Required | Purpose |
|---|---|---|
FOUNDRY_PROJECT_ENDPOINT |
yes | Foundry project endpoint, e.g. https://{name}.services.ai.azure.com/api/projects/{project} |
FOUNDRY_MODEL_DEPLOYMENT |
yes | Foundry model deployment name (e.g. gpt-5.4-mini-1) |
AZURE_TENANT_ID |
on Embr | SP tenant id |
AZURE_CLIENT_ID |
on Embr | SP client id |
AZURE_CLIENT_SECRET |
on Embr | SP client secret |
EMBR_AGENT_FORCE_RECREATE |
no | If 1, recreate all three agents on every cold start |
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
az login
export FOUNDRY_PROJECT_ENDPOINT="https://{name}.services.ai.azure.com/api/projects/{project}"
export FOUNDRY_MODEL_DEPLOYMENT="gpt-5.4-mini-1"
uvicorn app.main:app --reload --port 8000Then open http://localhost:8000 and try:
What is 17 * 23 plus the number of departments at Embr Corp?
The Planner should produce two steps: one calling do_math, one calling
lookup_fact("departments"). The Reviewer synthesizes the final number.
You can also POST directly:
curl -s http://localhost:8000/api/run \
-H 'content-type: application/json' \
-d '{"task":"What is 17 * 23 plus the number of departments at Embr Corp?","max_iterations":2}' \
| jq .| Method + path | Purpose |
|---|---|
GET /health |
{"status":"ok"} |
GET /api/config |
model, shape, auth mode, Foundry host, agent names |
GET /api/agents |
{planner_id, executor_id, reviewer_id} (creates on first call — surfaces 3× cold-start) |
POST /api/run |
body {task, max_iterations?} → full trace {final_answer, verdict, iteration_count, iterations[...], latency_ms} |
embr quickstart deploy embr-devs/embr-foundry-multi-agent-sample -i 120233234Then set env vars (replace placeholders with your SP credentials):
PROJ=<projectId>; ENV=<environmentId>
embr variables set FOUNDRY_PROJECT_ENDPOINT 'https://...services.ai.azure.com/api/projects/proj-default' -p $PROJ -e $ENV
embr variables set FOUNDRY_MODEL_DEPLOYMENT 'gpt-5.4-mini-1' -p $PROJ -e $ENV
embr variables set AZURE_TENANT_ID '<tenant-guid>' -p $PROJ -e $ENV
embr variables set AZURE_CLIENT_ID '<sp-client-id>' -p $PROJ -e $ENV
embr variables set AZURE_CLIENT_SECRET '<sp-client-secret>' -p $PROJ -e $ENVThis sample was built specifically to surface gaps Embr needs to close before "real" multi-agent workloads run well on the platform:
- 3-agent cold-start cost is linear. Each
list_agents()round-trip is ~500ms–2s of AAD + Foundry latency, and we do one per role on first use. With three agents, the first request after a deploy/restart pays ~3× the single-agent cost. Embr has no warm-pool / pre-warm primitive — the app has to either eagerly construct all agents at boot (slowing startup) or pay the cost on the first user request. - In-app orchestration only. All inter-agent state (plans, step results, reviewer feedback, iteration counter) lives in app memory. There is no durable workflow, no checkpoint, no resumable run primitive on Embr today. An app restart mid-task drops the entire run.
- Same SP creds across all agents. Planner, Executor, and Reviewer
share one
ClientSecretCredential. Embr exposes neither managed identity nor per-agent identity scoping, so there's no way to grant the Executor a narrower set of tool/data permissions than the Planner — a common ask for "least-privilege per agent" in customer-facing systems. - Long-running requests hit ingress timeouts. The orchestrator runs
the full
plan → execute → reviewloop synchronously inside the HTTP request thread. Two iterations × three steps × tool round-trips can easily exceed 30–60s, which butts up against ingress / proxy timeouts. Embr has no async-job / fan-out primitive to offload this — see the worker sample for the related finding.
embr-foundry-multi-agent-sample/
├── app/
│ ├── __init__.py
│ ├── agents.py # 3 agent factories + shared AgentsClient + creds
│ ├── orchestrator.py # planner → executor → reviewer loop
│ ├── tools.py # lookup_fact, do_math
│ ├── main.py # FastAPI: /api/run, /api/agents, /api/config
│ └── static/index.html # dark/mono trace UI
├── embr.yaml # Python 3.12, port 8000, /health
├── requirements.txt
└── README.md