Make cockpit instructions unambiguous

This commit is contained in:
saymrwulf 2026-04-27 15:21:42 +02:00
parent b9fda29c9a
commit 2f3b2bc55d
5 changed files with 136 additions and 31 deletions

View file

@ -12,7 +12,7 @@ That is the same as:
./scripts/ratchet next
```
It prints the cockpit: current state, exact next action, interpretation, safe commands, and ratchet rule.
It prints the cockpit: current state, exact next action, interpretation, reference commands, and ratchet rule.
## Your Job
@ -21,8 +21,8 @@ Your job is not to understand every metric.
Your job is:
1. Run `./scripts/ratchet`.
2. Do the first item under `What You Do Now`.
3. If a watch is running, leave it alone until it finishes.
2. Do only what it says under `DO THIS NOW`.
3. Ignore every other command unless `DO THIS NOW` tells you to run it.
4. After a watch finishes, run `./scripts/ratchet` again.
5. If you manually place a Braiins canary, write down the order details outside this repo and wait through the maturity window before judging it.

View file

@ -23,7 +23,7 @@ PYTHONPATH=src ./.venv/bin/python -m braiins_ratchet.cli <command>
## `next`
Prints the cockpit: current state, exact next operator action, interpretation, and safe commands.
Prints the cockpit: current state, exact next operator action, interpretation, and reference commands.
```bash
PYTHONPATH=src ./.venv/bin/python -m braiins_ratchet.cli next

View file

@ -3,6 +3,8 @@ from __future__ import annotations
from dataclasses import dataclass
from datetime import UTC, datetime
from decimal import Decimal
import json
import os
from pathlib import Path
import uuid
@ -12,6 +14,7 @@ from .report import build_text_report
REPORTS_DIR = REPO_ROOT / "reports"
EXPERIMENT_LOG = REPORTS_DIR / "EXPERIMENT_LOG.md"
ACTIVE_WATCH = REPORTS_DIR / "ACTIVE_WATCH.json"
@dataclass(frozen=True)
@ -60,6 +63,19 @@ def start_experiment(planned_cycles: int, interval_seconds: int, hypothesis: str
"- plan: collect public Braiins depth, collect OCEAN state, compute shadow canary, store every proposal.\n"
"- operator_action: none by default; manual action only if report later says manual_canary or manual_bid and operator agrees.\n"
)
ACTIVE_WATCH.write_text(
json.dumps(
{
"pid": os.getpid(),
"run_id": run_id,
"started_utc": started,
"planned_cycles": planned_cycles,
"interval_seconds": interval_seconds,
},
indent=2,
),
encoding="utf-8",
)
return ExperimentRun(run_id=run_id, started_utc=started)
@ -87,6 +103,7 @@ def finish_experiment(
report_path.write_text(_render_run_report(summary, text_report), encoding="utf-8")
with EXPERIMENT_LOG.open("a", encoding="utf-8") as handle:
handle.write(_render_log_completion(summary, report_path, status))
_clear_active_watch(run_id)
return str(report_path.relative_to(REPO_ROOT))
@ -242,6 +259,18 @@ def _ensure_log() -> None:
)
def _clear_active_watch(run_id: str) -> None:
if not ACTIVE_WATCH.exists():
return
try:
payload = json.loads(ACTIVE_WATCH.read_text(encoding="utf-8"))
except json.JSONDecodeError:
ACTIVE_WATCH.unlink()
return
if payload.get("run_id") == run_id:
ACTIVE_WATCH.unlink()
def _default_hypothesis() -> str:
return (
"Depth-aware fillable price plus a small overpay cushion is a better canary trigger "

View file

@ -1,9 +1,12 @@
from __future__ import annotations
from datetime import UTC, datetime
import json
import os
from pathlib import Path
import subprocess
from .experiments import EXPERIMENT_LOG, REPORTS_DIR
from .experiments import ACTIVE_WATCH, EXPERIMENT_LOG, REPORTS_DIR
from .storage import latest_market_snapshot, latest_ocean_snapshot, latest_proposal
@ -13,6 +16,7 @@ def build_operator_cockpit(conn) -> str:
proposal = latest_proposal(conn)
latest_report = _latest_report()
running_runs = _running_runs()
active_watch = _active_watch()
freshness = _freshness_minutes(market.timestamp_utc if market else None)
is_fresh = freshness is not None and freshness <= 30
@ -27,14 +31,16 @@ def build_operator_cockpit(conn) -> str:
f" Latest strategy action: {proposal.action if proposal else 'none'}",
f" Latest run report: {latest_report or 'none yet'}",
f" Experiment ledger: {EXPERIMENT_LOG.relative_to(REPORTS_DIR.parent) if EXPERIMENT_LOG.exists() else 'none yet'}",
f" Active watch: {active_watch or 'none detected'}",
]
if running_runs:
lines.append(f" Ledger has unfinished run markers: {', '.join(running_runs)}")
lines.extend(["", "What You Do Now"])
lines.extend(["", "DO THIS NOW"])
lines.extend(
_next_steps(
_do_this_now(
active_watch=active_watch,
has_ocean=ocean is not None,
has_market=market is not None,
is_fresh=is_fresh,
@ -51,9 +57,10 @@ def build_operator_cockpit(conn) -> str:
" Do not increase spend until multiple mature runs point in the same direction.",
]
)
lines.extend(["", "Safe Commands"])
lines.extend(["", "Reference Commands"])
lines.extend(
[
" Ignore this list unless DO THIS NOW explicitly tells you to use one.",
" ./scripts/ratchet next # read this cockpit",
" ./scripts/ratchet once # fetch one fresh sample and report",
" ./scripts/ratchet watch 2 # run a bounded 2-hour experiment",
@ -64,45 +71,56 @@ def build_operator_cockpit(conn) -> str:
return "\n".join(lines)
def _next_steps(has_ocean: bool, has_market: bool, is_fresh: bool, action: str | None) -> list[str]:
def _do_this_now(
active_watch: str | None,
has_ocean: bool,
has_market: bool,
is_fresh: bool,
action: str | None,
) -> list[str]:
if active_watch:
return [
" WAIT.",
" A watch is already running. Do not start another command.",
" After the watch terminal finishes by itself, run exactly:",
" ./scripts/ratchet",
]
if not has_ocean or not has_market:
return [
" 1. Run: ./scripts/ratchet setup",
" 2. Run: ./scripts/ratchet once",
" 3. Then run: ./scripts/ratchet next",
" Reason: the cockpit needs at least one OCEAN sample and one Braiins sample.",
" Run exactly:",
" ./scripts/ratchet setup",
" Reason: local setup is missing. The setup command will print the next command.",
]
if not is_fresh:
return [
" 1. If a watch is currently running in another terminal, do nothing until it finishes.",
" 2. If no watch is running, run: ./scripts/ratchet once",
" 3. Then run: ./scripts/ratchet next",
" Reason: the latest Braiins sample is stale; do not interpret old price action as a current signal.",
" Run exactly:",
" ./scripts/ratchet once",
" Reason: the latest Braiins sample is stale. The once command will fetch a fresh sample and print this cockpit again.",
]
if action == "manual_bid":
return [
" 1. Run: ./scripts/ratchet report",
" 2. Read the Plain English section.",
" 3. If you manually bid, keep spend tiny and write down the Braiins order parameters.",
" 4. After the order ends, wait through the maturity window before judging it.",
" Run exactly:",
" ./scripts/ratchet report",
" Then read only the Plain English section.",
" Manual Braiins action is allowed only after that report still says manual_bid.",
" Reason: manual_bid is the only profit-seeking signal, but execution is still manual.",
]
if action == "manual_canary":
return [
" 1. If a watch is currently running in another terminal, do nothing until it finishes.",
" 2. If no watch is running, run: ./scripts/ratchet watch 2",
" 3. After the watch finishes, run: ./scripts/ratchet next",
" 4. Read: ./scripts/ratchet experiments",
" Reason: manual_canary means the model sees a bounded learning opportunity, not proven profit.",
" Run exactly:",
" ./scripts/ratchet watch 2",
" Reason: manual_canary means the model sees a bounded learning opportunity, not proven profit. The watch command will print this cockpit again when it ends.",
]
return [
" 1. If a watch is currently running in another terminal, do nothing until it finishes.",
" 2. If no watch is running and you want more data, run: ./scripts/ratchet watch 2",
" 3. If you are done for now, stop. No action is expected from you.",
" STOP.",
" No Braiins action is expected from you.",
" If you want to continue passive learning later, run exactly:",
" ./scripts/ratchet watch 2",
" Reason: observe means the strategy did not find a useful action window.",
]
@ -158,6 +176,63 @@ def _running_runs() -> list[str]:
return [run for run in running if run not in completed]
def _active_watch() -> str | None:
from_state_file = _active_watch_from_state_file()
if from_state_file:
return from_state_file
return _active_watch_from_process_table()
def _active_watch_from_state_file() -> str | None:
if not ACTIVE_WATCH.exists():
return None
try:
payload = json.loads(ACTIVE_WATCH.read_text(encoding="utf-8"))
except json.JSONDecodeError:
return "state file exists but is unreadable"
pid = payload.get("pid")
run_id = payload.get("run_id", "unknown-run")
if isinstance(pid, int) and _pid_exists(pid):
return f"{run_id} pid={pid}"
return None
def _active_watch_from_process_table() -> str | None:
try:
output = subprocess.check_output(
["ps", "-axo", "pid=,command="],
text=True,
timeout=2,
)
except (OSError, subprocess.SubprocessError):
return None
current_pid = os.getpid()
for line in output.splitlines():
stripped = line.strip()
if not stripped:
continue
pid_text, _, command = stripped.partition(" ")
try:
pid = int(pid_text)
except ValueError:
continue
if pid == current_pid:
continue
if "braiins_ratchet.cli watch" in command or "./scripts/ratchet watch" in command:
return f"process pid={pid}"
return None
def _pid_exists(pid: int) -> bool:
try:
os.kill(pid, 0)
except ProcessLookupError:
return False
except PermissionError:
return True
return True
def _freshness_minutes(timestamp_utc: str | None) -> int | None:
if not timestamp_utc:
return None

View file

@ -96,8 +96,9 @@ class GuidanceTests(unittest.TestCase):
text = build_operator_cockpit(conn)
self.assertIn("Braiins sample freshness: stale", text)
self.assertIn("run: ./scripts/ratchet once", text)
self.assertIn("do not interpret old price action", text)
self.assertIn("./scripts/ratchet once", text)
self.assertIn("latest Braiins sample is stale", text)
self.assertIn("DO THIS NOW", text)
if __name__ == "__main__":