Skip to content

Commit 3bc80eb

Browse files
committed
feat: add OpenCode engine support as alternative to Codex
- Add forge-engine crate with Engine trait and Codex/OpenCode implementations - Add EngineKind enum (codex, opencode) in forge-config - Add --engine flag to run and analyze commands - Update doctor to validate configured engine - Rename codex_* fields to engine_* for generic engine support - Update documentation (README, AGENTS.md)
1 parent de1fced commit 3bc80eb

12 files changed

Lines changed: 852 additions & 516 deletions

File tree

AGENTS.md

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,22 @@ This document defines engineering guidance for AI/code agents working in this re
1414
- `crates/forge-cli`: CLI surface (`forge`, `run`, `status`, `monitor`, `sdd`)
1515
- `crates/forge-core`: loop engine, output analysis, rate limit, circuit breaker
1616
- `crates/forge-config`: `.forgerc` and env/flag precedence
17+
- `crates/forge-engine`: Engine trait and implementations (Codex, OpenCode)
1718
- `crates/forge-monitor`: TUI monitor
1819
- `crates/forge-types`: shared serializable types
1920

21+
## Engines
22+
23+
Forge supports multiple AI coding engines via the `Engine` trait in `forge-engine`:
24+
25+
- **Codex** (default): OpenAI's Codex CLI
26+
- **OpenCode**: Open source AI coding agent
27+
28+
To add a new engine:
29+
1. Implement the `Engine` trait in `forge-engine/src/lib.rs`
30+
2. Add the engine variant to `EngineKind` in `forge-config/src/lib.rs`
31+
3. Update `create_engine()` factory function
32+
2033
## SDD Workflow
2134

2235
When running `forge` with no subcommand, the assistant asks SDD questions and creates a snapshot under:
@@ -62,7 +75,7 @@ Status semantics:
6275

6376
- `run_started_at_epoch`: start of current `forge run`
6477
- `current_loop_started_at_epoch`: start of current loop command
65-
- `last_heartbeat_at_epoch`: last real output/stream heartbeat from Codex process
78+
- `last_heartbeat_at_epoch`: last real output/stream heartbeat from engine process
6679
- `.runner_pid`: PID of active forge run process (used by monitor to detect stale `running` state)
6780

6881
## Config Precedence
@@ -74,11 +87,12 @@ Always preserve precedence:
7487
3. `.forgerc`
7588
4. Defaults
7689

77-
Codex runtime global flags can be passed via:
90+
Engine runtime flags can be passed via:
7891

79-
- `forge run --codex-arg=<value>` (repeatable)
80-
- `forge run --full-access` (forces `--sandbox danger-full-access`)
81-
- `.forgerc` key `codex_pre_args = ["--sandbox", "danger-full-access"]`
92+
- `forge run --engine <codex|opencode>` (select engine)
93+
- `forge run --engine-arg=<value>` (repeatable)
94+
- `forge run --full-access` (forces sandbox bypass)
95+
- `.forgerc` key `engine_pre_args = ["--sandbox", "danger-full-access"]`
8296

8397
Monitor tuning:
8498

@@ -103,6 +117,7 @@ cargo test --workspace
103117

104118
- Add or update contract tests in `crates/forge-cli/tests/`
105119
- Add core behavior tests in `crates/forge-core/tests/`
120+
- Add engine tests in `crates/forge-engine/src/lib.rs`
106121
- Validate `fmt`, `clippy`, and `test` before proposing changes
107122

108123
## Documentation Sync
@@ -117,6 +132,7 @@ When changing user-facing behavior, update at least:
117132

118133
- Use `forge` naming only
119134
- Do not introduce legacy `ralph` naming in code, docs, env vars, or output
135+
- Use `engine` for generic engine references (not `codex` specific)
120136

121137
## Safety Rules
122138

Cargo.lock

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 58 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@ Standalone Rust CLI for autonomous coding loops.
55
## Prerequisites
66

77
- Rust stable toolchain
8-
- Codex CLI installed and available in `PATH`
8+
- Codex CLI or OpenCode CLI installed and available in `PATH`
99

10-
Check your Codex installation:
10+
Check your installation:
1111

1212
```bash
13-
codex --version
13+
codex --version # for Codex engine
14+
opencode --version # for OpenCode engine
1415
```
1516

1617
## Why
@@ -21,13 +22,34 @@ codex --version
2122
2. Write plan/spec artifacts.
2223
3. Execute implementation loop with guarded completion.
2324

25+
## Engines
26+
27+
Forge supports multiple AI coding engines:
28+
29+
- **Codex** (default) - OpenAI's Codex CLI
30+
- **OpenCode** - Open source AI coding agent
31+
32+
Select the engine via CLI flag:
33+
34+
```bash
35+
forge run --engine codex # default
36+
forge run --engine opencode # use OpenCode
37+
```
38+
39+
Or configure in `.forgerc`:
40+
41+
```toml
42+
engine = "opencode"
43+
```
44+
2445
## Commands
2546

2647
- `forge` (interactive assistant mode: asks SDD questions, writes plan/specs, then runs loop)
27-
- `forge run [--full-access] [--thinking off|summary|raw] [--max-loops N] [--timeout-minutes N]`
28-
- `forge analyze --modified-only`
48+
- `forge run [--engine codex|opencode] [--full-access] [--thinking off|summary|raw] [--max-loops N] [--timeout-minutes N]`
49+
- `forge analyze [--engine codex|opencode] --modified-only`
2950
- `forge status`
3051
- `forge monitor [--refresh-ms N] [--stall-threshold-secs N]`
52+
- `forge doctor`
3153
- `forge sdd list`
3254
- `forge sdd load <id>`
3355

@@ -81,26 +103,21 @@ The runtime state is stored in `.forge/`:
81103
- `.circuit_breaker_history`
82104
- `.runner_pid`
83105

84-
## Live visibility (Ralph-style)
106+
## Live visibility
85107

86-
`forge monitor` now shows, in real time:
108+
`forge monitor` shows, in real time:
87109

88110
- current loop
89111
- run timer and current command timer (`HH:MM:SS`)
90-
- current Codex activity extracted from `.forge/live.log`
112+
- current engine activity extracted from `.forge/live.log`
91113
- stalled detection based on heartbeat (`last_heartbeat_at_epoch`)
92114
- alert when heartbeat is stale (red status panel border and alert line)
93115
- alert when runner process is missing but status says `running` (stale status)
94-
- suppressed noise for repeated `codex_core::state_db record_discrepancy` warnings
95116

96117
`forge status` prints `run_timer` and `command_timer`.
97118

98119
`forge run` updates heartbeat (`last_heartbeat_at_epoch`) from real stream events during loop execution.
99-
If Codex emits no output for 120s, Forge triggers a no-output watchdog and kills that iteration to avoid permanent hangs.
100-
101-
### Monitor screenshot
102-
103-
![Forge monitor live view](docs/assets/forge-monitor-live.png)
120+
If the engine emits no output for 120s, Forge triggers a no-output watchdog and kills that iteration to avoid permanent hangs.
104121

105122
## Config precedence
106123

@@ -139,12 +156,18 @@ To run against a different folder without changing directories:
139156
cargo run -p forge -- --cwd /absolute/path/to/project
140157
```
141158

142-
To pass native `codex` global flags through `forge run`:
159+
To use OpenCode instead of Codex:
160+
161+
```bash
162+
forge --cwd /absolute/path/to/project run --engine opencode
163+
```
164+
165+
To pass native engine flags through `forge run`:
143166

144167
```bash
145168
forge --cwd /absolute/path/to/project run \
146-
--codex-arg=--sandbox \
147-
--codex-arg=danger-full-access
169+
--engine-arg=--sandbox \
170+
--engine-arg=danger-full-access
148171
```
149172

150173
Shortcut for full sandbox permissions:
@@ -153,7 +176,7 @@ Shortcut for full sandbox permissions:
153176
forge --cwd /absolute/path/to/project run --full-access
154177
```
155178

156-
Control thinking verbosity presets (mapped to Codex `--config` flags):
179+
Control thinking verbosity presets:
157180

158181
```bash
159182
forge --cwd /absolute/path/to/project run --thinking off
@@ -170,8 +193,9 @@ forge --cwd /absolute/path/to/project monitor --stall-threshold-secs 20
170193
You can also persist these args in `.forgerc`:
171194

172195
```toml
173-
codex_pre_args = ["--sandbox", "danger-full-access"]
174-
thinking_mode = "summary" # off | summary | raw
196+
engine = "codex"
197+
engine_pre_args = ["--sandbox", "danger-full-access"]
198+
thinking_mode = "summary"
175199
```
176200

177201
To force a new clean loop session (ignore previous runtime/session artifacts):
@@ -180,7 +204,7 @@ To force a new clean loop session (ignore previous runtime/session artifacts):
180204
forge --cwd /absolute/path/to/project run --fresh
181205
```
182206

183-
`--fresh` clears runtime state files in `.forge/` and adds `--ephemeral` to Codex execution to avoid reusing old sessions.
207+
`--fresh` clears runtime state files in `.forge/` and adds `--ephemeral` to engine execution to avoid reusing old sessions.
184208

185209
## Analyze modified files
186210

@@ -229,6 +253,19 @@ Fail if any operational warning remains:
229253
forge --cwd /absolute/path/to/project doctor --strict
230254
```
231255

256+
## Environment Variables
257+
258+
| Variable | Description |
259+
|----------|-------------|
260+
| `FORGE_ENGINE` | Engine to use (`codex` or `opencode`) |
261+
| `FORGE_ENGINE_CMD` | Path to engine binary |
262+
| `FORGE_ENGINE_PRE_ARGS` | Pre-args for engine |
263+
| `FORGE_ENGINE_EXEC_ARGS` | Exec args for engine |
264+
| `FORGE_THINKING_MODE` | Thinking mode (`off`, `summary`, `raw`) |
265+
| `FORGE_MAX_CALLS_PER_HOUR` | Rate limit (default: 100) |
266+
| `FORGE_TIMEOUT_MINUTES` | Timeout per iteration (default: 15) |
267+
| `FORGE_RUNTIME_DIR` | Runtime directory (default: `.forge`) |
268+
232269
## License
233270

234271
MIT. See `LICENSE`.

crates/forge-cli/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ serde.workspace = true
1616
chrono.workspace = true
1717
forge-config = { path = "../forge-config" }
1818
forge-core = { path = "../forge-core" }
19+
forge-engine = { path = "../forge-engine" }
1920
forge-monitor = { path = "../forge-monitor" }
2021
forge-types = { path = "../forge-types" }
2122

0 commit comments

Comments
 (0)