foundry-routines
Skill8 starsChanged 12 days agoMITView on GitHub
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).
- Installs packages
We split this skill into the rules it gives an agent
5 rules. About 0 are common, 3 are distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈3
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 80
Microsoft Foundry project with routines enabled: Check the current availability for the target project.
- Very specific: line 236
Create it with a positional routine name and an explicit project endpoint.
- Very specific: line 320
Every routine fire (scheduled or dispatched) is recorded.
- Distinctive: line 381
Do not infer the executing principal from the identity that authored the routine.
- Distinctive: line 509
Use the additional acceptance cases after explicit resource, cost, identity and cleanup approval.
≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
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 injectsFoundry-Features: Routines=V2Previewthrough its beta-operation proxy; the existing 2.4 path usesRoutines=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. 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
- Microsoft Foundry project with routines enabled. Check the current availability 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.
- Existing agent with a configured agent identity. A prompt
agent created via
project.agents.create_version(...)(seefoundry-prompt-agents) or a hosted agent (seefoundry-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. - 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.)
Python 3.9+ with the routines-capable SDK:
pip install "azure-ai-projects~=2.4.0" "azure-identity~=1.25.3" "httpx~=0.28.1"The existing Routines surface under
client.beta.routinesrequiresazure-ai-projects2.2.0 or later (preview). Earlier versions raiseAttributeErroronclient.beta.routines. The explicithttpxpin works around anazure-ai-projects2.4.0 packaging gap: the SDK importshttpxdirectly 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.- Authentication via
DefaultAzureCredentialfor SDK calls oraz account get-access-token --resource https://ai.azure.comfor raw REST. Routines are data-plane operations under the project endpoint. - Isolated CLI context per
azure-tenant-isolationbefore SDK,az, orazdwork. Set both tenant-specific config directories and verify the intended tenant/subscription before mutations. An explicit project endpoint does not replace the tenant guard. - 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.
3 · Author a routine
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.
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 inazure-ai-projects2.2.0)
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:
# 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.
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.
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_asyncwith?api-version=v1and 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
# 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.
6 · Run history
Every routine fire (scheduled or dispatched) is recorded. Query
with list_runs(routine_name):
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,--filterand--output json; use the explicit endpoint form in the azd reference. - 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
dispatch identity
before adopting the opt-in; it is not covered by this skill's existing SDK
lifecycle fixture. A service principal creating a routine does not acquire a
human user's delegated permissions simply by selecting creator.
Typed creator-identity management
MUST: use references/creator_routine.py
for disabled creation and saved-identity readback. It takes an existing project
client; it performs no login, consent, dispatch or role grants. Choose a fresh,
uniquely owned name, never a shared routine. The existence check is not an
atomic create-only operation; do not run concurrent authors against that name.
Authorization is ignored on update, so this helper refuses an existing name.
In the inspected 2.6.1 SDK, Routine lacks a typed authorization property,
despite Learn's saved_routine.authorization.identity example. The canonical
helper checks the preserved wire mapping from saved.as_dict() instead and
fails if it is missing, malformed or not creator. This is an offline-verified
SDK shape, not proof that a live service has stored the requested identity.
Install tests/requirements-management.txt
in an isolated management environment only. azure-ai-projects~=2.6.1
requires OpenAI 3, unlike the existing 2.4 consumer line. No MAF or hosting
upgrade is required or authorized by this recipe. Keep the existing pin file
and SDK lifecycle fixture on 2.4.
Readback of authorization.identity="creator" and enabled=False proves only
stored configuration. Before enabling, validate the intended creator's access
and consent for each tool, then a real trigger and downstream operation. Do not
silently fall back to agent identity on failure. Recreating under a different
principal changes the creator; use an approved replacement/cutover and verified
retirement rather than attempting an in-place authorization switch.
Governance notes
- Routine input strings are persisted in the run record; treat them as visible to anyone with project read access.
- Routine names are project-scoped — collisions across teams in
the same project will silently replace the prior definition on
create_or_update. Use a naming convention (<team>-<purpose>). - A routine has no built-in approval / four-eyes step. If you
need that, wrap the dispatch behind a Logic App or Functions
endpoint and call
dispatch()only after approval.
8 · Preview limitations (source alignment: September 2026)
This retained heading is a historical link target. These are service/client constraints, not a claim that the Routines service remains preview.
- One trigger and one action per routine. Multi-trigger or multi-action shapes are rejected by the service.
- Supported event providers only.
github_issueand thecustomTeams provider add event-driven automation; they do not make routines a generic webhook or queue receiver. - Only
invoke_agent_responses_apiandinvoke_agent_invocations_apiactions are supported. No HTTP-call or "run-a-function" actions. - Check availability and encryption requirements per § 2.
- 5-minute minimum interval between schedule fires. Cron expressions tighter than that are rejected.
- Agent identity required. Prompt-only agents without a configured agent identity are rejected when bound to a routine action. The agent must be a project-scoped agent (prompt-agent version published, or hosted agent deployed).
- CLI aliases differ from wire types. Inline scheduled creation
uses
--trigger recurring; manifests usetype: scheduleandcron_expression.createhas no--inputflag; stored input belongs in a manifest. Check the installed extension's help rather than inventing an unsupported flag. - Run history is available in azd through
azd ai routine run list, as well as SDKlist_runs, REST and the portal. - Input override is bounded. The
payload.inputfield indispatch()caps at 32,768 characters. agent_nameis bounded. ≤ 256 characters on both action types.- Header compatibility is client-specific. The Python 2.6.1 beta proxy
injects
Foundry-Features: Routines=V2Preview, not the 2.4 client'sRoutines=V1Preview. Current REST docs omit it. Header-free live acceptance and older-client migration remain distinct checks, not consequences of the service GA announcement.
9 · Anti-patterns
| Anti-pattern | Why it's wrong | What to do instead |
| Using a routine for multi-agent orchestration ("agent A then agent B based on A's output") | Routines have exactly one action — no branching, no chaining | Use Foundry workflows (preview), or invoke agent B from inside agent A as a sub-tool call |
Putting secrets / PII in routine input overrides | The input is persisted in the run record and visible to anyone with project read | Pass references (e.g., a Key Vault secret name) and resolve them inside the agent |
Sub-minute schedules (e.g. * * * * * expecting every-minute) | Service enforces a 5-minute minimum; tighter cron is rejected | For tighter intervals, use Logic Apps / Functions and call dispatch() from there |
| Treating the schedule as exact-second-precise | The service queues fires with some slack — don't build pipelines that assume sub-second alignment with the cron boundary | Build idempotency into the agent (deduplicate by an external key) |
| Re-creating a routine on every deploy with a different name | Each fresh name is a fresh routine — you accumulate orphans with old definitions still scheduled | Use a stable name and rely on create_or_update replacing the prior definition |
Calling dispatch() repeatedly to simulate high-frequency scheduling | Each dispatch is a separately-recorded run; you'll spam the run history | If you genuinely need high-frequency invocation, the agent should be called directly via the Responses API, not via a routine |
Mismatching payload.type with the routine's action type on dispatch() | The service rejects the call | Always set payload.type to the same string as the routine's action.type |
10 · References
- Routines concept doc — Microsoft Learn
- Automate agents with routines (how-to)
- azd lifecycle, event prerequisites and declarative deployment
azure-ai-projectson PyPI- Sibling skills:
foundry-prompt-agents— author the agent a routine will invokefoundry-hosted-agents— same, for container-hosted agentsfoundry-observability— observe routine runs via Foundry traces
11 · Acceptance by surface
The existing consumer fixture checks SDK 2.4 creation, manual enqueue, list and deletion. It deliberately does not wait for a scheduled fire or inspect downstream output. A PASS cannot certify those omitted behaviors. The earlier manual run-history observation in § 6 remains valid history, not new evidence for this candidate.
Use the additional acceptance cases after explicit resource, cost, identity and cleanup approval. Require manual dispatch correlation and a real timer/schedule delivery, separately. GitHub/Teams delivery, creator consent/revocation, private-network use and downstream business completion each need their own evidence. Record source, dispatch/run IDs and exact service/client versions without publishing live inventory or tokens. A completed delivery record alone is insufficient.
Version 1.2.0 adds GA/client maturity separation, typed creator management and these acceptance cases.
2026-09-25 scoped live acceptance: isolated SDK 2.6.1 ran the canonical
creator helper, verified disabled state and the saved creator wire mapping,
then exercised a separate creator-authorized one-shot timer. Its history
reported timer_delivery and completed before any manual dispatch. A subsequent
manual dispatch correlated by ID to a distinct completed queued_dispatch row.
The initial test's stricter source-label filter timed out; inspecting that same
dispatch proved it had completed, without redispatching or creating replacements.
Both original responses were retrieved through the agent-bound OpenAI client
and matched their distinct synthetic inputs. Project-level retrieval returned
404; no permission or identity change was used to work around it.
Both routines, the disposable prompt agent and both observed response records were removed with authenticated absence readback. No conversation IDs were returned. This proves the creator configuration and these two delivery/output paths on a public-access project. It does not prove delegated tool consent, GitHub/Teams events, a recurring schedule, private networking or header-free REST. The earlier September 13 readback limitation remains separate historical evidence; the newer result does not establish the cause of that earlier 404.
In this repository46 other files it gives its agents.
- AGENTS.mdAGENTS.md
- docs/llms.txtllms.txt
- agent-framework-harnessSkill
- auto-demo-producerSkill
- azd-patternsSkill
- azure-backup-readinessSkill
- azure-monitor-alert-baselineSkill
- azure-resource-diagnosticsSkill
- azure-sre-agentSkill
- azure-tenant-isolationSkill
- citadel-hub-deploySkill
- citadel-spoke-onboardingSkill
- copilot-doctorSkill
- foundry-agentopsSkill
- foundry-agtSkill
- foundry-caphost-lifecycleSkill
- foundry-cost-monitoringSkill
- foundry-cross-resourceSkill
- foundry-doc-vision-speechSkill
- foundry-evalsSkill
- foundry-hosted-agentsSkill
- foundry-iqSkill
- foundry-mcp-aca-jobsSkill
- foundry-mcp-acaSkill
- foundry-mcp-authSkill
- foundry-network-runbookSkill
- foundry-observabilitySkill
- foundry-prompt-agentsSkill
- foundry-rbac-auditSkill
- foundry-skill-catalogSkill
- foundry-teams-botSkill
- foundry-toolboxSkill
- foundry-vnet-deploySkill
- foundry-voice-agentsSkill
- foundry-voice-liveSkill
- gbb-humanizerSkill
- gbb-pptxSkill
- ghcp-cli-configSkill
- ghcp-hosted-agentsSkill
- html-deck-to-pptxSkill
- html-presentationSkill
- ip-catalogSkill
- paygo-ptu-cost-analyzerSkill
- progress-guardSkill
- token-economicsSkill
- web-experience-designSkill
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

