Documentation
¶
Overview ¶
Package agentframework integrates Arcjet Guard with Microsoft Agent Framework for Go (github.com/microsoft/agent-framework-go).
GuardTool wraps a tool.FuncTool so every call is evaluated by Arcjet before it runs. GuardTools does the same for a list of tools, including MCP client tools. GuardMiddleware screens the user text of a run and guards the tools the run carries as options. Two paths add tools after the middleware has run and cannot be reached from it: a ContextProvider contributing agent.WithTool, and toolautocall.Config.AdditionalTools. Wrap those with GuardTool before contributing them.
The helpers fail closed by default: when policy cannot be evaluated the tool does not run and the model receives arcjet.NewGuardUnavailableResult. A denial is returned to the model as a successful tool result carrying arcjet.GuardDenialResult, never as an error, because the framework hides tool error text from the model and aborts a run after repeated errors.
Microsoft Agent Framework for Go is a public preview. This module tracks it and may change with it; its own major version stays at zero until the framework's API settles. The go.mod requirement names the version this module is built and tested against; Go treats it as a lower bound, so a build that selects a newer framework compiles against that instead.
Index ¶
- Constants
- func Args[In any](fn func(context.Context, In) ([]arcjet.GuardRuleInput, error)) func(context.Context, json.RawMessage) ([]arcjet.GuardRuleInput, error)
- func GuardMiddleware(client *arcjet.GuardClient, cfg MiddlewareConfig) (agent.Middleware, error)
- func GuardTool(client *arcjet.GuardClient, t tool.FuncTool, policy ToolPolicy) (tool.FuncTool, error)
- func GuardTools(client *arcjet.GuardClient, tools []tool.Tool, ...) ([]tool.Tool, error)
- func MustGuardTool(client *arcjet.GuardClient, t tool.FuncTool, policy ToolPolicy) tool.FuncTool
- type InboundPolicy
- type MiddlewareConfig
- type ToolPolicy
Constants ¶
const CorrelationIDStateKey = "arcjet.correlationId"
CorrelationIDStateKey is the agent.Session state key GuardMiddleware reads a correlation ID from when the context carries none. Set it once, with an ID the application already has:
session.Set(agentframework.CorrelationIDStateKey, conversationID)
Session state is serialized with the session, so the ID survives a session that is persisted and restored. Nothing here generates an ID.
Variables ¶
This section is empty.
Functions ¶
func Args ¶
func Args[In any](fn func(context.Context, In) ([]arcjet.GuardRuleInput, error)) func(context.Context, json.RawMessage) ([]arcjet.GuardRuleInput, error)
Args adapts a typed rule resolver to the raw-JSON form ToolPolicy.Rules takes. In is decoded the way functool decodes it: a struct input is the arguments object itself; any other input type arrives wrapped in a single-property object. The wrapped form must carry exactly one property; zero or more than one fails the call closed.
Decoding uses encoding/json, not the framework's own decoder, which is unexported. Schema defaults are therefore not applied: a field the tool's schema defaults arrives here as its zero value while the tool's handler sees the default. Key a policy on a value the caller supplies rather than one the schema fills in.
func GuardMiddleware ¶
func GuardMiddleware(client *arcjet.GuardClient, cfg MiddlewareConfig) (agent.Middleware, error)
GuardMiddleware returns an agent.Middleware for agent.Config.Middlewares. Agent-level middleware runs before history and context providers and before the provider-owned tool loop, so it sees only the new turn's messages, and it sees the tools the run carries as options.
Per run it: puts the session's stored correlation ID on the context when the context has none; screens the user text when Inbound is set, ending the run with one assistant update on a denial or an unavailable guard; and replaces each tool option with its guarded form when Tools is set.
A blocked inbound turn returns without calling next, so the agent's history and context providers do not see that turn or the refusal: they run inside the invoke this middleware wraps. Screening cannot both stop the provider being called and still run the work that happens beneath it.
Two sources of tools are out of its reach. A ContextProvider may append agent.WithTool from its Invoking hook, which runs inside the agent's own invoke, after this middleware; only a provider-level middleware would see those, and the bundled provider constructors do not expose that seam. toolautocall.Config.AdditionalTools are merged straight into the callable set without ever becoming an option, so no middleware at any layer sees them. Tools from either source run unguarded unless the application wraps them with GuardTool itself.
func GuardTool ¶
func GuardTool(client *arcjet.GuardClient, t tool.FuncTool, policy ToolPolicy) (tool.FuncTool, error)
GuardTool wraps t so every call is evaluated by Arcjet first. The result keeps t's name, description, schemas, and approval-required status.
If t also needs tool.ApprovalRequiredFunc, apply that first and pass its result to GuardTool, not the other way round: ApprovalRequiredFunc's wrapper does not forward the guarded marker a later guarding pass looks for, so wrapping an already-guarded tool with it would let that tool be guarded a second time.
func GuardTools ¶
func GuardTools(client *arcjet.GuardClient, tools []tool.Tool, policy func(tool.Tool) (ToolPolicy, bool)) ([]tool.Tool, error)
GuardTools wraps every tool in tools that implements tool.FuncTool and for which policy returns true. Tools that are not function tools, tools the policy declines, and tools already wrapped by GuardTool pass through unchanged, so the result can be handed to the agent or to mcptool.AddTool in place of the input.// The skip relies on a marker only a tool GuardTool produced carries. A wrapper that embeds tool.FuncTool, such as tool.ApprovalRequiredFunc, hides it: Go promotes only that interface's own methods. Apply GuardTool outermost, or the tool is guarded twice and one model call spends two rate-limit tokens.
MCP client tools from mcptool.ListTools and agent-as-tool values are function tools, so this covers them. Hosted tools execute at the provider and cannot be guarded; they pass through.
policy is where a caller switches on tool.Name() to pick a hardcoded Action for each tool.
A guarded tool keeps the wrapped tool's ReturnSchema, so re-exporting one through mcptool.AddTool publishes that schema as the MCP output schema while a denial returns arcjet.GuardDenialResult instead. An MCP client that validates structured output rejects such a denial. Give that tool a ToolPolicy whose OnDeny shapes the denial to the tool's own schema, or leave its output schema unset.
func MustGuardTool ¶
func MustGuardTool(client *arcjet.GuardClient, t tool.FuncTool, policy ToolPolicy) tool.FuncTool
MustGuardTool is like GuardTool but panics on a configuration error. It is intended for package-level initialization.
Types ¶
type InboundPolicy ¶
type InboundPolicy struct {
Action string
Rules func(ctx context.Context, text string) ([]arcjet.GuardRuleInput, error)
Actor func(ctx context.Context, messages []*message.Message) (string, error)
// Inputs are typed values exposed to remote policies configured for
// this label. Like Rules and Actor, an error counts as unevaluated
// policy.
Inputs func(ctx context.Context, text string) (map[string]arcjet.GuardPolicyInput, error)
// CorrelationID, when set, wins over the ID carried by the context and
// over the session's stored ID.
CorrelationID string
Metadata arcjet.Metadata
OnGuardError arcjet.OnGuardError
// OnDeny, when set, builds the single response update returned on a DENY
// decision. By default the update is assistant text carrying the
// arcjet.GuardDenialResult message. It is not called when the guard is
// unavailable; that path returns the arcjet.NewGuardUnavailableResult
// message.
OnDeny func(arcjet.GuardDecision) *agent.ResponseUpdate
}
InboundPolicy screens the user text of a run before the provider is called. Rules receives the concatenated text of the run's user-role messages. Actor receives the messages themselves.
type MiddlewareConfig ¶
type MiddlewareConfig struct {
// Tools picks a policy for each tool the run can see, whether it came
// from agent.Config.Tools or from a per-run agent.WithTool option. It is
// applied through GuardTools, so tools it declines, non-function tools,
// and tools already wrapped by GuardTool pass through unchanged, unless
// a wrapper such as tool.ApprovalRequiredFunc hides the marker; see
// GuardTools.
Tools func(tool.Tool) (ToolPolicy, bool)
// Inbound screens the run's user text before the provider is called.
Inbound *InboundPolicy
}
MiddlewareConfig configures GuardMiddleware. Both fields are optional; a config with neither set produces a middleware that only propagates the session correlation ID.
type ToolPolicy ¶
type ToolPolicy struct {
Action string
Actor func(ctx context.Context, args json.RawMessage) (string, error)
Inputs func(ctx context.Context, args json.RawMessage) (map[string]arcjet.GuardPolicyInput, error)
Rules func(ctx context.Context, args json.RawMessage) ([]arcjet.GuardRuleInput, error)
// CorrelationID, when set, wins over the ID carried by the context and
// over the session's stored ID.
CorrelationID string
Metadata arcjet.Metadata
OnGuardError arcjet.OnGuardError
// OnDeny, when set, replaces the arcjet.GuardDenialResult returned to the
// model on a DENY decision. It is not called when the guard is
// unavailable; that path always returns arcjet.NewGuardUnavailableResult.
OnDeny func(arcjet.GuardDecision) any
}
ToolPolicy describes how one tool is guarded. Action is required and must be a hardcoded label such as "order.looked-up". The three resolvers receive the tool call's raw JSON arguments; a resolver error counts as unevaluated policy and follows OnGuardError.