POLYROB v1.0.3
[1.0.3] — 2026-09-19
Added
-
polyrob x-account import-session <storage_state.json> | --auth-token … --ct0 …and
capture-session --out <file>: a desktop-captured X login can now reach a headless
server. The encrypted session store is per-box (Fernet key + identity), so the hand-off
is plain Playwright storage state (or the two login cookies), stored under the server's
own key on import; a file with noauth_tokenis refused. -
Autonomy scheduling. A running board goal no longer starts just ahead of a due cron
job (GOAL_DISPATCH_CRON_HEADROOM_SEC, also honoured while a job is mid-run), and a cron
job classedmoney(payload.priority, set withpolyrob cron edit <id> --priority money) can pre-empt a running board goal: the goal returns toreadywith no failure
counted and aresume_note, the rail runs on the same tick (GOAL_YIELD_FOR_MONEY_RAIL,
default off). A human turn is never pre-empted.polyrob cron edit --schedule '<spec>'
re-times a job with its next run recomputed. -
Goal budgets.
goal_creategainedmax_steps(6–60) andreport_back; the default
budget for a goal that sets none isGOAL_DEFAULT_MAX_STEPS(30, was a literal 20). A goal
created from a chat reports back in one line unlessreport_back=true. -
Owner turns hold the shared workspace on headless surfaces. A Telegram/email owner turn
(like a REPL turn) marks the process busy, writes<data>/locks/turn.active, and takes the
cross-process workspace lock — never refusing the human — so cron/goal ticks and the deploy
waiter defer while you are mid-turn (INTERACTIVE_GATE_MARKER, default on; the lock dir is
derived fromPOLYROB_DATA_DIRwhen unset)./statusshowsturn: … active since HH:MM. -
Rail ledger on every status seat. Every
cron_runnow ends with a terminal event —
done,failed,cut_by_cap,held(owner pause),deferred(owner ask), or
cut_by_restart(orphan reclaimed after a restart) — and theloopsstatus section shows
each enabled job's last outcome (rail EXIT: done 04:18 · 7 steps · 6m, or
started HH:MM, no end recorded) plus arail_cuthealth warning with the remedy. -
External rails as health facts.
/statuswarns when the X browser rail is enabled
without a stored login session (namingpolyrob x-account capture-session), when the
anysite user-search endpoint answers empty three calls running (rail_probeevents), and
when an SMTP login was rejected (email_auth_rejected), each with the remedy. -
Filesystem verbs shaped for JSON-lines data.
jsonl_append(one compact line per
object),jsonl_remove(key, values)(backup → rewrite → verify → atomic replace; one bad
line refuses the whole rewrite),jsonl_validate(counts, bad lines, duplicates), and
copy_file(byte copy, no overwrite unless asked — the backup-before-rewrite primitive). -
x_browser.x_reply— reply under an existing X post through the saved browser session
(the lane the API tier refuses for non-mentioners); owner-approval-gated likex_post. -
Skill revisions from background turns. A background/forged turn's
skill_manage patchof an active skill lands a pending revision under.pending/<id>/for the owner's
polyrob owner promote skill— the active skill is never touched by a forged turn. -
Stale session-directory GC (
core/session_gc.py): per-session dirs untouched for 14
days are reported (dry-run,session_gcevent) and removed only with
SESSION_DIR_GC_APPLY=true; the first pass runs 10 minutes after start, then daily. The
deployer purges the pip cache and vacuums the journal after a successful install. -
The deployer gains an idle-wait entry point: it waits (up to the cron ceiling) for
no running cron job, no running goal and no live turn, then runsdeploy_prod.sh
(which now refuses a direct call unlessDEPLOY_FORCE=1). Units are runtime-masked while
the virtualenv is rewritten and unmasked on start and on rollback. -
polyrob cron edit <id> --max-duration N— change a scheduled job's hard cap
(tenant-scoped, ≤1800 s like the agent tool; applies from the next run). The
EXIT/SCOUT treasury rails had a 240 s cap and timed out on 22 of 24 runs; the
cron ceiling itself rose 600 → 1800 s because the hourly buyback rail runs
1-5 min per step and was cut at step 6 before its swap. -
The
/devowner→developer rail relays through a host spool (<data>/dev_rail/)
drained by an owner-run unit, because the hardened service identity cannot reach
the developer's tools directly.
Fixed
-
Context overflow no longer kills a run. The pre-LLM token check prunes once
(emergency_context_prune) and re-checks before raising; a five-page tool step used to end
the run withToken overflowand lose the round. -
Posture is a ceiling, not a request.
AGENT_COMPUTE_POSTURE>=1addscode_execution,
shellandcodingto autonomous toolsets only when each tool's own flag is on; a
deploy with the flags off no longer stamps a false[tool gap]line on every goal record.
The flag predicates live incore.config_policy.capability_toggles(tools delegate). -
emailis dropped from the effective autonomous toolset while its SMTP login is
rejected (core/credential_verdicts.py, fed by the email tool's 535 path and cleared on
the next success); the tool also remembers a rejected login for 15 minutes instead of
re-sending bad credentials at every session start. -
append_filegiven a JSON object wrote a pretty-printed multi-line record into a
JSON-lines store; it now writes one compact line per object.coding_str_replacecoerces a
JSON object passed asold_string/new_stringto its compact line and says so in the
result instead of failing validation. -
messageis not a missing tool. The goal vocabulary infers themessageaction from
"telegram"; the tool loader recorded it asgated:unknown-toolon every such goal.
Action ids are recognised (ACTION_IDS_NOT_TOOLS); an absent action names its flag. -
Docker socket unreachable → one honest refusal, not a retry storm.
run_tests/
run_coderefuse up front when the agent identity cannot open the Docker socket, naming
the posture and "do not retry". -
x_login_checkdeclares an explicit empty parameter model (ended a per-session WARNING). -
Twitter
get_timelineacceptsmax_results1–100 (the X floor is clamped inside the call). -
Browser stale-context reaper measures idle time, not allocation age.
-
A message drained on the
done()step earns the next step instead of ending the run. -
A host-level money-verb broadcast failure ends the run with the verbatim line.
-
Root-run confined writes take the parent's service group (dirs 0770, files 0660).
-
filesystem_read_fileon an over-cap multi-line file returns a numbered tail
window under aTRUNCATEDheader instead of refusing (append-only ledgers were
refused 15×/6h on prod). -
H-MEM (
TaskContextManager) base path was CWD-relativedata/auto— read-only
underProtectSystem=strict; now<data_home>/auto(DATA_PATHstill wins). -
One log file per service entrypoint (
bot.log/email.log/webview.log,
POLYROB_LOG_FILEoverride) — three non-root units sharing one rotating file
raisedPermissionErroron every emit in two of them. -
deploy_prod.showns$DATA_DIR/autofor the agent identity (its root-run
import-test created it 0700 root and every session then failed at init). -
polyrob update --applyinside a foreign git repo. A wheel whose
site-packages sat inside somebody else's checkout (/work/project/.venv/…)
was classifiedgit, and--applyrangit pull+pip install .against
THAT project.site-packages/dist-packagesnow never resolve a repo root.
Also:--applyon pip/pipx/systemd/docker exits 1 with a structured--json
payload (was exit 0 + nothing); the releasetag_nameis validated and
checked out asrefs/tags/<ref>; rollback isgit reset --keep; one snapshot
restore per failed step (migrate_guarded.pyremoved);--jsonnever blocks
on stdin; draft/pre-releases are filtered; snapshots pruned to 3 after an
apply;--channel gitmeasures the branch against its upstream instead of
the release list;ls /opt/polyrobno longer counts as a running agent. -
Migrations ran in filename order.
v1_10_0_*sorts beforev1_2_0_*;
shipped_migrationsnow sorts by parsed version. The boot-time pre-migration
snapshot now includes the configuredDB_PATH(on prod the one database
being migrated was the one NOT backed up) and takes the updater's
update.lock.session_registry.dbjoins the DB manifest. -
Deployer. One
pip install -c requirements.lock -e "/opt/polyrob[…]"
replaces a non-editable install that left a SECOND code copy in
site-packages (4 of 7 units ran that copy); every active unit sourced from
deployment/is reconciled (the webview unit carrying the de-root hardening
never was);polyrob-browser-server.serviceis quiesced with the family;
free space is checked on$TMP's mount; the character preflight honours
POLYROB_DATA_DIR; the wallet chown is guarded; the import test covers the
telegram surface. -
polyrob servetrustedX-Forwarded-Forfrom anyone — default is now
127.0.0.1; newUVICORN_FORWARDED_ALLOW_IPS. -
polyrob profile create --servicewrote a weakerpolyrob-<name>.service
(collided withpolyrob-email); it now renders the committed
polyrob@.servicetemplate (byte-pinned) and enablespolyrob@<name>. -
polyrob approvals */polyrob config setact on the DEPLOYED data home. -
setup_publish_vhost.shchowns the publish root to the agent (first publish
EACCES'd) and no longer guesses a port for/api/.
Changed
requirements.lockisuv pip compile --all-extras --universal(every extra
pinned, none installed unless named);requirements.txtis a thin pointer
(-c requirements.lock+ the prod extras). Root units that stay root gain
Group=polyrob-data+UMask=0002..dockerignoreadded; the image sets
UVICORN_HOST=0.0.0.0+POLYROB_IN_DOCKER=1.release.ymlruns only on
the public repository.
Removed
webview/webview.service,scripts/publish_prune.sh,
deployment/nginx_continuous_chat_fix.conf,.github/workflows/deploy-portal.yml;
the api+webgate units,nginx.conf, the Xvfb trio and the SSL scripts moved to
deployment/legacy/;deployment/CLEAN_DEPLOY.mdretired.
What's Changed
- release: POLYROB 1.0.3 by @themontreal in #19
Full Changelog: v1.0.2...v1.0.3