{"id":29456,"date":"2026-09-13T18:36:15","date_gmt":"2026-09-13T16:36:15","guid":{"rendered":"https:\/\/whiteduck.de\/controlling-microsoft-foundry-agents-with-token-control\/"},"modified":"2026-09-29T14:49:30","modified_gmt":"2026-09-29T12:49:30","slug":"controlling-microsoft-foundry-agents-with-token-control","status":"publish","type":"post","link":"https:\/\/whiteduck.de\/en\/controlling-microsoft-foundry-agents-with-token-control\/","title":{"rendered":"Controlling Microsoft Foundry Agents with Token Control"},"content":{"rendered":"<p>Microsoft Foundry has lowered the entry barrier for AI agents to such an extent that agents are no longer created solely within development teams. <a href=\"https:\/\/learn.microsoft.com\/en-us\/azure\/foundry\/agents\/overview\" target=\"_blank\" rel=\"noreferrer noopener\">Prompt Agents<\/a> are declarative agents: the model, instructions, and tools are described, not programmed. This is the speed that business departments need, and one of the reasons why we regularly recommend Foundry in customer projects. <\/p>\n<p>For a custom-developed application, the answer to governance and FinOps is simple because the endpoint is in the code. For a declaratively described agent, there is no code. Nevertheless, the same budgets, the same limits, and the same reporting requirements apply to it as to any other AI solution in the company.  <\/p>\n<figure class=\"wp-block-image aligncenter size-large is-resized\"><a href=\"https:\/\/whiteduck.de\/wp-content\/uploads\/2026\/09\/infografik-agent-laufzeiten.png\"><img decoding=\"async\" class=\"wp-image-25801\" style=\"aspect-ratio: 1.777793374508703; width: 846px; height: auto;\" src=\"https:\/\/whiteduck.de\/wp-content\/uploads\/2026\/09\/infografik-agent-laufzeiten-1024x576.png\" alt=\"\" title=\"\"><\/a><\/figure>\n<p><a href=\"https:\/\/tokencontrol.ai\/\" target=\"_blank\" rel=\"noreferrer noopener\">Token Control<\/a> is an AI control plane between your applications and your models, where budgets, access rules, and records converge. In this post, we show how you can use it to control all agents in Microsoft Foundry, regardless of whether they are declaratively described or custom-developed. <\/p>\n<h2 id=\"WarumRegelnunterhalbderModellauswahlliegenm\u00fcssen\">Why rules must sit below the model selection<\/h2>\n<p>When teams want to organize their AI usage, they almost always mention the same five requirements: reliable model access, real-time cost transparency, fixed budget limits, allocation per entity, and integration that does not touch existing solutions.<\/p>\n<p>With agents, these requirements become more urgent. A classic AI function consumes tokens per user request. An agent consumes tokens per work step, and the agent decides at runtime how many steps a task requires. What was once a predictable cost item becomes a variable one, and a variable item needs a limit that takes effect before the call.   <\/p>\n<p>The crucial factor is where this limit is placed. Anyone describing an agent declaratively selects a model from a list and formulates instructions. There is no application configuration where a platform team could enter an endpoint, and no build process where a policy could be attached. The rules must therefore lie below the model selection so that they reach the agent.   <\/p>\n<p>The same standard applies from a regulatory perspective. The EU AI Act and GDPR do not ask whether an agent was developed or configured. They ask who used it, with which model, in which region, and with what result. This information must come from the platform, not from the individual agent.   <\/p>\n<h2 id=\"PromptAgents\u00fcbereinadminconnectedModell\">Prompt Agents via an admin-connected model<\/h2>\n<p>Microsoft Foundry allows you to register your own model gateway as a connection. Microsoft calls the result an <a href=\"https:\/\/learn.microsoft.com\/en-us\/azure\/foundry\/agents\/how-to\/ai-gateway\" target=\"_blank\" rel=\"noreferrer noopener\">admin-connected model<\/a>, and it is created in the Foundry Portal. <\/p>\n<p>Two things are stored: the Token Control endpoint as the connection target and the associated API key. Foundry then calls the endpoint itself and appends the chat completions path, so only the base path needs to be entered. The models that should be usable via this connection are explicitly named.  <\/p>\n<figure class=\"wp-block-image aligncenter size-large\"><a href=\"https:\/\/whiteduck.de\/wp-content\/uploads\/2026\/09\/foundry-admin-connected-models-scaled-1.png\"><img decoding=\"async\" class=\"wp-image-25797\" style=\"aspect-ratio: 1.7268413732922472;\" src=\"https:\/\/whiteduck.de\/wp-content\/uploads\/2026\/09\/foundry-admin-connected-models-1024x593-1.png\" alt=\"\" title=\"\"><\/a><\/figure>\n<p>The registered model then appears as a deployment in the model picker of every agent in the project, addressed as <code>&lt;connection-name&gt;\/&lt;model-name&gt;<\/code>.<\/p>\n<figure class=\"wp-block-image aligncenter size-large is-resized\"><a href=\"https:\/\/whiteduck.de\/wp-content\/uploads\/2026\/09\/foundry-modell-picker-scaled-1.png\"><img decoding=\"async\" class=\"wp-image-25799\" style=\"aspect-ratio: 1.7268413732922472; width: 869px; height: auto;\" src=\"https:\/\/whiteduck.de\/wp-content\/uploads\/2026\/09\/foundry-modell-picker-1024x593-1.png\" alt=\"\" title=\"\"><\/a><\/figure>\n<p>That is all it takes. Anyone describing an agent selects a model from a list. If a model behind Token Control is in this list, the budget, limit, and record-keeping apply from the first call, without the agent&#8217;s author having to configure anything or even know about it.  <\/p>\n<h2 id=\"KontrollebenealsCodestattalsKlick\">Control plane as code instead of a click<\/h2>\n<p>The portal is sufficient for the first agent. For multiple environments and multiple teams, the connection and agent belong in code; otherwise, the control plane remains a one-time click and not a verifiable state. <\/p>\n<p>The connection is a control plane resource. We create it using the <a href=\"https:\/\/registry.terraform.io\/providers\/Azure\/azapi\/latest\/docs\" target=\"_blank\" rel=\"noreferrer noopener\">AzAPI<\/a> <a href=\"https:\/\/registry.terraform.io\/providers\/Azure\/azapi\/latest\/docs\" target=\"_blank\" rel=\"noopener\">Terraform Provider<\/a>, which addresses ARM resources directly: <\/p>\n<pre class=\"wp-block-code\"><code>resource \"azapi_resource\" \"token_control\" {\n  type      = \"Microsoft.CognitiveServices\/accounts\/projects\/connections@2025-04-01-preview\"\n  name      = local.names.token_control_connection\n  parent_id = azurerm_cognitive_account_project.main.id\n\n  body = {\n    properties = {\n      category      = \"ModelGateway\"\n      target        = var.token_control.base_url\n      authType      = \"ApiKey\"\n      isSharedToAll = false\n      metadata = {\n        deploymentInPath = \"false\"\n        models = jsonencode([{\n          name = var.token_control.deployment_name\n          properties = {\n            model = {\n              name    = var.token_control.model_name\n              version = var.token_control.model_version\n              format  = \"OpenAI\"\n            }\n          }\n        }])\n      }\n    }\n  }\n\n  sensitive_body = {\n    properties = {\n      credentials = { key = var.token_control_api_key }\n    }\n  }\n}<\/code><\/pre>\n<p>Two pieces of information are essential here. <code>isSharedToAll = false<\/code> limits the connection to one project instead of all projects in the account, keeping the control plane separable per environment. The Token Control API key enters the <code>sensitive_body<\/code> exclusively via an environment variable and never into a tfvars file. <\/p>\n<p>The separation of roles follows from the same resource. According to Microsoft, creation requires <a href=\"https:\/\/learn.microsoft.com\/en-us\/azure\/foundry\/agents\/how-to\/ai-gateway\" target=\"_blank\" rel=\"noreferrer noopener\">Foundry User or higher on the project and Contributor on the resource group<\/a>. The connection thus lies with administration, while the agents remain with the functional teams. This is exactly the separation platform teams seek when they want to introduce governance without becoming a bottleneck themselves.   <\/p>\n<p>On the other side is the agent. In the <a href=\"https:\/\/learn.microsoft.com\/en-us\/azure\/foundry\/how-to\/develop\/sdk-overview\" target=\"_blank\" rel=\"noreferrer noopener\">Foundry SDK<\/a>, it remains a standard definition, and the model reference is the only place that makes the difference: <\/p>\n<pre class=\"wp-block-code\"><code>RELEASE_NOTES_WRITER_SPEC = PromptAgentSpec(\n    name=\"release-notes-writer\",\n    description=(\n        \"Turns a raw change list into structured release notes, running on an \"\n        \"admin-connected model governed by Token Control.\"\n    ),\n    instructions=RELEASE_NOTES_WRITER_INSTRUCTIONS,\n    smoke_prompt=RELEASE_NOTES_WRITER_SMOKE_PROMPT,\n    validate_smoke=validate_smoke,\n    connected_model_env_var=\"FOUNDRY_CONNECTED_MODEL_DEPLOYMENT_NAME\",\n)<\/code><\/pre>\n<p>The declared variable takes precedence over the regular deployment name. The same agent runs on a different model with a different variable, without the instructions or logic changing. <\/p>\n<pre class=\"wp-block-code\"><code>export FOUNDRY_CONNECTED_MODEL_DEPLOYMENT_NAME=\"$(\n  terraform -chdir=infra output -raw connected_model_deployment_name\n)\"\nuv run deploy-agent release-notes-writer<\/code><\/pre>\n<p>Terraform thus owns the connection, while the SDK owns the agent definition. The control plane and agent are versioned together and are identical across all environments. <\/p>\n<h2 id=\"HostedAgentsundeigeneAgents\">Hosted Agents and custom Agents<\/h2>\n<p>The same control plane applies to custom-developed agents, connected via the agent&#8217;s configuration.<\/p>\n<p>Hosted Agents are containers that Foundry operates for you. They do not access the connection from the previous section but receive the endpoint and key via their own configuration. The base URL is a standard environment variable, and the API key belongs in a Foundry project connection and is <a href=\"https:\/\/learn.microsoft.com\/en-us\/azure\/foundry\/agents\/how-to\/configure-hosted-agent-env-variables\" target=\"_blank\" rel=\"noreferrer noopener\">resolved at runtime via a placeholder<\/a>:  <\/p>\n<pre class=\"wp-block-code\"><code>services:\n  release-notes-writer:\n    host: azure.ai.agent\n    env:\n      OPENAI_BASE_URL: https:\/\/&lt;ihr-token-control-host&gt;\/api\/v1\/openai\n      OPENAI_API_KEY: ${{connections.token-control.credentials.key}}<\/code><\/pre>\n<p>The key is therefore neither in the container image nor in the repository. Additionally, Foundry injects <code>FOUNDRY_PROJECT_ENDPOINT<\/code>, <code>FOUNDRY_AGENT_NAME<\/code>, and the connection to Application Insights at runtime, so your code knows its own identity and the telemetry target without managing them itself. <\/p>\n<p>The same pattern applies to Azure Container Apps, Azure Kubernetes Service, Azure Functions, and the local development environment, using a restricted API key per workstation.<\/p>\n<h2 id=\"WasSiedamitsteuern\">What you control with it<\/h2>\n<p>At this point, both paths converge. The declaratively described Prompt Agent and the custom-developed agent reach the same control plane, and from there, the same rules apply to them, managed by the same people. <\/p>\n<p><strong>Budgets that take effect before the call.<\/strong> You store budgets and limits in Euros per entity, i.e., per user group, department, cost center, or AI solution. Requests are blocked before a limit is exceeded. A rate limit on tokens per minute protects the performance of a platform, while a Euro budget protects a department&#8217;s budget.  <\/p>\n<figure class=\"wp-block-image aligncenter size-large is-resized\"><a href=\"https:\/\/whiteduck.de\/wp-content\/uploads\/2026\/09\/tokencontrol-budgets-kostenstellen.png\"><img decoding=\"async\" class=\"wp-image-25803\" style=\"aspect-ratio: 1.7095343680709534; width: 819px; height: auto;\" src=\"https:\/\/whiteduck.de\/wp-content\/uploads\/2026\/09\/tokencontrol-budgets-kostenstellen-1024x599.png\" alt=\"\" title=\"\"><\/a><\/figure>\n<p><strong>Access assigned to a solution.<\/strong> Every solution, project, and team receives its own restricted API key, the use and costs of which are tracked separately. A rotated or blocked key thus affects exactly one solution and not the entire AI usage. A TPM limit per application additionally ensures that no single application dominates capacity, and access remains restricted to the models you have approved.  <\/p>\n<p><strong>Records that withstand an audit.<\/strong> Every request is logged via a correlation ID, including tokens, model, region, timestamp, and cost.<\/p>\n<p>Prompt and response content are not stored; processing and storage take place in the EU. The compliance dashboard also shows the regional distribution of the models used and the status of the content filters. This creates an auditable basis that supports the transparency requirements of the EU AI Act without you having to archive your users&#8217; conversation content.  <\/p>\n<figure class=\"wp-block-image aligncenter size-large is-resized\"><a href=\"https:\/\/whiteduck.de\/wp-content\/uploads\/2026\/09\/tokencontrol-request-log.png\"><img decoding=\"async\" class=\"wp-image-25805\" style=\"aspect-ratio: 2.000031583601794; width: 842px; height: auto;\" src=\"https:\/\/whiteduck.de\/wp-content\/uploads\/2026\/09\/tokencontrol-request-log-1024x512.png\" alt=\"\" title=\"\"><\/a><\/figure>\n<p>The view from Foundry remains fully preserved. Runs, steps, latency, and errors continue to be visible via <a href=\"https:\/\/learn.microsoft.com\/en-us\/azure\/foundry\/observability\/concepts\/trace-agent-concept\" target=\"_blank\" rel=\"noreferrer noopener\">traces and the Monitor tab<\/a>, connected to your Application Insights resource. <\/p>\n","protected":false},"excerpt":{"rendered":"<p>Microsoft Foundry has lowered the entry barrier for AI agents  [&#8230;]<\/p>\n","protected":false},"author":6,"featured_media":29297,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[598],"tags":[586,988,989,720],"class_list":["post-29456","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-ai-2","tag-ai-2","tag-cost-control-3","tag-microsoft-foundry-agents-2","tag-token-control-2"],"acf":[],"_links":{"self":[{"href":"https:\/\/whiteduck.de\/en\/wp-json\/wp\/v2\/posts\/29456","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/whiteduck.de\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/whiteduck.de\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/whiteduck.de\/en\/wp-json\/wp\/v2\/users\/6"}],"replies":[{"embeddable":true,"href":"https:\/\/whiteduck.de\/en\/wp-json\/wp\/v2\/comments?post=29456"}],"version-history":[{"count":0,"href":"https:\/\/whiteduck.de\/en\/wp-json\/wp\/v2\/posts\/29456\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/whiteduck.de\/en\/wp-json\/wp\/v2\/media\/29297"}],"wp:attachment":[{"href":"https:\/\/whiteduck.de\/en\/wp-json\/wp\/v2\/media?parent=29456"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/whiteduck.de\/en\/wp-json\/wp\/v2\/categories?post=29456"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/whiteduck.de\/en\/wp-json\/wp\/v2\/tags?post=29456"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}