feat(rig): provision tg-ctl inbound daemon as a boot LaunchAgent#30
Draft
alex-mextner wants to merge 4 commits into
Draft
feat(rig): provision tg-ctl inbound daemon as a boot LaunchAgent#30alex-mextner wants to merge 4 commits into
alex-mextner wants to merge 4 commits into
Conversation
Add the tg_ctl config block (validate + plan) and the pure, effect-free TgCtlPlan that renders the ai.hyperide.tg-ctl.plist LaunchAgent XML byte-exact to the working hand-created file (sort_keys=False preserves the insertion order so a re-apply is a true no-op). Default-on, per-machine (GLOBAL layer), macOS-only. Mirrors the tmux block's schema style. boot:null and label:null resolve to their defaults (not bool(None)=False / str(None)="None"). Reviewed via multi-model `review`; findings addressed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Runner: _do_provision_tg_ctl writes the byte-exact plist, backs up a differing prior, ensures the log dir, tears down the stale predecessor (com.ultra.codex-tg-bot: bootout + timestamped backup + remove), and (re)loads via launchctl bootout/bootstrap in the gui/<uid> domain. A re-apply against the already-correct loaded plist is a skipped no-op. RIG_TG_CTL_DRY_RUN writes the plist but skips every live/destructive mutation (launchctl AND the stale teardown) so tests/smoke never touch the real launchd domain. Drift: _check_tg_ctl flags missing / divergent / written-but-not-loaded, a leftover plist when boot:false, and the stale predecessor (extra). CLI: GLOBAL status line shows installed / drifted / disabled / unsupported (off-darwin), resolved through the shared plan builder. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
test_tg_ctl.py mirrors test_tmux.py: config validation, byte-exact plist render (incl. against the live machine plist when present, read-only), create/idempotent/conflict/dry-run states, stale-predecessor teardown, drift (missing/modified/extra/not-loaded), status states, and the boot:null / label:null / dry-run-no-stale-removal / off-darwin regressions. conftest neutralizes the default-on tg_ctl provisioner + drift check and stubs the gui-domain launchctl seams suite-wide (dedicated tests restore the real ones with their own HOME-isolated tmp dirs); no test ever touches the real ~/Library/LaunchAgents or runs real launchctl. smoke.sh gains a focused, HOME-isolated, RIG_TG_CTL_DRY_RUN tg-ctl leg and prefers `uv run --extra test pytest`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
docs/config-schema.md: the tg_ctl section (keys, defaults, the byte-exact no-op contract, gui-domain (re)load, stale-predecessor teardown, drift, the RIG_TG_CTL_DRY_RUN seam, and the enabled:false vs boot:false distinction) + the validation paragraph. AGENTS.md: refine the "never mutate a LIVE service" rule — the stateless background daemons (models cron, tg_ctl) are the documented (re)load exceptions. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
rig init/rig applynow provision the tg-ctl inbound control daemon (tg-cli'slong-poll / inject-into-tmux / voice→text daemon, run as
tg-ctl run) as a macOS bootLaunchAgent so it auto-starts at login/boot — exactly like rig already does for the tmux
boot service. On a clean machine,
rig initalone sets it up (the block is default-ON).Design — mirrors tmux-boot
The whole pipeline follows the existing
tmuxpattern (one source of truth shared by plan,apply, drift):
riglib/tg_ctl.py— a pure, stdlib-only, effect-freeTgCtlPlan(analog ofriglib.tmux) that renders~/Library/LaunchAgents/ai.hyperide.tg-ctl.plist. It usesplistlib.dumps(..., sort_keys=False)so the key order matches the hand-created, workinglive plist byte-for-byte —
rig applyagainst the live file is a true no-op (skipped),never a spurious rewrite. Paths are derived from
$HOME; bun is discovered (which bun→~/.bun/bin/bunfallback); config dir honors$TG_CTL_CONFIG_DIR(default~/.config/tg-cli).runner._do_provision_tg_ctl— render → back up a differing prior (timestamped) → write →(re)load. Unlike tmux-boot (which only writes the plist), this agent is (re)loaded via
launchctl bootout/bootstrapin the per-usergui/<uid>domain, so a cleanrig initstarts the daemon without a reboot. Reuses
_timestamped_backup_pathand thefsutilconflict engine.
drift._check_tg_ctl— flags missing / divergent / written-but-not-loaded, a leftoverplist when
boot:false, and the stale predecessor (extra).rig statussurfaces it in theGLOBAL section (installed / drifted / disabled / unsupported-off-darwin).
Config block (GLOBAL layer)
Per-machine concern → belongs in
~/.config/rig/config.yaml(likeharness/tmux/git_hooks),NOT a committed repo
rig.yaml. Default ON;enabled/bootdefault true. Follows thetmux:schema style (fail-closed validation). See
docs/config-schema.md#tg_ctl.Stale-predecessor removal
If
~/Library/LaunchAgents/com.ultra.codex-tg-bot.plist(the dead predecessor) exists,rig applyboots it out, backs it up (timestamped), and removes it.Idempotency — proven against the live plist
A real
rig apply(no dry-run) against this machine's live, loadedai.hyperide.tg-ctl.plistis a
skippedno-op: the plist sha is unchanged and nobootstrap/bootoutfires (thebyte-identical-AND-loaded early-return). Verified directly.
Tests / smoke
510 passed(uv run --extra test pytest tests/).0(bash tests/smoke.sh), including a new focused, HOME-isolated,RIG_TG_CTL_DRY_RUNtg-ctl leg.~/Library/LaunchAgentsor runs reallaunchctl— conftest neutralizes the default-on provisioner + drift check and stubs thegui-domain launchctl seams suite-wide; dedicated tests restore the real ones with their own
temp HOME + a
RIG_TG_CTL_DRY_RUN-style dry-run seam. The real tg-ctl plist sha was unchangedby the entire test run.
Review
Ran multi-model
reviewon the diff; addressed its findings — fixed a dry-run disk-mutationleak in the stale teardown,
boot:null/label:nulldefaulting, off-darwin status wording,collapsed the launchctl bootout/bootstrap helpers, and added regression tests for each.
Do NOT merge — draft.
🤖 Generated with Claude Code