This document explains how to use each major feature, the order of operations, what data is passed where, what is persisted, and which exceptions appear (and how agc-api maps them to HTTP).
For Maven naming and adoption context, see LIBRARY.md. For config reference and modules, see ARCHITECTURE.md. For decision types and policy, see GOVERNANCE.md. For audit-mode behavior when the DB misbehaves, see FAILURE_MODES.md.
| Path | When to use | Your code calls |
|---|---|---|
| A. REST + orchestrator | Browser, Postman, or a service that only talks HTTP | POST /agent/execute (requires agc-api). The controller calls AgentOrchestrator.runUserTurn(...), which calls the LLM stub/client, then ToolInvocationGateway.invoke(...). |
| B. Gateway only | You already know the tool name (e.g. Spring AI callback, batch job, MCP bridge) | Inject ToolInvocationGateway and call invoke(ToolInvocationContext) with a fully built context (including toolName and optional arguments). |
Both paths end in DefaultToolInvocationGateway.invoke — the same governance pipeline, audit hooks, and McpToolExecutor.
Every governed invocation is described by a ToolInvocationContext (immutable record). Fields and how they flow:
| Field | Required | Meaning | Passed to |
|---|---|---|---|
traceId |
Yes | Correlates all audit rows for one logical agent run | Gateway validation, audit rows, logs/MDC |
correlationId |
Yes* | Sub-span id; defaults to traceId if blank |
Same as above |
tenantId |
No | Multi-tenant string; empty if null | Policy/guardrails if you use them; audit payload context |
principalId |
No | Acting user/service id | Policy (role resolution is separate); audit |
roles |
No | Set of role strings (lowercasing handled in policy layer as configured) | PolicyEvaluator / GuardrailEvaluator |
toolName |
Yes | Tool id, e.g. search or search:v2 |
Registry, pipeline, executor, audit |
arguments |
No | Map<String,Object> for the executor (JSON-friendly values) |
Only your McpToolExecutor implementation (gateway does not interpret keys) |
deadline |
No | If set and passed, gateway can deny with CONTEXT_DEADLINE |
DefaultGovernancePipeline |
*REST /agent/execute requires both traceId and correlationId in the JSON body (controller validates before the orchestrator runs).
Tool name rules: must match name or name:vN (see ToolNames.isValid). Invalid names → InvalidGovernanceContextException at gateway entry.
Version suffix: search:v2 is normalized to logical name search for registry allowlist matching; the full string is still passed to McpToolExecutor and audit.
- Controller checks
traceId,correlationId; ifagc.governance.mode=PRODUCTION, requires an authenticated Spring Security principal (401if missing). - Principal/roles:
TrustBoundaryPrincipalResolversetsprincipalIdandrolesfrom the security context when authenticated; otherwise uses request body values (development / untrusted). AgentOrchestrator.runUserTurn- Builds a context with
toolNameempty for the LLM step. - Persists audit:
REQUEST_RECEIVED(payload summary = user message). - Calls
LlmClient.complete(message, ctx)→ returns plannedtoolNamestring. - Persists audit:
LLM_INVOCATION(summary includesplannedTool=...). - Builds
ToolInvocationContextwith thattoolNameandargumentsempty (orchestrator does not parse tool args from the message; you extend this in your app if needed). - Calls
toolInvocationGateway.invoke(toolCtx)→ section 3.2 below.
- Builds a context with
Orchestrator-side persistence failures: auditRecorder.record can throw AuditPersistenceException. The REST controller does not map that to a dedicated status; it typically becomes 500 via the generic Exception handler. For production, consider wrapping the orchestrator call or using resilient audit configuration.
LLM failures: LlmClient throws LlmException → REST 500 (generic handler).
Inside DefaultToolInvocationGateway.invoke (same for A and B after the orchestrator hands off):
GatewayInvocationConstraints.validate(ctx)— missing/invalidtraceId,correlationId, ortoolName→InvalidGovernanceContextException.- MDC + metrics —
traceId,correlationId,principalId,toolNameput on MDC for logging. agc.enabled— iffalse, builds deny decisionAGC_DISABLED, persists governed audit (per audit mode), throwsToolInvocationDeniedException.- Tool registry — if
agc.tools.allowedis non-empty and logical name not allowed → denyTOOL_NOT_REGISTERED, audit,ToolInvocationDeniedException. - Governance pipeline —
DefaultGovernancePipeline.evaluatePreInvocation(ctx):- Deadline past → deny
CONTEXT_DEADLINE. - Policy then guardrails (see GOVERNANCE.md).
- Uncaught runtime inside evaluators → deny
GOVERNANCE_EVALUATION_FAILED(fail closed).
- Deadline past → deny
- Persist
GOVERNANCE_DECISIONaudit row (ALLOW/DENY/WARN) — behavior depends onagc.audit.mode(STRICT / ASYNC / BEST_EFFORT); on STRICT failure →GovernedPathAuditException. - If decision is DENY →
ToolInvocationDeniedException(no executor). - If ALLOW or WARN (still execute): Persist
TOOL_INVOCATION_REQUEST. McpToolExecutor.execute(ctx)— must run only under gateway scope (GatewayContextHolder); your implementation readsctx.arguments(), calls backends, returnsToolInvocationResult(success, outcomeSummary, duration).- Throws
ToolExecutionException→ gateway recordsSYSTEM_ERRORaudit (behavior depends on STRICT secondary audit — see FAILURE_MODES.md), then propagatesToolExecutionException.
- Throws
- On success: Persist
TOOL_INVOCATION_RESPONSEwithoutcomeSummary. - Return
ToolInvocationResultto caller. - Finally: clear MDC, exit gateway scope.
sequenceDiagram
participant C as Caller
participant O as AgentOrchestrator
participant L as LlmClient
participant G as ToolInvocationGateway
participant R as ToolRegistry
participant P as Policy+Guardrails
participant A as AuditRecorder
participant E as McpToolExecutor
C->>O: runUserTurn(...)
O->>A: REQUEST_RECEIVED
O->>L: complete(message, ctx)
L-->>O: toolName
O->>A: LLM_INVOCATION
O->>G: invoke(toolCtx)
G->>G: validate context
G->>R: isAllowed(toolName)
G->>P: evaluatePreInvocation
G->>A: GOVERNANCE_DECISION
alt DENY
G-->>C: ToolInvocationDeniedException
else ALLOW/WARN
G->>A: TOOL_INVOCATION_REQUEST
G->>E: execute(ctx)
E-->>G: ToolInvocationResult
G->>A: TOOL_INVOCATION_RESPONSE
G-->>C: ToolInvocationResult
end
- Orchestrator:
REQUEST_RECEIVED,LLM_INVOCATION(synchronousAuditRecorder.record). - Gateway:
GOVERNANCE_DECISION,TOOL_INVOCATION_REQUEST,TOOL_INVOCATION_RESPONSE, and optionallySYSTEM_ERRORafter tool failure.
JpaAuditRecorder assigns a monotonic sequenceNum per traceId via TraceSequenceAllocator so events are ordered for GET /audit/{traceId}.
- REST:
GET /audit/{traceId}returnsAuditEventEntitylist ordered bysequenceNumascending. - Fields of interest:
eventType,toolName,reasonCode,decisionType,payloadSummary,matchedRuleIds,createdAt.
agc.audit.max-payload-charsbounds stored summary text.- Optional hashing:
agc.audit.hash-payload(see properties class).
true(default): normal gateway behavior.false: every invocation denies with reasonAGC_DISABLEDafter writing the governance decision audit (per audit mode).
- Empty list: no extra allowlist; policy/guardrails still run.
- Non-empty: tool’s logical name must appear (after
:vNstripping). OtherwiseTOOL_NOT_REGISTEREDbefore policy.
DEVELOPMENT: REST may use bodyprincipalId/roleswhen not authenticated.PRODUCTION:POST /agent/executerequires a non-anonymous Spring Security authentication; principal and roles come from the security context.
- Maps role → allowed tool names (and
*for admin-style allow-all for that role). - Evaluated in
RoleToolPolicyEvaluator. Typical deny codes:POLICY_NO_ROLES,POLICY_TOOL_FORBIDDEN.
- Ordered rules: match
toolName, action DENY or WARN. - DENY stops execution; reason codes like
GUARDRAIL_<ruleId>. - WARN still allows execution but decision is recorded (see GOVERNANCE.md).
| Mode | Governed-path write fails | Effect |
|---|---|---|
| STRICT | Before or after tool (depending on phase) | GovernedPathAuditException — invocation aborts (fail closed). |
| BEST_EFFORT | Any | Logged warning; execution may continue (not for regulated production). |
| ASYNC | Enqueue fails | GovernedPathAuditException; if enqueue succeeds, failures logged async. |
Secondary audit after ToolExecutionException: controlled by agc.audit.strict-secondary-audit in STRICT mode (see FAILURE_MODES.md).
- Provide a
@Beanof typeMcpToolExecutor(demo:DemoMcpToolExecutor). With@Primary, it replaces the default echo executor. - Contract:
execute(ToolInvocationContext ctx)returnsToolInvocationResult. Usectx.arguments()for structured inputs from your own API layer. - Security: implement
GatewayContextHolder.isGatewayCall()guard (as the default echo executor does) so the bean is not invoked directly by accident. - Note:
McpToolExecutorlives incom.framework.agent.mcp.internal; application modules that implement it may need ArchUnit exemptions (the demo package is exempt in this repo).
- Replace
LlmClientwith@Primarybean: receiveuserMessage+ context (trace/tenant/principal/roles), return tool name string only in the stock orchestrator. - If you need arguments in
ToolInvocationContext, extend your flow: e.g. parse JSON from the model, then callToolInvocationGateway.invokeyourself with a populatedargumentsmap (bypassing the stock orchestrator for that turn).
- Add dependency
agc-api. POST /agent/executebody:traceId,correlationId, optionaltenantId,principalId,roles,message.GET /audit/{traceId}: read-back for support and compliance debugging.
| Exception | When | Typical HTTP (/agent/execute) |
Caller action |
|---|---|---|---|
ToolInvocationDeniedException |
Registry, kill switch, policy, guardrails, deadline | 403 Problem Details: decision, reasonCode, matchedRuleIds, traceId |
Do not run tool; show reason; same traceId for audit lookup |
InvalidGovernanceContextException |
Bad/missing context at controller or gateway validation | 400 | Fix request or build valid ToolInvocationContext |
GovernedPathAuditException |
Required audit write failed in STRICT (or strict secondary) | 503 | Retry or fix DB; see FAILURE_MODES.md |
ToolExecutionException |
Your McpToolExecutor failed the tool |
500 (generic handler) | Fix executor/backends; check SYSTEM_ERROR audit row if written |
LlmException |
LlmClient.complete failed |
500 | Fix LLM integration |
AuditPersistenceException |
Orchestrator audit record failed | 500 (generic) | Fix DB / transaction |
Authentication / PRODUCTION |
No authenticated user when mode is PRODUCTION | 401 | Authenticate before calling execute |
Direct gateway use (no REST): catch the same checked/runtime types in your service layer; map to gRPC/JSON-RPC errors as appropriate.
- Same trace across services: propagate
traceId(and optionallycorrelationId) in HTTP headers or message metadata so all audit lines land in one timeline. - Tool arguments: only
ToolInvocationContext.argumentsreachesMcpToolExecutor. The default orchestrator passes an empty map — populate arguments when you call the gateway (custom controller, MCP bridge, etc.). - User message → tool: default path uses LLM output string as
toolNameonly. For rich args, use structured model output in yourLlmClientimplementation and then callinvokewith a full context yourself.
- Dependencies:
agc-spring-boot-starter(+agc-apiif using REST). - Datasource + Flyway + JPA (as in QUICKSTART.md).
- Configure
agc.tools.allowed,agc.policy.roles,agc.guardrails.rules,agc.audit.mode. - Implement
McpToolExecutorfor real backends. - Replace or extend
LlmClientif the stock stub is insufficient. - Use
PRODUCTION+ Spring Security for real trust boundaries on REST. - Query
GET /audit/{traceId}after calls to verify sequence and denials.
- Gateway:
DefaultToolInvocationGateway,GatewayInvocationConstraints,DefaultGovernancePipeline - REST:
AgentExecuteController,TrustBoundaryPrincipalResolver - Orchestrator:
AgentOrchestrator - Audit persistence:
JpaAuditRecorder,AuditEventEntity