Anvil Cloud / Runtime
Anvil Agents
Anvil Agents are portable, inspectable, capability-bound runtime units.
They can operate an Anvil workspace, power a Cell workflow, or become the primary interface of an Agent Cell. Every agent compiles to a provider-neutral manifest before it is run or deployed, so the contract can be reviewed, tested locally, and handed to runtime adapters without leaking provider infrastructure into Cell code.
This is an MVP foundation, not a hosted agent platform. Useful. Refreshingly unglamorous. Keep the contract small enough to inspect.
The bigger runtime direction is Agent Sandboxes: isolated, sessionful, inspectable workspaces for agents that need to run tools, operate a repo, wait for approval, and resume with state. The first AWS Lambda MicroVM-backed provider is implemented behind the adapter boundary; the manifest contract starts here.
Contract boundary
Agent Definition
-> Provider-neutral Agent Manifest
-> Anvil Runtime Interfaces
-> Inference Provider
-> Adapter-specific Runtime Implementation
Core Anvil Agent concepts are provider-neutral:
- model provider id and model id
- messages, content parts, tool calls, and tool results
- structured output shape
- token usage
- approval gates
- capability checks
- execution metadata
- provider registry
- manifest validation
Provider SDKs, credentials, deployment resources, infrastructure templates, and provider response shapes stay outside the core manifest.
Define an agent
Agents are normal TypeScript definitions:
import { defineAgent } from "@anvil-cloud/runtime";
export default defineAgent({
name: "support-assistant",
description: "A support assistant for triaging tickets.",
instructions: "./instructions.md",
model: {
provider: "local",
model: "stub",
},
capabilities: {
cells: ["read"],
database: ["supportTickets.read", "supportTickets.update"],
network: { allow: ["github.com", "api.statuspage.io"] },
filesystem: "none",
secrets: "brokered",
git: ["clone"],
},
credentialBroker: {
credentials: [
{
credential: "GITHUB_TOKEN",
domains: ["github.com"],
inject: {
kind: "header",
name: "authorization",
scheme: "bearer",
},
},
],
},
approvals: {
requiredFor: ["supportTickets.bulkUpdate", "email.sendExternal"],
},
runtime: {
durability: "optional",
sandbox: "required",
humanApproval: "required",
},
metadata: {
owner: "support",
risk: "medium",
},
});
Least privilege is the default:
- omitted capabilities are unavailable
filesystemdefaults tononesecretsdefaults tononenetworkdefaults torestricted
Broker credentials to sandboxes
Brokered credentials let an agent reach a private external resource without placing the raw secret in sandbox environment variables.
The Cell declares the secret name:
capabilities: {
secrets: ["GITHUB_TOKEN"],
}
The mounted agent declares the broker policy:
capabilities: {
network: { allow: ["github.com"] },
secrets: "brokered",
},
credentialBroker: {
credentials: [
{
credential: "GITHUB_TOKEN",
domains: ["github.com"],
inject: {
kind: "header",
name: "authorization",
scheme: "bearer",
},
},
],
},
runtime: {
sandbox: "required",
}
The generated manifest records credential names, allowed domains, and
header/query injection targets. It does not contain secret values. Validation
fails if the Cell does not declare the credential, if the agent does not use
secrets: "brokered", if the agent is not sandbox-required, or if the target
domain is outside capabilities.network.allow.
Mount an agent in a Cell
Cell Agents are mounted inside the Cell definition:
import { app, channel, defineAgent, endpoint } from "@anvil-cloud/runtime";
const support = defineAgent({
name: "support",
instructions: "./agents/support/instructions.md",
model: { provider: "local", model: "stub" },
capabilities: {
cells: ["read"],
database: ["supportTickets.read"],
filesystem: "none",
secrets: "none",
},
subagents: {
triage: defineAgent({
name: "support-triage",
purpose: "Classify incoming support requests.",
model: { provider: "local", model: "stub" },
capabilities: {
cells: ["read"],
database: ["supportTickets.read"],
filesystem: "none",
secrets: "none",
},
}),
},
});
export default app({
agents: {
support,
},
endpoints: {
chat: endpoint({
method: "POST",
path: "/api/chat",
auth: "required",
agent: "support",
handler: async () => ({ ok: true }),
}),
},
channels: {
supportSlack: channel({
provider: "slack",
agent: "support",
sessionKey: "sender-thread",
events: ["app_mention", "message"],
}),
},
});
Builder validation catches endpoint, workflow, or channel references to missing
mounted agents. The generated Cell manifest includes mounted agent manifests
under agents, each agent manifest includes its explicit subagents tree, and
channel bindings under channels.
Subagents are shallow during alpha. A parent can declare subagents, but a subagent cannot declare its own nested subagents. Guard validation fails if a subagent declares capabilities outside the parent's capability set.
Add evals
Evals are colocated with the agent definition:
import { defineAgent, defineAgentEvalSuite } from "@anvil-cloud/runtime";
const support = defineAgent({
name: "support",
model: { provider: "local", model: "stub" },
evals: defineAgentEvalSuite({
scenarios: [
{
name: "answers support review",
input: "Review this Cell.",
expect: {
responseIncludes: "Review this Cell.",
toolCalls: { count: 0 },
capabilities: {
notUsed: ["network.api.statuspage.io"],
},
},
},
],
}),
});
Run them locally or in CI:
anvil-cloud eval --json
anvil-cloud eval --write-baseline --json
The JSON output includes scenario pass/fail, assertion results, tool calls, approval requests, capability usage, and baseline diffs. Failed assertions or baseline drift exit non-zero.
Project Agents, Cell Agents, and Agent Cells
| Shape | Use it for |
|---|---|
| Project Agent | Workspace operations: review a Cell manifest, inspect capability changes, draft release notes, prepare preview deployment, explain blast radius, or check policy. |
| Cell Agent | Runtime behavior inside a Cell: endpoint, job, workflow, mutation, or internal app behavior. |
| Agent Cell | A Cell whose primary product surface is an agent-powered experience. The Cell remains the boundary for auth, capabilities, approvals, validation, local runtime, and deployment adapters. |
Project-agent discovery currently reports instruction files under
agents/**/instructions.md. Mounted Cell Agents are wired through the current
Cell build and local runtime path.
Contract mode
Contract mode validates and compiles without calling a model provider or executing tools:
anvil cloud agents validate
anvil cloud agents manifest --json
anvil cloud agents discover --json
anvil cloud agents guardian --json
anvil cloud agents sandboxes --json
This checks mounted agents, model config, instructions files, capabilities,
approval rules, endpoint/workflow references, generated provider-neutral
manifests, project instruction files, and deterministic Guardian review
findings. Guardian uses the same trust report as anvil-cloud review; it does
not call a model provider.
Stub mode
Stub mode uses the deterministic local provider:
model: {
provider: "local",
model: "stub",
}
The local stub provider implements the generic inference provider interface, returns deterministic responses, and never calls an external API. Use it for tests and quick local development.
Invoke a mounted agent:
anvil cloud agents invoke support --input "Review this Cell" --json
Anvil Local also exposes:
GET /_anvil/agents
POST /_anvil/agents/:name
GET /_anvil/approvals
GET /_anvil/approvals/audit
POST /_anvil/approvals/:id/approve
POST /_anvil/approvals/:id/reject
POST /_anvil/agents/:name/sessions
POST /_anvil/agents/sessions/:sessionId/messages
GET /_anvil/agents/sessions/:sessionId/stream?after=:token
POST /_anvil/channels/simulate
Approval-gated tool actions persist pending decisions to
.anvil/local/approvals.json. Inspect and decide them from the CLI:
anvil-cloud approvals list --status pending --json
anvil-cloud approvals approve <approvalId> --by reviewer --reason "Checked"
anvil-cloud approvals reject <approvalId> --by reviewer --reason "Not safe"
anvil-cloud approvals audit --json
Lens shows the same pending queue and audit history in its Approvals tab. Local
runtimes can also set ANVIL_APPROVAL_WEBHOOK_URL to receive
approval.requested webhook events; webhook delivery failures are recorded in
the approval audit log and do not execute the gated action.
POST /_anvil/agents/:name accepts { "input": "..." } and returns the
provider-neutral AgentRuntime result. Session routes persist local agent event
history in .anvil/local/agent-sessions.json. Message sends return ordered
events and a continuation token; the stream route replays events after that
token as text/event-stream.
Channel simulation lets local dev and tests exercise Slack/GitHub/Discord-style inbound messages without real workspace credentials:
anvil cloud channels simulate \
--channel supportSlack \
--sender U123 \
--thread T456 \
--input "Can you review this issue?" \
--json
The runtime maps the normalized channel message to the mounted agent session
using the channel's sessionKey. Agent code stays channel-agnostic; provider
credentials and webhook verification remain platform-side.
Provider mode
Provider mode still runs through Anvil Local, but resolves a real inference provider from the registry:
model: {
provider: "aws-bedrock",
model: "anthropic.claude-3-5-sonnet-20241022-v2:0",
region: "eu-west-2",
}
Contracts, approval gates, and capability checks remain local. The provider-neutral request is passed to the registered provider, which maps the request and response at the provider boundary.
Inference providers
Providers implement:
type AgentInferenceProvider = {
id: string;
invoke(request: AgentInferenceRequest): Promise<AgentInferenceResponse>;
};
Runtime code resolves providers by id through AgentProviderRegistry.
import {
AgentProviderRegistry,
AgentRuntime,
LocalStubInferenceProvider,
} from "@anvil-cloud/runtime";
import { BedrockInferenceProvider } from "@anvil-cloud/aws";
const registry = new AgentProviderRegistry();
registry.register(new LocalStubInferenceProvider({ echoInput: true }));
registry.register(new BedrockInferenceProvider({ region: "eu-west-2" }));
const runtime = new AgentRuntime({ providers: registry });
AWS Bedrock provider
@anvil-cloud/aws includes the first AWS inference provider. It implements the generic Anvil inference provider interface, uses the standard AWS credential chain, maps Anvil messages to Bedrock Runtime requests, maps responses back to Anvil messages and token usage, and keeps raw provider responses out of provider-neutral manifests.
AWS compatibility reporting can say that aws-bedrock inference is supported while still warning about unimplemented adapter requirements such as durable agent execution, hosted memory, production approval UI, or sandbox execution.
Sandbox target
runtime.sandbox: "required" is a real manifest requirement, not decoration.
AWS compatibility treats it as supported when a Lambda MicroVM sandbox image is
configured through ANVIL_AWS_AGENT_SANDBOX_IMAGE.
That means normal Cell traffic still runs through the Lambda runtime, while agent-heavy work can move into an isolated session workspace:
Cell Lambda
-> approval and capability broker
-> Agent Sandbox
-> shell, browser, repo, tool, and generated-code execution
See Agent Sandboxes for the target architecture.
Tool and approval boundary
Tool execution is intentionally small in the MVP. Tools declare required capabilities and an optional action id:
const tool = {
definition: {
name: "bulkUpdateTickets",
action: "supportTickets.bulkUpdate",
requiredCapabilities: ["database.supportTickets.update"],
},
execute: async () => ({ ok: true }),
};
Before execution, Anvil checks:
- the agent declares each required capability
- the action is not approval-gated, or approval has been granted
If approval is required and not granted, the runtime returns an approval-required result instead of executing the tool. Decorative security is still decorative. This is the actual gate.
Manifest shape
Agent manifests are provider-neutral:
{
"kind": "anvil.agent",
"name": "support-assistant",
"exposure": "cell",
"model": {
"provider": "local",
"model": "stub"
},
"requires": {
"inference": true,
"toolCalling": false,
"memory": false,
"durableExecution": false,
"sandbox": true,
"humanApproval": ["email.sendExternal"]
},
"capabilities": {
"cells": ["read"],
"database": ["supportTickets.read"],
"network": "restricted",
"filesystem": "none",
"secrets": "none",
"git": [],
"deployments": []
}
}
The manifest may name provider ids such as local or aws-bedrock. It must not contain provider credentials, IAM policy, Lambda definitions, Step Functions definitions, CloudWatch config, DynamoDB config, or deployment resources.
Current limits
- Project-agent discovery is instruction-file based; standalone project-agent TypeScript manifests are future work.
- There is no hosted production agent orchestration.
- There is no chat UI.
- There is no hosted production approval UI. Local approval queues are persisted, visible in Lens, operable from the CLI, and auditable.
- There is no durable multi-step tool-calling loop.
- Memory and sandbox requirements are manifest-level contract fields, not complete hosted implementations.
- AWS support currently covers Bedrock inference, compatibility reporting, Lambda MicroVM sandbox lifecycle calls, deploy-plan entries, and CLI readiness output. Hosted policy brokering, streamed tools, workspace snapshots, and Lens views are still future work.
Read this as a contract foundation: enough to define, validate, inspect, mount, manifest, and run agents locally through the Anvil runtime model.