Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
171 changes: 171 additions & 0 deletions docs/ADRs/0065-provider-backed-policy-composition.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
---
title: "65. Provider-backed policy composition"
status: Accepted
relates_to:
- agent-infrastructure
topics:
- policy
- providers
- composition
- sandbox
---

# 65. Provider-backed policy composition

Date: 2026-06-22

## Status

Accepted

## Context

[ADR 0024](0024-harness-definitions.md) established per-agent harness
files with a `policy` field pointing to an OpenShell policy YAML.
Each policy file contains filesystem, process, and network
restrictions for one agent.
[ADR 0038](0038-universal-harness-access.md) introduced URL-based
access for harness resources, and
[ADR 0045](0045-forge-portable-harness-schema.md) defined
forge-portable harness composition with `base:` inheritance. Together
they establish that harness dependencies should be referenceable and
composable — but neither addresses how provider or profile definitions
are resolved when a base harness is remote (see [#2672](https://github.com/fullsend-ai/fullsend/issues/2672)).

In practice, every policy file duplicates the same network rule blocks
for shared services: Vertex AI inference endpoints, GitHub API access,
package registries (npm, PyPI, Go modules), and gitleaks binary
downloads. Six agents × four service groups = the same endpoints and
binaries repeated in every combination.

This duplication creates maintenance burden: when a service adds or
changes endpoints, every policy file must be updated independently.
The fix.yaml policy comments "Identical to the code agent policy,"
making the redundancy explicit.

OpenShell v0.0.37 introduced provider-backed policy composition
(NVIDIA/OpenShell#1037). When a provider is attached to a sandbox and
has a registered profile, the gateway merges the profile's network
rules into the effective policy at fetch time under reserved
`_provider_*` keys. This is additive-only and keeps provider rules
isolated from user/agent rules.

## Options considered

### Option 1: Monolithic per-agent policies (legacy approach)

Each agent has its own policy YAML with all network rules inline.
Agent designers specify every endpoint and binary in a single file.

**Pros:**
- Self-contained — one file per agent, easy to read in isolation.
- No dependency on providers v2 or OpenShell version.
- Full control per agent.

**Cons:**
- Duplicated network blocks across agents.
- Maintenance burden grows with agent count and service count.
- Easy to drift — one agent's policy gets an endpoint fix, others
don't.

### Option 2: Composable policies with agent designer freedom

Provide composable provider profiles as an option. Agent designers
choose whether to use providers for network access or keep inline
network rules. A shared `base.yaml` covers filesystem, landlock, and
process restrictions. Agents can mix: use providers for common services
and inline rules for agent-specific endpoints.

**Pros:**
- Flexible — agent designers pick the approach that fits.
- Incremental adoption — no big-bang migration needed.
- Lowest friction to introduce — no convention change required.

**Cons:**
- Two ways to do the same thing — harder to reason about effective
policy.
- Duplicated endpoints may reappear if designers don't adopt providers.
- No single source of truth for a service's endpoints.

### Option 3: All network access through providers (chosen)

Provider profiles are the single mechanism for granting network access.
Policy files define only non-composable sandbox restrictions:
filesystem access, landlock, and process identity. A single
`base.yaml` replaces per-agent policy files for these non-network
concerns. Harnesses declare which providers they need — each
contributes network rules via composition.

**Pros:**
- Single source of truth per service — endpoint changes update one
profile.
- Policy files are small and uniform (no network section).
- Clear separation: profiles own network access, policies own sandbox
restrictions.
- New agents get network access by declaring providers, not copy-pasting
endpoint blocks.

**Cons:**
- Agent designers must understand the provider model.
- Credential-less providers require a workaround
([NVIDIA/OpenShell#1978](https://github.com/NVIDIA/OpenShell/issues/1978)).

## Decision

**Adopt Option 3 as a best practice for fullsend default agents.**

Harnesses declare providers for network rules and policies control
filesystem, landlock, and process restrictions only.

This is a best practice, not an enforcement. Custom agents with
network rules in their policies continue to work — composition is
additive, so duplicated rules between providers and policies are
redundant but harmless. Agent designers who bring their own agents can
choose whichever approach fits their needs. The default agents serve
as the reference implementation.

Six custom profiles ship with the default agents:

| Profile ID | Service | Access |
|---|---|---|
| `fullsend-vertex-ai` | Anthropic API, Google Cloud APIs | read-write |
| `fullsend-github` | GitHub API, Git transport | read-write |
| `fullsend-github-ro` | GitHub API, Git transport | read-only |
| `fullsend-package-registries` | npm, PyPI, Go modules | read-only |
| `fullsend-gitleaks` | GitHub releases for gitleaks | read-only |
| `fullsend-github-artifacts` | GitHub Actions artifact download | read-only |
Comment thread
maruiz93 marked this conversation as resolved.
Resolved

Custom profiles are used instead of OpenShell built-ins because they
bundle endpoint combinations specific to fullsend (e.g.,
fullsend-vertex-ai combines Anthropic API + GCP APIs).

Provider definitions use dummy credentials as a workaround for
[NVIDIA/OpenShell#1978](https://github.com/NVIDIA/OpenShell/issues/1978)
— providers exist for network policy contribution only. Credential
delivery continues via host_files ([ADR 0025](0025-provider-credential-delivery-for-sandboxed-agents.md) tier 4).

The `providers_v2_enabled` setting and profile imports are managed
automatically by `fullsend run`.

## Consequences

- Scaffold agents use provider profiles for all network access. Policy
files contain only filesystem, landlock, and process restrictions.
- Network rules for each service are defined once in a profile YAML.
Adding or changing endpoints updates one file.
- All scaffold agents share a single `policies/base.yaml`. Per-agent
policy files are eliminated from the scaffold.
- Custom agents are not required to adopt this pattern. Inline network
policies continue to work, but the scaffold and guides recommend
providers as the preferred approach.
- The building-custom-agents guide is updated to show providers as
the recommended way to grant network access.
- Requires OpenShell >= v0.0.37 and the `providers_v2_enabled` gateway
setting (set automatically).
- GitHub access is split into read-write (`fullsend-github`) and
read-only (`fullsend-github-ro`) profiles, matching the access
levels agents had under the previous per-agent policy model.
Review and retro agents use the read-only variant.
- Provider and profile definitions are currently resolved from local
directories only. Portability for URL-referenced base harnesses
requires harness-level declaration and URL-based resolution ([#2672](https://github.com/fullsend-ai/fullsend/issues/2672)).
11 changes: 8 additions & 3 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,14 @@ Sandbox defaults (network policy, filesystem restrictions) are configured in the
**Open questions:**

- What is the right isolation level — process, container, microVM, or separate cluster? (See [agent-infrastructure.md](problems/agent-infrastructure.md) and [security-threat-model.md](problems/security-threat-model.md).)
- How granular is network regulation? Allowlist of endpoints, or coarser controls? (A **protocol gateway** toward approved model and MCP endpoints is one way to narrow egress without handing agents raw internet access; see [landscape.md](landscape.md#agent-gateway).)
- ~~How granular is network regulation? Allowlist of endpoints, or coarser controls?~~ Decided in [ADR 0065](ADRs/0065-provider-backed-policy-composition.md): network access is granted through provider profiles with per-endpoint allowlists.
- Does the sandbox provide a pre-built environment (tools, language runtimes, repo clones), or does the agent set up its own workspace within the sandbox?
- ~~Is the sandbox the same for all agent roles, or does each role get a differently-scoped sandbox?~~ Decided in [ADR 0020](ADRs/0020-composable-single-responsibility-agents-with-individual-sandboxes.md): each agent gets its own sandbox with policies designed for its responsibility.

**Decided:**

- Provider-backed policy composition: network access is granted through provider profiles declared in harness files. Policy files define only non-composable sandbox restrictions (filesystem, landlock, process). A single `base.yaml` replaces per-agent policy files in the scaffold. Inline `network_policies` continue to work but providers are the recommended approach ([ADR 0065](ADRs/0065-provider-backed-policy-composition.md)).

## Agent Harness

The configuration and context layer that prepares an agent for its task. Responsible for providing skills, system prompts, codebase context, tool definitions, and behavioral instructions to the agent runtime.
Expand Down Expand Up @@ -600,7 +604,8 @@ GitHub event ──► SHIM WORKFLOW (fullsend.yml in enrolled repo)
║ │ Loads harness/code.yaml: │ ║
║ │ agent: agents/code.md │ ║
║ │ image: ghcr.io/fullsend-ai/fullsend-code:latest │ ║
║ │ policy: policies/code.yaml │ ║
║ │ policy: policies/base.yaml │ ║
║ │ providers: [vertex-ai, github, package-registries] │ ║
║ │ skills: [skills/code-implementation] │ ║
║ │ pre_script: scripts/pre-code.sh │ ║
║ │ post_script: scripts/post-code.sh │ ║
Expand All @@ -611,7 +616,7 @@ GitHub event ──► SHIM WORKFLOW (fullsend.yml in enrolled repo)
║ │ ┌───────────────────────────────────────────────────────┐ │ ║
║ │ │ OPENSHELL SANDBOX │ │ ║
║ │ │ │ │ ║
║ │ │ Created with --from image, --policy code.yaml. │ │ ║
║ │ │ Created with --from image, --policy base.yaml. │ │ ║
║ │ │ Bootstrapped via openshell upload/exec: │ │ ║
║ │ │ agent def → /sandbox/claude-config/agents/ │ │ ║
║ │ │ skills → /sandbox/claude-config/skills/ │ │ ║
Expand Down
67 changes: 44 additions & 23 deletions docs/guides/user/building-custom-agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,10 @@ image: ghcr.io/fullsend-ai/fullsend-sandbox:latest
policy: customized/policies/my-agent.yaml
role: my-agent

providers:
- vertex-ai # Required: model access (Anthropic API + GCP)
- github # GitHub API + Git transport

host_files:
# GCP credentials for Vertex AI (required for model access)
- src: env/gcp-vertex.env
Expand Down Expand Up @@ -178,6 +182,8 @@ The agent never has direct access to credentials. The pre-script uses credential

## Step 3: Define the sandbox policy

The sandbox policy controls filesystem, process, and landlock restrictions. Network access is handled separately through provider profiles (see below).

Create `.fullsend/customized/policies/my-agent.yaml`:

```yaml
Expand All @@ -191,43 +197,58 @@ landlock:
process:
run_as_user: sandbox
run_as_group: sandbox
```

Most custom agents can reuse the scaffold's `policies/base.yaml` instead of creating their own. Override only when your agent has specific filesystem or process requirements.

### Network access via providers (recommended)

The recommended way to grant network access is through provider profiles declared in the harness. Add a `providers` field to your harness YAML:

```yaml
providers:
- vertex-ai # Anthropic API + GCP (required for model access)
- github # GitHub API + Git transport
- package-registries # npm, PyPI, Go modules (optional)
```

Each provider has a profile that defines its endpoints and binaries. When the sandbox starts, the gateway composes these profiles into the effective network policy automatically. This keeps endpoint definitions in one place and avoids copy-pasting network blocks across agents.

The scaffold ships with profiles for common services. To see what's available:

```bash
ls .fullsend/providers/ # provider definitions (name + type)
ls .fullsend/profiles/ # profile YAMLs (endpoints + binaries)
```

For services not covered by existing profiles, you can either create a custom profile or use inline `network_policies` in your policy YAML (both approaches work — composition is additive).

### Network access via inline policies (alternative)

You can also define network rules directly in the policy YAML. This is useful for one-off endpoints specific to a single agent:

```yaml
network_policies:
# Required: Vertex AI for model access
vertex_ai:
name: vertex-ai
endpoints:
- host: "*.googleapis.com"
port: 443
protocol: rest
enforcement: enforce
access: read-write
- host: "api.anthropic.com"
port: 443
protocol: rest
enforcement: enforce
access: read-write
binaries:
- path: "**/claude"
- path: "**/node"
# Optional: GitHub API access (if agent needs it)
github_api:
name: github-api
my_service:
name: my-service
endpoints:
- host: "api.github.com"
- host: "api.example.com"
port: 443
protocol: rest
enforcement: enforce
access: read-only
binaries:
- path: "**/gh"
- path: "**/curl"
```

Inline rules and provider-composed rules coexist — composition is additive. If both define the same endpoint, the duplicate is harmless.

### Policy design principles

- **Vertex AI is always required** — the agent needs it to talk to the LLM.
- **Vertex AI is always required** — the agent needs it to talk to the LLM. Use the `vertex-ai` provider.
- **Add network access only for what the agent needs.** If the agent doesn't need web search, don't allow it.
- **Use `binaries` to restrict which programs can access each endpoint.** This prevents the agent from using unexpected tools to exfiltrate data.
- **Prefer providers for shared services.** Use inline policies only for agent-specific endpoints.
- **Never allow APIs from the sandbox.** All network reads happen in pre-scripts; all network writes happen in post-scripts.

## Step 4: Define the output schema
Expand Down
12 changes: 10 additions & 2 deletions docs/guides/user/customizing-agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ A minimal harness configuration (based on actual fullsend agent harnesses):
agent: agents/code.md
model: opus
image: ghcr.io/fullsend-ai/fullsend-code:latest
policy: policies/code.yaml
policy: policies/base.yaml
providers:
- vertex-ai
- github
- package-registries
timeout_minutes: 35

skills:
Expand Down Expand Up @@ -335,7 +339,11 @@ Create `.fullsend/customized/harness/code.yaml` to override the code agent's exe
agent: agents/code.md
model: claude-opus-4-6 # Changed from: opus
image: ghcr.io/fullsend-ai/fullsend-code:latest
policy: policies/code.yaml
policy: policies/base.yaml
providers:
- vertex-ai
- github
- package-registries
timeout_minutes: 45 # Changed from: 35

skills:
Expand Down
Loading
Loading