Skip to content

Repository files navigation

Agent Harness + CodeAct with .NET

Demo and technical article showing how to connect two Microsoft Agent Framework concepts correctly:

  • Agent Harness — the agent control plane (plans, tracks tasks, manages state, requests approvals).
  • CodeAct — the execution plane for tool-intensive analysis (loops, aggregations, scoring).

The use case: prioritising a legacy application portfolio for modernisation.


How it works — sequence diagram

The diagram below shows the full flow from user input to result, end-to-end.

sequenceDiagram
    autonumber
    actor User
    participant Console as Interactive Console
    participant Harness as Agent Harness<br/>(control plane)
    participant Model as gpt-4o<br/>(Microsoft Foundry)
    participant CodeAct as Hyperlight Sandbox<br/>(CodeAct / Python)
    participant Tools as C# Tools<br/>(read-only)
    participant AppInsights as Application Insights

    User->>Console: "Analyse the portfolio and rank applications"
    Console->>Harness: Forward prompt + session context

    Note over Harness: PLAN MODE
    Harness->>Model: Understand intent, build task list
    Model-->>Harness: Plan + todos (3-5 tasks)
    Harness-->>Console: Show plan to user

    Note over Harness: EXECUTE MODE
    Harness->>Model: Execute task 1 — retrieve and score portfolio
    Model->>CodeAct: execute_code(Python program)

    Note over CodeAct: Runs inside Hyperlight micro-VM
    CodeAct->>Tools: call_tool("get_applications")
    Tools-->>CodeAct: [APP-001, APP-002, APP-003, APP-004]

    loop For each application
        CodeAct->>Tools: call_tool("get_incidents", applicationId)
        Tools-->>CodeAct: IncidentSummary
        CodeAct->>Tools: call_tool("get_costs", applicationId)
        Tools-->>CodeAct: CostSummary
    end

    Note over CodeAct: Normalise + score + rank
    CodeAct-->>Model: Ranked JSON result

    Harness->>AppInsights: OpenTelemetry traces (plan, tools, tokens, duration)

    Model->>Harness: Task 2 — explain ranking
    Harness-->>Console: Ranking + explanation

    User->>Console: "Create a work item for the top application"
    Console->>Harness: Forward request
    Harness->>Model: Evaluate intent
    Model->>Harness: Call create_modernization_work_item (approval required)
    Harness-->>Console: ⚠ Approval requested — show details
    User->>Console: Approve
    Console->>Harness: Approved
    Harness->>Tools: create_modernization_work_item(APP-001, ...)
    Tools-->>Harness: WorkItemId = MOD-APP-001-001
    Harness->>AppInsights: Trace approval event
    Harness-->>Console: Work item created
Loading

What the demo shows, step by step

This walkthrough is written for anyone — technical or not. Each step maps to a numbered arrow in the diagram above and explains what is happening, what problem it solves, and why it matters.


Step 1 — The user sends a single sentence

"Analyse the portfolio and rank applications"

What this is: a natural-language instruction, not a form, not a script.

What it demonstrates: the agent understands intent. The user does not need to know which systems to query, which formula to apply, or in what order. That is precisely the point — the intelligence gap between a business question and its technical execution is bridged by the agent.


Step 2 — The Harness intercepts the request and enters PLAN MODE

Before doing anything, the agent stops and plans.

The Harness asks the model: "What tasks are needed to answer this?" The model returns a structured task list — three to five steps describing what it intends to do. That list is shown to the user.

What this demonstrates: the agent does not act first and explain later. It proposes a plan and exposes it. This is the governance boundary: a human can see the intent before execution begins.

This is analogous to an employee saying "here is what I am going to do" before starting — not a courtesy, but a control mechanism.


Step 3 — The Harness switches to EXECUTE MODE

The Harness moves the agent from planning to execution, task by task.

It manages the session (what has been done), the context (what the model can see), and the loop (whether to continue or stop). The model does not manage any of this itself.

What this demonstrates: the model is not the agent. The model reasons. The Harness governs. These are different responsibilities, and separating them is what makes the agent reliable across a long, multi-step task.


Step 4 — The model writes a program instead of making individual tool calls

For the analysis task, the model writes a short Python program and submits it to the sandbox via execute_code.

That program — in a single unit — does everything:

  1. Retrieves all four applications.
  2. Loops over each one.
  3. Calls get_incidents and get_costs per application.
  4. Normalises every dimension to a 0–100 scale.
  5. Applies the weighted scoring formula.
  6. Sorts and returns the ranked result.

What this demonstrates: instead of going back and forth between the model and each tool (which is slow, expensive, and hard to audit), the model expresses the entire workflow as a coherent program. The sandbox runs it in one shot.

Think of it as the difference between asking someone a question eight times vs. handing them a clear brief and letting them work through it once.


Step 5 — The program runs inside an isolated sandbox (Hyperlight)

The Python program runs inside a Hyperlight micro-VM — a hardware-isolated environment. It can only reach the tools it was explicitly given (get_applications, get_incidents, get_costs). It cannot access the network, the file system, credentials, or anything else.

What this demonstrates: isolation is not optional. The model writes code dynamically. That code must run in a controlled space. The sandbox enforces the boundary — the model's program cannot do more than what the tools allow.


Step 6 — The tools are narrow C# functions with defined contracts

Each tool does exactly one thing:

Tool Returns
get_applications The four applications — ID, name, technology, age, criticality
get_incidents Severity 1, severity 2, monthly incident count for one app
get_costs Annual run cost and average change lead time for one app

What this demonstrates: tools are not general-purpose APIs. They are deliberately narrow. A tool like RunSql(string query) or CallApi(string url) would be a back door — the agent could do anything. Narrow contracts are the first line of defence.


Step 7 — The ranked result comes back as structured data

The sandbox returns a JSON ranking:

[
  { "applicationId": "APP-001", "name": "Payments Legacy",       "score": 100.00 },
  { "applicationId": "APP-002", "name": "Customer Operations",   "score": 68.78  },
  { "applicationId": "APP-003", "name": "Portfolio Reporting",   "score": 37.83  },
  { "applicationId": "APP-004", "name": "Notification Service",  "score": 18.32  }
]

What this demonstrates: the output is not an opinion — it is a computed, reproducible result. The same inputs always produce the same ranking. The model then explains the result in natural language, but the numbers come from the algorithm, not from the model's judgement.


Step 8 — Every step is traced to Application Insights

While the agent works, the Harness sends structured telemetry to Application Insights: the plan it generated, every tool it called, the tokens it consumed, how long CodeAct took, and when the loop ended.

What this demonstrates: a correct result is not enough. An enterprise needs to know how the result was reached — what the agent did, what evidence it used, what it was not allowed to do. The trace is the audit trail.


Step 9 — The user requests a write operation

"Create a work item for the top application"

What this demonstrates: the agent correctly distinguishes analysis from action. It spent steps 1–8 reading data and computing results. This is the first request to change something in an external system.


Step 10 — The Harness pauses and requests explicit approval

Before calling create_modernization_work_item, the agent stops. It shows the user exactly what it is about to do — the application ID, the title, the rationale, the priority score — and waits.

What this demonstrates: write operations are not buried inside the analysis loop. The approval gate is architecturally enforced: the write tool is registered outside the sandbox, with ApprovalRequiredAIFunction wrapping it. The agent cannot skip this step.

This is the critical difference between an AI that does things for you and one that does things with your explicit consent.


Step 11 — After approval, the work item is created and traced

The Harness calls create_modernization_work_item, receives WorkItemId = MOD-APP-001-001, and logs the approval event — including who approved it and when.

What this demonstrates: the end state is not just a created ticket. It is a traceable chain from user intent → plan → analysis → approval → action → audit record.


What the demo does NOT do — intentionally

Not in the demo Why
The model decides the scoring weights The formula is fixed in code and in the system prompt — the model cannot redefine risk
CodeAct creates the work item Write operations require individual approval; burying them inside a loop would hide them
The agent acts without a plan Plan mode forces intent to be explicit before execution begins
The sandbox can access credentials Tools run in the C# host; the sandbox only calls named, bounded functions

These are not missing features. They are deliberate architectural boundaries.


What each component does

Component Role Technology
Interactive Console Renders the agent UI (plan/execute mode indicator, todo list, approval prompts) Harness_Shared_Console (Reactive)
Agent Harness Governs the full task lifecycle: planning, session, context, memory, approvals, loop Microsoft.Agents.AI.Harness
gpt-4o Reasons, plans, writes CodeAct programs, explains results Microsoft Foundry (Responses API)
Hyperlight Sandbox Executes the Python program in an isolated micro-VM Microsoft.Agents.AI.Hyperlight
C# Tools Narrow, auditable read operations — portfolio, incidents, costs AIFunctionFactory.Create(...)
create_modernization_work_item Write operation with mandatory approval — registered outside CodeAct ApprovalRequiredAIFunction
Application Insights Receives OpenTelemetry traces: plan, tool calls, token usage, approvals, duration Log Analytics workspace

What's inside

.
├── AgentHarnessCodeAct.slnx
├── .env.example                           ← env var template
├── global.json
├── infra
│   ├── main.bicep                         ← Foundry + project + Log Analytics + App Insights
│   ├── model.bicep                        ← Model deployment (separate)
│   ├── deploy.sh
│   ├── deploy.ps1
│   └── README.md
├── src
│   ├── ModernizationAgent.Core            ← Domain model + deterministic scoring
│   └── ModernizationAgent.LocalDemo       ← Validates scoring without AI
├── tests
│   └── ModernizationAgent.Tests           ← xUnit tests (scoring algorithm baseline)
└── preview
    └── HarnessCodeAct
        ├── Program.cs                     ← Drop-in for official agent-framework sample
        ├── README.md
        ├── apply-to-agent-framework.sh
        └── apply-to-agent-framework.ps1

Step 1 — Validate the domain locally (no AI required)

Requires the .NET 10 SDK.

dotnet run --project src/ModernizationAgent.LocalDemo

Expected output:

MODERNIZATION RANKING

Pos.  Application                  Technology                   Score
----------------------------------------------------------------------
1     Payments Legacy              COBOL / Mainframe           100.00
2     Customer Operations          VB6 / COM+                   68.78
3     Portfolio Reporting          .NET Framework 4.6           37.83
4     Notification Service         .NET 8                       18.32

TOP CANDIDATE DETAIL

Application: Payments Legacy (APP-001)
Final score: 100.00
Scoring breakdown:
- Criticality:  100.00
- Incidents:    100.00
- Cost:         100.00
- Lead time:    100.00
- Age:          100.00

Recommended next step:
Conduct a technical and functional assessment. Work item creation is a separate, subsequent, approvable operation.

What this validates:

  • Portfolio tools have narrow, well-defined contracts (IPortfolioReader).
  • The scoring algorithm is deterministic (same inputs → same outputs, always).
  • The expected ranking is reproducible across environments.
  • A write operation (create_modernization_work_item) is separated from the analysis phase — it is never called by the local demo.

You can also run the xUnit tests to verify the scoring formula deterministically:

dotnet test tests/ModernizationAgent.Tests

Expected output: 6 tests passed — four parametric assertions (one per application, matching the expected scores above) plus ordering and count checks.


Step 2 — Deploy Azure infrastructure

What gets created

main.bicep deploys in one shot:

Resource Purpose
Microsoft.CognitiveServices/accounts (AIServices) Microsoft Foundry resource
Microsoft.CognitiveServices/accounts/projects Foundry project
Microsoft.OperationalInsights/workspaces Log Analytics workspace for App Insights
Microsoft.Insights/components Application Insights — receives OpenTelemetry traces
Microsoft.Authorization/roleAssignments Foundry User role on the project (optional)

model.bicep deploys a model on the existing Foundry resource (separately, because model availability varies by region and subscription).

Deploy

export RESOURCE_GROUP=rg-agent-harness-codeact
export LOCATION=swedencentral
export PRINCIPAL_OBJECT_ID=$(az ad signed-in-user show --query id -o tsv)

./infra/deploy.sh

On PowerShell:

$env:RESOURCE_GROUP       = "rg-agent-harness-codeact"
$env:LOCATION             = "swedencentral"
$env:PRINCIPAL_OBJECT_ID  = (az ad signed-in-user show --query id -o tsv)

./infra/deploy.ps1

List available models for your region and subscription before deploying one:

az cognitiveservices account list-models \
  --name <foundry-name> \
  --resource-group <resource-group> \
  --query "[].{model:name,format:format,version:version,sku:skus[0].name}" \
  --output table

Deploy a model:

az deployment group create \
  --resource-group <resource-group> \
  --template-file infra/model.bicep \
  --parameters \
    foundryName=<foundry-name> \
    deploymentName=gpt-4o \
    modelName=gpt-4o \
    modelVersion=2024-11-20 \
    modelFormat=OpenAI \
    skuName=Standard \
    capacity=1

Step 3 — Run Harness + CodeAct

The Hyperlight backend requires a Linux host with KVM. The integration runs on top of the official microsoft/agent-framework repository.

git clone https://github.com/microsoft/agent-framework.git
./preview/HarnessCodeAct/apply-to-agent-framework.sh /path/to/agent-framework

Set environment variables (copy from .env.example):

export FOUNDRY_PROJECT_ENDPOINT="https://<foundry-name>.services.ai.azure.com/api/projects/<project-name>"
export FOUNDRY_MODEL="gpt-4o"

Run in an interactive terminal (the console requires a real TTY):

cd agent-framework
dotnet run \
  --project dotnet/samples/02-agents/Harness/Harness_Step04_CodeExecution \
  --framework net10.0

Suggested prompts

Analysis (read-only, runs via CodeAct):

Analyse the full portfolio. Compute the modernisation priority index for each
application, rank the results, and explain why the top application should be
modernised before the others. Do not create any work item yet.

After the agent responds, check the task list:

/todos

Write operation (triggers approval):

Create the work item for the top application with its summary and score.

The agent must pause and request explicit approval because create_modernization_work_item is registered outside CodeAct and wrapped in ApprovalRequiredAIFunction.

Console commands

Command What it does
/mode Toggle between plan mode (think + propose) and execute mode (act)
/todos Show the current task list and completion status
Any text Send a message to the agent

Application Insights — observability queries

All agent traces land in Application Insights via OpenTelemetry (source name: ModernizationAgent.HarnessCodeAct).

Open the Logs blade in your Application Insights resource and run these queries.

Agent sessions — overview

traces
| where timestamp > ago(1h)
| where isnotempty(customDimensions["AgentName"])
| summarize
    Sessions    = dcount(tostring(customDimensions["SessionId"])),
    TotalTokens = sum(toint(customDimensions["TotalTokens"])),
    AvgDuration = avg(duration)
  by AgentName = tostring(customDimensions["AgentName"])
| order by Sessions desc

CodeAct executions — how long did each program take?

dependencies
| where timestamp > ago(1h)
| where name == "execute_code"
| project
    timestamp,
    DurationMs   = duration,
    Success      = success,
    SessionId    = tostring(customDimensions["SessionId"])
| order by timestamp desc

Tool calls inside the sandbox

dependencies
| where timestamp > ago(1h)
| where name startswith "call_tool"
| summarize
    Calls       = count(),
    AvgDurationMs = avg(duration)
  by ToolName = tostring(customDimensions["ToolName"])
| order by Calls desc

Approval events — who approved what

traces
| where timestamp > ago(24h)
| where message == "Approval granted"
| project
    timestamp,
    Tool       = tostring(customDimensions["ToolName"]),
    SessionId  = tostring(customDimensions["SessionId"]),
    User       = tostring(customDimensions["ApprovedBy"])
| order by timestamp desc

Token consumption over time

traces
| where timestamp > ago(1h)
| where isnotempty(customDimensions["PromptTokens"])
| summarize
    PromptTokens     = sum(toint(customDimensions["PromptTokens"])),
    CompletionTokens = sum(toint(customDimensions["CompletionTokens"]))
  by bin(timestamp, 5m)
| render timechart

Errors and exceptions

exceptions
| where timestamp > ago(1h)
| project timestamp, type, outerMessage, SessionId = tostring(customDimensions["SessionId"])
| order by timestamp desc

Why two demos?

They are not two separate architectures.

The local demo tests the domain in isolation — no AI, no network, fully deterministic.

The Harness + CodeAct demo tests agentic orchestration:

Harness (control plane)        CodeAct (execution plane)
───────────────────────        ─────────────────────────
planning                       loops over applications
task list (todos)              calls get_incidents
session and context            calls get_costs
approval requests              normalises dimensions
OpenTelemetry traces           computes scores
termination criterion          produces ranked JSON

Technology status

Component Status Notes
.NET 10 LTS Stable
Agent Harness for .NET Available Current official sample targets net10.0
Hyperlight CodeAct for .NET Preview Requires Linux host with KVM
Hyperlight backend Preview Not available on Windows or arbitrary PaaS
  • Tools called via call_tool(...) run in the C# host process — credentials stay in C#, not in the sandbox.
  • CodeAct approval applies to the full execute_code block, not to individual call_tool calls inside it. That is why create_modernization_work_item is registered directly on the agent, outside CodeAct.

About

Agent Harness (control plane) + CodeAct (Hyperlight sandbox) in .NET 10 — a governed legacy-modernization scoring agent with approval-gated writes.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Contributors

Languages