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
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@ The system is orchestrated by `src/cli.ts` and managed by `src/docker-manager.ts
- Ports: 10000 (OpenAI), 10001 (Anthropic), 10002 (Copilot), 10003 (Gemini) — these are discrete ports, not a contiguous range

**4. Unified Enclaves (optional)** — `containers/enclave/`
- Enabled via `enclaves.enabled` in the AWF config file
- Enabled by declaring `enclaves` entries in the AWF config file
- One AWF-owned MCP server (`enclave-mcp-server`) exposes enabled enclave executors only through compiler-launched `gh-aw-mcpg`; the primary agent gets no direct enclave socket, wrapper binary, capability, or private transport
- `enclave_run_script` launches a no-network, read-only, single-use Python executor and returns one canonical JSON result
- `enclave_run_agent` launches a single-use Copilot enclave on the dedicated `internal` `awf-enclave-agent` network whose sole peer is the dedicated API proxy; Squid, the primary agent, the general API proxy, safe outputs, and the MCP gateway are excluded
- Script and agent executors share one trusted `enclaves.privateRepos` list, one per-run information ledger, and one AWF-owned admission lane
- Script and agent executors are configured as a top-level `enclaves` array of `script`/`agent` entries whose merged `repos` lists form one trusted repository catalog, sharing one per-run information ledger and one AWF-owned admission lane
- Rollout depends on the compiler handoff contract in `github/gh-aw#50920` and late backend rediscovery in `github/gh-aw-mcpg#10784`, which requires MCP Gateway spec 1.15.0 and the first mcpg release after v0.4.8 containing it
- While the gateway backend is still coming up, AWF retries retryable HTTP `503 backend_unavailable` responses within `AWF_ENCLAVE_MCP_READINESS_TIMEOUT_MS`
- See [docs/enclaves-architecture.md](docs/enclaves-architecture.md) and [docs/awf-config-spec.md](docs/awf-config-spec.md) §14
Expand Down
11 changes: 11 additions & 0 deletions containers/enclave/agent-entrypoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,13 @@ def main() -> int:
max_output = int(os.environ["AWF_ENCLAVE_AGENT_MAX_OUTPUT_BYTES"])
timeout = int(os.environ["AWF_ENCLAVE_AGENT_DEADLINE_SECONDS"])
model = os.environ["AWF_ENCLAVE_AGENT_MODEL"]
max_model_requests = os.environ.get("AWF_ENCLAVE_AGENT_MAX_MODEL_REQUESTS")
max_model_tokens = os.environ.get("AWF_ENCLAVE_AGENT_MAX_MODEL_TOKENS")
if (
(max_model_requests is not None and int(max_model_requests) < 1)
or (max_model_tokens is not None and int(max_model_tokens) < 1)
):
raise ValueError("invalid model limits")
except (KeyError, OSError, UnicodeDecodeError, ValueError, json.JSONDecodeError):
append_event({"event": "failure", "category": "input-invalid"})
return EXIT_INPUT_INVALID
Expand Down Expand Up @@ -185,6 +192,10 @@ def main() -> int:
"--log-level", "all",
"--log-dir", str(copilot_logs),
]
if max_model_requests is not None:
command.extend(["--max-model-requests", max_model_requests])
if max_model_tokens is not None:
command.extend(["--max-model-tokens", max_model_tokens])
deadline = time.monotonic() + timeout
completed = None
stdout = ""
Expand Down
6 changes: 6 additions & 0 deletions containers/enclave/agent-executor/enclave-runner-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,12 @@ function deriveEnclaveContainerSpec({ config, runId, invocationId, seedId, runti
if (runtimeName !== undefined) {
launchArgs.push('--runtime', runtimeName);
}
if (config.maxModelRequests !== undefined) {
launchArgs.push('--env', `AWF_ENCLAVE_AGENT_MAX_MODEL_REQUESTS=${config.maxModelRequests}`);
}
if (config.maxModelTokens !== undefined) {
launchArgs.push('--env', `AWF_ENCLAVE_AGENT_MAX_MODEL_TOKENS=${config.maxModelTokens}`);
}
launchArgs.push('--entrypoint', '/usr/local/bin/run-enclave-agent', config.enclaveImage);

return Object.freeze({
Expand Down
8 changes: 8 additions & 0 deletions containers/enclave/mcp-server/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ function positiveInt(name, fallback, maximum = Number.MAX_SAFE_INTEGER) {
return value;
}

function optionalPositiveInt(name) {
const raw = process.env[name];
if (raw === undefined || raw === '') return undefined;
return positiveInt(name, 1);
}

function dockerSize(name, fallback) {
const value = process.env[name] || fallback;
if (!/^[1-9][0-9]*[bkmgBKMG]$/.test(value)) {
Expand Down Expand Up @@ -226,6 +232,8 @@ function loadAgentConfig(server) {
maxOutputBytes: positiveInt('AWF_ENCLAVE_AGENT_MAX_OUTPUT_BYTES', MAX_RESULT_BYTES, MAX_RESULT_BYTES),
maxPromptBytes: positiveInt('AWF_ENCLAVE_AGENT_MAX_PROMPT_BYTES', 4096, MAX_TASK_BYTES),
maxInvocations: positiveInt('AWF_ENCLAVE_AGENT_MAX_INVOCATIONS', 8),
maxModelRequests: optionalPositiveInt('AWF_ENCLAVE_AGENT_MAX_MODEL_REQUESTS'),
maxModelTokens: optionalPositiveInt('AWF_ENCLAVE_AGENT_MAX_MODEL_TOKENS'),
runLabelKey: ENCLAVE_RUN_LABEL,
invocationLabelKey: ENCLAVE_INVOCATION_LABEL,
containerPrefix: AGENT_CONTAINER_PREFIX,
Expand Down
88 changes: 51 additions & 37 deletions docs/awf-config-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,36 +265,26 @@ AWF settings MAY be supplied via config files, including stdin (`--config -`).
- `platform.type` → *(config-only; maps to `AWF_PLATFORM_TYPE`)*
- `runner.topology` → *(config-only; sets runner deployment model — `standard` or `arc-dind`; when `arc-dind`, enables sysroot staging and emits RUNNER_TOOL_CACHE warnings)*
- `runner.sysrootImage` → *(config-only; sysroot init-container image for `arc-dind` topology; defaults to `<container.imageRegistry>/build-tools:<container.imageTag>`, where `container.imageRegistry` defaults to `ghcr.io/github/gh-aw-firewall`)*
- `enclaves.enabled` → *(config-only; no CLI equivalent, see §14)*
- `enclaves.privateRepos[]` → *(config-only; no CLI equivalent, see §14)*
- `enclaves.executors.script.enabled` → *(config-only; no CLI equivalent, see §14)*
- `enclaves.executors.script.runtime` → *(config-only; no CLI equivalent, see §14)*
- `enclaves.executors.script.image` → *(config-only; no CLI equivalent, see §14)*
- `enclaves.executors.script.network` → *(config-only; no CLI equivalent, see §14)*
- `enclaves.executors.script.interpreter` → *(config-only; no CLI equivalent, see §14)*
- `enclaves.executors.script.timeout` → *(config-only; no CLI equivalent, see §14)*
- `enclaves.executors.script.memoryLimit` → *(config-only; no CLI equivalent, see §14)*
- `enclaves.executors.script.cpuLimit` → *(config-only; no CLI equivalent, see §14)*
- `enclaves.executors.script.pidsLimit` → *(config-only; no CLI equivalent, see §14)*
- `enclaves.executors.script.tmpfsLimit` → *(config-only; no CLI equivalent, see §14)*
- `enclaves.executors.script.maxOutputBytes` → *(config-only; no CLI equivalent, see §14)*
- `enclaves.executors.script.maxScriptBytes` → *(config-only; no CLI equivalent, see §14)*
- `enclaves.executors.script.maxInvocations` → *(config-only; no CLI equivalent, see §14)*
- `enclaves.executors.agent.enabled` → *(config-only; no CLI equivalent, see §14)*
- `enclaves.executors.agent.runtime` → *(config-only; no CLI equivalent, see §14)*
- `enclaves.executors.agent.image` → *(config-only; no CLI equivalent, see §14)*
- `enclaves.executors.agent.network` → *(config-only; no CLI equivalent, see §14)*
- `enclaves.executors.agent.engine` → *(config-only; no CLI equivalent, see §14)*
- `enclaves.executors.agent.profile` → *(config-only; no CLI equivalent, see §14)*
- `enclaves.executors.agent.model` → *(config-only; no CLI equivalent, see §14)*
- `enclaves.executors.agent.timeout` → *(config-only; no CLI equivalent, see §14)*
- `enclaves.executors.agent.memoryLimit` → *(config-only; no CLI equivalent, see §14)*
- `enclaves.executors.agent.cpuLimit` → *(config-only; no CLI equivalent, see §14)*
- `enclaves.executors.agent.pidsLimit` → *(config-only; no CLI equivalent, see §14)*
- `enclaves.executors.agent.tmpfsLimit` → *(config-only; no CLI equivalent, see §14)*
- `enclaves.executors.agent.maxOutputBytes` → *(config-only; no CLI equivalent, see §14)*
- `enclaves.executors.agent.maxTaskBytes` → *(config-only; no CLI equivalent, see §14)*
- `enclaves.executors.agent.maxInvocations` → *(config-only; no CLI equivalent, see §14)*
- `enclaves[]` → *(config-only; no CLI equivalent, see §14)*
- `enclaves[].repos[]` → *(config-only; no CLI equivalent, see §14)*
- `enclaves[].timeout` → *(config-only; no CLI equivalent, see §14)*
- `enclaves[].runtime` → *(config-only; no CLI equivalent, see §14)*
- `enclaves[].image` → *(config-only; no CLI equivalent, see §14)*
- `enclaves[].memoryLimit` → *(config-only; no CLI equivalent, see §14)*
- `enclaves[].cpuLimit` → *(config-only; no CLI equivalent, see §14)*
- `enclaves[].pidsLimit` → *(config-only; no CLI equivalent, see §14)*
- `enclaves[].tmpfsLimit` → *(config-only; no CLI equivalent, see §14)*
- `enclaves[].maxOutputBytes` → *(config-only; no CLI equivalent, see §14)*
- `enclaves[].maxInvocations` → *(config-only; no CLI equivalent, see §14)*
- `enclaves[].script` → *(config-only; no CLI equivalent, see §14)*
- `enclaves[].script.maxScriptBytes` → *(config-only; no CLI equivalent, see §14)*
- `enclaves[].agent` → *(config-only; no CLI equivalent, see §14)*
- `enclaves[].agent.engine` → *(config-only; no CLI equivalent, see §14)*
- `enclaves[].agent.profile` → *(config-only; no CLI equivalent, see §14)*
- `enclaves[].agent.model` → *(config-only; no CLI equivalent, see §14)*
- `enclaves[].agent.maxTaskBytes` → *(config-only; no CLI equivalent, see §14)*
- `enclaves[].agent.maxModelRequests` → *(config-only; no CLI equivalent, see §14)*
- `enclaves[].agent.maxModelTokens` → *(config-only; no CLI equivalent, see §14)*

When `container.dockerHostPathPrefix` points at a daemon-visible shared `/tmp` path, the implementation stages the invoking CLI binary together with `/etc/passwd`, `/etc/group`, and the generated chroot `/etc/hosts` under that shared path so chroot mode can bootstrap on split-filesystem ARC/DinD hosts.

Expand Down Expand Up @@ -1676,16 +1666,40 @@ Each record follows the `blocked-request-diag/v<version>` schema:

## 14. Unified Enclaves

The optional `enclaves` object defines AWF's sole supported private-repository execution surface. AWF stages immutable repository seeds on the host, starts one AWF-owned `enclave-mcp-server`, maintains one shared per-repository ledger for the run, and exposes enabled executors only through compiler-launched `gh-aw-mcpg`.
The optional top-level `enclaves` array defines AWF's sole supported private-repository execution surface. It is structurally identical to the gh-aw compiler's enclave frontmatter: every entry declares exactly one `script` or `agent` executor, its own `repos` list, and entry-level shared controls including `timeout`, `runtime`, `image`, resource limits, and disclosure limits. AWF stages immutable repository seeds on the host, starts one AWF-owned `enclave-mcp-server`, maintains one shared per-repository ledger for the run, and exposes configured executors only through compiler-launched `gh-aw-mcpg`.

### 14.1 Executors and shared configuration

`enclaves.privateRepos` is the only trusted repository list. Every enabled executor shares it, and every admitted invocation debits the same live per-repository information budget.
```yaml
enclaves:
- script: {}
repos:
- repo: octo-org/private-service
sensitivity: confidential
timeout: 45
- agent:
model: gpt-5
maxModelRequests: 3
maxModelTokens: 10000
runtime: gvisor
memoryLimit: 256m
maxOutputBytes: 2048
maxInvocations: 3
repos:
- repo: octo-org/private-service
sensitivity: confidential
timeout: 180
```

- **Script executor** — an entry keyed by `script`; launches a no-network, read-only, single-use Python sandbox. An empty `script: {}` object is valid and selects AWF's pinned defaults.
- **Agent executor** — an entry keyed by `agent`; launches a bounded single-use Copilot enclave whose only network peer is the dedicated API proxy. `agent.model` is REQUIRED.
- **Entry-level controls** — `runtime`, `image`, `memoryLimit`, `cpuLimit`, `pidsLimit`, `tmpfsLimit`, `maxOutputBytes`, and `maxInvocations` apply to the entry's selected executor. `script.maxScriptBytes` and agent `maxTaskBytes`, `maxModelRequests`, and `maxModelTokens` remain executor-specific. Network and interpreter are AWF-owned invariants, not input fields.

At most one entry MAY exist per executor kind, and each entry MUST declare exactly one executor key. Every entry's `repos` list is merged into one trusted repository catalog: a repository shared by both entries MUST declare the same `sensitivity`, because sensitivity fixes one shared per-run information budget that both executors debit.

- **Script executor** — configured under `enclaves.executors.script`; launches a no-network, read-only, single-use Python sandbox.
- **Agent executor** — configured under `enclaves.executors.agent`; launches a bounded single-use Copilot enclave whose only network peer is the dedicated API proxy.
`timeout` is a per-invocation wall-clock bound in seconds. It defaults to `30` for `script` entries and `120` for `agent` entries, and values above `540` are rejected. The canonical enclave MCP tools use a fixed `toolTimeout` of `630` seconds, covering the 600-second disclosure bucket plus a 30-second transport allowance.

At least one executor MUST be enabled when `enclaves.enabled` is `true`. `gvisor` requires an exactly registered `runsc` runtime and never falls back. `sbx` remains fail-closed for both executors until the audited capability proof lands.
`gvisor` requires an exactly registered `runsc` runtime and never falls back. `sbx` remains fail-closed for both executors until the audited capability proof lands.

The agent executor additionally requires `enableApiProxy`, a configured provider route for its fixed engine/profile, a configured `model`, and the absence of `enableDind`. AWF validates those requirements before repository staging.

Expand Down Expand Up @@ -1737,8 +1751,8 @@ The legacy private-repository surfaces are **removed, not deprecated**:

| Removed surface | Replacement |
| --- | --- |
| `boundedQueries` | `enclaves.privateRepos` + `enclaves.executors.script` |
| `boundedAgents` | `enclaves.privateRepos` + `enclaves.executors.agent` |
| `boundedQueries` | an `enclaves` entry keyed by `script` with its `repos` list |
| `boundedAgents` | an `enclaves` entry keyed by `agent` with its `repos` list |
| `bounded-query` wrapper / generated skill | `enclave_run_script` |
| `bounded-agent` wrapper / generated skill | `enclave_run_agent` |
| Separate legacy ledgers | One shared ledger inside `enclave-mcp-server` |
Expand Down
Loading
Loading