microsoft / microsoft/agent-framework
.NET: Proposal: Integrating Foundgine as a Semantic Execution Layer for AI Agents
@sphenry is already working on this.
Since Aug 27, 2026.
- Dominant language
- Python
- Stars
- 13.6k
- Forks
- 2.3k
- Avg merge
- 2d 45m
- Merged PRs (30d)
- 358
Description
# 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](https://cristianbarragan.github.io/Foundgine/docs-site/architecture/index.html)
## 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](https://github.com/CristianBarragan/Foundgine/tree/main/benchmarks/AgentEndToEnd/Run5SameClient) · [Run5b results](https://cristianbarragan.github.io/Foundgine/docs-site/agent-benchmark/run-5b/index.html)
**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](https://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`](https://github.com/CristianBarragan/Foundgine/blob/main/docs/WHY-FOUNDGINE.md), [`docs/APPLICATION-CATEGORIES.md`](https://github.com/CristianBarragan/Foundgine/blob/main/docs/APPLICATION-CATEGORIES.md), [`docs/ARCHITECTURE.md`](https://github.com/CristianBarragan/Foundgine/blob/main/docs/ARCHITECTURE.md), [`docs/AUTHORIZATION.md`](https://github.com/CristianBarragan/Foundgine/blob/main/docs/AUTHORIZATION.md), [`docs/SECURITY.md`](https://github.com/CristianBarragan/Foundgine/blob/main/docs/SECURITY.md), [`docs/AI-AGENT.md`](https://github.com/CristianBarragan/Foundgine/blob/main/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`](https://github.com/CristianBarragan/Foundgine/tree/main/samples/Foundgine.SupplyChain.Advanced/MCP.Foundgine), whose `Program.cs` registers it with `builder.Services.AddFoundgineMcp(...)` and `.WithTools()`.
### 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:
```json
{
"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.
```csharp
// 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
```csharp
// src/Foundgine.Providers/Tools/MCP/FoundgineMcpTools.cs (real, trimmed)
[McpServerTool(Name = "foundgine_query")]
public async Task 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`):
```csharp
// Typed C# — written by hand in application code
var result = await foundgine
.Query()
.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) → `FoundgineMcpTools` → `ReadIntent` (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:
- **Starter:** [`samples/Foundgine.SupplyChain`](https://github.com/CristianBarragan/Foundgine/tree/main/samples/Foundgine.SupplyChain) — the smallest realistic application boundary
- **Advanced:** [`samples/Foundgine.SupplyChain.Advanced`](https://github.com/CristianBarragan/Foundgine/tree/main/samples/Foundgine.SupplyChain.Advanced) — richer semantics, grounding, retrieval, authorization and adversarial testing (start at [`docs/00-Overview-And-Setup.md`](https://github.com/CristianBarragan/Foundgine/blob/main/docs/00-Overview-And-Setup.md), follow 01–05)
Conceptual path: [`docs/README.md`](https://github.com/CristianBarragan/Foundgine/blob/main/docs/README.md) or the [documentation site](https://cristianbarragan.github.io/Foundgine/docs-site/index.html).
## Evidence
The repository contains controlled benchmarks and deterministic security tests, distinguishing measured tool calls, latency, RPS, and success/failure counts from estimated context metrics:
- [Agent benchmark explorer](https://cristianbarragan.github.io/Foundgine/docs-site/agent-benchmark/run-5b/index.html)
- Supply Chain E2E
- Security PenTest
- [`benchmarks/AgentEndToEnd/README.md`](https://github.com/CristianBarragan/Foundgine/blob/main/benchmarks/AgentEndToEnd/README.md)
Benchmark results are workload-specific and should not be generalized beyond the published experiment.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.