Skip to main content

Capabilities

You already use capabilities every day. When a browser tab asks for your camera, or an app asks for your contacts, you grant one tightly scoped permission, and the sandbox around that tab or app enforces it. The tab doesn't get the rest of your computer.

Three permission prompts. A browser tab asks to use your camera, with Block and Allow buttons. A phone app asks to use your contacts, with Don't allow and Allow buttons. A laptop app asks to share the screen, with Deny and Allow buttons.

The Agent SDK applies the same model to agents. A capability is one specific kind of access handed to one component on purpose, such as a single folder or a single website. Every part of an agent starts with none and gets only the capabilities its job needs. This page explains what can be granted, how that differs from most agent SDKs, and why what code can reach is the boundary that matters.

Give each piece only the capabilities it needs​

An agent is several pieces working together: the harness, the loop that asks the model what to do next and runs the tools it picks; the model, which decides the next step; the tools, which do the work; and the session, which keeps the conversation. Breaking an agent down this way is what lets you limit each piece separately.

The host, drawn as a dashed boundary, holds the keys and makes the model calls. Inside it, the agent harness can use its granted tools and ask for a model by name, but has no network, files or API keys. A web search tool can reach the search API and nothing else. A database tool can read one file and nothing else.

The harness reaches nothing on its own. Each tool reaches one thing, and only what the platform allows. Each component also has its own private memory, and the host connects them only through typed interfaces, like libraries.

The agent still holds every grant its tools hold, because it can ask a tool to use its grant, so keep each grant narrow.

What you can grant​

A component can use the capabilities it was granted and nothing else, and it has no way to make up new ones. The Learning Hub’s guide to capabilities covers the idea in depth.

In the Agent SDK, each tool and the agent harness get their own set of capabilities, called grants. A grant is one of a handful of things:

  • A folder, read only or writable. The component sees that folder and nothing outside it.
  • A list of hosts it may call over the network. An empty list reaches nothing.
  • Settings and environment values the host chooses to show it. Everything else is invisible.
  • A store, such as a key-value bucket the platform created.
  • A secret, by reference. The grant names the secret; the host resolves the value, so it never appears in a spec, a log or the agent's conversation.

Anything not granted does not exist for that component. There is no shell to escape to, no network stack to misuse, and no home directory to wander into.

Start closed and grant only what the agent needs​

Most agent SDKs run the agent as an ordinary process. That process has whatever its user or container has: the files, the network, the environment variables and the tokens that come with it. Safety is then added on top, as permission prompts or a sandbox you can turn on.

That approach starts open and subtracts. Every limit is something you have to remember to add and keep current, and anything you forget stays open.

A component starts from the other end. It has nothing to trim, because it begins with nothing, and each grant is a deliberate addition.

So the question is never "what did we forget to block?" It is "what did we grant?", and the answer is a short list you can read.

None of this is new as an idea. Running each service with only the permissions it needs is standard practice for Kubernetes workloads and cloud IAM roles. The difference is the default.

With a container you build least privilege by adding limits: a network policy, a read-only root filesystem, no mounted service account token. With a component, least privilege is where you start, and each grant is a deliberate addition. The Principle of Least Authority page covers the background, and Deny by default covers why denylists and auto-approve fall short.

Grant a few capabilities, then let the agent compose​

The pattern that works is simple: for each task, list the handful of capabilities it needs, deny everything else by default, and let the agent combine its tools however it likes inside that space.

Freedom inside the boundary matters. A model that can freely combine a folder reader, a search tool and an HTTP fetcher can solve problems you did not anticipate. Because the boundary is what those tools can reach, not which steps the model takes, you do not need to predict its steps to keep it safe.

What matters is what code can reach​

Many ordinary programs can run other programs or arbitrary code:

  • python -c '...' runs any Python program you can fit in a string, including one that opens sockets or reads your SSH keys.
  • find . -exec ... \; runs any command on every file it finds.
  • git can run hooks, tar can run a program to compress, and most interpreters and build tools can run whatever they are told.

Hundreds of common Unix tools have known escapes like these, to say nothing of escapes that haven't been identified yet. Knowing which program is running tells you little about what it can do. What you actually care about is what that program can reach: which files, which servers, and which secrets.

It's not about the strength of the wall​

Stronger isolation is the usual response to agent risk. gVisor, Kata Containers and microVMs are all isolation walls, and they're good ones. But the isolation wall decides what gets out of the box. Grants decide what code can do inside.

The escapeIn a default gVisor or microVM sandboxAs a component
sqlite3 .shellRuns a shell in the boxNo processes to start
curl file:///Reads any file in the boxSees only its own folder
curl and sqlite3 togetherShared access to the box, or one VM per toolSeparate grants per tool, in one process

The two work together. Components run inside an ordinary host, so you can keep your walls around it and add grants inside. See The wall and the grants.

Give it a grant, not a binary​

When an agent needs the web, give it an HTTP tool scoped to the hosts it needs, not a native curl binary. The tool can reach api.github.com and nothing else, and every request it makes passes through the host, which checks it.

Allowing a native curl binary allows whatever its flags can do: any URL, uploading local files, writing output to any path. Restricting it means chasing flags. An HTTP tool with a host list has nothing to chase. If you do want curl itself, compile it to a component, and it sees only the hosts and folder it was granted, whatever flags it's given.

Agents adapt well to this. Models use the tools they are told about, and a clear tool such as "fetch a URL from these hosts" is easier for a model to use correctly than a command line with dozens of options.

Each step up: fewer calls, more meaning​

The same action can be described at different levels. The higher the level, the fewer calls it takes and the more each call means, which is what makes policy readable.

LevelWhat a call looks likeWhat a policy can say
System callsThousands of connect, read and write calls on file descriptorsAlmost nothing a person can review
Sockets (wasi:sockets)Open a connection to an address and port"May connect to 203.0.113.10 on port 443"
HTTP (wasi:http)Send a request to a URL"May call api.example.com"
A domain callsell(photo)"May list photos for sale, may not delete them"

At the bottom, a policy is a wall of low-level rules nobody can read. At the top, the policy is written against actions people understand. The Agent SDK aims as high as it can: tools are components with meaningful functions, and each component is sized to be one decision, such as "read this folder" or "read public GitHub."

You do not have to learn the interface definitions behind these levels to use them. What matters is the capability each tool holds, and that the host enforces it on every call.