Skip to content

control socket - part d (final) - #5594

Closed
M-Maciej wants to merge 7 commits into
Hmbown:mainfrom
M-Maciej:pr/control-socket
Closed

control socket - part d (final)#5594
M-Maciej wants to merge 7 commits into
Hmbown:mainfrom
M-Maciej:pr/control-socket

Conversation

@M-Maciej

@M-Maciej M-Maciej commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Closes #5533

Summary

The control surface for supervised operation: an opt-in, Unix-only,
newline-framed JSON-RPC socket per running session.

[control_socket]
enabled = true    # default false: feature OFF, behavior unchanged

When enabled, the interactive TUI binds
<sessions-dir>/<session-id>/control.sock (mode 0600) for the running
session, with four verbs:

  • message — structured user message through the composer dispatch path
    (queued under load instead of dropped).
  • interrupt — the extracted Esc cancel body, shared with the Esc key path.
  • relaunch — dispatches the /relaunch slash-command path (seam, no
    mechanics duplicated here).
  • status — turn/goal snapshot answered by the socket thread.

Robustness properties:

  • Typed error codes on every failure envelope.
  • Bounded request line (1 MiB hard cap) and per-verb handler timeouts (5 s),
    so a wedged client cannot stall the session.
  • Socket lifecycle rides the session lifecycle: stale-file takeover on bind,
    live-bind refusal with exponential retry backoff (a refused takeover cannot
    become a per-frame connect-probe and log flood).
  • BSD fix: on BSD/macOS, accept() inherits O_NONBLOCK from the
    nonblocking listener (Linux does not), so an oversized request could drop
    the connection mid-write with BrokenPipe. The accepted stream is set
    blocking after accept — a no-op on Linux, required on macOS.

Windows parses the config key but refuses to bind with a clear error; the
protocol/parsing tests compile and run on Windows.

Merge-order note

Merge order: after the cadence fix (a). The relaunch verb dispatches
/relaunch through the generic command seam — no compile dependency on
(c); until (c) merges, the verb returns command_error. Zero interaction
with the outbox in either direction.

Dead-code budget note (per the #5535 review)

The six protocol types (Request, Method, MessageParams, EmptyParams,
ControlCommand, ResponseResult) are reachable on Windows only from the
portable protocol tests; the plain Windows lib build leaves them unreachable
and CI denies dead code there. Each carries a cfg_attr(not(unix), allow(dead_code)) with an inline comment explaining exactly that scope —
per the review's "removed or justified inline" — and because they are the
same items the single-PR version of this work had flagged, the
scripts/dead-code-budget.json ceiling is unchanged in this branch
(v0.9.11's actual count sits well under the 448 ceiling).

Testing

<!-- Exact gate commands (including the clippy allow list) are in
CONTRIBUTING.md → "Pre-push verification". -->

  • cargo fmt --all -- --check
  • cargo clippy --workspace --all-targets --all-features --locked (warning-free under the CI allow list)
  • cargo test --workspace --all-features --locked

Results: clippy clean on stable 1.98.0 under the CONTRIBUTING.md allow
list; full suite 13,379 passed / 0 failed (same one pre-existing
environmental skip as above). Focused control_socket suite: 14/14
(protocol parsing, typed errors, oversized-request rejection,
bind/takeover/backoff lifecycle, status snapshot).

Checklist

  • Updated docs or comments as needed — docs/CONFIGURATION.md
    documents the new table; Unreleased changelog entry; the module doc
    references the filed Feature: the control surface for supervised operation #5533
  • Added or updated tests where relevant
  • Verified TUI behavior manually if UI changes — I think the socket verbs were
    exercised live against running sessions during development before split, but honestly I don't remember if all, maybe I'll recheck it tomorrow.
  • Harvested/co-authored credit uses a GitHub numeric noreply address —
    every commit is authored under
    130112810+M-Maciej@users.noreply.github.com
  • Every commit carries a Signed-off-by (DCO)
  • No dead-code-budget change (see note above)

…/status verbs

Config-gated [control_socket] table (off by default) binds
<sessions-dir>/<session-id>/control.sock (0600) per running session,
speaking a newline-framed JSON-RPC. Verbs: message (structured
user message through the composer dispatch path; queued under load),
interrupt (the extracted Esc cancel body, shared with the Esc key path),
relaunch (seam: dispatches the /relaunch slash-command path — no
mechanics duplicated here), status (turn/goal snapshot answered by the
socket thread). Wiring: run_event_loop constructs SessionControl and
reconciles/updates/drains once per iteration; the socket runs on
background threads with bounded reads (1 MiB) and 5 s dispatch
timeouts. Unix-only; non-unix parses the key but refuses to bind.

Signed-off-by: M-Maciej <130112810+M-Maciej@users.noreply.github.com>
A second live process holding a session's socket made the per-frame
reconcile retry the connect-probe and warn-log every iteration. Retries
now back off (5 s in prod, 200 ms under test) keyed on the session id,
so switching sessions is never delayed by another session's refusal.

Signed-off-by: M-Maciej <130112810+M-Maciej@users.noreply.github.com>
…atforms

The listener is nonblocking, and on macOS/FreeBSD an accepted socket
inherits O_NONBLOCK from the listener (Linux accepted sockets are
blocking). The connection handler assumes blocking reads, so on macOS a
large request hit EAGAIN mid-frame, the handler dropped the connection,
and the client's in-flight write failed with BrokenPipe — the
oversized-request test failed exactly this way on macOS CI. Setting the
accepted stream back to blocking (a no-op on Linux) makes the handler's
bounded-read model hold on every platform.

Signed-off-by: M-Maciej <130112810+M-Maciej@users.noreply.github.com>
The Windows CI gate (cargo test --no-run) denies unused imports and dead
code under -D warnings. On non-unix targets the socket transport does not
exist, so its imports, timing constants, and request/response types are
unreachable there. Split the io/atomic imports and gate the five socket
timing constants with cfg(unix), and mark the six protocol types
(Request, Method, MessageParams, EmptyParams, ControlCommand,
ResponseResult) with a scoped allow: they stay reachable in the portable
protocol/parsing tests and on unix builds, and are only unreachable in
the plain Windows lib build. The dead-code budget file is untouched.

Signed-off-by: M-Maciej <130112810+M-Maciej@users.noreply.github.com>
…mbown#5533)

Regenerated crates/tui/CHANGELOG.md via scripts/sync-changelog.sh.

Signed-off-by: M-Maciej <130112810+M-Maciej@users.noreply.github.com>
…t module doc

The module doc still carried the pre-filing placeholder numbering; the
issue is filed as Hmbown#5533 and the changelog/PR text already use it.

Signed-off-by: M-Maciej <130112810+M-Maciej@users.noreply.github.com>
@M-Maciej
M-Maciej requested a review from Hmbown as a code owner August 24, 2026 00:52
@github-actions

Copy link
Copy Markdown
Contributor

Thanks @M-Maciej for taking the time to contribute.

This repository is observing a maintainer-managed PR intake gate in dry-run mode, so this pull request is staying open. This note helps maintainers prepare the allowlist before any enforcement is considered.

Please read CONTRIBUTING.md for the expected contribution shape. A maintainer can grant recurring PR access by commenting /lgtm on a pull request.

@Hmbown

Hmbown commented Aug 25, 2026

Copy link
Copy Markdown
Owner

Reviewed from the 0.9.12 integration lane: implements #5533 (unlabeled for v0.9.12 in #5573), currently DIRTY against main — needs a rebase regardless. Same disposition as #5592/#5593: @Hmbown's call on 0.9.12 inclusion; I'll take + verify on request.

Resolves the only conflict: main's `## [Unreleased]` grew a `### Added`
list while this branch appended its control-socket bullet at the same
insertion point. Kept both — main's list, with this PR's bullet appended —
and regenerated crates/tui/CHANGELOG.md via scripts/sync-changelog.sh.

No source files conflicted; every .rs change auto-merged.
@Hmbown Hmbown mentioned this pull request Aug 25, 2026
9 tasks
@Hmbown

Hmbown commented Aug 25, 2026

Copy link
Copy Markdown
Owner

Hi @M-Maciej — maintainer housekeeping on this PR, no code of yours was touched.

What I found. Two separate things were keeping this un-judgeable:

  1. The merge conflict was trivial. I merged origin/main in and the only conflict was CHANGELOG.md + its generated slice crates/tui/CHANGELOG.md: main's ## [Unreleased] / ### Added list grew while your bullet landed at the same insertion point. Every source file — including crates/tui/src/tui/ui/event_loop.rs, crates/tui/src/lib.rs, crates/config/src/lib.rs — auto-merged cleanly. I resolved it as "keep both" (main's list with your bullet appended) and regenerated the slice with scripts/sync-changelog.sh. cargo check --workspace --all-targets passes on the result.

  2. CI had never actually run on this PR. Not once. The repo is set to approval_policy: first_time_contributors for fork PRs, so your pull_request workflows were withheld pending a maintainer click — and nobody was watching that queue. The only two green checks you were seeing (gate, GitGuardian) are the two paths exempt from that gate. That is our process failure, not anything you did. Pushing the merge commit as a maintainer released the gate, so the full matrix is running on this PR right now for the first time.

I also prepended Closes #5533 to the PR description — the required link check wants an explicit closing keyword, and your commits already pointed at that issue.

Heads-up on what CI will report. main is currently red on two required checks that every open PR inherits, so ignore these if you see them:

  • Version driftmain is missing a changelog receipt for feat(tui): make Fleet roster editing discoverable #5604.
  • Test (windows-latest) — two Windows verbatim-path tests (tools::shell::tests::readonly_operands_are_workspace_bounded_and_symlink_aware, tools::subagent::tests::read_only_inspection_roles_execute_pwd_and_absolute_git_log).

Both are fixed by #5610, which is waiting to land. Anything else that goes red is worth your attention.

Sorry this sat for so long without a real signal. Same treatment applied to #5592 and #5593.

@Hmbown

Hmbown commented Aug 25, 2026

Copy link
Copy Markdown
Owner

Merged onto the v0.9.12 integration branch in 397b9cb; re-verified in-tree: crates/tui/src/tui/control_socket.rs (opt-in Unix-only newline-framed JSON-RPC session socket) with its relaunch verb. Closing as landed — thanks @M-Maciej!

@Hmbown Hmbown closed this Aug 25, 2026
Hmbown pushed a commit that referenced this pull request Aug 25, 2026
Opt-in Unix `[control_socket]` JSON-RPC at
`<sessions-dir>/<id>/control.sock` (0600) with message/interrupt/
relaunch/status verbs. Authored by @M-Maciej; taken into the 0.9.12
integration branch with its authorship preserved.

Adapted onto the #5586 extracted modules and the already-landed
lifecycle outbox: doctor posture lives in doctor_cli.rs, config merge
in config/merge.rs, and both `[lifecycle_outbox]` and `[control_socket]`
tables are kept. The `relaunch` verb dispatches `/relaunch` through the
existing command seam.

Evidence:
  rustfmt --edition 2024
  cargo test -p codewhale-config --lib --locked --offline --
    lifecycle_outbox_toml control_socket_toml -> 3 passed
  cargo test -p codewhale-tui --lib --locked --offline --
    control_socket doctor_reports_control_socket_posture
    tui_config_parses_control_socket_table
    -> 15 passed

Co-Authored-By: M-Maciej <130112810+M-Maciej@users.noreply.github.com>
Co-Authored-By: Grok 4.6 <noreply@anthropic.com>
Hmbown pushed a commit that referenced this pull request Aug 25, 2026
/#5594

Replay tonight's M-Maciej PRs onto the integration commits that landed
while the take was in flight (FEAT-019 /loop handler, image attach,
computer-use fallbacks). No further adaptation.

Co-Authored-By: Grok 4.6 <noreply@anthropic.com>
Hmbown pushed a commit that referenced this pull request Aug 25, 2026
/#5594

Pick up the MiniMax fact-guard commit that landed after the previous
replay so the take branch stays a descendant of integration.

Co-Authored-By: Grok 4.6 <noreply@anthropic.com>
Hmbown pushed a commit that referenced this pull request Aug 25, 2026
Take:
- #5592 lifecycle outbox (`[lifecycle_outbox]`, opt-in JSONL + webhook)
- #5593 /relaunch (save like /exit, Unix self-exec resume)
- #5594 per-session control socket (`[control_socket]`, Unix JSON-RPC)

Authorship of the original commits is preserved. Adapted onto the
#5586 extracted modules (doctor_cli, exec_agent, cli_args, config/merge)
and existing /loop locale keys rather than clobbering them. Did not
merge origin/main.

Co-Authored-By: M-Maciej <130112810+M-Maciej@users.noreply.github.com>
Co-Authored-By: Grok 4.6 <noreply@anthropic.com>
Entire-Checkpoint: 01M0W25Y7BH0J5342G4WRNQZJT
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.

Feature: the control surface for supervised operation

2 participants