5.1 KiB
RX agent handoff protocol
Purpose
The RX laptop watches origin/video-development with ordinary local Python code. Git polling does not call an LLM, ChatGPT or an agent. A detected task is never executed until the user approves it. The first implementation uses only ManualExecutor: it prepares a local task outbox and does not start a model itself.
Task header
docs/handoff/NEXT_RX_TASK.md starts with these plain key-value fields:
TASK_ID = RF001A_CLKIN_001
TASK_TITLE = Verify RF-001A slave CLKIN
SOURCE_BRANCH = video-development
EXPECTED_HEAD = <full 40-character base commit hash>
REQUIRES_APPROVAL = YES
HARDWARE_ACCESS = YES
RF_RX = YES
RF_TX = NO
ALLOW_COMMIT = NO
ALLOW_PUSH = RX_RESULT_ONLY
RISK_LEVEL = YELLOW
TASK_ID is immutable and unique. A revised task receives a new ID. EXPECTED_HEAD names the known base commit against which the task was written; the watcher requires it to be an ancestor of the fetched task commit. This avoids an impossible self-reference to the commit that contains the header. ALLOW_PUSH = RX_RESULT_ONLY permits only a small result publication to rx-node; it never permits a push to video-development. ALLOW_COMMIT governs task changes in the source checkout; a separately authorized result-only commit on rx-node follows ALLOW_PUSH. RISK_LEVEL is an optional plain field used to declare RED actions other than RF TX.
The watcher rejects missing, duplicate or invalid fields. It reads the file with git show origin/video-development:docs/handoff/NEXT_RX_TASK.md, so it does not need to change the local checkout.
Safety levels and approval
| Level | Operations | Approval rule |
|---|---|---|
| GREEN | Software-only checks, parsing and local result preparation | The task's normal approval applies |
| YELLOW | Hardware inventory, clock, gain or bounded RF RX | The user must choose [A] Approve |
| RED | RF TX, firmware, CPLD, Zadig, USB driver changes, destructive Git or filesystem operations | A normal approval is insufficient; an executor must require the exact separate action AUTHORIZE RF TX or an equivalent task-specific explicit confirmation |
The watcher derives RED whenever RF_TX = YES and rejects a lower declared level. Firmware, CPLD, Zadig, USB driver changes and destructive actions must declare RISK_LEVEL = RED. A non-RF RED approval requires AUTHORIZE <TASK_ID>; RF TX requires exactly AUTHORIZE RF TX. The watcher cannot authorize RED itself, and the executor must reject a body/header conflict.
At the prompt the minimum choices are:
[A] Approve
[D] Defer
[S] Stop watcher
The watcher records the task ID before leaving the prompt. Deferred and already seen IDs are not shown again on every polling cycle. A changed task must have a new TASK_ID.
Local state and executor boundary
Runtime state is stored in .local/rx_agent_state.json; prepared prompts and result skeletons are stored in .local/rx_agent_outbox/<TASK_ID>/. .local/ is ignored by Git. State includes last_seen_task_id, last_completed_task_id, deferred_task_id, running and the watcher PID. Writes use a temporary file followed by an atomic replace.
TaskExecutor is the backend interface. Version 1 provides ManualExecutor, which writes TASK.md, RX_RESULT.md and RX_RESULT.json after approval. It refuses to overwrite an existing task outbox. It does not invoke an agent, access SDR hardware, commit or push. A CodexExecutor may be added only after the Codex CLI is found and tested on the RX laptop. Availability on the main PC is not sufficient evidence.
After a human or separately authorized executor finishes the task, mark the local ID complete:
python -B tools/rx_agent_watcher.py --mark-completed <TASK_ID>
Result and rx-node branch
The result follows RX_RESULT_TEMPLATE.md and has matching Markdown and JSON. The proposed publication branch is rx-node.
The RX laptop must never commit or push to video-development. A separately approved publication step may place only these items on rx-node:
- handoff result Markdown;
- result JSON;
- small log metadata needed to interpret the result.
Raw IQ, video, waveform files, large logs, secrets and machine-local state remain outside Git. The watcher does not create rx-node, switch branches, commit or push. This keeps branch creation and publication subject to the repository's current instructions and a separate explicit authorization.
The main PC later uses git fetch origin rx-node and reads the result without merging it blindly.
Token and model behavior
- Git polling itself does not call an LLM.
- When no new task exists, no model runs.
- A detected but unapproved or deferred task does not run a model.
- After approval,
ManualExecutoronly prepares an outbox. A separately selected executor may invoke one agent. - The executor stops after task completion. The watcher returns to Git polling and consumes no model tokens.
Dry-run contract
Dry-run requires a local --task-file; it does not fetch Git, access SDR, invoke an agent, perform RF RX/TX, commit or push. The tracked fake task is tests/fixtures/watcher_test_task.md with TASK_ID = WATCHER_TEST_001 and RF_TX = NO.