Documentation
¶
Overview ¶
Brokered system tools of the Agent Runtime MCP endpoint: the sandboxed Agent reaches the Model execution layer and the Memory store through the same MCP surface it uses for tenant tools, with the fenced attempt identity and capability grants injected by the runtime worker. Nothing here trusts agent-supplied identity: the AttemptContext (and its capability grants) come from the IdentityResolver the worker controls.
Package mcp implements the Model Context Protocol edge adapter: the JSON-RPC 2.0 message model and the Streamable HTTP transport boundary the Tool Gateway is exposed through (architecture §15.1: Agent ↔ Tool = MCP). Every input is treated as untrusted: strict JSON, size caps, bounded handling and no session state beyond the request (the pinned 2026-07-28 core has no session).
Per-attempt identity binding for shared Agent endpoints: a runtime worker opens one execution window per assignment and the brokered MCP tools resolve the fenced identity from the execution id the agent echoes in the X-Fenced-Execution header. Concurrent attempts through one endpoint stay correctly fenced, and calls outside any open window deny closed.
Index ¶
- Constants
- Variables
- func ExecutionID(ctx context.Context) string
- func ParseRequest(body []byte) (*Request, *Error)
- type AttemptContext
- type Broker
- type Error
- type ExecutionIdentityResolver
- type ExecutionRegistry
- type Handler
- type IdentityResolver
- type KernelMemoryBroker
- type MailboxAckInput
- type MailboxAckOutcome
- type MailboxBroker
- type MailboxMessage
- type MailboxReceiveInput
- type MailboxSendInput
- type MailboxSendOutcome
- type MemoryBroker
- type ModelBroker
- type Request
- type Response
- type Server
- type SpawnOutcome
- type SpawnRequest
- type StaticIdentity
- type ToolAdapter
- type ToolInvoker
- type WorkflowSpawner
Constants ¶
const ( SystemModelInvoke = "fenced.model.invoke" SystemMemoryPut = "fenced.memory.put" SystemMemorySearch = "fenced.memory.search" SystemTaskSpawn = "fenced.task.spawn" SystemIPCSend = "fenced.ipc.send" SystemIPCReceive = "fenced.ipc.receive" SystemIPCAck = "fenced.ipc.ack" )
System tool names exposed alongside the tenant tool registry.
const ExecutionHeader = "X-Fenced-Execution"
ExecutionHeader carries the agent's execution id (the Runtime Interface executionId, i.e. the attempt id) so a shared endpoint can scope brokered calls to one open execution window.
const ProtocolVersion = "2026-07-28"
ProtocolVersion is the pinned MCP protocol version this adapter implements (tech baseline §6.3). Negotiation always answers with this version.
Variables ¶
var ErrNoActiveWindow = errors.New("no active execution window")
ErrNoActiveWindow reports a header-less call with no open window.
var ErrNoExecutionContext = errors.New("no open execution window for the reported execution id")
ErrNoExecutionContext reports an MCP call whose execution id has no open window (default deny).
Functions ¶
func ExecutionID ¶
ExecutionID returns the execution id of the inbound call, empty when the agent did not report one.
func ParseRequest ¶
ParseRequest decodes exactly one strict JSON-RPC message. Batches, trailing documents, unknown fields, duplicate keys and non-2.0 versions are rejected; notifications carry no id.
Types ¶
type AttemptContext ¶
type AttemptContext struct {
TenantID string
TaskID uuid.UUID
RunID uuid.UUID
AttemptID uuid.UUID
FencingToken int64
AgentVersionRef string
// WorkflowID names the workflow run this attempt's task belongs to
// (empty for standalone tasks); the brokered spawn tool extends that
// run with dynamic steps.
WorkflowID uuid.UUID
// WorkflowVersion is the observed resource version of the workflow run
// at assignment time.
WorkflowVersion int64
// ParentStepName names the workflow step this attempt's task executes
// (empty for standalone tasks); spawned steps carry it as lineage.
ParentStepName string
// AllowedModels is the AgentVersion's declared model capability grant;
// the brokered model tool denies everything outside it (default deny).
AllowedModels []string
// AllowedMemoryNamespaces is the declared memory capability grant; the
// brokered memory tools deny writes and reads outside it.
AllowedMemoryNamespaces []string
// AllowedMemorySensitivities is independent of namespace access; absent
// grants default to internal only.
AllowedMemorySensitivities []string
// CanSpawnTasks is the explicit AgentVersion capability for dynamic
// workflow expansion. It defaults to false.
CanSpawnTasks bool
// AllowedChildAgents is the immutable allowlist of AgentVersion refs the
// calling version may spawn. An empty list denies every child.
AllowedChildAgents []string
}
AttemptContext is the fenced execution identity an MCP call is bound to. The adapter never trusts MCP-supplied identity: it comes from the resolver, which in production is injected by the runtime that owns the Attempt.
type Broker ¶
type Broker struct {
// contains filtered or unexported fields
}
Broker extends a ToolAdapter with the system tools: tools/list merges the tenant registry with the brokered declarations, tools/call dispatches the system names and delegates everything else to the tenant adapter.
func NewBroker ¶
func NewBroker(tools *ToolAdapter, models ModelBroker, memories MemoryBroker, spawner WorkflowSpawner, mailbox MailboxBroker, identity IdentityResolver) *Broker
NewBroker builds the brokered MCP surface. models, memory, spawner and mailbox may be nil: the corresponding tools are then not listed and their calls deny closed.
type Error ¶
type Error struct {
Code int `json:"code"`
Message string `json:"message"`
Data any `json:"data,omitempty"`
}
Error is a JSON-RPC 2.0 error object.
type ExecutionIdentityResolver ¶
type ExecutionIdentityResolver interface {
ResolveExecution(context.Context, string) (AttemptContext, error)
}
ExecutionIdentityResolver resolves the identity of one open execution window from the execution id the agent reports; shared Agent endpoints use it to keep concurrent attempts correctly fenced.
type ExecutionRegistry ¶
type ExecutionRegistry struct {
// contains filtered or unexported fields
}
ExecutionRegistry binds attempt identities to open execution windows. The most recently opened window also serves header-less calls, preserving the single-assignment runtime behavior.
func NewDevelopmentExecutionRegistry ¶
func NewDevelopmentExecutionRegistry() *ExecutionRegistry
NewDevelopmentExecutionRegistry explicitly enables the single-execution convenience fallback. It must only be used by loopback development setups.
func NewExecutionRegistry ¶
func NewExecutionRegistry() *ExecutionRegistry
NewExecutionRegistry builds a production registry. Header-less execution lookup is disabled: every shared-endpoint request must report its execution id and resolve through ResolveExecution.
func (*ExecutionRegistry) Open ¶
func (r *ExecutionRegistry) Open(identity AttemptContext) func()
Open registers the fenced identity for one assignment and returns the closer that must run on every exit path of that assignment.
func (*ExecutionRegistry) Resolve ¶
func (r *ExecutionRegistry) Resolve(context.Context) (AttemptContext, error)
Resolve implements IdentityResolver for header-less callers: the most recently opened window, denying when none is open.
func (*ExecutionRegistry) ResolveExecution ¶
func (r *ExecutionRegistry) ResolveExecution(_ context.Context, executionID string) (AttemptContext, error)
ResolveExecution implements the header-scoped lookup.
type Handler ¶
type Handler interface {
// ListTools returns the MCP tool declarations (result object).
ListTools(context.Context, json.RawMessage) (any, *Error)
// CallTool executes one tool invocation (result object).
CallTool(context.Context, json.RawMessage) (any, *Error)
}
Handler implements the MCP methods beyond the core lifecycle. Params are raw JSON for the method; implementations parse them strictly.
type IdentityResolver ¶
type IdentityResolver interface {
Resolve(context.Context) (AttemptContext, error)
}
IdentityResolver supplies the fenced identity for inbound MCP calls.
type KernelMemoryBroker ¶
KernelMemoryBroker adapts the kernel memory.Gateway to the broker surface, filling tenant and source provenance from the fenced identity.
func (KernelMemoryBroker) Put ¶
func (k KernelMemoryBroker) Put(ctx context.Context, identity AttemptContext, in memory.PutInput) (store.MemoryRecord, bool, error)
Put writes with tenant and provenance derived from the fenced identity.
func (KernelMemoryBroker) Search ¶
func (k KernelMemoryBroker) Search(ctx context.Context, identity AttemptContext, in memory.SearchInput) ([]store.MemoryRecord, error)
Search reads with the tenant scope of the fenced identity.
type MailboxAckInput ¶
type MailboxAckInput struct {
MessageIDs []string
}
MailboxAckInput acknowledges a batch the caller has drained.
type MailboxAckOutcome ¶
MailboxAckOutcome partitions the batch into receipts this call created and receipts an earlier call had already created, so a retry is distinguishable from a first attempt.
type MailboxBroker ¶
type MailboxBroker interface {
SendMessage(context.Context, AttemptContext, MailboxSendInput) (MailboxSendOutcome, error)
ReceiveMessages(context.Context, AttemptContext, MailboxReceiveInput) ([]MailboxMessage, error)
AcknowledgeMessages(context.Context, AttemptContext, MailboxAckInput) (MailboxAckOutcome, error)
}
MailboxBroker sends, drains and acknowledges durable agent mailboxes with the fenced identity of the calling attempt. As with MemoryBroker the identity is explicit: the tenant, the sender's version reference and the mailbox a drain reads are all derived from the fence, so no agent argument can widen them.
Delivery is pull-only. The platform cannot notify a running agent that mail has arrived — fenced.runtime.v1 exposes pull RPCs and its HeartbeatResponse carries no payload — so an agent discovers mail by draining its own mailbox. A drain may wait for a bounded time, which is a poll, not a push.
type MailboxMessage ¶
type MailboxMessage struct {
MessageID string
FromAgentVersionRef string
ToAgentVersionRef string
ToRunID string
Kind string
PayloadJSON json.RawMessage
AttachmentsJSON json.RawMessage
CorrelationID string
ReplyToMessageID string
SentAt time.Time
Deadline time.Time
}
MailboxMessage is one message as drained. Attachments travel as references: a body above the mailbox payload limit has to be referenced, not inlined.
type MailboxReceiveInput ¶
type MailboxSendInput ¶
type MailboxSendInput struct {
// ToAgentVersionRef and ToRunID name the receiving mailbox. The run id,
// not an attempt id: a mailbox follows the run so a message outlives the
// attempt that was draining it.
ToAgentVersionRef string
ToRunID string
Kind string
// PayloadJSON is the message body as a JSON document; empty means {}.
PayloadJSON json.RawMessage
// IdempotencyKey is chosen by the caller on purpose: retrying a send must
// resolve to the message the first attempt stored, while two deliberate
// sends of the same body must remain two messages.
IdempotencyKey string
CorrelationID string
ReplyToMessageID string
// Deadline is optional; the zero time means the message carries none.
Deadline time.Time
}
MailboxSendInput is one send with the sender taken from the fenced identity.
type MailboxSendOutcome ¶
type MemoryBroker ¶
type MemoryBroker interface {
Put(context.Context, AttemptContext, memory.PutInput) (store.MemoryRecord, bool, error)
Search(context.Context, AttemptContext, memory.SearchInput) ([]store.MemoryRecord, error)
}
MemoryBroker reads and writes the tenant memory store with fenced provenance. The identity of the calling attempt is explicit: brokers never trust tenant or provenance fields from the agent-supplied arguments.
type ModelBroker ¶
type ModelBroker interface {
InvokeStream(context.Context, kernelmodel.InvokeInput, func(string)) (kernelmodel.InvokeOutput, error)
}
ModelBroker executes fenced model invocations. The kernel model.Invoker and the runtime-adapter gRPC client both satisfy it.
type Request ¶
type Request struct {
JSONRPC string `json:"jsonrpc"`
ID json.RawMessage `json:"id,omitempty"`
Method string `json:"method"`
Params json.RawMessage `json:"params,omitempty"`
}
Request is the inbound JSON-RPC message. ID is absent for notifications.
func (*Request) IsNotification ¶
IsNotification reports whether the request carries no id.
type Response ¶
type Response struct {
JSONRPC string `json:"jsonrpc"`
ID json.RawMessage `json:"id,omitempty"`
Result any `json:"result,omitempty"`
Error *Error `json:"error,omitempty"`
}
Response is the outbound JSON-RPC message.
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server exposes a Handler over the MCP Streamable HTTP transport: POST / with a strict JSON-RPC 2.0 body, answered as application/json or text/event-stream depending on the client's Accept header. Notifications are acknowledged with 204. There is no session state.
func NewServer ¶
NewServer constructs the MCP boundary. name/version identify the server in the initialize handshake. Concurrent in-flight requests are capped (O8); NewServerWithConcurrencyLimit overrides the default.
type SpawnOutcome ¶
type SpawnOutcome struct {
Code string // "created" | "replayed" | guard denial code
Message string
StepName string
SpawnDepth int
}
SpawnOutcome is the authoritative result of one spawn call.
type SpawnRequest ¶
type SpawnRequest struct {
TenantID string
AttemptID uuid.UUID
FencingToken int64
WorkflowID uuid.UUID
ParentStepName string
Name string
Goal string
AgentVersionRef string
Spec json.RawMessage
MaxAttempts int
IdempotencyKey string
Arguments json.RawMessage
}
SpawnRequest is one dynamic-step spawn derived from the fenced identity: the workflow lineage (workflow id, version, parent step) comes from the worker-injected execution window, never from agent arguments.
type StaticIdentity ¶
type StaticIdentity struct {
Context AttemptContext
}
StaticIdentity is a fixed identity for deployments where the attempt context is known at construction (tests and explicitly wired dev setups).
func (StaticIdentity) Resolve ¶
func (s StaticIdentity) Resolve(context.Context) (AttemptContext, error)
type ToolAdapter ¶
type ToolAdapter struct {
// contains filtered or unexported fields
}
ToolAdapter exposes a Tool Gateway as an MCP tools server. Mapping rules (documented, v0.1):
- MCP tool name is the descriptor name; version and risk ride in the description, and calls resolve the latest granted version. tools/list shows exactly one entry per name (P1-02): the resolved default version, so the model never sees two same-named tools, while an explicit "name@version" reference still pins any granted older version.
- action defaults to the descriptor's first declared action ("invoke" when none), resource to the first resource pattern ("tool:<name>" when none).
- the idempotency key is derived deterministically from the attempt, the resolved tool version and the canonical arguments, so identical calls replay instead of repeating an external side effect while different versions of one tool never share replay semantics (at-least-once + idempotent consumer).
func NewToolAdapter ¶
func NewToolAdapter(invoker ToolInvoker, identity IdentityResolver) *ToolAdapter
func (*ToolAdapter) CallTool ¶
func (a *ToolAdapter) CallTool(ctx context.Context, params json.RawMessage) (any, *Error)
CallTool implements tools/call.
func (*ToolAdapter) ListTools ¶
func (a *ToolAdapter) ListTools(ctx context.Context, params json.RawMessage) (any, *Error)
ListTools implements tools/list.
type ToolInvoker ¶
type ToolInvoker interface {
InvokeTool(context.Context, tool.InvokeInput) (tool.InvokeResult, error)
ListTools(context.Context, string) ([]store.ToolDescriptor, error)
}
ToolInvoker is the fenced decision chain MCP calls flow through. The tool.Gateway satisfies it.
type WorkflowSpawner ¶
type WorkflowSpawner interface {
Spawn(context.Context, SpawnRequest) (SpawnOutcome, error)
}
WorkflowSpawner executes dynamic-step spawns through the kernel's guarded transaction. The runtime-adapter gRPC client satisfies it.