Skip to content

Permissions

How Compozy applies static permission modes, workspace boundaries, and interactive approvals during session execution.

For people running agent work6 pages in this section

Compozy applies permissions in two layers:

  1. a daemon-side static policy for built-in ACP tool surfaces
  2. an interactive approval flow for ACP session/request_permission

Both layers are scoped to the session workspace. approve-all never means "full machine access".

When provider-native tool interception is active, Compozy may keep the upstream ACP session in an approval-mediated mode so provider-native tool calls return through the Compozy execution gateway before any real side effect. That does not change the configured policy outcome: approve-all still auto-allows, approve-reads still auto-allows reads only, and deny-all still auto-rejects at the execution boundary.

Permission modes

Compozy supports three static modes:

ModeStatic behaviorCross-workspace requests
deny-allCompozy auto-allows none of the built-in file or terminal operations.Denied everywhere, with no prompt.
approve-readsCompozy auto-allows fs/read_text_file only.Asks at the native-tool boundary; every other boundary denies.
approve-allCompozy auto-allows fs/read_text_file, fs/write_text_file, and terminal/create.Allowed without asking.

The same mode decides both columns. Cross-workspace access explains what that means for a session that asks for another workspace.

Defaults and overrides

The current built-in default configuration is:

[permissions]
mode = "approve-all"

That global mode can be overridden by an agent definition. Dream sessions are forced to approve-all regardless of the configured mode.

If a session somehow reaches startup with no resolved permission string at all, the session manager falls back to approve-reads instead of launching with an empty policy.

What the static policy actually covers

These are the built-in operations Compozy checks directly:

Operationdeny-allapprove-readsapprove-all
fs/read_text_fileblockedallowedallowed
fs/write_text_fileblockedblockedallowed
terminal/createblockedblockedallowed

Static auto-allow is only one part of the full story. Compozy still enforces workspace scoping and network-turn restrictions after the mode check passes.

Workspace boundaries

Every permission decision is evaluated relative to the session workspace root.

Compozy resolves paths carefully:

  • absolute paths are checked directly
  • relative paths are resolved under the workspace root
  • existing paths are resolved through symlinks
  • missing paths are resolved through the first existing ancestor

If the final path escapes the workspace root, Compozy rejects it with a path-outside-workspace error.

Cross-workspace access

A session belongs to one workspace. When an agent asks for a different one — a native tool call that names another workspace, an agent identity lookup, a task claim, a spawn, or a workspace coordination command — the daemon sends that request through a single policy, and the session's own permission mode decides it:

Session modeCross-workspace outcome
approve-allAllowed. The session crosses without being asked.
approve-readsAsks at the native-tool boundary when the session is running; every other boundary denies.
deny-allDenied at every boundary, including the native-tool one. Compozy never prompts for crossing.

Operators are not governed by this policy — it applies to agent sessions. Extensions, automation, network peers, and the daemon itself keep the behavior they have today; a permissive mode never widens them.

deny-all reads differently on each axis, and that is deliberate. For tool risk it means "auto-allow nothing, ask instead". For the workspace boundary it means "never cross, never ask". deny-all is the lockdown setting for a session that must stay inside its own workspace.

The trade-off is that one mode now governs both axes. An approve-all session is open on tools and open across workspaces; there is no separate setting for permissive tools with an isolated workspace. The built-in default is approve-all, so set [permissions] mode — or the agent's permissions field — to approve-reads or deny-all when a session should stay in its workspace.

Denials

A denied request keeps the error shape it has today and adds one hint:

cross-workspace access is denied by this session's permission mode; ask the operator to set the agent's permissions.mode to approve-all, or approve the prompt when asked

Native tool denials carry the workspace_access_denied reason code alongside that message.

The prompt in approve-reads

In approve-reads, a cross-workspace native tool call from a running session becomes a pending permission request instead of an immediate denial. Compozy computes the option set:

Option IDLabelEffect
allow_onceAllow onceAllows this request only. The next crossing asks again.
allow_sessionAllow for this sessionAllows this request and every later crossing by that session, at every boundary.
reject_onceReject onceDenies this request only. The next crossing asks again.
reject_sessionReject for this sessionDenies this request and every later crossing by that session, at every boundary.

Answer it like any other pending permission — the approvals bell in Web, compozy session approve, or the approve route. The allow-once, allow-always, reject-once, and reject-always decisions map onto the four options above in that order. The wait is bounded by [tools.policy] approval_timeout_seconds; a timeout, a transport failure, or an answer outside the option set denies the request and stores no session answer.

Crossings at every other boundary are denied outright in approve-reads — they never raise a prompt. Once a session has answered allow_session, those boundaries start allowing.

Session answers do not outlive the session

allow_session and reject_session are held in daemon memory, keyed by session ID. They apply to every boundary for the rest of that session and disappear when the session stops or the daemon restarts. Nothing is written to disk, and there is no list or revoke surface: stopping the session is how you clear the answer. Persisted audit events show how later policy evaluations were decided.

Audit

Each policy evaluation attempts a best-effort audit append — workspace.access_granted for an allow or workspace.access_denied for a deny, including a prompt-eligible deny — scoped to the actor's own workspace. The payload carries the target workspace, boundary, decision source, and resolved mode. An audit write failure is logged and does not change the access decision. Read persisted events with compozy logs --type <event-type> or GET /api/logs.

See Workspace Resolver for what workspace isolation covers today and what it does not.

Network-originated turn restrictions

Network-originated turns are stricter than normal user turns, even under approve-all.

Current daemon-side restrictions:

  • fs/write_text_file is blocked for network turns
  • terminal/create is only allowed for a narrow allowlist of compozy network ... commands

This is enforced after the normal mode check.

Interactive approval flow

Agents can also ask for permission explicitly with ACP session/request_permission.

That flow behaves like this:

  1. the agent sends a permission request with one or more offered outcomes
  2. Compozy checks the workspace boundary first
  3. Compozy either auto-selects an outcome or records a pending permission event
  4. a client approves or rejects it
  5. Compozy returns the selected ACP outcome to the agent

Auto decisions vs pending decisions

Session modeRequest kindImmediate result
approve-allany valid in-workspace requestallow-once
approve-readsACP read requestallow-once
approve-readsnon-read ACP requestpending approval
deny-allany ACP requestreject-once

Outside-workspace requests are rejected immediately instead of becoming pending.

Decisions clients can send

The approval request body accepts these decisions:

  • allow-once
  • allow-always
  • reject-once
  • reject-always

Example HTTP approval:

curl -X POST http://localhost:2123/api/workspaces/ws_alpha/sessions/sess-1234/approve \
  -H "Content-Type: application/json" \
  -d '{
    "request_id": "req-42",
    "turn_id": "turn-9abc",
    "decision": "allow-always"
  }'

Success response:

{
  "status": "approved"
}

Approval timeout

Pending interactive permission requests wait up to 5 minutes by default. If no approval arrives, Compozy resolves the request as reject-once.

What allow-always and reject-always mean

For a generic ACP permission request, Compozy picks the closest option offered by the agent and returns it. The agent runtime owns the long-lived meaning of "always" for that request.

Native tools routed through Compozy's approval gateway have an additional daemon-owned contract. Selecting allow-always or reject-always stores an exact workspace + agent + tool + input-digest decision. The next matching invocation uses that decision before prompting, and the decision survives daemon restart. Operators and agents can inspect or revoke these records through Web, compozy tool approvals, HTTP/UDS, or the compozy__tool_approvals_* native tools. This does not change ACP subprocess permissions or sandbox policy. See Policy and Invocation.

Permission events

Permission requests and resolutions are visible in both prompt streams and persisted session history.

Example canonical permission payload:

{
  "schema": "compozy.session.event.v1",
  "type": "permission",
  "session_id": "acp-session-77",
  "turn_id": "turn-9abc",
  "request_id": "req-42",
  "timestamp": "2026-04-16T01:22:00Z",
  "action": "session/request_permission",
  "resource": "/absolute/path/to/repo/internal/session/manager.go",
  "decision": "allow-always",
  "title": "Write file",
  "tool_call_id": "tool-call-7",
  "raw": {
    "tool_call": {
      "kind": "edit",
      "title": "Write file"
    }
  }
}

If a request is still pending, the initial permission event is recorded with no final decision yet. The resolved decision appears when approval completes.

Current transport status

Interactive approval is exposed differently across Compozy transports today:

TransportStatus
HTTPimplemented at POST /api/workspaces/:workspace_id/sessions/:session_id/approve
UDSimplemented at POST /api/workspaces/:workspace_id/sessions/:session_id/approve
CLIimplemented at compozy session approve <id>

That means browser-facing clients, UDS clients, and the local CLI all drive the same approval surface today. Cross-workspace prompts arrive through that same surface. They add no route, command, or Settings section of their own, and a session-scoped answer has no management surface at all.

Clarification questions are not approvals

compozy__clarify lets an active managed session ask one bounded question and wait for an operator. It is a read-only, non-destructive tool with its own live broker; it does not reuse permission requests, approval decisions, or remembered native-tool grants. One session can have at most one pending clarification.

The question may offer up to four unique choices or accept free text. The HTTP/UDS wire uses a zero-based choice_index; the CLI presents one-based choice numbers:

compozy session clarify pending <session-id> -o json
compozy session clarify answer <session-id> <request-id> --choice 1 -o json
compozy session clarify answer <session-id> <request-id> --text "Use staging" -o json

HTTP and UDS use the same workspace-scoped routes:

  • GET /api/workspaces/:workspace_id/sessions/:session_id/clarifications
  • POST /api/workspaces/:workspace_id/sessions/:session_id/clarifications/:request_id/answer

The live pending list is exact daemon truth. Durable clarify events preserve the pending, resolved, timed-out, and canceled transitions for timeline and audit. Timeout returns exactly {"choice":null,"text":"","fallback":true}; stopping the session or daemon cancels the wait.

Practical configuration example

Global default in ~/.compozy/config.toml:

[permissions]
mode = "approve-reads"

Per-agent override in an AGENT.md definition:

---
name: reviewer
provider: codex
permissions: deny-all
---

Review code changes and explain the risk.

Use the global default for the team baseline, then override individual agents only when they need a different safety envelope. There is no compozy session new --permissions flag today, and the session create HTTP payload does not currently accept a permissions override either.

Next steps

  • Use Session Lifecycle to see where approvals fit into create, active, and stop behavior.
  • Use Event Streaming to follow permission events and audit what happened in one session.

On this page