Operator CLI
The autoswe CLI is a Click-based operator interface designed to manage the lifecycle, state, and monitoring of the autonomous Go-rewrite loop. It provides a dual-mode interaction model: a menu-driven interactive TUI for quick status checks and manual interventions, and a suite of subcommands for programmatic or scriptable control of the systemd-managed service. The CLI acts as a thin wrapper around core logic, delegating service control to systemctl, file-system-based state management (sentinels), and direct execution of helper scripts for live monitoring.
Installation and Entry Point
Section titled “Installation and Entry Point”The CLI is not distributed via pip. Instead, the executable ~/.local/bin/autoswe is a symlink to scripts/autoswe.py 1. This script inserts scripts/ onto sys.path to make the autoswe_cli package importable, then delegates to the Click group defined in cli.py. Any standalone script importing from this package must perform the same sys.path insertion.
Command Structure and Registration
Section titled “Command Structure and Registration”The main entry point is the cli Click group, which supports invoke_without_command=True 2. If no subcommand is provided, it defaults to launching the interactive TUI. Commands are registered dynamically by importing them from sibling modules and adding them to the group.
Key command groups include:
- Lifecycle (
ops.py):start,stop,restart,pause,resume,status,logs,attach,serve,watch-agent,verdicts,journey,rules-review. - State Management (
state.py):state_read,state_merge,select_module,write_status,plan_status,judge_status,verify_cells. - Sandbox & Runs (
sandbox.py,runs.py):sandbox,runs_group,preflight_cmd,parity_flip_cmd. - Journey & Gating (
journey.py,gating.py):journey_synth,baseline_capture_cmd,enable_autonomous_cmd,lock_check_cmd. - Utilities (
units.py,lease.py,images.py):install-units,lease_ready_cmd,images.
Lifecycle Control (ops.py)
Section titled “Lifecycle Control (ops.py)”The ops.py module provides commands to control the autoswe systemd service. These commands are protected by a check for the AUTOSWE_RUN_CONFIG environment variable; if set, they refuse to execute to prevent accidental interference with config-run shells 3.
start: Starts theautosweservice, theautoswe-watchdog.timer, and theautoswe-meta-judge.timerviasystemctl --user.stop: Stops the watchdog timer, meta-judge timer, and theautosweservice.restart: Restarts theautosweservice.status: Renders a rich table showing the current state fromstate.json, parity progress (GREEN/RED cell counts), last heartbeat age, GPU status, and proxy health 4.logs: Tails the user journal for theautosweunit, showing the last 50 lines and following 3.serve: Launches thejourney_server.pyHTTP server in the foreground, defaulting to port 8765.
Pause and Resume Mechanism
Section titled “Pause and Resume Mechanism”Pause and resume operations are implemented via file-system sentinels rather than direct process signals, allowing the Go loop to check for pause states at specific hook points.
pause: Creates aPAUSEsentinel file in the runtime directory. An optional--hardflag also creates aPAUSE_HARDsentinel.- Soft Pause: The loop finishes the current vertex, then blocks on the next one 4.
- Hard Pause: Denies every tool call immediately at the next hook fire 3.
resume: Removes bothPAUSEandPAUSE_HARDsentinel files. The loop wakes up within 60 seconds from its nextScheduleWakeuptick 4.
The status command distinguishes between these states:
PAUSE_HARDexists: “[bold red]HARD PAUSED[/]”.PAUSEexists and state isWEDGE_PAUSED: “[bold red]WEDGE-PAUSED[/] - {reason}”.PAUSEexists: “[bold yellow]SOFT PAUSED[/] - {sentinel_text}”.- Neither exists: “[bold green]running[/]”.
Monitoring and Attach
Section titled “Monitoring and Attach”The CLI provides several ways to monitor the agent’s activity.
attach: Creates a tmux session namedautosweif it doesn’t exist, then attaches the user to it 3. The session has a 4-pane layout:- Live agent transcript via
watch_active.py4. - State and parity progress, updated every 5 seconds.
- GPU status and proxy health, updated every 5 seconds.
- Live tail of the
VERDICTSfile.
- Live agent transcript via
watch-agent: Live-tails the currently active Workflow agent transcript by executingscripts/watch_active.py3.verdicts: Displays the last 10 verdicts from theVERDICTSfile, color-coded by outcome (ACCEPT: green, REVISE: yellow, REVERT: red) 4.journey: Displays the latest journey entry fromdocs/journey/iter-*.md.rules-review: Shows pending rule-refinement proposals fromRULES_QUEUE, indicating which are auto-applicable (additive) and which require operator approval (relaxation/removal).
Interactive TUI
Section titled “Interactive TUI”Running autoswe without arguments, or explicitly calling autoswe tui, launches an interactive menu-driven interface 2. The TUI provides a quick-access menu for common operations:
s: Refresh status 5.p: Soft pause.P: Hard pause.r: Resume.a: Attach to tmux session.v: View verdicts.j: View journey.R: Rules review.q: Quit.
The TUI calls core.render_status() to display the current state, parity, and health metrics in a rich panel.
Systemd Unit Installation
Section titled “Systemd Unit Installation”The install-units command renders systemd unit templates from the deploy/ directory and installs them into ~/.config/systemd/user/ 6. It substitutes the @AUTOSWE_REPO_ROOT@ placeholder with the actual repository root path. The units are installed but not enabled or started; the operator must run autoswe start to begin the loop.
"""autoswe_cli - the operator CLI for the autonomous Go-rewrite loop.
There is no pip packaging in this repo: ``~/.local/bin/autoswe`` is a symlink to
``scripts/autoswe.py``, which inserts ``scripts/`` onto ``sys.path`` and delegates
here. Any standalone entry into this package (or anything importing ``journey_synth``)
must do the same ``sys.path`` insertion - ``scripts/`` is not importable by default.
"""
from __future__ import annotations
__version__ = "0.4.0"
"""The `autoswe` Click group. No subcommand → interactive TUI (preserves the old
default). Mirrors the autosre CLI conventions (group + lazy imports in bodies)."""
from __future__ import annotations
import click
from . import __version__
@click.group(invoke_without_command=True)
@click.version_option(version=__version__)
@click.pass_context
def cli(ctx: click.Context) -> None:
"""autoswe - operator interface for the autonomous Go-rewrite loop."""
if ctx.invoked_subcommand is None:
from .tui import run_tui
run_tui()
@cli.command()
def tui() -> None:
"""Open the interactive operator TUI."""
from .tui import run_tui
run_tui()
from .gating import baseline_capture_cmd, enable_autonomous_cmd, lock_check_cmd # noqa: E402
from .images import images # noqa: E402
from .journey import journey_synth # noqa: E402
from .lease import lease_ready_cmd # noqa: E402
from .loop import run # noqa: E402
from .reset import reset_epoch # noqa: E402
from .ops import ( # noqa: E402
attach,
journey,
logs,
pause,
restart,
"""Operator lifecycle/status Click commands (start/stop/restart/status/pause/
resume/logs/serve/attach/verdicts/journey/rules-review). Thin wrappers over
``core.py``; service control via ``systemctl --user``."""
from __future__ import annotations
import subprocess
import click
from . import core
from .core import console
from .paths import REPO
@click.command()
def status() -> None:
"""Show the current state.json + parity progress + last verdict."""
core.render_status()
@click.command()
@click.option("--hard", "-H", is_flag=True, help="HARD pause - deny every tool call immediately.")
def pause(hard: bool) -> None:
"""Pause the loop (soft by default; --hard for an incident stop)."""
core.action_pause(hard=hard)
@click.command()
def resume() -> None:
"""Remove both PAUSE sentinels and let the loop continue."""
core.action_resume()
def _refuse_if_config_env() -> None:
"""These commands control the LEGACY `autoswe` systemd unit. Refuse if a config run
env is active (AUTOSWE_RUN_CONFIG set) so an operator in a config shell can't
accidentally start/stop/restart the live Qwen service. Config runs are supervised
(`autoswe run --config`), they have no systemd unit."""
import os
if os.environ.get("AUTOSWE_RUN_CONFIG"):
"""Shared status helpers + operator actions for the autoswe CLI.
Relocated verbatim (logic-preserving) from the original ``scripts/autoswe.py`` so
both the Click commands (``ops.py``) and the interactive TUI (``tui.py``) share one
implementation. New control-flow (loop/sandbox/journey) lives in sibling modules.
"""
from __future__ import annotations
import json
import os
import subprocess
import time
from rich.console import Console
from rich.panel import Panel
from rich.table import Table
from rich.text import Text
from .paths import (
PARITY,
PAUSE,
PAUSE_HARD,
REPO,
RULES_QUEUE,
RUNTIME,
STATE,
TMUX_SESSION,
VERDICTS,
proxy_base,
)
console = Console()
def _state() -> dict:
if not STATE.exists():
return {"status": "UNINITIALIZED"}
try:
return json.loads(STATE.read_text())
except json.JSONDecodeError as e:
"""The interactive rich-based operator TUI (default when `autoswe` is run with no
subcommand). Logic-preserving move of the menu loop from the original autoswe.py."""
from __future__ import annotations
import time
from rich.prompt import Prompt
from . import core
from .core import console
MENU = """
[bold cyan]s[/] status [bold cyan]p[/] pause [bold cyan]P[/] pause HARD
[bold cyan]r[/] resume [bold cyan]a[/] attach tmux
[bold cyan]v[/] verdicts [bold cyan]j[/] journey [bold cyan]R[/] rules review
[bold cyan]q[/] quit
[dim]CLI: autoswe {run|start|stop|restart|status|pause|resume|journey-synth|sandbox|serve|logs} for non-interactive control[/]
"""
def run_tui() -> None:
while True:
console.clear()
console.rule(f"[bold]sddc-rebuild operator TUI[/] {time.strftime('%H:%M:%S')}")
core.render_status()
console.print(MENU)
try:
choice = Prompt.ask("> ", default="s").strip()
except (KeyboardInterrupt, EOFError):
return
if choice == "q":
return
if choice == "s":
input("\npress Enter to refresh...")
continue
if choice == "p":
core.action_pause(hard=False)
elif choice == "P":
"""Render + install the systemd --user units from the ``deploy/`` templates.
The shipped unit files use the ``@AUTOSWE_REPO_ROOT@`` placeholder instead of a
hardcoded ``~/sddcinfo/repos/autoswe`` so the same templates install correctly
wherever the repo lives (a monorepo checkout, ``/data/repos/autoswe`` on the
appliance, ...). ``install-units`` substitutes the resolved root and writes the
rendered copies into ``~/.config/systemd/user/``: wired but never enabled or
started (the loop stays idle until an operator runs ``autoswe start``).
"""
from __future__ import annotations
import subprocess
from pathlib import Path
import click
from .core import console
from .paths import autoswe_repo_root
PLACEHOLDER = "@AUTOSWE_REPO_ROOT@"
_UNIT_GLOBS = ("*.service", "*.timer", "*.path")
def render_unit(text: str, repo_root: Path) -> str:
"""Substitute the repo-root placeholder in a unit template."""
return text.replace(PLACEHOLDER, str(repo_root))
def _unit_templates(repo_root: Path) -> list[Path]:
deploy = repo_root / "deploy"
out: list[Path] = []
for pattern in _UNIT_GLOBS:
out.extend(sorted(deploy.glob(pattern)))
return out
def install_units(repo_root: Path | None = None, dest: Path | None = None) -> list[Path]:
"""Render every ``deploy/`` unit template and write it to the user systemd dir.
Returns the list of written paths. Does NOT enable or start anything.