Skip to content

.NET: Proposal: Integrating Foundgine as a Semantic Execution Layer for AI Agents #7834

Description

@CristianBarragan

Foundgine + Microsoft AI: a semantic execution boundary for AI agents

What is Foundgine?

Foundgine separates what a caller wants from how the application executes it.

A caller submits structured intent. Foundgine resolves that intent against an application-defined semantic model, validates the requested capabilities, applies authorization constraints, builds an execution plan, and sends the plan to a provider such as SQL or InMemory.

The result is a reusable execution boundary that can sit underneath multiple interfaces:

                 Intent Sources

     API       GraphQL       Automation       AI Agent
       \          |              |             /
        \         |              |            /
         └────────┴──────────────┴───────────┘
                          │
                          ▼
                  ┌───────────────┐
                  │   Foundgine   │
                  │               │
                  │ Semantic      │
                  │ Authorization │
                  │ Planning      │
                  │ Execution     │
                  └───────┬───────┘
                          │
              ┌───────────┼───────────┐
              ▼           ▼           ▼
             SQL       InMemory     Providers

Architecture reference: cristianbarragan.github.io/Foundgine/docs-site/architecture

Why is this interesting for AI agents?

Modern applications increasingly have many callers: web and mobile apps, APIs, GraphQL clients, internal services, automation, and now AI agents.

An agent can determine what it wants to accomplish, but the application should remain authoritative over:

  • what capabilities exist
  • what the caller is allowed to do
  • which data can be accessed
  • which relationships can be traversed
  • how mutations are validated
  • how the operation is translated into execution
  • what actually gets committed

Without an explicit execution boundary, it's easy for an agent integration to collapse into a scattered pattern, where every tool reimplements its own authorization, validation, and data access:

Before:  AI Agent → Tool → Application code → ORM / SQL / API

After:   AI Agent → Structured Intent → Semantic Model → Capability Validation
                  → Authorization → Execution Plan → Provider → Result / Evidence

The question this raises: could Foundgine provide a semantic execution layer underneath an AI agent framework?

Foundgine wouldn't replace the agent framework — the framework stays responsible for reasoning, orchestration and interaction. Foundgine sits underneath it as an application-defined, strongly typed execution boundary.

This also opens up generated agent capabilities: instead of exposing arbitrary application methods or database operations to an agent, the application exposes capabilities derived from its semantic model (Application Semantic Model → Capability Graph → Agent-facing tools → Structured Intent → Foundgine).

The agent reasons about application capabilities; the application stays in control of authorization and execution. This matters most for mutations — e.g. TransferFunds, which needs tenant isolation, account ownership, account state, amount validation, authorization revalidation, idempotency, atomic execution, and audit/evidence, all enforced by the application rather than by the agent's own judgment.

Benchmark: Run5SameClient · Run5b results

The agent can request the operation. It should not get to redefine the security rules for the operation. That separation is the architectural idea behind Foundgine.

Questions for the Microsoft AI / Agent Framework / Semantic Kernel community

  • Where should this boundary sit relative to Agent Framework?
  • Should agent tools be generated from application capabilities?
  • How should authorization be represented between an agent and the application runtime?
  • Is a semantic capability graph useful to an agent?
  • Could this complement existing Microsoft AI patterns rather than introducing another agent abstraction?

Open source: github.com/CristianBarragan/Foundgine

How the boundary works in practice

Since posting the original proposal above, Foundgine has evolved into a fuller semantic-policy stack. It introduces a single, application-controlled semantic execution boundary between caller intent, domain meaning, authorization rules, and the operations actually performed on data, APIs, GraphQL, MCP tools, or backend systems — so agents can propose actions, but only the application decides what's allowed, safe, and executed.

The problem: as applications expose more functionality to callers, each tool/endpoint tends to grow its own validation, authorization, query logic, and business rules. Without a shared semantic layer, each surface (application code, GraphQL, JSON, AI-generated intent) invents its own rules for what entities/fields exist, which relationships can be traversed, which filters are valid, what's authorized, and how requests become database operations — producing duplicated semantics and inconsistent security boundaries. Foundgine centralizes that into one boundary: retrieval can discover candidates and evidence, but retrieval is not authorization. The application owns identity and policy; providers execute the already-authorized artifact.

Example: alias resolution

Two callers can ask for the same thing in different words:

  • Canonical: "show me overdue purchase orders from our top supplier in Texas"
  • Paraphrase: "show me the overdue buys from our top seller in Texas"

In the Supply Chain semantic contract, Buy/Buys are declared aliases of PurchaseOrder, and Seller is a declared alias of Supplier. Both sentences ground onto the same canonical semantic identities before authorization or planning ever runs.

# Layer What happens
1 Caller intent The caller sends either sentence — no SQL or provider instructions.
2 Intent representation The request becomes structured intent.
3 Semantic Model Exposes canonical meanings and aliases: PurchaseOrder ← Buy, Buys, Supplier ← Vendor, Seller.
4 Semantic Operation Graph Becomes application meaning: overdue purchase-order semantics, ranked "top supplier," Texas constraint.
5 Retrieval Relational, fuzzy/full-text, BM25/search, or graph strategies propose candidates and evidence — never grant authority.
6 Semantic Resolution buys → PurchaseOrder, seller → Supplier; aliases normalize to the same canonical identities.
7 Authorization Application policy runs against the resolved semantic graph and caller identity. Retrieval can't bypass this.
8 Plan Binding The authorized decision binds to a provider-independent execution plan.
9 ExecutionIR Carries the resolved plan and its authorization provenance across the boundary.
10 Provider Only now does a physical provider (e.g. PostgreSQL) receive the already-authorized artifact.
11 Execution The provider executes the constrained plan; it doesn't reinterpret caller vocabulary.
12 Evidence The result carries evidence of what was resolved and executed — evidence doesn't grant authority.

Invariant: alias matching changes vocabulary, not authority. "Buys" doesn't create a new capability, and "seller" doesn't create a second supplier meaning.

Tests: SupplyChainGroundingAliasTests.cs (advanced Supply Chain sample) · SemanticAliasSynonymGroundingTests.cs (core semantics).

Example: when more than one meaning is legal

Aliases collapse different words onto one meaning; sometimes ambiguity runs the other way — the same word legally matches two different meanings, and neither the graph nor a retrieval score can break the tie alone.

Take "active customers." Both are structurally valid readings:

  • a customer whose account is enabled (Customer.AccountEnabled)
  • a customer who placed a recent order (Customer.HasRecentOrder)

A fuzzy/BM25/vector retriever can legitimately return both, with close scores (0.91 vs. 0.89). Foundgine doesn't break the tie by picking the higher score — a higher score isn't evidence of intent, and authorization can't rescue a wrong guess; a request built from the wrong meaning is still a fully authorized request, just a perfectly authorized misunderstanding.

Stage What happens
Retrieval (fuzzy) Every plausible reading returns as a candidate with its own score and evidence. Retrieval only proposes.
Graph-constrained resolution Both AccountEnabled and HasRecentOrder form a legal path — a legal path proves an interpretation is possible, not that it's intended.
Grounding decision SemanticLexicalResolver.Ground compares the two paths' signatures. Neither dominates on confidence, so the outcome is GroundingOutcome.RequiresClarification: Committed stays null, both readings listed in CompetingInterpretations with their own steps, confidence, and evidence.
Caller chooses The competing meanings surface as a clarifying question — "Did you mean customers with an enabled account, or customers with a recent order?" — instead of silently executing a guess.
Same boundary as everyone else Once chosen, that interpretation goes through the same Authorization → Planning → Execution path as any other request.

Invariant: a legal semantic path is not proof of intent. The same fail-closed mechanism applies when a resource limit (token count, search budget, timeout) stops the search before proving a single meaning (GroundingOutcome.BudgetExceeded), and when no legal interpretation exists at all (GroundingOutcome.Unresolved). Neither case falls back to a best-effort guess.

Further reading: Lexical grounding (fuzzy retrieval, resolver complexity bounds, adversarial examples) and Grounding decisions (full GroundingDecision shape, "different evidence for the same meaning" vs. "different meanings," the complete active-customers walkthrough).

Why the boundary matters

The number of independent execution surfaces is a security and maintenance multiplier. A tool-per-capability design can give an agent dozens of places where authorization, tenant filtering, and query construction are implemented differently. Foundgine centralizes the semantic decision without making a transport or database the center of the architecture.

Deeper rationale: docs/WHY-FOUNDGINE.md, docs/APPLICATION-CATEGORIES.md, docs/ARCHITECTURE.md, docs/AUTHORIZATION.md, docs/SECURITY.md, docs/AI-AGENT.md

New: exposing MCP with open intent

Foundgine.Providers.Tools.MCP is Foundgine's actual Model Context Protocol adapter — it lets an MCP client reach the same semantic execution boundary described above, instead of getting its own bespoke tool-by-tool integration. (The standalone Foundgine.MCP NuGet package is legacy and deprecated — MCP support now ships inside Foundgine.Providers, installed as dotnet add package Foundgine.Providers.Tools.MCP.) It's wired up in the sample at samples/Foundgine.SupplyChain.Advanced/MCP.Foundgine, whose Program.cs registers it with builder.Services.AddFoundgineMcp(...) and .WithTools<FoundgineMcpTools>().

The tools it actually exposes

FoundgineMcpTools (src/Foundgine.Providers/Tools/MCP/FoundgineMcpTools.cs) registers three read-side MCP tools:

Tool What it does
foundgine_capabilities Discovery only — returns the semantic capability contract for the current caller. Per its own description: "Discovery is descriptive; authorization is re-evaluated during execution."
foundgine_query Open intent. Takes either a natural-language string (its doc example: "show customer orders") or a JSON read intent. Natural language is run through lexical grounding (SemanticLexicalReadIntentGrounder) before execution.
foundgine_query_semantic Structured intent only — same JSON shape, but for callers that already resolved the semantic model themselves; skips grounding.

A companion FoundgineMcpMutationTools class adds the write-side equivalents (foundgine_mutation_dry_run, foundgine_mutation_approve, and others) through Foundgine's separate mutation boundary.

A real MCP call

This is an actual tools/call request an MCP client would send to invoke foundgine_query. The intentJson shape (rootEntity, selections, filter, order, limit/offset/after) is the real schema JsonReadIntentAdapter parses — not a guess:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "foundgine_query",
    "arguments": {
      "intentJson": "{\"rootEntity\":\"Customer\",\"selections\":[{\"field\":\"Id\"},{\"field\":\"Name\"}],\"limit\":50}"
    }
  }
}

intentJson can also just be plain language ("show customer orders", per the tool's own description) — foundgine_query grounds that against the semantic contract first; foundgine_query_semantic skips grounding and expects the structured JSON directly.

// samples/Foundgine.SupplyChain.Advanced/MCP.Foundgine/Program.cs
app.UseWhen(
    ctx => ctx.Request.Path.StartsWithSegments("/mcp"),
    branch => branch.Use(async (ctx, next) =>
    {
        await OpenIntentDemoSecurity.PopulateSecurityContextAsync(ctx);
        await next();
    }));
app.MapMcp("/mcp");

— never from the MCP client's intentJson arguments.

What Foundgine does with it

// src/Foundgine.Providers/Tools/MCP/FoundgineMcpTools.cs (real, trimmed)
[McpServerTool(Name = "foundgine_query")]
public async Task<string> ExecuteQueryAsync(string intentJson, CancellationToken cancellationToken)
{
    var security = _securityContextProvider.RequireSecurityExecutionContext("MCP", "execution");
    var intent = ParseOpenIntent(intentJson, cancellationToken);   // JSON or NL -> ReadIntent
    intent = intent with { Security = security };                  // host-owned, not client-supplied
    var result = await _foundgine.ExecuteAsync(intent, _contextFactory(), cancellationToken);
    return JsonSerializer.Serialize(new { rows = result.Rows, pageInfo = result.PageInfo,
        evidence = result.Evidence, receipt = result.Receipt });
}

This calls the same IFoundgine.ExecuteAsync(ReadIntent, ...) entry point that Foundgine's in-process fluent query API compiles down to (src/Foundgine.Runtime/Query/QueryBuilder.cs):

// Typed C# — written by hand in application code
var result = await foundgine
    .Query<Customer>()
    .Select(c => new { c.Id, c.Name })
    .Take(50)
    .ExecuteAsync();

// Dynamic C# — the same shape FoundgineMcpTools builds internally from intentJson
var result = await foundgine
    .Query("Customer")
    .Where("Id", SemanticFilterOperator.Eq, customerId)
    .Take(50)
    .ExecuteAsync();

Typed C#, dynamic C#, and MCP's intentJson are three ways of authoring the same ReadIntent. Everything downstream of that — resolution → authorization → planning → execution — doesn't know or care which one produced it.

Full chain for an MCP call: MCP client → tools/call (foundgine_query, JSON-RPC) → FoundgineMcpToolsReadIntent (host attaches SecurityExecutionContext) → IFoundgine.ExecuteAsync → resolution → authorization → planning → execution → { rows, pageInfo, evidence, receipt } back to the client.

This is what keeps an MCP-exposed agent from becoming a tool-surface liability: instead of one authorization/validation implementation per exposed tool (the "50 tools, 50 security surfaces" problem), every MCP call resolves through the one semantic + authorization boundary that GraphQL, JSON, and other callers already share.

Package shape

Package Responsibility
Foundgine.Core Semantic model, metadata, intent, planning and provider-independent contracts
Foundgine.Runtime Application-facing orchestration, authorization and execution
Foundgine.Providers Storage, AI/model, MCP, AOT and other concrete integrations
Foundgine.Extensions Optional framework integrations such as Hot Chocolate GraphQL

The normal application starting point is Foundgine.Runtime + Foundgine.Providers.

Get started

The fastest path is the Supply Chain sample pair:

Conceptual path: docs/README.md or the documentation site.

Evidence

The repository contains controlled benchmarks and deterministic security tests, distinguishing measured tool calls, latency, RPS, and success/failure counts from estimated context metrics:

Benchmark results are workload-specific and should not be generalized beyond the published experiment.

Activity

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: .Net

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions