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
16 changes: 10 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,13 @@ cargo run -p decodex --bin decodex -- status
cargo run -p decodex --bin decodex -- diagnose --json
cargo run -p decodex --bin decodex -- maintenance prune --dry-run
cargo run -p decodex --bin decodex -- run --dry-run
cargo run -p decodex --bin decodex -- serve --interval 60s --listen-address 127.0.0.1:8912
cargo run -p decodex --bin decodex -- serve --listen-address 127.0.0.1:8912
```

Project-scoped commands accept `--config <PROJECT_DIR>` after the subcommand when the
operator wants to override registry-based project resolution for that command.
`decodex serve` owns the default scheduler cadence of 15 seconds; pass
`--interval <INTERVAL>` only when deliberately overriding that poll interval.

### Install from Source

Expand Down Expand Up @@ -224,11 +226,13 @@ node dev/operator-dashboard-mock.mjs --listen-address 127.0.0.1:57399 --use-code
```

Use hidden `decodex serve --dev --listen-address 127.0.0.1:8912` only when
developing Decodex App's bundled server path or the local account/app snapshot APIs
against real runtime state. Dev mode deliberately does not register projects, poll
Linear, dispatch work, or accept `--config` or `--interval`. For real automation,
use ordinary `decodex serve --interval ...`; for dashboard-only UI work, prefer the
mock server above.
developing local account/app snapshot APIs against real runtime state while explicitly
avoiding scheduler activity. Dev mode deliberately does not register projects, poll
Linear, dispatch work, or accept `--config` or `--interval`. Decodex App's normal
fallback server is ordinary `decodex serve --listen-address 127.0.0.1:8912`; the CLI
owns the default scheduler interval, currently 15 seconds. App launch connects to an
existing live default listener instead of starting a duplicate server. For
dashboard-only UI work, prefer the mock server above.

The dashboard semantics and local-vs-external state boundary live in
`docs/reference/operator-control-plane.md`.
Expand Down
27 changes: 14 additions & 13 deletions apps/decodex-app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ The first Decodex App release manages the shared Codex account pool through the
bundled Rust app helper so account UI stays on the same CLI-owned files even when a
long-running local `decodex serve` is older than the app bundle. On launch the app also
connects to an existing `decodex serve` on the default local endpoint when one is
available; otherwise it starts the bundled Decodex binary in its hidden dev endpoint
mode as `decodex serve --dev --listen-address 127.0.0.1:8912` for operator snapshot
and WebUI routes. App-started servers do not poll registered projects or dispatch
Linear work. The helper owns account
operations and interactive login flows that need streamed command output:
available; otherwise it starts the bundled Decodex binary as a normal scheduler with
`decodex serve --listen-address 127.0.0.1:8912`. The CLI owns the default scheduler
interval, currently 15 seconds. App-started servers load the enabled project registry
and own the same operator listener as a manually started `decodex serve`. The helper
owns account operations and interactive login flows that need streamed command output:

- list accounts without printing token material
- pin future Decodex runs to one account
Expand All @@ -26,9 +26,10 @@ operations and interactive login flows that need streamed command output:
- run isolated Codex device login, then import the resulting auth file
- remove a stored account from the local pool

The app does not schedule Decodex runs, own project registration, or replace
`decodex serve`. It is a native UI over the shared Rust account-management service,
not a wrapper around the `decodex` CLI binary.
The app does not schedule Decodex runs itself, own project registration, or replace the
Rust control plane. It is a native UI over the shared Rust account-management service
and uses the bundled `decodex` server only when no compatible local server is already
running.

The app and operator dashboard share account-pool state through the Rust account API:
stored accounts come from `~/.codex/decodex/accounts.jsonl`, run routing and account
Expand Down Expand Up @@ -72,11 +73,11 @@ DECODEX_APP_HELPER="$(pwd)/target/debug/decodex-app-helper" \
swift run --package-path apps/decodex-app DecodexApp
```

The app-started server path is the main reason to use `decodex serve --dev`
manually: it lets you test the same local account APIs, app snapshot API, and
dashboard routes without starting the scheduler. Do not use `--dev` to validate
project registration, Linear polling, queue intake, or retained-lane execution; use
ordinary `decodex serve --interval ...` for those paths.
Use hidden `decodex serve --dev` only when manually testing local account APIs, the app
snapshot API, or dashboard routes while deliberately avoiding scheduler activity. The
normal app fallback is ordinary `decodex serve --listen-address 127.0.0.1:8912`. Do
not use `--dev` to validate project registration, Linear polling, queue intake, or
retained-lane execution; use ordinary `decodex serve` for those paths.

The staging script follows the local Rsnap-style signing path: it writes
`target/decodex-app/Decodex App.app`, signs the bundle with an Apple Development
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,6 @@ actor DecodexServerBridge {
process.executableURL = try decodexExecutableURL()
process.arguments = [
"serve",
"--dev",
"--listen-address", defaultListenAddress,
]
process.standardOutput = nullDevice
Expand Down
10 changes: 3 additions & 7 deletions apps/decodex/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -314,13 +314,13 @@ impl RunCommand {
struct ServeCommand {
#[command(flatten)]
project_config: ProjectConfigArgs,
/// Poll interval between control-plane ticks, for example `60s` or `5m`.
/// Override the scheduler poll interval, for example `15s` or `5m`.
#[arg(long, value_name = "INTERVAL", value_parser = parse_duration_arg)]
interval: Option<Duration>,
/// Operator UI listen address.
#[arg(long, value_name = "ADDR", default_value = "127.0.0.1:8912")]
listen_address: String,
/// Start the Decodex App/dev endpoint without polling or dispatching projects.
/// Start the local dev endpoint without polling or dispatching projects.
#[arg(long, hide = true)]
dev: bool,
}
Expand All @@ -334,11 +334,7 @@ impl ServeCommand {

orchestrator::run_control_plane(ServeRequest {
config_path: self.project_config.as_path(),
poll_interval: if self.dev {
None
} else {
Some(self.interval.unwrap_or_else(|| Duration::from_secs(60)))
},
poll_interval: if self.dev { None } else { self.interval },
listen_address: &self.listen_address,
dev: self.dev,
})
Expand Down
1 change: 1 addition & 0 deletions apps/decodex/src/orchestrator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ const OPERATOR_STATE_HEADER_TERMINATOR: &[u8] = b"\r\n\r\n";
const OPERATOR_DASHBOARD_WS_HEARTBEAT_INTERVAL: Duration = Duration::from_secs(20);
const OPERATOR_RUN_ACTIVITY_STREAM_INTERVAL: Duration = Duration::from_secs(1);
const OPERATOR_DEV_SNAPSHOT_STREAM_INTERVAL: Duration = Duration::from_secs(1);
const DEFAULT_CONTROL_PLANE_POLL_INTERVAL: Duration = Duration::from_secs(15);
const PULL_REQUEST_REVIEW_STATE_QUERY: &str = r#"
query($owner: String!, $name: String!, $number: Int!, $reviewThreadsAfter: String) {
repository(owner: $owner, name: $name) {
Expand Down
2 changes: 1 addition & 1 deletion apps/decodex/src/orchestrator/entrypoints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ pub(crate) fn run_control_plane(request: ServeRequest<'_>) -> Result<()> {

let poll_interval = request
.poll_interval
.unwrap_or_else(|| Duration::from_secs(60));
.unwrap_or(DEFAULT_CONTROL_PLANE_POLL_INTERVAL);

if poll_interval.is_zero() {
eyre::bail!("serve interval must be greater than zero.");
Expand Down
12 changes: 5 additions & 7 deletions docs/reference/operator-control-plane.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,13 @@ Decodex currently runs as a local, single-machine control plane:
Decodex App is a native shell over the same local runtime and account-pool state. On
launch it connects to an existing default local listener when one is reachable; if
not, it starts the bundled `decodex` binary as
`decodex serve --dev --listen-address 127.0.0.1:8912`. Dev mode serves the dashboard,
account APIs, and `GET /api/operator-snapshot` for the app, but it does not register
projects, poll Linear, dispatch work, or accept `--config` or `--interval`. Use
ordinary `decodex serve --interval ...` for the automation loop.
`decodex serve --listen-address 127.0.0.1:8912`. The app fallback is a normal
control-plane server: it loads the enabled project registry, uses the CLI-owned default
15-second poll interval, and serves the dashboard, account APIs, and
`GET /api/operator-snapshot` from the single local listener.

Use `--dev` only for local development and app-owned startup:
Use `--dev` only for isolated local development:

- Decodex App may start the bundled server with `--dev` when no compatible default
listener is already running.
- Developers may use `--dev` to exercise real account APIs, `GET /api/operator-snapshot`,
and dashboard routes against local runtime state without starting automation.
- Do not use `--dev` for operator automation, queue intake, retained-lane recovery,
Expand Down
24 changes: 15 additions & 9 deletions docs/runbook/self-dogfood-pilot.md
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ is still unmerged, use the normal reviewed lane checkout instead.
After `probe`, `project add`, `run --dry-run`, and `run` all behave as expected, use `serve` for the long-running pilot loop:

```sh
cargo run -p decodex --bin decodex -- serve --interval 60s
cargo run -p decodex --bin decodex -- serve
```

### Installed-binary observer loop
Expand Down Expand Up @@ -419,16 +419,16 @@ wants to observe the self-bootstrap loop without reading source code.
```sh
decodex project add ~/.codex/decodex/projects/decodex
decodex project list
decodex serve --interval 60s --listen-address 127.0.0.1:8912
decodex serve --listen-address 127.0.0.1:8912
```

`serve` owns one operator UI and schedules all enabled registered projects from the
local runtime database. Passing `--config` refreshes that project registration
before the scheduler starts.

Do not use `decodex serve --dev` for this step. Dev mode is only for Decodex App
and local account/app snapshot API development; it does not register projects,
poll Linear, dispatch work, or accept `--config` or `--interval`.
Do not use `decodex serve --dev` for this step. Dev mode is only for local
account/app snapshot API development while avoiding scheduler activity; it does not
register projects, poll Linear, dispatch work, or accept `--config` or `--interval`.

Pass `decodex serve --config <PROJECT_DIR>` when you want `serve` to refresh one
project registration before it starts. Omit it when the registry already contains
Expand Down Expand Up @@ -618,12 +618,18 @@ Decodex is intentionally Unix-only, and the control plane relies on Unix file-de
`decodex serve` owns the local operator console. Use `--listen-address` when you need a non-default bind address:

```sh
decodex serve --interval 60s --listen-address 127.0.0.1:8912
decodex serve --listen-address 127.0.0.1:8912
```

Use hidden `decodex serve --dev` only for Decodex App or local account/app snapshot API
development. It is not a scheduler and must not be used for this runbook's automation,
queue intake, project registration, or retained-lane recovery steps.
Omit `--interval` to use the CLI default 15-second scheduler cadence. Pass
`--interval <INTERVAL>` only when this runbook step deliberately needs a non-default
poll interval.

Use hidden `decodex serve --dev` only for local account/app snapshot API development
while deliberately avoiding scheduler activity. Decodex App's fallback server uses
ordinary `decodex serve` and leaves scheduler cadence to the CLI default. Dev mode is
not a scheduler and must not be used for this runbook's automation, queue intake,
project registration, or retained-lane recovery steps.

The listener serves the operator console from the canonical `GET /` and `GET /dashboard` routes, the same JSON operator snapshot used by `cargo run -p decodex --bin decodex -- status --json` through the `/dashboard/control` WebSocket, and the minimal `GET /livez` liveness probe on the same listener. The single console keeps `Projects`, `Running Lanes`, `Intake Queue`, `Review & Landing`, `Recovery Worktrees`, and `Run Ledger` visible together. Intake candidates that are already claimed by a running lane are shown as active queue echoes, capacity-bound candidates are shown as waiting rather than blocked, running lane worktrees stay with their owning lane, and retained/recovery worktrees remain folded until diagnostics are needed:

Expand Down
14 changes: 7 additions & 7 deletions plugins/decodex/skills/automation/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ decodex project add "$HOME/.codex/decodex/projects/<service-id>"
decodex status
decodex run --dry-run
decodex run
decodex serve --interval 60s
decodex serve
```

From the Decodex repo while developing the runtime:
Expand All @@ -43,7 +43,7 @@ cargo run -p decodex --bin decodex -- project add "$HOME/.codex/decodex/projects
cargo run -p decodex --bin decodex -- status
cargo run -p decodex --bin decodex -- run --dry-run
cargo run -p decodex --bin decodex -- run
cargo run -p decodex --bin decodex -- serve --interval 60s
cargo run -p decodex --bin decodex -- serve
```

Use `decodex serve --config <project-dir>` or
Expand All @@ -52,11 +52,11 @@ wants to register that project and start the scheduler in one command.
Use `decodex run <ISSUE>` or `cargo run -p decodex --bin decodex -- run <ISSUE>` only
for a deliberate one-issue automation pass; it still uses the same retained-lane
eligibility and lifecycle rules.
Do not use hidden `serve --dev` for automation. That mode belongs to Decodex App and
local development: it serves local dashboard/account/app snapshot APIs, but it does
not register projects, poll Linear, or dispatch lanes, and it rejects `--config` and
`--interval`. Use it only when testing the app-owned endpoint without starting the
scheduler.
Do not use hidden `serve --dev` for automation. That mode is for isolated local
development: it serves local dashboard/account/app snapshot APIs, but it does not
register projects, poll Linear, or dispatch lanes, and it rejects `--config` and
`--interval`. Decodex App's fallback server uses ordinary `serve` when no compatible
local listener is already running.

## Intake and Ownership

Expand Down
9 changes: 5 additions & 4 deletions plugins/decodex/skills/manual-cli/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,11 @@ Manual commit and landing are separate narrow workflows:
- Use `run --dry-run` before live automation to validate project loading, issue
discovery, eligibility, and worktree planning without tracker mutation.
- Use `probe stdio://` before relying on the Codex app-server boundary.
- Treat hidden `serve --dev` as Decodex App and local-development infrastructure
only. It serves dashboard, account, and app snapshot APIs, but it does not register
projects, poll Linear, dispatch work, or accept `--config` or `--interval`. Use it
only when testing the app-owned endpoint without starting the scheduler.
- Treat hidden `serve --dev` as isolated local-development infrastructure only. It
serves dashboard, account, and app snapshot APIs, but it does not register projects,
poll Linear, dispatch work, or accept `--config` or `--interval`. Decodex App's
fallback server uses ordinary `serve` when no compatible local listener is already
running.
- For `skills/list` app-server preflight output, enabled skills plus scan diagnostics
are local evidence, not a lane blocker. Missing cwd coverage or zero enabled skills
are blockers; inspect `first_error_path` and `first_error` before changing plugin or
Expand Down