Skip to content

feat(justfile): operator recipes for managing STT LaunchAgents - #9

Merged
vr000m merged 7 commits into
mainfrom
feat/stt-agents-justfile
Jun 8, 2026
Merged

feat(justfile): operator recipes for managing STT LaunchAgents#9
vr000m merged 7 commits into
mainfrom
feat/stt-agents-justfile

Conversation

@vr000m

@vr000m vr000m commented Jun 8, 2026

Copy link
Copy Markdown
Owner

What

Adds a repo-root justfile (macOS / launchctl only) as a thin operator layer for managing the multiple pipecat.stt-server* LaunchAgents that run side by side. scripts/install_stt_agent.sh manages exactly one agent per invocation — once whisper/parakeet/nemotron are all installed there's no single way to see them or stop the idle ones. This fills that gap.

Recipes (<backend> ∈ whisper/parakeet/nemotron)

  • just stt-list — cross-agent prefix sweep: state, pid, and live backend/model per canonical socket
  • just stt-status <backend> — wire health probe for one backend (explicit --socket-path)
  • just stt-disable <backend>launchctl bootout: down until next login, keeps the plist
  • just stt-enable <backend>bootstrap + kickstart from the existing plist
  • just stt-install / just stt-uninstall <backend>delegate to install_stt_agent.sh (no plist reimplementation)

Why this shape

  • disable ≠ uninstall / stop: the plist sets RunAtLoad + KeepAlive, so the script's stop (SIGTERM) respawns. stt-disable (bootout) is the session-scoped stop; stt-uninstall removes the plist for good. Documented in the README.
  • One map, mirrored: the backend→(label,socket) map is a checked mirror of the README per-ASR table — a test fails CI on drift.
  • Probe correctness: every status call passes --socket-path explicitly so it can't fall back to a stale STT_WS_*/default socket and probe the wrong agent.

Koda-safety (no pin bump)

Touches neither stt_server/ (the imported client + wire protocol) nor install_stt_agent.sh's existing subcommands — purely additive launchd tooling. A test asserts this negative invariant against the branch diff, so the no-pin-bump claim is mechanically enforced.

Post-review hardening

Two follow-up commits fold in deep-review + adversarial-review findings (see the plan's Issues & Solutions for the full list):

  • Command injection closed{{backend}} is shell-escaped via just's quote() at every call site (it was interpolated as raw shell; just stt-status '$(touch X)' ran the payload). The case arms remain the allowlist.
  • launchctl failures no longer maskedset -uo pipefail does not abort on a failed simple command, so a failed bootout/bootstrap/kickstart was overwritten by the success echo (exit 0 while the agent was still running or never started). Each state change is now guarded and exits non-zero on failure; stt-enable skips kickstart once bootstrap fails.
  • Robustness/test quality_resolve emits one field per line so spaced socket paths parse correctly (bash-3.2-safe, no mapfile); the resolution + README-mirror tests drive the public stt-install recipe instead of the private _resolve; the uv stub dispatch is argv-position-aware; the README-table parser anchors on its header before indexing columns.

Tests

tests/test_justfile_recipes.py (21 tests) uses the repo's hermetic stub-launchctl/id/uv harness (same pattern as test_install_migration.py): disable-keeps-plist, README mirror, probe --socket-path (ignores stale STT_WS_SOCKET), custom-label prefix sweep, stopped-socket tolerance, exact delegation env, idempotency guards, failed-bootout/bootstrap/kickstart propagation, and the Koda-surface diff guard. Full suite: 314 passed, 2 skipped; ruff format + ruff check clean.

Test plan

  • uv run python -m pytest tests/test_justfile_recipes.py -q — recipe suite green (21 passed)
  • uv run python -m pytest -q — full suite green (314 passed, 2 skipped)
  • uv run ruff format --check . && uv run ruff check . — clean
  • just --list — shows the six stt-* recipes; _resolve stays private
  • Injection probe: just stt-status '$(touch /tmp/x)' exits non-zero and creates no file
  • just stt-list verified live against three running agents

Plan

docs/dev_plans/20260607-feature-stt-agents-justfile.md (reviewed via 5-lens /review-plan; all Critical/Important findings folded in before implementation; Status: Complete).

Note

This branch also carries an unrelated docs-only commit (17f003e, "Add STT initial_prompt protocol design") that was auto-committed onto it from a separate workstream. It's docs-only (no code) and intentionally left in place.

vr000m added 7 commits June 7, 2026 21:10
Add a design/handoff doc proposing an optional session-config field "initial_prompt" for STT prompt/vocabulary biasing. Explains why work must start server-side (wire protocol lacks the field), current backend capabilities, and the proposed protocol-first changes: add an "initial_prompt" string to protocol.py, extract and thread it through server config to backends, use it for mlx_whisper.transcribe(..., initial_prompt=...), and have parakeet/nemotron accept-and-ignore. Also documents constraints (≈224-token cap with defensive truncation+logging, soft-bias semantics, session-static, backward compatibility), rollout steps, and verification notes for library support.
Add a development plan (docs/dev_plans/20260607-feature-stt-agents-justfile.md) proposing a repo-root justfile to operate pipecat.stt-server* LaunchAgents. The plan outlines objectives, requirements, phased implementation (stt-list/stt-status; stt-disable/stt-enable and delegated stt-install/uninstall; README/docs), tests (stubbed launchctl/uv harness), CI guards (map mirrors README, Koda-safety diff check), and operator UX decisions (bootout vs uninstall, explicit --socket-path probing, idempotency). This is a planning doc for branch feat/stt-agents-justfile and does not modify scripts or implementation code.
install_stt_agent.sh manages exactly one agent per invocation, so running
whisper/parakeet/nemotron side by side leaves no single way to see them or
stop the idle ones. Add a repo-root justfile (macOS/launchctl) as a thin
operator layer:

- stt-list      cross-agent prefix sweep: state, pid, live backend per socket
- stt-status    wire health probe for one backend (explicit --socket-path)
- stt-disable   launchctl bootout (down until next login; keeps the plist)
- stt-enable    bootstrap + kickstart from the existing plist
- stt-install / stt-uninstall  delegate to install_stt_agent.sh (no plist
                reimplementation)

The backend->(label,socket) map is a checked mirror of the README per-ASR
table; a test fails CI on drift. Koda-safe: touches neither stt_server/, the
wire protocol, nor install_stt_agent.sh's existing subcommands, so no client
pin bump is required.

Tests use the repo's hermetic stub-launchctl/id/uv harness: disable-keeps-
plist, README mirror, probe --socket-path (ignores stale STT_WS_SOCKET),
custom-label sweep, stopped-socket tolerance, exact delegation env,
idempotency, and a Koda-surface diff guard.
stt-list identified agents by backend/label, but consumers (e.g. onoats
config.toml [stt] ws_socket) select by socket path — no shared vocabulary to
correlate the two. Print each canonical agent's socket in the same ~-form the
config uses, so an operator can match a config line to a running agent. Worst
case is whisper, whose socket is stt.sock (not whisper.sock); the socket line
removes that guesswork. Custom labels show '(custom label — not in the
canonical map)' since their socket isn't derivable from the label.
Security:
- Shell-escape the {{backend}} recipe arg via just's quote() at every call
  site and inside _resolve, closing a command-injection vector (an
  attacker-influenced backend was interpolated as raw shell). The case arms
  remain the allowlist; unknown backends still exit non-zero.

Correctness:
- _resolve now emits LABEL/SOCKET/BACKEND one field per line; callers parse
  with three reads (bash-3.2-safe — macOS system bash has no mapfile) so a
  socket path containing spaces no longer mis-splits.

Tests:
- Drive the resolution + README-mirror tests through the public stt-install
  recipe (delegation stub) instead of the private _resolve helper.
- Make the uv stub dispatch argv-position-aware (run / stt_server / status)
  rather than a substring match on the whole arg string.
- Anchor _readme_map on the table header and assert column order before
  indexing, so a future README column change fails loudly.

Docs:
- Add a CHANGELOG [Unreleased] entry for the operator recipes.
- Mark the dev plan Complete and fill Final Results.
- Document the cache_dir/la_dir runtime-eval + override behaviour and the
  deliberate stt-list `exit 0`.
set -uo pipefail does not abort on a failed simple command, so a failed
launchctl bootout/bootstrap/kickstart was masked by the subsequent success
echo — operators could see "booted out" / "bootstrapped + kickstarted" with
exit 0 while the agent was still running or never started.

Guard each state change explicitly: print an error to stderr and exit 1 on
failure, only print success after the operation succeeds. stt-enable skips
kickstart once bootstrap fails.

Tests: extend the launchctl stub with a fail_actions hook and add coverage
for failed bootout, failed bootstrap (kickstart skipped), and failed
kickstart — each asserting non-zero exit and no success line.

Found by adversarial review.
- Dev plan: check all acceptance-criteria boxes, fill Issues & Solutions
  (injection, spaced-path split, launchctl masking, test coupling), and add
  the launchctl failure-propagation fix to Final Results.
- CHANGELOG: add a Fixed entry for stt-disable/stt-enable propagating
  launchctl failures instead of masking them.
@vr000m
vr000m merged commit f029587 into main Jun 8, 2026
4 checks passed
@vr000m
vr000m deleted the feat/stt-agents-justfile branch June 8, 2026 05:18
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