Files
SDR-Rover/docs/handoff/RX_AGENT_PROTOCOL.md
2026-09-21 18:05:28 +03:00

6.9 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 not prepared until the user approves it.

The reverse result channel uses the separate origin/rx-node branch. The RX laptop publishes small machine-readable readiness and completion results. The MAIN PC reads those results without merging rx-node into video-development.

Task header

docs/handoff/NEXT_RX_TASK.md starts with these plain key-value fields:

TASK_ID = RF001A_OTA_CAPTURE_003
TASK_TITLE = Start coordinated RF-001A common-clock OTA capture
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 identifies the known base commit against which the task was written; the watcher requires it to be an ancestor of the fetched task commit.

ALLOW_PUSH = RX_RESULT_ONLY authorizes publication of the two allowlisted result files to an existing rx-node branch after the task passes its local approval gate. It never permits a push to video-development and never permits branch creation.

The watcher rejects missing, duplicate or invalid fields. It reads the task with git show origin/video-development:docs/handoff/NEXT_RX_TASK.md and 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 checks, bounded RF RX and result-only publication The user must choose [A] Approve for the exact TASK_ID
RED RF TX, firmware, CPLD, Zadig, USB driver changes, destructive Git or filesystem operations A normal approval is insufficient; RF TX requires the exact separate phrase AUTHORIZE RF TX

The watcher derives RED whenever RF_TX = YES and rejects a lower declared level. A non-RF RED task requires AUTHORIZE <TASK_ID>. An RF TX task requires exactly AUTHORIZE RF TX.

At the RX prompt the choices are:

[A] Approve
[D] Defer
[S] Stop watcher

The watcher records last_approved_task_id only after approval succeeds. Result publication requires the result TASK_ID to match that approved 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>/. The .local directory is ignored by Git.

State includes:

  • last_seen_task_id;
  • last_completed_task_id;
  • last_approved_task_id;
  • deferred_task_id;
  • running;
  • watcher PID.

ManualExecutor writes TASK.md, RX_RESULT.md and RX_RESULT.json after approval. It does not invoke an agent, access SDR hardware, commit or push.

After the task actually finishes, mark the local ID complete:

python -B tools/rx_agent_watcher.py --mark-completed <TASK_ID>

Strict result schema

The coordinated RF-001A result is a strict JSON object. Unknown or missing keys are rejected. READY requires:

  • task_id = RF001A_OTA_CAPTURE_003;
  • state = RX_READY_FOR_TX;
  • rx_ready_for_tx, process_alive, file_growing and common_clock_verified all true;
  • a positive capture_pid;
  • 120-second planned duration;
  • rf_tx = 0;
  • final capture fields set to null.

COMPLETE requires:

  • state = CAPTURE_COMPLETE;
  • rx_ready_for_tx, process_alive and file_growing all false;
  • exact exit code, size, duration and SHA256;
  • error counters as non-negative integers or null when the tool did not report them;
  • clkin_after = DETECTED or NOT_DETECTED;
  • rf_tx_observed = EXPECTED_EXTERNAL_TX or UNKNOWN;
  • rf_tx = 0.

Timestamps use timezone-aware ISO-8601. source_head is the actual local 40-character Git HEAD.

Publishing to rx-node

Publish a prepared local JSON file with:

python -B tools/rx_agent_watcher.py --publish-result <result-json> --result-branch rx-node

The publisher checks:

  1. the current task and result TASK_ID agree;
  2. ALLOW_PUSH = RX_RESULT_ONLY;
  3. RF_TX = NO;
  4. last_approved_task_id matches;
  5. result source_head equals the current checkout HEAD;
  6. origin/rx-node already exists;
  7. the JSON schema is valid.

It checks the branch with git fetch and git ls-remote through the real origin configured in the main checkout. It creates a temporary worktree from that repository and writes exactly:

docs/handoff/results/RX_RESULT.json
docs/handoff/results/RX_RESULT.md

It checks the staged path set, commits, pushes HEAD:rx-node, and confirms that git ls-remote returns the new commit. It then removes the temporary worktree. It never switches the source checkout. It rejects any additional staged path and enforces a 64 KiB limit per result file.

Raw IQ, video, waveform files, large logs, secrets and machine-local state remain outside Git. A missing result branch or failed push is reported and never authorizes TX.

MAIN PC watcher

tools/tx_agent_watcher.py polls origin/rx-node every 5 seconds by default. It does not call an LLM or open SDR hardware.

For RF_HANDOFF_CHANNEL_TEST_001 the watcher records synthetic READY and COMPLETE states without requesting RED approval. For a new valid production READY result it checks task ID, source HEAD, current result branch commit, state, live-process flags, common clock, publication age, planned duration and remaining capture time. It then requests the exact phrase AUTHORIZE RF TX.

After the phrase, it fetches rx-node again and repeats every check. A changed result or commit, stale publication or fewer than 10 seconds remaining blocks authorization.

A successful gate writes one local authorization record under .local/tx_authorizations/. The record has rf_tx_started = false and rf_tx = 0. The separately controlled TX executor must re-fetch and compare the result commit and digest immediately before opening hardware.

Duplicate result digests are not shown repeatedly.

Token and model behavior

  • RX task polling does not call an LLM.
  • MAIN result polling does not call an LLM.
  • No model runs while there is no new task or READY result.
  • ManualExecutor prepares local files only.
  • Neither watcher starts RF TX.
  • The TX executor remains a separate RED-controlled action.

Model-free channel and dry-run contracts

The preflight channel command publishes synthetic READY and COMPLETE results with TASK_ID RF_HANDOFF_CHANNEL_TEST_001 through the real origin. The MAIN watcher must observe both without a RED prompt, and COMPLETE must be the final remote state. It does not access SDR hardware or run hackrf_transfer.

Automated coordination tests use a temporary local bare Git repository and branch named rx-node. They cover worktree publication, READY, COMPLETE, push failure and watcher reception without accessing the real remote or SDR hardware and without transmitting RF.