- name
- foundry-routines
- description
- Schedule and dispatch Foundry agent invocations via Routines GA: cron, timer, GitHub issue and supported custom events. Covers the azure-ai-projects 2.4.x `client.beta.routines` lifecycle, azd run history and declarative azure.yaml, Responses/Invocations actions, client/header compatibility and isolated SDK creator-identity configuration. USE FOR: routines, scheduled agent, timer trigger, recurring trigger, cron schedule, agent automation, run history, dispatch_async, Foundry-Features header, RoutineDispatchPayload, azd ai routine, GitHub issue trigger, Teams message trigger. DO NOT USE FOR: multi-step orchestration or multi-agent coordination (use workflows), branching/approval logic (use workflows), in-cluster cron outside Foundry (use Azure Functions / Logic Apps), agent runtime (use foundry-prompt-agents or foundry-hosted-agents).
- metadata
- {"version":"1.2.0"}
# Microsoft Foundry Routines — Reference Guide
A **routine** is a named automation rule that fires an existing
Foundry agent on a schedule (cron), at a specific moment (timer), or
when a supported external event arrives.
The Foundry service queues the invocation, runs the agent, and
stores a run record you can inspect later. Routines remove the need
to host your own scheduler (Functions, Logic Apps, cron jobs)
around an agent that already lives in Foundry.
> **Service status: GA; Python surface: `client.beta.routines`.** The SDK
> namespace is not the service maturity label. Existing SDK 2.4 consumers are
> preserved. SDK 2.6.1 injects `Foundry-Features: Routines=V2Preview`
> through its beta-operation proxy; the existing 2.4 path uses `Routines=V1Preview`.
> Current Learn REST examples omit a feature header.
> Do not infer header retirement from GA or remove it from older validated
> clients without testing that exact service/client path. See § 7 for the
> additive creator-identity path and § 11 for acceptance scope.
The SDK examples remain the existing consumer path. For imperative CLI
and source-controlled deployment, use the
[azd routines workflow](references/azd-routines.md). It distinguishes CLI
aliases from API fields and preserves the existing SDK/REST alternatives.
---
## 1 · What & when
A routine has exactly one **trigger** and one **action**.
| Concept | Values |
|---|---|
| **Trigger** | `schedule` (cron, ≥ 5 min interval), `timer` (one-shot), `github_issue`, or `custom` with a supported provider |
| **Action** | `invoke_agent_responses_api` (call agent via Responses API) or `invoke_agent_invocations_api` (call via Invocations API) |
| **Lifecycle** | Created enabled or disabled, then `enable` / `disable` / `delete` |
| **Run history** | Query via SDK, REST, portal, or `azd ai routine run list` |
### When to reach for routines
| Scenario | Use routines? |
|---|---|
| Run a Foundry agent every weekday at 07:00 UTC | ✅ Yes — schedule trigger |
| Run a Foundry agent once at a fixed future timestamp | ✅ Yes — timer trigger |
| Test an agent on-demand without waiting for its schedule | ✅ Yes — `dispatch()` manual dispatch |
| Multi-step workflow (call agent A, branch on result, call agent B) | ❌ No — use Foundry workflows |
| React to an opened/closed GitHub issue | ✅ Yes — `github_issue` with an authorized connector connection |
| React to a Teams channel message | ✅ Yes — `custom` with the supported `teams` provider |
| Arbitrary HTTP webhook, queue message, or file upload | ❌ Not a generic event receiver — use Functions / Event Grid / Logic Apps unless a supported routine provider covers that event |
| Sub-minute precision schedule | ❌ No — 5-minute minimum interval |
**Routines complement, not replace,** `foundry-prompt-agents` and
`foundry-hosted-agents` — those skills create the agent; this
skill automates its invocation.
---
## 2 · Prerequisites
1. **Microsoft Foundry project with routines enabled.** Check the
[current availability](https://learn.microsoft.com/azure/foundry/agents/how-to/use-routines#prerequisites)
for the target project; the historical eight-region preview list is
not a current allowlist. The September 2026 documentation excludes
UK West, Switzerland West, Japan West, UAE North and Norway East.
Routines inherit the project's private networking configuration but
do not support customer-managed key encryption.
2. **Existing agent with a configured agent identity.** A prompt
agent created via `project.agents.create_version(...)` (see
`foundry-prompt-agents`) or a hosted agent (see
`foundry-hosted-agents`) both qualify. Pure prompt-only agents
without an agent identity are rejected by the service when
bound to a routine action. Workflow agents are not supported.
3. **Foundry User role** (or higher) on the project scope. (The
Foundry RBAC roles were recently renamed — Foundry User /
Foundry Owner / Foundry Account Owner / Foundry Project Manager
were previously Azure AI User / Owner / etc. The role IDs and
permissions are unchanged.)
4. **Python 3.9+** with the routines-capable SDK:
```bash
pip install "azure-ai-projects~=2.4.0" "azure-identity~=1.25.3" "httpx~=0.28.1"
```
The existing Routines surface under `client.beta.routines` requires
`azure-ai-projects` 2.2.0 or later (preview). Earlier versions
raise `AttributeError` on `client.beta.routines`. The explicit
`httpx` pin works around an `azure-ai-projects` 2.4.0 packaging gap:
the SDK imports `httpx` directly but does not declare it. Typed creator
authorization was added in 2.6.0 and is documented/tested offline here on
2.6.1 in a separate management environment; do not upgrade the agent runtime.
5. **Authentication** via `DefaultAzureCredential` for SDK calls
or `az account get-access-token --resource https://ai.azure.com`
for raw REST. Routines are data-plane operations under the
project endpoint.
6. **Isolated CLI context** per `azure-tenant-isolation` before SDK,
`az`, or `azd` work. Set both tenant-specific config directories and
verify the intended tenant/subscription before mutations. An explicit
project endpoint does not replace the tenant guard.
7. **Event connections** require separate authorization and connector
consent. A manual dispatch does not prove that an external event
source can deliver a trigger; see the [event prerequisites](references/azd-routines.md#event-triggers).
---
## 3 · Author a routine
```python
import os
from azure.identity import DefaultAzureCredential
from azure.ai.projects import AIProjectClient
PROJECT_ENDPOINT = os.environ["FOUNDRY_PROJECT_ENDPOINT"]
# Format: https://<account>.services.ai.azure.com/api/projects/<project>
client = AIProjectClient(
endpoint=PROJECT_ENDPOINT,
credential=DefaultAzureCredential(),
)
```
### Schedule trigger (recurring cron)
Minimum interval is **5 minutes**. The cron expression follows the
standard 5-field form (`minute hour day-of-month month day-of-week`).
`time_zone` accepts any IANA zone (e.g. `America/Los_Angeles`); set
to `UTC` for a UTC-anchored schedule.
```python
routine = client.beta.routines.create_or_update(
routine_name="daily-summary",
description="Runs a daily summary agent on weekday mornings.",
enabled=True,
triggers={
"weekday-morning": {
"type": "schedule",
"cron_expression": "0 7 * * 1-5", # required
"time_zone": "UTC", # required
}
},
action={
"type": "invoke_agent_responses_api",
"agent_name": "my-summary-agent", # required, ≤ 256 chars
"input": "Summarize the activity since the previous run.",
# "conversation_id": "...", # optional
},
)
print(f"Routine: {routine.name}, enabled={routine.enabled}")
```
### Timer trigger (one-shot)
Fires exactly once. The `at` field accepts three shapes:
- ISO 8601 timestamp with explicit UTC offset: `"2030-09-01T09:00:00Z"`
- Local timestamp paired with `time_zone`:
`"at": "2030-09-01T09:00:00", "time_zone": "America/Los_Angeles"`
- A positive duration from now: `"30m"`, `"2h"` (introduced in
`azure-ai-projects` 2.2.0)
```python
routine = client.beta.routines.create_or_update(
routine_name="once-on-release-day",
description="Runs the agent once on release day.",
enabled=True,
triggers={
"release-day": {
"type": "timer",
"at": "2030-09-01T09:00:00Z", # replace with a future timestamp
}
},
action={
"type": "invoke_agent_responses_api",
"agent_name": "release-bot",
"input": "Produce the release summary.",
},
)
```
### Action types
Exactly one action per routine. Choose based on how the agent is
exposed:
| Action type | Required field | Optional | Use when |
|---|---|---|---|
| `invoke_agent_responses_api` | `agent_name` (≤ 256) | `conversation_id` | Calling a prompt agent or hosted agent via the Responses API (default for new prompt agents) |
| `invoke_agent_invocations_api` | `agent_name` (≤ 256) | `session_id` | Calling a hosted agent via the Invocations API (long-running session pattern) |
### YAML manifest equivalent
For consumers who prefer YAML / `azd ai routine`, manifest fields use
the API wire names, not CLI aliases or flags:
```yaml
# routine.yaml
name: daily-summary
description: Runs a daily summary agent on weekday mornings.
enabled: true
triggers:
weekday-morning:
type: schedule
cron_expression: "0 7 * * 1-5"
time_zone: UTC
action:
type: invoke_agent_responses_api
agent_name: my-summary-agent
input: Summarize the activity since the previous run.
```
Create it with a positional routine name and an explicit project endpoint;
see [CLI setup and creation](references/azd-routines.md#imperative-lifecycle).
The manifest's `action.input` is persisted. A manual dispatch override
does not update that stored input.
---
## 4 · Trigger a run
### Wait for the schedule
The schedule fires automatically once enabled — no further code.
The routine's `enabled=True` field gates whether the schedule is
honoured.
### Manually dispatch a run
`dispatch()` queues a one-off run without waiting for the next
scheduled fire. Useful for smoke tests, on-demand reruns, or
end-to-end verification right after creation. The payload type
**must match** the routine's action type.
```python
result = client.beta.routines.dispatch(
routine_name="daily-summary",
payload={
"type": "invoke_agent_responses_api",
"input": "Run the daily summary for testing.", # optional, ≤ 32768 chars
},
)
print(f"dispatch_id: {result.dispatch_id}")
print(f"task_id: {result.task_id}")
```
The `dispatch_id` is the handle you use to find this specific run
in run history (§ 6). `action_correlation_id` is the
downstream-service correlation handle (e.g. the Responses API
response ID).
An enqueue acknowledgment is not agent completion. Inspect the correlated
run for delivery status, then obtain independent downstream evidence before
reporting that the agent's business task succeeded.
> **REST equivalent:** `POST {endpoint}/routines/{name}:dispatch_async`
> with `?api-version=v1` and the same payload. Preserve the compatibility
> header for the existing validated REST path described above. The endpoint
> suffix is `:dispatch_async` (note the colon),
> not `/dispatch`.
---
## 5 · Lifecycle
```python
# Pause a routine without deleting it
client.beta.routines.disable("daily-summary")
# Re-enable a paused routine
client.beta.routines.enable("daily-summary")
# Fetch the current definition
routine = client.beta.routines.get("daily-summary")
print(f"{routine.name} enabled={routine.enabled}")
# Iterate all routines in the project
for r in client.beta.routines.list():
print(f"{r.name} enabled={r.enabled} triggers={list(r.triggers.keys())}")
# Remove a routine permanently
client.beta.routines.delete("daily-summary")
```
To update supported fields, read the current definition and preserve fields
you are not changing. Do not assume `create_or_update` permits replacing a
trigger: the live service can reject changes to the **trigger definition**
even when its type is unchanged. Use a description-only update for a harmless
round-trip check. A schedule change needs an explicitly coordinated replacement
when the service rejects in-place updates; see the
[azd update boundary](references/azd-routines.md#imperative-lifecycle).
---
## 6 · Run history
Every routine fire (scheduled or dispatched) is recorded. Query
with `list_runs(routine_name)`:
```python
runs = client.beta.routines.list_runs("daily-summary", limit=20)
for run in runs:
print(
f"{run.id} phase={run.phase} source={run.attempt_source} "
f"started={run.started_at} ended={run.ended_at}"
)
if run.phase == "failed":
print(f" error: {run.error_type} — {run.error_message}")
```
Useful `RoutineRun` fields:
| Field | Meaning |
|---|---|
| `id` | Run record ID |
| `phase` | Delivery lifecycle `queued` / `dispatching` / `completed` / `failed`; not a business-result assertion |
| `attempt_source` | `schedule_delivery`, `timer_delivery`, `event_fire`, `manual_dispatch` or `queued_dispatch`; correlate manual work by `dispatch_id`, not one source label |
| `trigger_type` | `schedule`, `timer`, `github_issue` or `custom` |
| `started_at` / `ended_at` | UTC timestamps |
| `dispatch_id` | Matches the `dispatch_id` from a manual `dispatch()` call |
| `response_id` | Correlation handle for the downstream response; it does not guarantee that the current caller can retrieve its body |
| `error_type` / `error_message` | Populated when `phase == "failed"` |
Manual `dispatch()` can produce a `queued_dispatch` history row even when it
completes successfully. A poll that requires `attempt_source == "manual_dispatch"`
will falsely time out. Match the returned `dispatch_id` and inspect terminal
phase; record the actual source instead of replacing it with an expected label.
### CLI, portal & REST alternatives
- CLI: `azd ai routine run list <name>` supports `--top`, `--filter`
and `--output json`; use the explicit endpoint form in the
[azd reference](references/azd-routines.md#imperative-lifecycle).
- The Foundry portal exposes a run table on each routine's detail
page with the same fields, plus links to the full agent response.
- REST: `GET {endpoint}/routines/{name}/runs?api-version=v1`; retain the
compatibility header on the existing validated client path.
**Completion and response readback are different checks.** In manual
acceptance, the run reached `phase=completed`, `status=Finished`, with no
error fields, but retrieving its `response_id` returned 404 through both
project and agent OpenAI clients. Do not report model-output assertions as
passed from the run record alone. The cause of that readback boundary was
not established; preserve the run/dispatch correlation for investigation
rather than inferring a permission fix or silently changing identities.
---
## 7 · RBAC & governance
| Identity | Role required | Why |
|---|---|---|
| **Caller** authoring the routine | Foundry User (or higher) on the project scope | Authors `routines/*` operations |
| **Agent identity** (default dispatch) | Permissions required by the agent's model and tools | Current routines default to the agent identity, not the authoring caller |
| **Connector connection identity** | Access and consent for the watched repository or Teams channel | Authenticates event delivery independently of agent dispatch |
Do not infer the executing principal from the identity that authored the
routine. Identify the actual agent runtime principal before diagnosing or
granting downstream RBAC. The project MI grants used by the existing CI
fixture are fixture preconditions, not a universal identity model.
The current service also documents **creator identity** as an explicit
create-time opt-in for delegated tools. It means the routine creator,
not the agent creator, a later editor or an arbitrary dispatch user.
Changing that setting requires recreation; an update does not change it.
SDK 2.6.0 added `authorization=RoutineAuthorization(identity="creator")`;
the existing 2.4 examples and fixture still use the default agent identity.
The current Learn example selects SDK 2.6.1 for typed configuration. See
عرض على GitHub