Skip to content

Python: .NET: [Feature]: Add IDecisionClient (reference of dotnet/extensions#7764), DecisionLoopEvaluator, and a TypeSafe (Jev) provider package #8562

Description

Description

Summary. Add first-class support for decision-oriented inference in .NET Agent Framework: a provider-neutral IDecisionClient (an experimental reference implementation of the shape proposed for Microsoft.Extensions.AI in dotnet/extensions#7764), a DecisionLoopEvaluator that uses it to judge loop completion, and a Microsoft.Agents.AI.TypeSafe provider package for TypeSafe AI's Jev ("System One") model. An implementation with an ADR, tests, and a working sample is ready on a branch; this issue records the scope and the design questions for maintainers before the PR.

Relationship to existing issues. #8545 asks for the same integration but proposes waiting for the MEAI abstraction and adding nothing to MAF meanwhile. #8556 asks for Jev support in the Python SDK. This issue is the .NET counterpart of #8556 and a concrete, implementable variant of #8545: the upstream proposal is a day-old, untriaged community issue with no maintainer response, and MEAI 10.10.0 (the version this repository references) contains no decision types, so gating on it blocks everything, including calibration work and evaluation frameworks that want one shared contract.

What a decision model is, and why it is not an IChatClient

A decision model does not generate text. It takes a piece of state plus one or more bounded, typed questions and returns typed probabilistic answers in one call:

  • Binary: P(true) for a proposition (Jev: noul), no separate confidence;
  • Choice: the selected member of a caller-defined set plus a probability distribution over every member (Jev: choice);
  • Score: a position on a caller-defined ordered scale plus a distribution over its levels (Jev: score).

Prompting an IChatClient for {"answered": true} gives syntactic structure; it does not give the probability, the distribution, batching of heterogeneous questions against one state, or a closed output domain declared before inference. Several MAF decision points are exactly this shape: should the loop continue, is the task complete, which authorized tools or Skills are relevant, which participant acts next, should a result be escalated. Jev is fast and cheap relative to a generative judge (vendor: $0.042 per million input tokens, output free), which matters for control-plane decisions that run on every iteration.

Proposed design

  1. Contract in Microsoft.Agents.AI.Abstractions, [Experimental(MAAI001)], mirroring [API Proposal]: Add a provider-neutral abstraction for decision-oriented AI models dotnet/extensions#7764 name for name: IDecisionClient : IDisposable (GetResponseAsync(DecisionRequest, DecisionOptions?, ct), GetService), DecisionRequest (JsonElement State, IList<DecisionQuestion> Questions), DecisionOptions, DecisionQuestion with BinaryDecisionQuestion / ChoiceDecisionQuestion / ScoreDecisionQuestion, DecisionResponse (answers keyed by question id, ModelId, UsageDetails), DecisionAnswer with BinaryDecisionAnswer / ChoiceDecisionAnswer / ScoreDecisionAnswer, DecisionClientMetadata, and a GetResponseAsync<TState>(state, JsonTypeInfo<TState>, ...) extension. Two additions beyond the proposal, offered upstream too: a classified DecisionClientException (Authentication, InvalidRequest, RateLimited, Overloaded, ProviderUnavailable, InvalidResponse, Unknown; IsTransient), and range validation on answers so an out-of-range probability cannot exist as an object. The types are a staging ground: when MEAI ships an equivalent, they are deleted and consumers retarget the namespace. MAF does not evolve the shape independently.
  2. DecisionLoopEvaluator : LoopEvaluator in Microsoft.Agents.AI, next to AIJudgeLoopEvaluator. After each iteration it asks one binary question ("Has the agent fully addressed the user's original request?") against a minimal, source-generated text projection of the loop state and applies a configurable completion threshold (default 0.90). It continues with a deterministic feedback message rather than model prose. Failure policy: Throw (default), Continue, or DeferToNextEvaluator; provider failure is never interpreted as "incomplete"; cancellation always propagates; non-text request content is rejected unless a StateFactory is supplied. Because LoopAgent evaluates evaluators in order and stops only when all return Stop, [DecisionLoopEvaluator, AIJudgeLoopEvaluator] is a cheap-then-strong cascade with no new abstraction: the generative judge runs only when the decision model believes the work is complete.
  3. Microsoft.Agents.AI.TypeSafe: TypeSafeDecisionClient over POST https://api.typesafe.ai/v1/systemone (or OpenRouter's relay), strict parsing, HTTP failure classification, no silent retries, API key never in exception messages, feature-usage index 75. Follows the existing provider-package precedent (.OpenAI, .Foundry, .CopilotStudio).
  4. Not in scope: tool or Skill shortlisting, group-chat routing, workflow branching, model routing, or any authorization decision. Those reuse existing seams (AIContextProvider, AgentSkillsProvider, GroupChatManager) later; a decision model may shortlist among already-authorized candidates but never authorize or bypass approval.

Status

Implemented on a fork branch (joslat/adr-0042-decision-loop-evaluator) with ADR-0042, PublicAPI baselines for all target frameworks, 45 evaluator test cases, 47 provider test cases, the feature-registry validation, and a Harness_Step06_DecisionLoop sample. Verified live against Jev jev-1.13.0 with an OpenAI-compatible primary model: the cascade finished a three-part task in four iterations with a single generative judge call; a mixed batch returned a binary probability, a choice distribution, and a weighted score in one call.

Questions for maintainers

  1. Is an experimental reference copy of the MEAI proposal acceptable in Microsoft.Agents.AI.Abstractions (deleted when MEAI ships), or should it live in a satellite package such as Microsoft.Agents.AI.Decisions?
  2. Is Microsoft.Agents.AI.TypeSafe the right home for the provider, or should it be provider-owned from the start?
  3. Should DecisionClientException and answer validation be proposed to [API Proposal]: Add a provider-neutral abstraction for decision-oriented AI models dotnet/extensions#7764 as part of the contract?
  4. .NET: [Feature]: Integrate Microsoft.Extensions.AI decision-model inference into agent decision points #8545 suggests that a decision evaluator should default to continue when the decision is unavailable. The branch keeps throw as the general default (matching AIJudgeLoopEvaluator; a rejected key is not evidence of incompleteness) and adds a separate TransientFailureBehavior for rate limits, overload, and outages, which is what .NET: [Feature]: Integrate Microsoft.Extensions.AI decision-model inference into agent decision points #8545's concern is really about. It also adopts .NET: [Feature]: Integrate Microsoft.Extensions.AI decision-model inference into agent decision points #8545's observability point: an optional ILoggerFactory yields one debug line per decision (iteration, probability, threshold, outcome) and a warning when a failure policy is applied, never the state. Is that split acceptable?

Code Sample

using IDecisionClient jev = new TypeSafeDecisionClient(apiKey, new() { ModelId = "jev-1.13.0" });

// Cheap decision model first; the generative judge verifies only when the decision model believes the work is done.
AIAgent loop = new LoopAgent(
    innerAgent,
    [
        new DecisionLoopEvaluator(jev, new()
        {
            CompletionThreshold = 0.90,
            FailureBehavior = DecisionLoopFailureBehavior.DeferToNextEvaluator,
        }),
        new AIJudgeLoopEvaluator(strongJudgeChatClient),
    ],
    new LoopAgentOptions { MaxIterations = 6 });

// Direct use: three primitives, one call, one state.
DecisionResponse response = await jev.GetResponseAsync(new DecisionRequest(state,
[
    new BinaryDecisionQuestion("refund_requested", "Does the customer request a refund?"),
    new ChoiceDecisionQuestion("request_type", "What is the main request?", [new("refund"), new("rebooking"), new("information")]),
    new ScoreDecisionQuestion("frustration", "How frustrated is the customer?", [new("calm"), new("concerned"), new("angry")]),
]));

var refund = (BinaryDecisionAnswer)response.Answers["refund_requested"];   // refund.TrueProbability

Language/SDK

.NET

Activity

  1. added
    .NETUsage: [Issues, PRs], Target: .Net
    pythonUsage: [Issues, PRs], Target: Python
    triageUsage: [Issues], Target: All issues that still need to be triaged
    on Sep 20, 2026
  2. changed the title [-].NET: [Feature]: Add IDecisionClient (reference of dotnet/extensions#7764), DecisionLoopEvaluator, and a TypeSafe (Jev) provider package[/-] [+]Python: .NET: [Feature]: Add IDecisionClient (reference of dotnet/extensions#7764), DecisionLoopEvaluator, and a TypeSafe (Jev) provider package[/+] on Sep 20, 2026
  3. removed
    triageUsage: [Issues], Target: All issues that still need to be triaged
    on Sep 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

.NETUsage: [Issues, PRs], Target: .NetagentsUsage: [Issues, PRs], Target: Single agentpythonUsage: [Issues, PRs], Target: Python

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions