> ## Content Index
> Fetch the complete content index at: https://devinside.cloud/llms.txt
> Use this file to discover other available public pages before exploring further.

# An Agent Tool Is an API with a Different Caller
- URL: https://devinside.cloud/an-agent-tool-is-an-api-with-a-different-caller/
- Published: 2025-10-10T13:30:00.000Z
- Updated: 2026-11-12T17:08:25.000Z
- Description: An agent can select a function, assemble arguments and explain why it wants to call it. None of those abilities grants permission to perform the operation.
- Author: Marcin Borecki
- Tags: Engineering, Microsoft AI Engineering, #m10, #devinside-review

START WITH THE MECHANISM

Interactive companion and laboratory update: 12 November 2026\. The editorial date places the topic on the timeline; this current explanation and its measured evidence retain their actual preparation and execution dates.

THE FULL INVESTIGATION

## An Agent Tool Is an API with a Different Caller

Editorial context: 10 October 2025 · Prepared and updated: 12 November 2026

Dates and technology context

This is a retrospective editorial timeline. Topic context: 10 October 2025\. First publicly available: 12 November 2026\. Current text, visuals and laboratory revisions were prepared on 12 November 2026.

The November appendix introduces actual MEAI 11.0.0 and Microsoft.Agents.AI 1.28.0 callers on .NET10.0.15\. These packages cannot appear as available components in October 2025.

An agent can select a function, assemble arguments and explain why it wants to call it. None of those abilities grants permission to perform the operation.

This distinction is easy to lose in a demonstration. The developer creates a useful function, gives it a clear description and watches the model call it correctly. The successful example appears to establish a general relationship between intent and execution. What it actually establishes is much narrower: one model response happened to request one valid action.

Once the function can affect customer data, send a message or commit money, the application needs the same disciplines it would require from any other caller. In several respects it needs them more urgently, because the caller can generate unexpected combinations of otherwise plausible arguments. An agent tool is an API with a different caller, not an exemption from API engineering.

## Begin with a capability that has a boundary

Consider an assistant that helps a support worker prepare an order adjustment. A broad function called `ExecuteSql` would be easy to implement and difficult to govern. A narrower function called `PrepareOrderAdjustment` can describe a specific business capability with explicit limits.

The name alone is not enough. Specify the allowed resource, the authenticated actor, the required state of the order, the permitted adjustment range and the evidence needed before execution. Decide whether the function reads, prepares or commits. Those verbs should not be interchangeable.

A useful first version might only prepare a proposal. It returns the affected order, the proposed change, any warnings and an immutable proposal identifier. A separate operation commits that proposal after an authorized person approves the exact content. This makes the boundary reviewable.

There is a legitimate counterargument: splitting preparation from execution adds latency and state. For a reversible, low-impact operation, a single authorized call may be appropriate. The design should follow the consequences of failure, not a blanket preference for approval screens. What matters is that the consequences are understood before autonomy is granted.

## Keep identity out of model-generated arguments

01 / See the idea

### The server supplies the actor

![Model arguments and trusted identity enter separate paths before a policy decision.](https://devinside.cloud/content/images/2026/11/devinside/M10-v1.svg)

[Enlarge diagram](https://devinside.cloud/content/images/2026/11/devinside/M10-v1.svg)

A hypothetical model proposes an order adjustment and supplies an order ID. The authenticated application supplies the actor; a model-written administrator flag carries no authority.

Interpretation and evidence
- Arguments express intent.
- Identity comes from authentication.
- Resource policy decides permission.
conceptual/hypothetical, not measured

The model may need an order identifier. It does not need to decide which tenant it belongs to or which user it represents. Those values should come from authenticated server context and be bound to the tool invocation by trusted code.

If a tool accepts `tenantId` and `isAdministrator` as ordinary arguments, the model has been given a vocabulary for making security claims about itself. A helpful description does not repair that design. The application must derive authority independently.

Resource-level authorization is also different from authenticating the request. A user can be signed in and still lack permission to adjust a particular order. ASP.NET Core's resource-based authorization guidance describes the need to evaluate access against the resource after it is loaded. [ASP.NET Core resource-based authorization](https://learn.microsoft.com/en-us/aspnet/core/security/authorization/resource-based?view=aspnetcore-9.0&ref=devinside.cloud).

The illustrative .NET 9 boundary below separates caller identity from model-controlled arguments. It has not been compiled or executed for this manuscript. Application-specific interfaces represent responsibilities, not built-in framework APIs.

```csharp
public sealed record AdjustmentRequest(
    Guid OrderId,
    decimal RequestedCredit,
    string Reason);

public async Task<AdjustmentProposal> PrepareAsync(
    AdjustmentRequest request,
    AuthenticatedActor actor,
    CancellationToken cancellationToken)
{
    var order = await orders.LoadAsync(
        actor.TenantId, request.OrderId, cancellationToken);

    await policy.RequireCanAdjustAsync(
        actor, order, cancellationToken);

    return await proposals.CreateAsync(
        order, request, actor, cancellationToken);
}

```

The actual policy must inspect the requested amount and business state. The example is intentionally not a complete security implementation. Its important property is the direction of trust: the request expresses a proposed action, while the server supplies the actor and decides whether the action is permitted.

## Validate meaning, not just JSON

Schema validation can establish that an amount is numeric and a reason is a string. It cannot establish that the credit is commercially justified or that the order belongs to the authenticated customer.

Separate structural validation, authorization and business invariants. Structural validation rejects malformed identifiers and oversized text. Authorization determines who may act on the resource. Business validation determines whether the operation is valid in its current state. A cancelled order, a fulfilled order and a disputed order may require different rules even for the same employee.

Use bounded inputs. An unbounded array of order identifiers can turn a convenience function into a bulk action. A free-form destination can turn a report generator into an exfiltration route. A timeout parameter can become a way to occupy resources indefinitely. Defaults are not limits unless the server enforces maximums.

Return errors that support recovery without revealing unnecessary information. “This operation is not permitted” may be preferable to disclosing that another tenant owns the identifier. But an internal audit event should retain enough classification to explain which boundary rejected the request. The model-facing response and the operator-facing evidence serve different audiences.

## Make the Semantic Kernel adapter deliberately boring

Semantic Kernel plugins expose application functionality as functions that a model can request. The framework handles the integration surface, but the application still owns the consequences of the function. Microsoft's plugin documentation describes encapsulating existing APIs and using dependency injection for the services behind them. [Semantic Kernel plugins](https://learn.microsoft.com/en-us/semantic-kernel/concepts/plugins/?ref=devinside.cloud).

For the historical baseline, the adapter uses only the established `KernelFunction` attribute and ordinary dependency injection. It does not depend on newer agent orchestration APIs. The fragment assumes the Semantic Kernel 1.0.1 function-attribute surface; package restoration and compilation remain unperformed. The pinned package is publicly recorded in the [Semantic Kernel package registry](https://www.nuget.org/packages/Microsoft.SemanticKernel/1.0.1?ref=devinside.cloud).

```csharp
using Microsoft.SemanticKernel;
using System.ComponentModel;

public sealed class OrderTools(
    IAdjustmentService adjustments,
    IRequestActor requestActor)
{
    [KernelFunction("prepare_order_adjustment")]
    [Description("Prepare a credit proposal. This does not commit it.")]
    public Task<AdjustmentProposal> PrepareAsync(
        Guid orderId,
        decimal requestedCredit,
        string reason,
        CancellationToken cancellationToken = default)
    {
        var request = new AdjustmentRequest(
            orderId, requestedCredit, reason);
        return adjustments.PrepareAsync(
            request, requestActor.RequireAuthenticated(),
            cancellationToken);
    }
}

```

The plugin should be created within the appropriate request scope. Do not put a mutable “current user” into a singleton and let concurrent conversations overwrite it. If execution continues after the request ends, persist the identity and authorization context needed for the job through a deliberate server-side design, not through a reference to a disposed request object.

The adapter's small size is a feature. Business tests should be able to exercise the service without a model. Framework migration should not require rewriting the credit policy. The tool description helps the caller choose the function; it does not implement the function's safety rules.

## Repetition is normal, so define it

02 / A concrete example

### Same key is not enough

![A three-row example separates a first request, an identical replay and a conflicting replay.](https://devinside.cloud/content/images/2026/11/devinside/M10-v2.svg)

[Enlarge diagram](https://devinside.cloud/content/images/2026/11/devinside/M10-v2.svg)

Hypothetical operation K7 first requests a credit of 25 units and then repeats the same request. A later request for 30 units under K7 is a conflict, not another valid replay.

Interpretation and evidence
- Bind key to canonical content.
- Identical replay returns recorded outcome.
- Changed payload must be rejected.
conceptual/hypothetical, not measured

A caller can retry because of a timeout, a transport error or uncertainty about the previous result. An agent may also ask for the same operation twice while revising its plan. A valid request therefore needs a definition of repetition.

For a commit operation, use a stable operation identifier bound to the actor, resource and canonical proposal. A repeated request with the same identifier and same content can return the recorded result. The same identifier with different content must be rejected. Otherwise an idempotency key becomes an alias for whichever payload arrived most recently.

Do not claim exactly-once execution merely because a table stores request identifiers. The difficult interval is between an external effect and durable recording of that effect. If a remote system accepts a change and the local service crashes before saving the result, the service must reconcile the uncertainty. An idempotent downstream API, transactional outbox or explicit state machine may be required.

Retention matters too. If the operation record expires before the caller's retry window, the same request may become “new” again. Document the lifetime of replay protection and the behavior after it ends. A time boundary hidden in storage cleanup is still part of the API contract.

## Bind approval to the thing that will happen

03 / Watch the mechanism

### Approval travels with exact content

![An approval matches a fixed proposal digest; a changed proposal is stopped before execution.](https://devinside.cloud/content/images/2026/11/devinside/M10-v3.svg)

Approval travels with exact contentAn approval matches a fixed proposal digest; a changed proposal is stopped before execution.M10-V3 / TECHNICAL EXPLANATIONApproval travels with exact contentConceptual / hypothetical example. Not measured results.Proposal P7digest AApprovalbinds AExecutorcompares digestChanged targetdigest BA ≠ B → stop

Play animation[Enlarge diagram](https://devinside.cloud/content/images/2026/11/devinside/M10-v3.svg)

Static view. Play to see how the mechanism unfolds.

Suppose proposal P7 is approved with digest A, then an agent changes its destination and produces digest B. The executor compares the binding and stops the changed action.

Interpretation and evidence
- Approve a concrete proposal.
- Check expiry and current authority.
- A changed digest needs a new decision.
conceptual/hypothetical, not measured

“The user said yes” is not a sufficient approval artifact. Yes to which resource, which amount, which destination and which version of the proposal?

An approval should bind to canonical action content and a defined expiry. At execution time, verify that binding and recheck authorization. Permissions may have changed since the proposal was created. The resource may also have changed in a way that invalidates the proposed operation.

A useful state machine distinguishes prepared, approved, executing, succeeded, failed and uncertain. “Uncertain” is important because a transport failure does not prove that an external effect failed. Recovery from that state should inspect the downstream operation rather than blindly issue another action.

For low-impact actions, the approval step may be an existing application policy rather than a human click. For high-impact actions, the user may need to review a clear summary. In either case, approval belongs to trusted application state. A model-generated sentence claiming approval cannot substitute for it.

## Treat tool output as another input boundary

A tool that reads a document may return hostile instructions embedded in that document. A database field may contain text telling the assistant to ignore its previous task. The fact that the text arrived through a trusted transport does not make the content authoritative.

Keep data and control separate. Return structured results with explicit fields, bounded sizes and provenance. Avoid tools that combine a retrieval result with new operational instructions in the same free-form string. A source can be evidence for an answer without being allowed to alter the agent's capabilities.

Apply the same discipline to errors. An exception message may contain a connection string, internal URL or customer data. Do not send raw exceptions to the model by default. Map failures into safe categories and retain restricted diagnostic detail in an appropriately protected system.

This is also a reason to keep the tool surface small. The fewer capabilities an agent can request during a task, the easier it is to understand the consequences of a confused or manipulated plan. Least privilege is not a statement in the prompt. It is the set of operations the runtime makes possible.

## Test the service before testing the agent

Begin with deterministic tests against the application boundary. An unauthorized actor must not produce a proposal for another tenant. A stale proposal must not commit after the order changes. A repeated commit must not create a second effect. A changed payload under the same operation identifier must be rejected.

Then test the adapter: argument conversion, cancellation propagation, safe error mapping and request-scope isolation. These tests locate failures that a conversational demonstration can easily miss. They also give a future SDK migration a stable contract to preserve.

Finally, evaluate model behavior. Use malformed requests, contradictory instructions, ambiguous user intent and retrieved text containing hostile directives. Record what the model requested and what the service actually allowed. The important result is not merely whether the model “behaved well.” It is whether an unsafe request remained unable to cross the deterministic boundary.

Keep the test environment incapable of real external effects. A reference order store and a recording fake destination can expose whether an action would have occurred without issuing a refund or sending a message. When a later integration test uses a real dependency, record the environment and execution date honestly.

## An API contract is how autonomy becomes accountable

There is a tempting shortcut in agent development: make the model smarter and trust it to call the tools more carefully. Better reasoning can improve task selection and reduce mistakes. It cannot replace identity, authorization, transactional reasoning or recovery.

The stronger architecture assumes that the caller will occasionally be wrong. It makes valid actions easy to express, invalid actions impossible to execute and uncertain outcomes possible to investigate. That architecture benefits deterministic clients as well as agents.

The agent may decide what to try next. The application decides what is permitted to happen. Keeping those responsibilities separate is not a concession to immature models. It is how a useful API remains trustworthy when its caller becomes more capable.

## Execution appendix, 12 November 2026: one service, three callers

The earlier sections describe the design in its October 2025 context. This appendix adds an actual local experiment performed on 12 November 2026\. It uses later package versions deliberately and does not imply that those packages existed at the original context date. The earlier illustrative fragments remain illustrative; the executable project described here is a separate, versioned artifact.

The reference now has three real entry paths into the same domain service: an ASP.NET Core HTTP endpoint, a function invoked through `Microsoft.Extensions.AI`, and a `ChatClientAgent` from Microsoft Agent Framework. All three perform the same synthetic command against the same kind of SQLite-backed operation ledger and local receiver. There is no external financial action or remote language-model call.

The package graph pins `Microsoft.Extensions.AI` 11.0.0, `Microsoft.Agents.AI` 1.28.0, `Microsoft.Data.Sqlite` 10.0.0 and the SQLite native bundle selected in the lockfile. The process runs on SDK 10.0.400 and runtime 10.0.15\. The public package records establish the selected releases, while the successful build and invocation establish that this particular graph executed together. [Microsoft.Extensions.AI 11.0.0](https://www.nuget.org/packages/Microsoft.Extensions.AI/11.0.0?ref=devinside.cloud), [Microsoft.Agents.AI 1.28.0](https://www.nuget.org/packages/Microsoft.Agents.AI/1.28.0?ref=devinside.cloud).

This experiment gives the article's central claim a concrete test. If the agent is another caller of an application capability, its path should preserve the same authorization and effect contract as the ordinary API. A common method name alone would not establish that. The test must cross the actual binding layers and inspect the receiver's state.

## The command cannot supply its own identity

The new command carries only operation, quote and approval identifiers. Identity is a separate immutable context:

```csharp
public sealed record RequestContext(string ActorId, string TenantId);
public sealed record Command(
    string OperationId, string QuoteId, string ApprovalId);

```

The HTTP fixture maps a small set of clearly labelled test authorization values to server-selected contexts. `Fixture alice` represents Alice in tenant A; a foreign-tenant fixture represents the same actor name in tenant B. These are synthetic credentials in an isolated loopback test, not a production authentication scheme.

The body also includes deliberately misleading `actorId` and `tenantId` fields, and the request includes a tenant header. Those values do not participate in context construction. The endpoint binds the command's three identifiers, resolves the fixture context on the server, and passes both to the domain method. The test confirms that the unauthorized actor and foreign tenant still produce no receiver effect.

The tool path follows the same direction of trust. Its function takes command identifiers while closing over the request-local context:

```csharp
RequestContext context = scenario.Context;
string ExecuteCommand(
    string operationId, string quoteId, string approvalId)
{
    observed = scenario.Execute(
        new(operationId, quoteId, approvalId), context);
    return JsonSerializer.Serialize(observed);
}

```

The scripted provider deliberately adds identity-looking arguments to its function call. They cannot replace the context captured by the host. This verifies the actual generated-function binding in the selected package rather than assuming that an attractive function description enforces a policy.

The context is immutable, but immutability is not authentication. A real host must obtain it from a trusted authentication and tenancy mechanism. The reference demonstrates where the boundary belongs and that these adapters preserve it; it does not implement token issuance or prove every deployment's identity configuration.

## Real HTTP and real function middleware reach the same method

01 / Watch the mechanism

### Three real callers preserve one policy

![Three real callers preserve one policy. HTTP, MEAI and MAF each reach the same domain method with host-supplied context. Additional identity-looking arguments cannot replace that context.](https://devinside.cloud/content/images/2026/11/devinside/M10-appendix-1.svg)

Three real callers preserve one policyHTTP, MEAI and MAF each reach the same domain method with host-supplied context. Additional identity-looking arguments cannot replace that context.M10 / NOVEMBER 2026 EXECUTION APPENDIXThree real callers preserve one policyHTTPMEAI functionMAF agentOne domain serviceTrusted actor + tenantSQLite ledgerLocal receiverLocal synthetic fixture. No live-model or payment claim.

Play animation[Enlarge diagram](https://devinside.cloud/content/images/2026/11/devinside/M10-appendix-1.svg)

Static view. Play to see how the mechanism unfolds.

HTTP, MEAI and MAF each reach the same domain method with host-supplied context. Additional identity-looking arguments cannot replace that context.

Interpretation and evidence
- Keep identity outside tool arguments.
- Share the domain policy.
- Inspect the receiver effect.
conceptual sequence matching executed local fixture

The HTTP test starts an ASP.NET Core application on `127.0.0.1` with an ephemeral port. It sends real HTTP requests using `HttpClient`, checks the response status and reads the serialized domain outcome. The listener is stopped and disposed after the finite scenario set. This is not an in-memory invocation disguised as an HTTP result.

For the tool path, the local provider implements `IChatClient`. Its first response contains a `FunctionCallContent` for `execute_command`; after receiving the result it emits a fixed acknowledgment. `FunctionInvokingChatClient` performs the actual dispatch through an `AIFunction` generated from the local method. The wrapper allows at most four provider iterations and processes functions serially.

The Agent Framework path wraps that configured client in a real `ChatClientAgent`:

```csharp
var agent = new ChatClientAgent(
    functions,
    instructions: "Execute the supplied fixture command once.",
    name: "FixtureAgent",
    tools: [tool]);

await agent.RunAsync("Run the controlled command.");

```

The framework provides its agent pipeline around the same chat abstraction. The application method remains responsible for the command's meaning. Microsoft's constructor and pipeline documentation identify this relationship; the fixture executes it with the exact restored package. [ChatClientAgent constructors](https://learn.microsoft.com/en-us/dotnet/api/microsoft.agents.ai.chatclientagent.-ctor?view=agent-framework-dotnet-latest&ref=devinside.cloud), [agent pipeline architecture](https://learn.microsoft.com/en-us/agent-framework/agents/agent-pipeline?ref=devinside.cloud).

The provider is scripted, not intelligent. It makes the call shape predictable so a failure can be assigned to binding, context, policy or persistence. Nothing in this result establishes how a live model would choose an operation or interpret an ambiguous user request.

## Compare effects and traces across nine scenarios

The suite runs valid execution, wrong actor, wrong tenant, expiry, stale resource version, changed quote, exact replay, consumed-approval reuse and lost-response recovery through all three paths. Each path gets a fresh, equivalent fixture, so a previous caller cannot accidentally make the next caller pass by leaving behind its result.

The domain outcome includes state, receiver-effect count, held units and committed units. The test compares these fields and the ordered domain trace across paths. The transport wrappers are allowed to differ: HTTP returns a status code, whereas the chat paths perform two local provider rounds. Those presentation and orchestration differences must not change the application effect.

The accepted run completed 75 checks over 27 path-and-scenario combinations. A valid command produced one effect. Exact replay retained one effect. Consumed-approval reuse was denied while preserving the already existing first effect. Unauthorized, foreign-tenant, expired, stale and changed-quote cases produced no new effect. The distinction between no effects ever and no additional effect is important when testing a replay boundary.

The lost-response case committed a synthetic receiver effect while leaving the local operation Unknown and its amount held. Reopening the ledger and reconciling against the receiver moved it to Succeeded without creating another effect. That behavior was the same for all three callers.

The trace contains application-level events such as request, tenant acceptance, reservation state, dispatch state and reconciliation. It deliberately does not depend on framework-internal event names. An adapter upgrade can reorganize its own callbacks while the application's observable responsibility remains stable.

## What the replay measurement includes

02 / See the idea

### Local replay cost across the three paths

![Local replay cost across the three paths. These measured calls replay an already successful operation. They do not include HTTP transport, a live model or a new receiver effect.](https://devinside.cloud/content/images/2026/11/devinside/M10-appendix-2.svg)

[Enlarge diagram](https://devinside.cloud/content/images/2026/11/devinside/M10-appendix-2.svg)

These measured calls replay an already successful operation. They do not include HTTP transport, a live model or a new receiver effect.

Interpretation and evidence
- Replay is not a new commit.
- Process-wide allocation has a broad boundary.
- Overlapping ranges do not prove a reliable ranking.
actual shared check or measurement report

The performance experiment measures already-established successful operations being replayed through three local routes: direct domain invocation, function middleware and Agent Framework. Each route reuses a valid operation identity. It does not create another receiver effect or consume another approval.

Each sample contains sixteen calls after setup, with six measured samples and one warm-up sample per route. The direct route is not an HTTP baseline. It isolates the domain method, while the other routes include constructing the local chat wrappers and invoking the scripted provider. HTTP correctness was tested separately and is not timed here.

Because these paths use asynchronous APIs, the allocation counter is process-wide rather than current-thread. That avoids silently losing allocations if a continuation changes threads, but it also means unrelated process allocation can contribute. The measurement is a small local comparison with explicit scope, not a forecast of hosted-model latency.

The shared measurement completed on 12 November 2026 at 14:05 UTC on .NET 10.0.15, with exit code zero. It contains six samples for each path. The table reports milliseconds per successful replay and process-wide managed bytes allocated per replay; all three paths are retained.

| Local replay path | Median ms | Observed ms range | Allocated bytes range |
| ----------------- | --------- | ----------------- | --------------------- |
| direct-domain     | 1.298     | 1.267–1.374       | 37,203.5–37,222.5     |
| meai              | 1.338     | 1.318–1.406       | 42,435.5–42,498.5     |
| maf               | 1.435     | 1.351–1.583       | 49,907.5–49,978.5     |

The medians differ, but the observed timing ranges overlap. This small single-process run does not establish a statistically reliable overhead percentage or identify which internal layer caused a difference. Connection creation, SQLite reads, validation, wrapper construction and scripted function calls are all part of the declared boundaries. No empty-loop or storage baseline was subtracted.

The allocation counter covers the process because the calls use asynchronous APIs. It does not measure retained heap, native SQLite allocations or a live provider's memory. Each replay is checked for the expected Succeeded state and exactly one receiver effect. That correctness gate is more important than a lower time obtained by accidentally omitting policy or persistence work.

These are deliberately local measurements. The direct route does not include HTTP, and neither chat route includes a remote model request. Their labels identify the exercised code paths, not the latency a user should expect from an AI application. The same raw report is shared across the three article appendices and is counted as one experiment, not three independent repetitions.

## Keep the result narrower than the architecture promise

This experiment strengthens one claim: these concrete adapters preserved the same tested domain contract. It does not mean every tool schema is safe, every tenant resolver is correct or every model instruction is harmless. A new capability, a new argument shape or a new lifecycle requires checks at its own boundary.

The SQLite ledger and receiver are separate local files. Their relationship deliberately contains the same uncertainty gap that a real external effect would create, but the fixture is not a payment integration or a distributed production database. Its state transitions are reusable reasoning, not a certification of another environment.

The useful next step for a team adopting this pattern is to replace one fixture boundary at a time. Introduce the real identity provider and verify context construction. Introduce the real domain capability and verify its invariants. Introduce the real destination and verify idempotency and reconciliation. Only then evaluate a live model's ability to select the right operation, keeping deterministic denials in place throughout.

The original article called the agent an API caller with a different source of arguments. The executed appendix now demonstrates that claim in a bounded setting: three callers, one domain method, equivalent effects and denials, and an explicit record of where the evidence ends.

The result also suggests how to design the next integration check. A new tool parameter should receive a negative case before it becomes a production capability. A new identity mechanism should be tested through the HTTP resolver and the tool closure separately. A different response formatter should be allowed to change presentation only when the domain outcome remains compatible. Those are concrete consequences of treating the agent as another API caller: the adapter can evolve, but its authority must continue to come from the application boundary.

Historical versions and verification boundaries

These pins existed before the context date. Availability and static API checks do not establish a successful build or a production-safe patch level. Execution claims are limited to explicitly identified companion checks.

| Component                             | Version | Available by | Source                                                                                                                  |
| ------------------------------------- | ------- | ------------ | ----------------------------------------------------------------------------------------------------------------------- |
| .NET SDK                              | 9.0.101 | 2024-12-03   | [Primary record](https://dotnetcli.blob.core.windows.net/dotnet/release-metadata/9.0/releases.json?ref=devinside.cloud) |
| .NET runtime                          | 9.0.0   | 2024-12-03   | [Primary record](https://dotnetcli.blob.core.windows.net/dotnet/release-metadata/9.0/releases.json?ref=devinside.cloud) |
| ASP.NET Core shared framework         | 9.0.0   | 2024-12-03   | [Primary record](https://dotnetcli.blob.core.windows.net/dotnet/release-metadata/9.0/releases.json?ref=devinside.cloud) |
| C#                                    | 12.0    | 2024-12-03   | [Primary record](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-12?ref=devinside.cloud)               |
| Microsoft.SemanticKernel              | 1.0.1   | 2023-12-18   | [Primary record](https://www.nuget.org/packages/Microsoft.SemanticKernel/1.0.1?ref=devinside.cloud)                     |
| Microsoft.SemanticKernel.Abstractions | 1.0.1   | 2023-12-18   | [Primary record](https://www.nuget.org/packages/Microsoft.SemanticKernel.Abstractions/1.0.1?ref=devinside.cloud)        |

[Download the complete laboratory and execution records](https://devinside.cloud/content/files/2026/11/M10-lab.zip).