A compact Microsoft Agent Framework and Microsoft Foundry demo for turning a customer opportunity into an enterprise-ready solution assessment. The samples show the progression from model-only reasoning to organizational grounding with Foundry IQ, enterprise tools, hosted execution, and observability.
The included healthcare records and opportunity details are synthetic. The project is intended for demonstrations and learning, not as production clinical software.
Foundry IQ integration uses the
2026-05-01-previewAzure AI Search API and a beta Azure Search SDK. Review preview compatibility before production use.
Demo 03 is a conversational Agent Framework sandbox rendered as an observable pixel-art office. Avery, the session-scoped lead agent, retains chat history, decides whether each turn needs a plan, and selects bounded tools. Retrieval visits, delegation to Riley (the style-review agent), retries, and publication all come from real Harness tools and middleware events; the composer stays locked while a turn is active.
Current public research is delegated to Mona, a separate Agent Framework agent whose only capability is Foundry's native web-search tool. Mona cannot run until the user approves the precise delegated task with the in-office checkmark; rejection returns control to Avery without web access.
The chat accepts one PDF, DOCX, TXT, Markdown, CSV, or JSON attachment per turn, up to 5 MB. Documents are parsed in memory, capped at 60,000 extracted characters, treated as untrusted source material, and sent to the configured model as part of the turn.
| Idle office | Style-review subagent |
|---|---|
![]() |
![]() |
Start the local experience after completing Setup:
.\start.ps1Harness Office opens at http://127.0.0.1:8090.
- Single-agent assessment - Publishes a prompt agent that calls an Agent Framework tool to read the opportunity and create a concise brief.
- Foundry IQ proposal generation - Uses hybrid agentic retrieval to match prior opportunities, retrieves their linked proposals, and writes a cited draft.
- Enterprise tools and hosting - Grounds an agent in CRM, architecture, and cost-profile tools and can expose the agent through the Foundry Responses protocol.
- Evaluation and tracing - Exports Agent Framework traces and records a simple enterprise-coverage score.
flowchart LR
H[Seven synthetic cross-industry solutions] --> I[Ingestion script]
P[Seven linked proposals] --> I
I --> OS[Opportunity Search index]
I --> PS[Proposal Search index]
OS --> OKB[Opportunity knowledge base]
PS --> PKB[Proposal knowledge base]
O[New customer opportunity] --> OT[Opportunity tool]
OT --> D1[Demo 1: baseline assessment]
O --> D2[Demo 2: grounded proposal]
OKB --> D2
PKB --> D2
D2 --> B[Timestamped Markdown proposal]
O --> D3[Demo 3: Harness orchestration]
OKB --> D3
PKB --> D3
D3 --> SR[Style-review Agent]
D3 --> WR[Mona web-research Agent]
WR --> HA[Human approval]
HA --> WS[Foundry web search]
SR --> GP[Review-gated publish]
GP --> B
T[Approved enterprise tools] --> D4[Demo 4: tool-grounded agent]
D4 --> R[Local run or Responses host]
O --> D5[Demo 5: observable assessment]
D5 --> M[Foundry Toolkit, OTLP, or Application Insights]
The Foundry IQ path is deliberately separate from model generation:
ingest_foundry_iq.pycreates separate opportunity and proposal indexes with semantic ranking, vectors, Azure OpenAI query-time vectorizers, knowledge sources, and planner-backed knowledge bases.02_patterns.pybuilds an explicit Agent Framework workflow graph with four nodes and three edges: opportunity retrieval → linked-proposal retrieval → proposal drafting → Sources assembly.- The assessment agent receives both the new opportunity and retrieved evidence. Historical claims use citations such as
[0], while unsupported choices are labeled as recommendations or assumptions. The script prints only the uploaded proposal URL.
| Path | Purpose |
|---|---|
kickoffdemos/01_intro.py |
Tool-sourced baseline opportunity assessment |
kickoffdemos/ingest_foundry_iq.py |
Cross-industry Azure AI Search and Foundry IQ ingestion |
kickoffdemos/02_patterns.py |
Two-stage hybrid agentic retrieval and grounded proposal generation |
kickoffdemos/03_harness_simple.py |
Standalone Harness hello world with one synthetic tool |
kickoffdemos/harness_simple_agent.py |
Reusable minimal Harness factory for local and hosted execution |
kickoffdemos/03_harness.py |
Adaptive Harness orchestration with todos, bounded tools, and a style-review subagent |
kickoffdemos/hosted_proposal_agent.py |
Foundry Hosted Agent adapter for the complete Demo 02 workflow |
hosted_harness_simple.py |
Foundry Responses host for the minimal Harness demo |
kickoffdemos/04_hosted_tools.py |
Enterprise tool calling and optional Responses host |
kickoffdemos/05_observability.py |
Tracing and a custom coverage score |
tests/test_demo_contracts.py |
Offline regression tests for ingestion, authentication, cleanup, and story contracts |
RUN_DEMOS.md |
Detailed setup, execution, validation, and troubleshooting runbook |
DEMO_GUIDE.md |
Presenter runbook for the consistent five-demo story |
docs/images/ |
Release screenshots used by this README |
.github/workflows/ci.yml |
Offline tests, Python compilation, and frontend syntax validation |
.env.example |
Safe configuration template |
requirements.txt |
Base dependencies for local demos |
requirements-hosted.txt |
Optional prerelease dependency for Demo 4 hosted mode |
LICENSE |
MIT license |
The local demos and hosted deployment have different requirements. You do not need Python 3.13, the Azure Developer CLI, or Foundry Toolkit to run the numbered demos or Harness Office locally.
- Python 3.12 for the numbered demos and Harness Office
- Azure CLI
- A Microsoft Foundry project with at least one deployed chat model
- Permission for your Azure identity to use the project and invoke its model deployment
- For Demos 2 and 3: an Azure AI Search service, an embedding deployment, and either:
- a Search connection in the Foundry project, or
- Azure RBAC access to the Search service for the signed-in Azure CLI identity
- Optional Azure Storage for Demo 2; without it, proposals are written under
outputs/ - Optional Application Insights, an OTLP endpoint, or the Foundry Toolkit trace viewer for Demo 5
Check the required local tools from PowerShell:
py -3.12 --version
az --versionIf the Python launcher is unavailable but python --version reports Python 3.12, use python in
place of py -3.12 below.
The hosted sections additionally require:
- Python 3.13 or later
- Azure Developer CLI (
azd) with the Microsoft Foundry agent commands - Foundry Toolkit for VS Code only when using the F5 and Agent Inspector workflow
Verify that the hosted toolchain is available before following a hosted section:
py -3.13 --version
azd version
azd ai agent --helpLive model, Search, Storage, and telemetry calls may incur Azure charges.
Run all commands from the repository root.
In PowerShell:
py -3.12 -m venv .venv
.\.venv\Scripts\python.exe -m pip install --upgrade pip
.\.venv\Scripts\python.exe -m pip install -r requirements.txtUsing the environment's Python executable directly avoids PowerShell execution-policy issues, so activation is optional. On macOS or Linux, use:
python3.12 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -r requirements.txtaz login
az account show --output tableConfirm that the selected tenant and subscription can access the Foundry project and Search service.
The scripts use AzureCliCredential; do not put Azure access keys or client secrets in .env.
Typical access for the full demo is:
- Foundry project access that permits model invocation and agent-version creation
Search Service ContributorandSearch Index Data Contributoron the Search service when using direct or keyless Search accessStorage Blob Data Contributoron the optional Storage accountCognitive Services OpenAI Userfor the Search service managed identity on the embedding resource, andCognitive Services Useron the Foundry planner resource
An administrator may need to assign these roles, and new assignments can take several minutes to propagate. A key-based Foundry Search connection supplies its Search key in memory and does not require Search data-plane roles for the local user.
Create the ignored local configuration file:
Copy-Item .env.example .envEdit .env and replace the applicable your-... placeholders. Every model demo needs the Foundry
project endpoint and a chat-model deployment name:
FOUNDRY_PROJECT_ENDPOINT=https://your-resource.services.ai.azure.com/api/projects/your-project
AZURE_AI_MODEL_DEPLOYMENT_NAME=your-chat-deployment-name
# Harness Office starts on its Medium profile. Point it to a deployment in the same project.
HARNESS_MODEL_MEDIUM_DEPLOYMENT=your-chat-deployment-nameCopy the project endpoint from the Foundry project, not the browser address or account-level endpoint. Use the deployment name shown under the project's deployed models, which may differ from the model family name.
The full Foundry IQ path in Demos 2 and 3 also needs an embedding deployment and one Search access mode. The recommended project-connection mode is:
FOUNDRY_IQ_SEARCH_CONNECTION_NAME=your-search-connection-name
AZURE_OPENAI_EMBEDDING_ENDPOINT=https://your-embedding-resource.openai.azure.com
AZURE_OPENAI_EMBEDDING_DEPLOYMENT=your-embedding-deployment-name
AZURE_OPENAI_EMBEDDING_MODEL=text-embedding-3-largeFor direct, keyless Search access, comment out the connection-name setting and use this instead:
FOUNDRY_IQ_SEARCH_ENDPOINT=https://your-search-service.search.windows.netIf you select the Strong or Small profile in Harness Office, also set
HARNESS_MODEL_STRONG_DEPLOYMENT or HARNESS_MODEL_SMALL_DEPLOYMENT to deployments that exist in the
same project. The Configuration table documents aliases and optional overrides.
Never commit .env or credentials. The repository ignores .env and tracks only .env.example.
.\.venv\Scripts\python.exe -B -m unittest discover -s tests -v
.\.venv\Scripts\python.exe -B kickoffdemos\ingest_foundry_iq.py --dry-runThe tests should finish with OK. The dry run should validate seven synthetic projects without
contacting Azure.
For a model-only smoke test that does not use Search, run:
.\.venv\Scripts\python.exe -B kickoffdemos\03_harness_simple.pyFor the full Foundry IQ and Harness Office experience, ingest the synthetic project memory once, then start the local server:
.\.venv\Scripts\python.exe -B kickoffdemos\ingest_foundry_iq.py
.\start.ps1Open http://127.0.0.1:8090. Ingestion creates or updates persistent Search indexes, knowledge
sources, and knowledge bases; it is safe to rerun. See RUN_DEMOS.md for expected output,
the complete five-demo sequence, troubleshooting, and presenter checks.
The setup above lists the minimum values for the primary local experience. The remaining settings are aliases or optional overrides unless their description says otherwise.
| Variable | Used by | Description |
|---|---|---|
FOUNDRY_PROJECT_ENDPOINT |
All model demos; Search connection mode | Foundry project endpoint |
AZURE_AI_PROJECT_ENDPOINT |
azure.yaml, azd, and all model demos |
Foundry project endpoint alias; use the same value as FOUNDRY_PROJECT_ENDPOINT |
AZURE_AI_MODEL_DEPLOYMENT_NAME |
Demos 1-5 | Model deployment name |
HARNESS_MODEL_STRONG_DEPLOYMENT |
Demo 3 playground | Optional strong-tier deployment override; defaults to gpt-5.2 |
HARNESS_MODEL_MEDIUM_DEPLOYMENT |
Demo 3 playground | Optional medium-tier deployment override; defaults to gpt-4.1 |
HARNESS_MODEL_SMALL_DEPLOYMENT |
Demo 3 playground | Optional small-tier deployment override; defaults to gpt-5-nano |
FOUNDRY_IQ_SEARCH_CONNECTION_NAME |
Ingestion and Demo 2 | Foundry project Search connection; required unless a direct Search endpoint is set |
AZURE_AI_SEARCH_CONNECTION_NAME |
Hosted Demo 2 | Alias for the Foundry project Search connection used by azure.yaml |
FOUNDRY_IQ_SEARCH_ENDPOINT |
Ingestion and Demo 2 | Optional direct Search endpoint using AzureCliCredential |
AZURE_SEARCH_ENDPOINT |
Ingestion and Demo 2 | Alias for the direct Search endpoint |
FOUNDRY_IQ_OPPORTUNITY_INDEX_NAME |
Ingestion | Optional index-name override |
FOUNDRY_IQ_OPPORTUNITY_KNOWLEDGE_SOURCE_NAME |
Ingestion and Demo 2 | Optional knowledge-source override |
FOUNDRY_IQ_OPPORTUNITY_KNOWLEDGE_BASE_NAME |
Ingestion and Demo 2 | Optional knowledge-base override |
FOUNDRY_IQ_PROPOSAL_INDEX_NAME |
Ingestion | Optional proposal index-name override |
FOUNDRY_IQ_PROPOSAL_KNOWLEDGE_SOURCE_NAME |
Ingestion and Demo 2 | Optional proposal knowledge-source override |
FOUNDRY_IQ_PROPOSAL_KNOWLEDGE_BASE_NAME |
Ingestion and Demo 2 | Optional proposal knowledge-base override |
AZURE_OPENAI_EMBEDDING_ENDPOINT |
Ingestion | Azure OpenAI resource root used for document and query vectorization |
AZURE_OPENAI_EMBEDDING_DEPLOYMENT |
Ingestion | Embedding deployment name |
AZURE_OPENAI_EMBEDDING_MODEL |
Ingestion | Embedding model name; defaults to the deployment name |
AZURE_OPENAI_EMBEDDING_DIMENSIONS |
Ingestion | Optional vector dimensions; inferred for known embedding models |
FOUNDRY_IQ_PLANNER_MODEL_NAME |
Ingestion | Optional model-name override for agentic query planning |
AZURE_STORAGE_ACCOUNT_URL |
Demo 2 | Optional Blob service URL; absent or blocked storage falls back to outputs/ |
AZURE_STORAGE_PROPOSAL_CONTAINER_NAME |
Demo 2 | Optional container override; defaults to opportunity-proposals |
APPLICATIONINSIGHTS_CONNECTION_STRING |
Demo 5 | Optional Application Insights export |
OTEL_EXPORTER_OTLP_ENDPOINT |
Demo 5 | Optional generic OpenTelemetry export |
AZURE_AI_PROJECT_ENDPOINT, project_endpoint, FOUNDRY_MODEL, and deployment_name remain supported aliases in the sample code.
The ingestion and retrieval scripts support two modes:
- Foundry connection: Set
FOUNDRY_PROJECT_ENDPOINTandFOUNDRY_IQ_SEARCH_CONNECTION_NAME. The scripts resolve the Search endpoint from that project connection. Key-based connections use their in-memory key; keyless connections useAzureCliCredentialand require the signed-in identity to have the appropriate Azure AI Search roles. - Direct endpoint: Set
FOUNDRY_IQ_SEARCH_ENDPOINTorAZURE_SEARCH_ENDPOINT. The scripts useAzureCliCredential; assign the signed-in identity the appropriate Azure AI Search roles for index management, document upload, and retrieval.
The default persistent resource names are:
- Opportunity index/base:
si-healthcare-opportunity-history/si-healthcare-opportunity-assessment-kb - Proposal index/base:
si-healthcare-opportunity-proposals/si-healthcare-opportunity-proposals-kb
Running ingestion again updates both vectorized indexes and uploads the same three IDs per index, making the operation idempotent. The Search service managed identity needs Cognitive Services OpenAI User on the embedding resource and Cognitive Services User on the Foundry planner resource.
Demo 2 uses AzureCliCredential to create the proposal container when needed and upload a Markdown blob. Assign the signed-in identity Azure Storage Blob Data Contributor at the storage-account scope so it can upload and create a user-delegation read SAS.
New containers remain private. For a private container, the printed URL contains a one-hour read-only SAS. If the account and existing container already permit anonymous blob access, the script detects that setting and prints the direct blob URL instead. It never enables public access itself.
Validate the sample records without contacting Azure:
python -B kickoffdemos/ingest_foundry_iq.py --dry-runCreate or update the index, documents, knowledge source, and knowledge base:
python -B kickoffdemos/ingest_foundry_iq.pyRun the baseline assessment:
python -B kickoffdemos/01_intro.pyCreate, upload, and get the URL for the grounded proposal:
python -B kickoffdemos/02_patterns.pyShow each retrieval and generation stage in the terminal while preserving the final output path on stdout:
python -B kickoffdemos/02_patterns.py --verboseRun the standalone minimal Harness example. It has one synthetic weather tool and no opportunity, retrieval, workflow, or publication logic:
python -B kickoffdemos/03_harness_simple.pyThen compare the fixed graph with the fuller Harness agent that plans the same job through built-in todos and plan/execute modes. It receives separate opportunity retrieval, linked-proposal retrieval, and publication tools; those tools still enforce ordering, citations, and the final Sources section:
python -B kickoffdemos/03_harness.py --verboseLaunch the observable pixel-art Harness Office at http://127.0.0.1:8090:
.\start.ps1Send sequential messages through the chat composer. The same local Agent Framework session handles
every turn and retains conversation history; the composer is disabled until both execution and office
animation finish. Simple questions can be answered without a plan. For multi-step work, the agent can
use TodoProvider and choose a request-specific plan with no predetermined step count. Structured
middleware and bounded-tool events drive the work plan, speech bubbles, timeline, and route, which
records every workstation return and retry without exposing private chain-of-thought. Use
.\start.ps1 -Port <port> when port 8090 is occupied. The first turn opens with a visitor handing over
an attached document; plain chat wakes Avery in place and creates no route stops unless planning or a
tool is actually used. Before publication, the main agent
walks to the neighboring style studio and invokes Riley, a separate riley-style-reviewer Agent Framework
agent. The publishing tool accepts only the exact revised Markdown returned by Riley.
When Avery needs current public information, Mona appears at the web-search desk and requests approval
inside the same illustration. The chat remains locked while the checkmark/reject decision is pending.
Attach a supported document with the paperclip button before sending. The app never writes attachment
bytes to disk. Extracted text remains in the in-memory Agent Framework session context for follow-up
turns; the visible transcript retains only the safe filename. Model responses keep store: False.
After retrieval, select either library shelf in Harness Office to browse the synthetic Foundry IQ
references backed by Azure AI Search. The inspector shows source metadata, reranker scores, index and
knowledge-base names, and the full retrieved document content; credentials and connection details are
never sent to the browser.
Launch the browser-based Agent Framework DevUI:
python -B kickoffdemos/02_patterns.py --devuiIf port 8080 is occupied, choose another loopback port:
python -B kickoffdemos/02_patterns.py --devui --devui-port 8081In DevUI, select opportunity-proposal-workflow. Its structured input is titled Customer Opportunity / Call for Offer. Paste the complete customer request, RFP excerpt, or opportunity brief into Customer Opportunity / Call-for-Offer Text, including the business goal, current process, constraints, required controls, target outcomes, and mandatory human approvals. The graph displays retrieve_opportunities, retrieve_linked_proposals, draft_proposal, and assemble_sources as separate nodes connected by directed edges. The first three nodes emit intermediate progress events; the final node emits the cited proposal. DevUI binds only to 127.0.0.1 and disables authentication for this local development experience.
Open DevUI's Traces tab, expand an executor.process ... span, and select its
workflow.output ... child span. The child span includes a readable summary and a
demo.workflow.output.data attribute containing that stage's full synthetic output: Foundry IQ
grounding and references for retrieval stages, or Markdown for drafting stages. This content capture
is enabled only by --devui; normal CLI workflow traces retain counts and titles without raw bodies.
After the final stage persists the draft, DevUI also shows a separate workflow.output draft_file
span. Its demo.workflow.output.file_name and demo.workflow.output.storage_type attributes identify
the actual timestamped Markdown file and whether it was saved to Azure Blob Storage or outputs/.
The harness-simple-agent service is the shortest Hosted Agent demonstration: one reusable Agent
Framework Harness factory, one synthetic tool, and a Responses host. It uses
DefaultAzureCredential and keeps default_options={"store": False}. Foundry injects
FOUNDRY_PROJECT_ENDPOINT; the service configuration supplies only the model deployment name.
Create the dedicated Python 3.13 environment once:
py -3.13 -m venv .venv-hosted
.\.venv-hosted\Scripts\python.exe -m pip install -r requirements-hosted.txtThe F5 task invokes .venv-hosted directly. Optionally select that interpreter in VS Code so editor
analysis uses the same packages. Open Run and Debug, select Debug Harness Simple Hosted Agent,
and press F5. Foundry Toolkit opens Agent Inspector after the Responses server reaches port 8088.
Send:
What is the weather in Seattle?
The same smoke test is available from two terminals:
$env:AZURE_DEV_USER_AGENT = "microsoft_foundry_skill"
azd ai agent run harness-simple-agent --no-client --start-command ".\.venv-hosted\Scripts\python.exe hosted_harness_simple.py"
# In a second terminal:
azd ai agent invoke harness-simple-agent --local "What is the weather in Seattle?"The second hosted service is configured in azure.yaml as opportunity-proposal-agent using direct
code deployment and the Responses protocol. It returns proposal Markdown directly instead of a
container-local output path.
Run locally and invoke once:
$env:AZURE_DEV_USER_AGENT = "microsoft_foundry_skill"
azd ai agent run opportunity-proposal-agent --no-client
# In a second terminal:
azd ai agent invoke opportunity-proposal-agent --local "<customer opportunity>"Deploy when the local smoke test succeeds:
$env:AZURE_DEV_USER_AGENT = "microsoft_foundry_skill"
azd deploy opportunity-proposal-agent --no-prompt
azd ai agent show --output json
azd ai agent invoke opportunity-proposal-agent "<customer opportunity>"Each deployment creates an immutable hosted-agent version. The deployed agent identity must be able to read the Foundry project connection and the two Azure AI Search knowledge bases.
You can pass a different opportunity as the positional argument. Retrieval quality depends on its similarity to the seven cross-industry projects.
Run the tool-grounded agent locally:
python -B kickoffdemos/04_hosted_tools.pyHosted mode uses a prerelease package and requires a configured Foundry Hosted Agent environment:
python -m pip install -r requirements-hosted.txt
python -B kickoffdemos/04_hosted_tools.py --serveRun the observable assessment:
python -B kickoffdemos/05_observability.pyPrompt and completion content capture is disabled by default. Use --include-content only with non-sensitive data and an approved telemetry destination.
After installing the base requirements, run the standard-library unittest suite without contacting Azure:
python -B -m unittest discover -s tests -vThe suite checks ingestion, Search authentication, retrieval cleanup, timestamped non-overwriting proposal uploads, public and private URL behavior, untrusted-evidence instructions, disabled response storage, the shared opportunity, and Demo 5 coverage.
- The ingestion records are synthetic and contain no patient data.
- Retrieved documents are treated as evidence, not executable instructions.
- The opportunity assessment keeps clinician approval as a mandatory boundary.
- Stateless agent calls set
store=Falseso model responses are not stored by the Responses API. - Demo 1 prompt-agent versions and the Demo 2 Search, knowledge, container, and proposal blobs persist after the scripts exit; this repository does not delete Azure resources.
- The custom Demo 5 coverage score checks for expected phrases. It is illustrative, not a production quality or safety evaluation.
- Review identity, networking, content safety, evaluations, error handling, and lifecycle management before adapting these samples for production.
- Confirm
.env, caches, traces, and credentials are not staged. - Review all resource names and sample content for your organization.
- Run the dry run and all
--helpcommands in a clean environment. - Run live ingestion and Demo 2 against a non-production Azure environment.
- Review the preview SDK/API pins before upgrading.
- Confirm the MIT license is compatible with your organization's release policy.
This project is licensed under the MIT License.



