Skip to content

Desktop: Phase 4 (batch) broker owns queue + autonomy - #77

Merged
zkann merged 1 commit into
mainfrom
desktop-broker-actions2
Jun 18, 2026
Merged

Desktop: Phase 4 (batch) broker owns queue + autonomy#77
zkann merged 1 commit into
mainfrom
desktop-broker-actions2

Conversation

@zkann

@zkann zkann commented Jun 18, 2026

Copy link
Copy Markdown
Owner

Next Phase 4 batch: two more action endpoints off FastAPI.

endpoint engine op
POST /api/queue enqueue a run for later (lib.queue_run)
POST /api/autonomy set the per-procedure autonomy dial — gate + fingerprint re-stamp

The autonomy refactor (trust-critical, shared)

Autonomy is the security-sensitive one (the dial PR's fingerprint/drift protection). Its gate + re-stamp/drift logic is moved to a shared stdlib module scripts/sop_writes.py (set_autonomy raising typed exceptions: BadLevel/UnknownSop/DraftNotAllowed/SopDrifted). dashboard_app's /api/autonomy now calls set_autonomy too, mapping the typed exceptions to the same HTTP codes/messages — so the app and the engine gate + write through one implementation, never re-implemented in Node. The dashboard suite is unchanged (78), confirming parity through the refactor.

Engine + broker

  • engine_action: queue + autonomy subcommands, mapping to the existing exit-code→HTTP convention (8→400, 4→404, 9→409, 0→200).
  • broker: both added to the action dispatcher; a shared sanitizeId for the positional id; queue passes --launch-cwd=process.cwd() for FastAPI parity.

Verification

End-to-end against the real engine (autonomy on_its_own → 200 persisted; bad-level/unknown/draft refusals; queue → 200; no token → 401) and tests: engine subcommands + the broker dispatch/exit-code map. 289 stdlib, 78 dashboard, 35 node.

Remaining Phase 4

The osascript launch family (launch / launch-sop / open-session / apply-item) and settings. Then FastAPI is removable.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added broker-gated /api/queue to queue SOP runs with optional stdin inputs and launch working directory settings.
    • Added broker-gated /api/autonomy to set procedure autonomy levels, including validation and consistent drift handling shared across components.
    • Updated the dashboard /api/autonomy endpoint to delegate autonomy persistence and return standardized error codes.
  • Bug Fixes
    • Improved action id handling for /api/run by slug-sanitizing it when constructing argv.
  • Tests
    • Extended CLI and broker tests to cover queue/autonomy, including token-gating, invalid inputs, unknown/draft SOPs, drift behavior, and persistence checks.

@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4879843d-2d67-405e-9087-c92ea5e97c06

📥 Commits

Reviewing files that changed from the base of the PR and between caa40af and 2db70ff.

📒 Files selected for processing (6)
  • desktop/broker.js
  • desktop/broker.test.js
  • scripts/dashboard_app.py
  • scripts/engine_action.py
  • scripts/sop_writes.py
  • tests/test_engine_action.py
🚧 Files skipped from review as they are similar to previous changes (4)
  • desktop/broker.test.js
  • desktop/broker.js
  • scripts/sop_writes.py
  • tests/test_engine_action.py

📝 Walkthrough

Walkthrough

A new shared scripts/sop_writes.py module centralizes SOP autonomy drift-checking and atomic file writes. Two new CLI subcommands (queue, autonomy) are added to engine_action.py. The broker gains routing and token-gating for /api/queue and /api/autonomy. The dashboard /api/autonomy endpoint is refactored to delegate entirely to sop_writes. Tests are added for all new paths.

Changes

Queue and Autonomy Endpoints (Full Stack)

Layer / File(s) Summary
New sop_writes shared module
scripts/sop_writes.py
Introduces typed exception hierarchy (SetAutonomyError, BadLevel, UnknownSop, DraftNotAllowed, SopDrifted), _write_autonomy for drift-checked atomic SOP file replacement using content-hash fingerprinting and os.replace, and set_autonomy as the shared public API consumed by both the dashboard and engine CLI.
Engine CLI queue and autonomy handlers
scripts/engine_action.py, tests/test_engine_action.py
Adds _queue handler (reads stdin or --inputs, calls lib.queue_run with scope and optional --launch-cwd, maps unknown-task ValueError to exit code 8) and _autonomy handler (calls sop_writes.set_autonomy, maps domain exceptions to exit codes 4/8/9); wires both into argparse subcommand registry with their arguments; tests error and success paths for autonomy validation, drift refusal, re-stamping, and queueing.
Dashboard endpoint delegation
scripts/dashboard_app.py
Imports sop_writes, removes inline _SopDrifted and _write_autonomy helpers, and rewrites the /api/autonomy endpoint to delegate to sop_writes.set_autonomy via asyncio.to_thread with exception-to-HTTP-status mapping (BadLevel→400, UnknownSop→404, DraftNotAllowed/SopDrifted→409, other OSError/ValueError→500).
Broker routing and tests
desktop/broker.js, desktop/broker.test.js
Adds sanitizeId() helper for centralized action-id sanitization, expands actionRequest() with argv construction for /api/queue (with --scope, optional --launch-cwd, optional stdin inputs) and /api/autonomy (with --level flag), adds both routes to ACTION_PATHS allowlist for token-gating, and extends broker tests with stub engine subcommands and token-gating assertions for both new routes.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Broker as desktop/broker.js
    participant EngineAction as scripts/engine_action.py
    participant SopWrites as scripts/sop_writes.py

    rect rgba(100, 149, 237, 0.5)
        Note over Client,SopWrites: /api/autonomy flow
        Client->>Broker: POST /api/autonomy (token, id, level)
        Broker->>Broker: token-gate via ACTION_PATHS
        Broker->>EngineAction: spawn autonomy --sop_dir --id --level
        EngineAction->>SopWrites: set_autonomy(sop_dir, sop_id, level)
        SopWrites->>SopWrites: drift check + atomic os.replace
        SopWrites-->>EngineAction: {id, autonomy} or raises
        EngineAction-->>Broker: JSON + exit code
        Broker-->>Client: HTTP 200 / 4xx
    end

    rect rgba(144, 238, 144, 0.5)
        Note over Client,EngineAction: /api/queue flow
        Client->>Broker: POST /api/queue (token, id, inputs, scope)
        Broker->>Broker: token-gate via ACTION_PATHS
        Broker->>EngineAction: spawn queue --sop_dir --id --scope
        EngineAction->>EngineAction: lib.queue_run(...)
        EngineAction-->>Broker: JSON {run_id, project} + exit code
        Broker-->>Client: HTTP 200 / 4xx
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • zkann/smbos#47: The retrieved PR wires the Procedures UI's Queue/Run buttons to the backend queue/run endpoints, while this PR adds the broker+engine implementation for /api/queue (and related argv/subcommand handling), so the UI's queue action depends directly on this PR's /api/queue behavior.
  • zkann/smbos#67: Both PRs touch the same autonomy feature surface—especially scripts/dashboard_app.py's /api/autonomy handler/gating logic—though this PR shifts persistence into the new sop_writes module while the retrieved PR originally added the endpoint and dial behavior.
  • zkann/smbos#75: Both PRs modify the same broker-invoked CLI entrypoint in scripts/engine_action.py (extending its subcommand wiring beyond the run flow), so this PR's new queue/autonomy actions build directly on the retrieved phase-4 engine-action architecture.

Poem

🐇 Hop hop, the broker now knows the way,
Two new paths gated and set to stay.
Drift is checked in one shared lair,
Atomic writes handled with utmost care.
The queue and autonomy dance in line—
This rabbit's code review finds it fine! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: moving queue and autonomy endpoints from FastAPI to the desktop broker in Phase 4.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch desktop-broker-actions2

Comment @coderabbitai help to get the list of available commands and usage tips.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 43f83dc759

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread desktop/broker.js Outdated
return { argv, input: inputs }
}
case '/api/queue': {
const argv = ['queue', sopDir, sanitizeId(body.id), '--scope=' + String(body.scope || 'here'), '--launch-cwd=' + process.cwd()]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Pass the dashboard launch cwd to queued runs

When the broker owns POST /api/queue, this now records the Electron/broker process cwd as --launch-cwd, but the queue semantics depend on the dashboard server's launch cwd: queue_run writes that value into the queue file's project: field, and serve_dashboard.launch(... kind="queue") later opens the Claude session in that folder. If the desktop app is started from Finder/an app bundle, or simply from a different shell than the FastAPI dashboard, “Queue here” will be persisted with an unrelated directory and the queued run will launch in the wrong project.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, fixed. The broker no longer passes its own process.cwd() (an Electron app dir, maybe /) as --launch-cwd -- that would persist an unrelated directory into the queue file's project: and launch the run there. Now it passes --launch-cwd only when the desktop app sets $SMBOS_LAUNCH_CWD (the real launch folder); otherwise it omits it, so the engine uses None and a folder-less SOP simply gets no project (a declared-folder SOP still gets its own folder). Verified: a folder-less SOP queued via the broker now persists project="" instead of the broker cwd.

@zkann

zkann commented Jun 18, 2026

Copy link
Copy Markdown
Owner Author

Adversarial self-review: clean on the trust-critical refactor. Verified line-by-line that sop_writes.set_autonomy + _write_autonomy is a verbatim move of the original inline gate + _write_autonomy (same order: level-validate before find_sop; same sid regex; same status gate; same is_drifted-before-write + always-re-stamp + atomic mkstemp/os.replace). Exception mapping matches the original on both callers (BadLevel->400, UnknownSop->404, DraftNotAllowed/SopDrifted->409, OSError/ValueError->500). The drift/re-stamp security property holds (and the dashboard tests that assert it are unchanged + green).

Two P3s:

  • sanitizeId leading-dash strip (id "--foo" -> "foo" for queue/autonomy, where FastAPI kept "--foo" -> 404): accepted -- the strip is required because the id is an argparse positional, it is owner-authenticated, the UI sends clean slugs, the status gate + drift check still run on whatever resolves, and it just makes queue/autonomy consistent with run (which already stripped). Documented divergence, not an elevation.
  • coverage gap: added an engine-side drift test under the stdlib job (stamp -> clean write re-stamps -> drift the body -> next autonomy write returns 9/409), so the shared trust boundary is exercised from the engine path too, not only via the venv dashboard tests.

@zkann
zkann force-pushed the desktop-broker-actions2 branch 2 times, most recently from 07b615d to caa40af Compare June 18, 2026 18:43

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
scripts/dashboard_app.py (1)

606-611: ⚡ Quick win

Chain the mapped HTTPException raises.

Ruff B904 flags these raises inside except blocks; from exc keeps the original autonomy refusal available for diagnostics and clears the warning.

Proposed fix
         except sop_writes.BadLevel as exc:
-            raise HTTPException(status_code=400, detail=str(exc))
+            raise HTTPException(status_code=400, detail=str(exc)) from exc
         except sop_writes.UnknownSop as exc:
-            raise HTTPException(status_code=404, detail=str(exc))
+            raise HTTPException(status_code=404, detail=str(exc)) from exc
         except (sop_writes.DraftNotAllowed, sop_writes.SopDrifted) as exc:
-            raise HTTPException(status_code=409, detail=str(exc))
+            raise HTTPException(status_code=409, detail=str(exc)) from exc
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/dashboard_app.py` around lines 606 - 611, Chain the exception context
in all three HTTPException raises within the except blocks to comply with Ruff
B904. In the except blocks for sop_writes.BadLevel, sop_writes.UnknownSop, and
the combined except block for sop_writes.DraftNotAllowed and
sop_writes.SopDrifted, add "from exc" to each raise statement so that the
original exception is chained and diagnostics are preserved while clearing the
Ruff warning.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/engine_action.py`:
- Around line 43-48: The exception handlers for sop_writes.BadLevel,
sop_writes.UnknownSop, and the combined handler for sop_writes.DraftNotAllowed
and sop_writes.SopDrifted all use semicolons to combine the print statement and
return statement on single lines, which violates the Ruff E702 rule. Split each
of these three exception blocks so that the print statement and return statement
are on separate lines instead of being joined by a semicolon, maintaining the
same exit code values for each exception type.

---

Nitpick comments:
In `@scripts/dashboard_app.py`:
- Around line 606-611: Chain the exception context in all three HTTPException
raises within the except blocks to comply with Ruff B904. In the except blocks
for sop_writes.BadLevel, sop_writes.UnknownSop, and the combined except block
for sop_writes.DraftNotAllowed and sop_writes.SopDrifted, add "from exc" to each
raise statement so that the original exception is chained and diagnostics are
preserved while clearing the Ruff warning.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ca3960ee-5ad8-416c-9591-2c7afd6ef33b

📥 Commits

Reviewing files that changed from the base of the PR and between 43f83dc and caa40af.

📒 Files selected for processing (6)
  • desktop/broker.js
  • desktop/broker.test.js
  • scripts/dashboard_app.py
  • scripts/engine_action.py
  • scripts/sop_writes.py
  • tests/test_engine_action.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • desktop/broker.test.js
  • scripts/sop_writes.py
  • desktop/broker.js

Comment thread scripts/engine_action.py Outdated
Two more action endpoints off FastAPI:
- POST /api/queue     enqueue a run for later (lib.queue_run)
- POST /api/autonomy  set the per-procedure autonomy dial (gate + fingerprint re-stamp)

The autonomy write is the trust-critical one (the security work from the dial PR), so its
gate + re-stamp/drift logic is MOVED to a shared stdlib module scripts/sop_writes.py
(set_autonomy raising typed exceptions: BadLevel/UnknownSop/DraftNotAllowed/SopDrifted).
dashboard_app's /api/autonomy endpoint now calls set_autonomy too (mapping the typed
exceptions to the same HTTP codes/messages), so the app + the engine gate + write through
ONE implementation -- never re-implemented in Node, no divergence. The dashboard suite is
unchanged (78), confirming parity through the refactor.

- engine_action: queue (lib.queue_run) + autonomy (sop_writes.set_autonomy) subcommands,
  mapping to the existing exit-code -> HTTP convention (8->400, 4->404, 9->409, 0->200).
- broker: /api/queue + /api/autonomy added to the action dispatcher; a shared sanitizeId
  for the positional id; queue passes --launch-cwd=process.cwd() for FastAPI parity.

Verified end-to-end against the real engine (autonomy on_its_own -> 200 persisted; the
bad-level/unknown/draft refusals; queue -> 200; no token -> 401) and by tests: engine
subcommands + the broker dispatch/exit-code map. 289 stdlib, 78 dashboard, 35 node.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@zkann
zkann force-pushed the desktop-broker-actions2 branch from caa40af to 2db70ff Compare June 18, 2026 18:51
@zkann
zkann merged commit 151cf69 into main Jun 18, 2026
7 checks passed
@zkann
zkann deleted the desktop-broker-actions2 branch June 18, 2026 19:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant