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 .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ FORGE_WORKER_CLAIM_TIMEOUT_SECONDS=5
# generated files.
# FORGE_WORK_PACKAGE_EXECUTION=1
# FORGE_HOST_REPOSITORY_WRITES=1
# ACP package execution remains separately disabled by default because ACP
# adapters are local processes and are not OS-confined by Forge.
# ACP package execution is enabled after task approval. Set this to 0 when local
# adapter process access is not acceptable; ACP is not OS-confined by Forge.
# FORGE_ACP_WORK_PACKAGE_EXECUTION=0

# Workspace root — where Forge stores user-owned operational files: projects,
Expand Down
16 changes: 9 additions & 7 deletions docs/acp-zed-connector.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,20 +71,22 @@ When a task uses an ACP provider, Forge:
Forge uses one adapter process per call. It does not keep a long-lived pool of
ACP agents.

Executable Workforce packages do not use ACP providers by default. ACP adapters
are local processes and Forge does not OS-confine them to the package attempt
sandbox. Set `FORGE_ACP_WORK_PACKAGE_EXECUTION=1` only after accepting that
local process risk for the repositories where Forge runs.
Executable Workforce packages may use a configured ACP provider after task
approval. ACP adapters are local processes and Forge does not OS-confine them
to the package attempt sandbox. Set `FORGE_ACP_WORK_PACKAGE_EXECUTION=0` to
disable ACP package execution on hosts where that local process risk is not
Comment thread
Joncallim marked this conversation as resolved.
acceptable.

## Current Limits

- ACP output is plain text in Forge's current provider interface.
- Forge does not receive detailed token usage from ACP.
- Tool calls from the underlying coding agent are not exposed as Forge runtime
MCP grants.
- ACP package execution is separately opt-in. When enabled, it relies on the
package attempt working directory and Forge's execution JSON path guards; it is
not an OS sandbox or a general live tool grant.
- ACP package execution is enabled after task approval unless the operator sets
`FORGE_ACP_WORK_PACKAGE_EXECUTION=0`. When active, it relies on the package
attempt working directory and Forge's execution JSON path guards; it is not an
OS sandbox or a general live tool grant.
- If a runtime does not expose model selection through ACP, Forge stores the
selected model on the provider record but cannot force the local runtime to
use it.
Expand Down
7 changes: 4 additions & 3 deletions docs/adr/0006-executable-workforce-beta-boundary.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@ edits and a reviewable sandbox copy of every generated file.
disables model execution and keeps the handoff-artifact-only path.
- `FORGE_HOST_REPOSITORY_WRITES=0`, `false`, `off`, `no`, or `disabled` lets
package models run but keeps generated files sandbox-only.
- ACP-backed work-package execution is separately disabled by default. Set
`FORGE_ACP_WORK_PACKAGE_EXECUTION=1` only when the operator accepts that ACP
adapters are local processes and are not OS-confined by Forge.
- ACP-backed work-package execution is enabled after an operator configures an
ACP provider and approves the task. ACP adapters are local processes and are
not OS-confined by Forge. Set `FORGE_ACP_WORK_PACKAGE_EXECUTION=0`, `false`,
`off`, `no`, or `disabled` to prevent ACP package execution.
- After Architect plan approval, Forge may execute one eligible specialist work
package at a time. Parallel specialist execution remains out of scope.
- Forge may collect bounded read-only host-repository context before a package
Expand Down
10 changes: 5 additions & 5 deletions docs/developer-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ back through the same provider interface used by the worker. The currently wired
Agent Client Protocol adapters wrap local tools such as Codex CLI and Claude
Code; the underlying CLI must already be installed, authenticated, and runnable
on the worker host. Architect ACP calls run in an isolated runtime directory.
Executable work-package ACP calls are disabled by default because ACP adapters
are local processes, not OS-confined sandboxes. Operators can opt in with
`FORGE_ACP_WORK_PACKAGE_EXECUTION=1` after accepting that risk. See [ACP and the
Zed connector](acp-zed-connector.md).
Executable work-package ACP calls are enabled after task approval. ACP adapters
are local processes, not OS-confined sandboxes. Operators can opt out with
`FORGE_ACP_WORK_PACKAGE_EXECUTION=0` when that risk is not acceptable. See [ACP
and the Zed connector](acp-zed-connector.md).

## Local Development

Expand Down Expand Up @@ -155,7 +155,7 @@ Feature flag defaults:
| `FORGE_WORK_PACKAGE_HANDOFF` | enabled | Set `0` or `false` to stop package handoff claims. |
| `FORGE_WORK_PACKAGE_EXECUTION` | enabled | Set `0`, `false`, `off`, `no`, or `disabled` to stop specialist package execution and create handoff artifacts only. |
| `FORGE_HOST_REPOSITORY_WRITES` | enabled | Set `0`, `false`, `off`, `no`, or `disabled` to keep generated files sandbox-only and skip local project edits. |
| `FORGE_ACP_WORK_PACKAGE_EXECUTION` | disabled | Set `1`, `true`, `on`, `yes`, or `enabled` only when local ACP package execution is an accepted operator risk. |
| `FORGE_ACP_WORK_PACKAGE_EXECUTION` | enabled | Set `0`, `false`, `off`, `no`, or `disabled` to block ACP package execution when local adapter process access is not acceptable. |
| `FORGE_RUNNING_WORK_PACKAGE_STALE_SECONDS` | `900` | Recovery window before a retry marks an interrupted running work package blocked and starts the next eligible attempt. |

### Executable Workforce Beta
Expand Down
12 changes: 6 additions & 6 deletions docs/operator-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,11 +153,11 @@ For the currently wired ACP adapters:
- The adapter wraps the local `codex` or `claude` CLI.
- The local CLI must already be installed and logged in.
- The Forge project must have a local folder so Forge can validate and bound
repository context. Architect planning uses an isolated runtime directory;
executable work-package ACP sessions are disabled by default because local ACP
adapters are not OS-confined by Forge. Set
`FORGE_ACP_WORK_PACKAGE_EXECUTION=1` only for repositories where that local
process access is acceptable.
repository context. Architect planning uses an isolated runtime directory.
Executable work-package ACP sessions are enabled after task approval, but the
local adapter is not OS-confined by Forge. Set
`FORGE_ACP_WORK_PACKAGE_EXECUTION=0` where that local process access is not
acceptable.
- Installing the Zed editor is not required; Forge uses Agent Client Protocol
adapter packages, not the editor itself.

Expand Down Expand Up @@ -317,7 +317,7 @@ Worker and workspace options:
| `FORGE_WORK_PACKAGE_HANDOFF` | Set `0` or `false` to disable default work-package handoff claims |
| `FORGE_WORK_PACKAGE_EXECUTION` | Set `0`, `false`, `off`, `no`, or `disabled` to disable default package execution and create handoff artifacts only |
| `FORGE_HOST_REPOSITORY_WRITES` | Set `0`, `false`, `off`, `no`, or `disabled` to keep generated files sandbox-only and skip local project edits |
| `FORGE_ACP_WORK_PACKAGE_EXECUTION` | Set `1`, `true`, `on`, `yes`, or `enabled` only when local ACP package execution is an accepted operator risk |
| `FORGE_ACP_WORK_PACKAGE_EXECUTION` | Set `0`, `false`, `off`, `no`, or `disabled` to block ACP package execution when local adapter process access is not acceptable |
| `FORGE_RUNNING_WORK_PACKAGE_STALE_SECONDS` | Defaults to `900`; retry handoff treats older running package rows as interrupted and recovers them before continuing |
| `FORGE_WORKSPACE_ROOT` | Fixed workspace root override |
| `FORGE_MCPS_ROOT` | Fixed shared MCP root override |
Expand Down
48 changes: 29 additions & 19 deletions web/__tests__/work-package-execution-context.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,29 +104,39 @@ describe('loadWorkPackageExecutionContext', () => {
expect(context.validatedProjectRoot).toBe('/workspace/real-project')
})

it('blocks ACP-backed executable work packages unless explicitly enabled', async () => {
vi.clearAllMocks()
const project = { id: 'project-1', localPath: '/workspace/project' }
const task = { id: 'task-1', projectId: 'project-1', pmProviderConfigId: 'provider-task' }
const workPackage = { id: 'pkg-1', assignedRole: 'backend' }
mocks.dbSelect
.mockReturnValueOnce(chain([{ task, project, workPackage }]))
.mockReturnValueOnce(chain([{ id: 'agent-backend', providerConfigId: null }]))
mocks.getProvider.mockResolvedValue({
config: { providerType: 'acp', modelId: 'codex-cli::gpt-5.3-codex-spark' },
})

await expect(loadWorkPackageExecutionContext('task-1', 'pkg-1'))
.rejects.toThrow(/ACP work-package execution is disabled/i)
it.each(['0', 'flase'])(
'blocks ACP-backed executable work packages when the setting is %s',
async (setting) => {
vi.clearAllMocks()
const previous = process.env.FORGE_ACP_WORK_PACKAGE_EXECUTION
process.env.FORGE_ACP_WORK_PACKAGE_EXECUTION = setting
const project = { id: 'project-1', localPath: '/workspace/project' }
const task = { id: 'task-1', projectId: 'project-1', pmProviderConfigId: 'provider-task' }
const workPackage = { id: 'pkg-1', assignedRole: 'backend' }
mocks.dbSelect
.mockReturnValueOnce(chain([{ task, project, workPackage }]))
.mockReturnValueOnce(chain([{ id: 'agent-backend', providerConfigId: null }]))
mocks.getProvider.mockResolvedValue({
config: { providerType: 'acp', modelId: 'codex-cli::gpt-5.3-codex-spark' },
})

try {
await expect(loadWorkPackageExecutionContext('task-1', 'pkg-1'))
.rejects.toThrow(/ACP work-package execution is disabled/i)
} finally {
if (previous === undefined) delete process.env.FORGE_ACP_WORK_PACKAGE_EXECUTION
else process.env.FORGE_ACP_WORK_PACKAGE_EXECUTION = previous
}

expect(mocks.assertProjectLocalPathForExecution).not.toHaveBeenCalled()
expect(mocks.getModel).not.toHaveBeenCalled()
})
expect(mocks.assertProjectLocalPathForExecution).not.toHaveBeenCalled()
expect(mocks.getModel).not.toHaveBeenCalled()
},
)

it('allows ACP-backed executable work packages when ACP execution is explicitly enabled', async () => {
it('allows ACP-backed executable work packages by default', async () => {
vi.clearAllMocks()
const previous = process.env.FORGE_ACP_WORK_PACKAGE_EXECUTION
process.env.FORGE_ACP_WORK_PACKAGE_EXECUTION = '1'
delete process.env.FORGE_ACP_WORK_PACKAGE_EXECUTION
const project = { id: 'project-1', localPath: '/workspace/project' }
const task = { id: 'task-1', projectId: 'project-1', pmProviderConfigId: 'provider-task' }
const workPackage = { id: 'pkg-1', assignedRole: 'backend' }
Expand Down
Loading
Loading