arena-agent
AGENTS.md8 starsChanged 3 months agoMITView on GitHub
This repository is intentionally modular. Do not add new runtime logic to thin compatibility entrypoints or large catch-all files. Architecture - Keep unified_bridge.py a thin compatibility/CLI entrypoint. - Keep wrapper scripts in scripts/ and bin/ thin; real logic belongs under arena/. - Product files must stay under the modularity limit enforced by tests/test_project_modularity.py (currently 1600 lines). Runtime modules under arena/ have an additional limit in tests/test_architecture_boundaries.py (currently 600 lines). Readable code beats squeezed code — if a file is close…
- Deletes or force-pushes
- Installs packages
- Commits and pushes
Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.
Of the AGENTS.md files we read, the 31,774 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
In this repository11 other files it gives its agents.
- arena-bridgeSkill
- book-of-eternitySkill
- browseractSkill
- build-capabilitySkill
- cleanupSkill
- digestSkill
- healthSkill
- snapshotSkill
- auto-fixSkill
- sys-snapshotSkill
- researchSkill
The file
Show its rules and text
We split this AGENTS.md into the rules it gives an agent
23 rules. About 0 are common, 6 are distinctive, and 17 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈6
Distinctive · estimate
This author's own choices. Worth a look.
17
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 10
Keep unified_bridge.py a thin compatibility/CLI entrypoint.
- Very specific: line 11
Keep wrapper scripts in scripts/ and bin/ thin.
- Very specific: line 18
Do not import unified_bridge.py from arena/* modules.
- Very specific: line 19
Every new module in arena/admin/ (tunnels/network providers) must be cross-platform: platform.system() branches, no Linux-only assumptions, …
- Very specific: line 22
Every user-facing installer path (install.sh, install.bat) must
- Very specific: line 23
verify: after installing dependencies — never trust a silent pip install ...
- Very specific: line 25
Never store per-release scratch notes in the repository.
- Distinctive: line 32
Never execute ad-hoc or unrecorded side tasks.
- Very specific: line 39
Sandbox Escape via Bridge: Never download heavy engines (like Godot), 3D assets, or datasets into the 128 MB agent workspace /home/user.
- Very specific: line 47
Current sequence: read docs/TASK_BOARD.md for what is open.
- Very specific: line 134
Use tempfile.NamedTemporaryFile(delete=False) for a single file or tempfile.mkdtemp() (which returns 0o700) when a downstream tool needs to …
- Very specific: line 181
Use
relay.sendinstead of guessing: If a task hinges on a decision only the operator can make (which release to cut, whether to delete … - Very specific: line 202
Run pytest with --no-cov when running a subset, or it fails on Required test coverage of 46% not reached.
- Very specific: line 206
Do not run tests/e2e/test_bridge_live.py through the bridge: it rotates the live bearer token and takes the running service down (issue …
- Very specific: line 246
Never use pytest.importorskip for a dependency a gate relies on.
- Very specific: line 306
If the PID was lost, match on the *worktree path* the run was started in and explicitly exclude the bridge PID -- never on a bare pytest …
- Very specific: line 317
After any such cleanup, confirm /health still answers 200 before continuing.
- Distinctive: line 322
Always re-read git rev-parse HEAD in the worktree before trusting a result.
- Distinctive: line 337
Verify by execution, not by reading: Before claiming "done", run the real artifact end-to-end: build the wheel, install it into a CLEAN …
- Distinctive: line 361
Platform assumptions need a Linux-runnable test: When a fix rests on filesystem or OS behaviour, simulate the hostile platform locally …
- Distinctive: line 379
Do not rely on this rule alone.
- Distinctive: line 396
Before completing any task that creates or modifies files, run the checks that apply to what you touched:
- Very specific: line 416
If anything is reported, fix ALL of it and run again to confirm zero.
≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Arena Agent Codebase Guide for AI Maintainers
This repository is intentionally modular. Do not add new runtime logic to thin compatibility entrypoints or large catch-all files.
Hard rules
Architecture
- Keep
unified_bridge.pya thin compatibility/CLI entrypoint. - Keep wrapper scripts in
scripts/andbin/thin; real logic belongs underarena/. - Product files must stay under the modularity limit enforced by
tests/test_project_modularity.py(currently 1600 lines). Runtime modules underarena/have an additional limit intests/test_architecture_boundaries.py(currently 600 lines). Readable code beats squeezed code — if a file is close to the limit, split it by responsibility instead of collapsing whitespace. - Do not import
unified_bridge.pyfromarena/*modules. - Every new module in
arena/admin/(tunnels/network providers) must be cross-platform:platform.system()branches, no Linux-only assumptions, never invokesudodirectly. - Every user-facing installer path (
install.sh,install.bat) must verify after installing dependencies — never trust a silentpip install ... 2>/dev/null || true. - Never store per-release scratch notes in the repository; use
/tmp/when drivinggh release create --notes-file.
Task Board Discipline & Spec-Kit Protocol (T0..Tn) (v4.169.39+)
- Every action belongs to a tracked task. Any newly discovered bug, refactoring,
or feature must be recorded in
docs/TASK_BOARD.mdwith an identifierT0..Tn. Never execute ad-hoc or unrecorded side tasks. - Strict Definition of Done (DoD):
- Root-cause fix: resolve the true invariant failure, never suppress or silence.
- Deterministic Parity Suite: write isolated unit tests without unmocked sockets or clocks.
- 0 Mutation Survivors: verify with
mutmutand pin the ratchet inscripts/mutation_baseline.json. - Mandatory Bilateral Sabotage: prove that breaking the fix fails tests, and healthy code is 100% green.
- Preflight Pass:
python scripts/preflight.pymust be completely green before release.
- Sandbox Escape via Bridge: Never download heavy engines (like Godot), 3D assets, or datasets
into the 128 MB agent workspace
/home/user. Route heavy workloads, game production, and persistent storage to the host machine via Skainet Bridge (skills/arena-bridge/SKILL.mdanddocs/GODOT_INTEGRATION.md). - Serena Continuity: To prevent context loss across session truncations, read
docs/TASK_BOARD.mdand runpython scripts/serena_reminder.pyupon session bootstrap.
Active work continuity (re-check against the latest operator message and the Task Board)
- Current sequence: read
docs/TASK_BOARD.mdfor what is open; it is the single source of truth for task state and this document deliberately does not duplicate it. Naming specific task numbers here is how this section went stale: it spent three releases telling agents to finish a task that was already closed, and to treat a completed calibration as in-flight.tests/test_agents_md_truthful.pynow fails if this file claims work on a task the board marks complete. A newer operator instruction always overrides this line. - Parallel-agent coordination: Мост is the primary implementation/merge/release agent. The second agent is an independent diagnostic reviewer whose normal output is Issues and reproduction evidence. Treat those findings as untrusted review input: deduplicate and reproduce before changing code. Мост may also perform diagnostic tasks, owns final prioritization, and prevents competing writes or duplicate PRs.
- Upstream game rules: Lottarend explicitly permits independent findings. Prefer
an Issue titled
[Arena AI Finding] ...; Arena cannot assign the label itself. If Issue creation remains unavailable, useIvanSkainet/The-Book-of-Eternity-Rebornand PR intoStanislavSmetaninSSM/The-Book-of-Eternity-Reborn:main. Never push to upstream directly. Rebase and duplicate-search first, account for the ongoing entity- materialization rewrite, keep diffs minimal, and leave review/approval/merge to Codex plus Lottarend as CODEOWNER. - Reviewer probation: the synthetic-corpus calibration for external
reviewers is an initial calibration, not a final product verdict. Keep external reviewers informational and exact-head
triaged;
skipped/quota errors are no signal. Measure at least 10 substantial real PRs before removing an App for low signal. Functional overlap is acceptable when it buys independent quota. Never enable required status, blind autofix, or automatic commits from an external reviewer. - Untrusted fork PRs: inspect the complete diff, author/check provenance, and
review surfaces before approving first-time-contributor Actions. Do not execute
fork code with credentials;
pull_request_targetremains forbidden.
Agent workspace hygiene (added v4.165.0 -- learned the hard way)
An agent sandbox has a snapshot budget (128 MB / 10 000 files on Arena). Blow it and the session starts truncating mid-generation, which reads to the operator as "the platform is broken" rather than "the agent filled the disk". It cost Ivan an afternoon and nearly cost the session. Rules:
- Install tooling outside the snapshot root.
pip install --userwrites to~/.local, which IS captured:ruffandpyreflyalone are 57 MB of binaries. UsePYTHONUSERBASE=/tmp/tools pip install ...and put/tmp/tools/binonPATH. The tools do not survive a snapshot anyway, so paying for them in the budget buys nothing. - Never copy the repository to work on it. A
cp -aof the tree for a background mutation sweep doubles everything. Work in/tmp/, or run in place and rely on the sweep's own dirty-tree check. - Check before finishing a long session:
du -sh ~ && find ~ -type f | wc -l. Anything over ~80 MB deserves a look atdu -sh ~/.*too -- dotfiles are where it hides. git gc --aggressive --prune=nowreclaims real space after heavy fetching (44 MB -> 30 MB here) and is safe on a pushed tree.pipalso leaves ~19 MB in~/.cache; pass--no-cache-diror delete it afterwards.
The working setup, for copy-paste at the start of a session:
export PYTHONUSERBASE=/tmp/tools PATH=/tmp/tools/bin:$PATH
pip install --user -q --no-cache-dir ruff bandit "mutmut==2.5.1" cryptography zizmorMeasured result: 202 MB / 4 079 files (over budget, session truncating)
became 55 MB / 1 603 files with every tool still on PATH.
Security (added v4.46.0 -- these are non-negotiable)
- Every change must pass
make security-scanlocally before commit. The same three gates (bandit / semgrep / pip-audit) run in CI and will block the push regardless. Local iteration is faster. - Never delete a
# nosecor# nosemgrepannotation without also verifying the underlying finding is genuinely no longer applicable. Each existing annotation carries a specific-rationale comment; if you refactor the line and the annotation is stale, remove it and re-run the scan to confirm the rule no longer fires. If it does fire, either fix the finding or write a new annotation with a fresh rationale. - New
# nosec/# nosemgrepin a PR requires a rationale after the marker in the shape# nosec B602 -- <who feeds this input, why the shell/whatever is safe here>. Reviewers grep for the rationale text; a bare# nosecwill bounce. - Never inline a credential-shape string as a test fixture (raw
ghp_...,xoxb-...,AKIA..., etc.). GitHub secret-scanning push protection will reject the commit even for legitimate redaction-test fixtures. Build the fixture at runtime via concatenation ("ghp" + "_" + suffix) — this is the pattern intests/test_observability_redact.pyandtests/test_audit_value_redaction.py. - Never use
tempfile.mktemp()— TOCTOU-racy since Python 2.3. Usetempfile.NamedTemporaryFile(delete=False)for a single file ortempfile.mkdtemp()(which returns 0o700) when a downstream tool needs to create the file itself. - Never use bare
zipfile.ZipFile.extractall()— route througharena.files.safe_extract.safe_extract_zip()which does the pre-scan for zip-slip / symlinks / zip-bomb. - Never use
os.system()— always argv-formsubprocess.run([...], check=False).shell=Trueis allowed only for CLI-side helpers (operator-fed input) with a per-line# nosec B602 -- <specific rationale>naming who feeds the string. - Redaction lives in one place:
arena/observability/redact.py. If you add a new credential shape, extend_VALUE_PATTERNSthere AND add a test intests/test_observability_redact.py. Never inline the regex in a new emit site. - Any new HTTP handler that takes a numeric query/body parameter
must use
arena.handler_helpers.safe_float()orsafe_int(), not rawfloat()/int()— that closes the NaN/±Inf-injection class. - File-mode discipline on anything under
~/.arena/,~/arena-bridge/, or tempfile paths:chmod 0o600on files,chmod 0o700on directories, and re-apply afterrename()because some filesystems reset the mode across rename (ACL-proof pattern established inarena/agentctl_cli/url_cache.py::save).
See SECURITY.md for the full threat model and env-variable reference. CONTRIBUTING.md has the "Security-sensitive areas" section pointing at every file that carries one of these invariants.
The operator can leave you messages (v4.166.0+)
There is a mailbox between you and the operator. It is not a notification you will be shown -- you have to look.
relay.check # MCP tool: oldest unread message, non-blocking
relay.check(wait=25) # block briefly when you want an answer now
relay.reply(in_reply_to=..., body=...)
relay.send(body=...) # ask a question mid-task instead of guessingCheck it at the start of a session and between long steps. The
operator may have left an instruction while no session was running --
overnight, or during a CI wait. relay.check with no arguments costs
milliseconds and returns immediately when the box is empty, so there is
no reason to ration it.
Use relay.send instead of guessing. If a task hinges on a decision
only the operator can make (which release to cut, whether to delete
something), ask. A queued question the operator answers in ten minutes
beats a confident wrong turn you spend an hour on. Say plainly that you
are blocked and on what.
Do not treat silence as agreement. A message sits in the queue until somebody reads it. If nobody replies, that means nobody has looked -- not that they approved. Same rule as everywhere else here: absence of a signal is not a signal.
Same queue as the Dashboard Relay tab and bin/arena-relay, so a
message is visible wherever the operator happens to be.
Traps that have cost real sessions
Concrete, reproducible obstacles. Each one was diagnosed the slow way at least once; none is guesswork.
Test suite
- Run pytest with
--no-covwhen running a subset, or it fails onRequired test coverage of 46% not reached. pytest-randomlyis enabled. Order-dependent failures are real; reproduce with-p no:randomlybefore blaming your own change.- Do not run
tests/e2e/test_bridge_live.pythrough the bridge: it rotates the live bearer token and takes the running service down (issue #209). - A stale, inaccessible
%TEMP%\pytest-of-<user>makes everytmp_pathtest error out at once (2064 of them, in one observed run). Symptom isPermissionError: [WinError 5]inside pytest's owngetbasetemp. Work around it with--basetemp=<fresh dir>(issue #208). - Compare failures by test id, never by count, when deciding whether a branch regressed. Run the same command on the base commit and diff the sets.
Gates and tooling
scripts/quality_ratchet.pyruns locally. Install the analyzers outside the snapshot root and run it before pushing:PYTHONUSERBASE=/tmp/tools pip install pyrefly import-linter vulture ruff python scripts/quality_ratchet.py # vulture + pyrefly vs the baseline python -m importlinter.cli lint-imports # the blocking contractsThis note used to say the analyzers were "not installable in every sandbox" and that findings such as
bad-assignmentcould only be seen in CI. Measured on Linux (Python 3.13) and on the operator's Windows host (Python 3.14): pyrefly 1.2.0, import-linter 2.14 and vulture 2.16 install with plainpipon both,lint-importsreturns 0 on both, and an injectedbad-assignmentis caught locally with the same message CI prints. The claim cost a 20-minute push-and-wait cycle before anyone tried it (#239).Run it on Linux for a clean comparison. pyrefly analyses the whole tree for the platform it is running on, so on Windows it reports
missing-attributefor POSIX-only modules --scripts/chat_append.pyusesfcntl, which does not exist there. Those findings are real for Windows and absent from CI's Linux baseline, so the ratchet exits 1 on Windows against a baseline generated on Linux. That is a platform difference, not a broken install and not new debt.The ratchet fails closed by design: it refuses to report success from a missing or broken pyrefly rather than passing silently. Treat that as "fix the install", not "this can only be checked in CI".
- Never use
pytest.importorskipfor a dependency a gate relies on. If the package is absent the check silently vanishes, and CI reports success while verifying nothing. Pin the dependency in the rightrequirements-*.inand import it plainly. - Locks are hash-pinned. Never hand-edit a
.lock; regenerate with the exact command in its header (uv pip compile --universal --generate-hashes --strip-extras --python-version 3.10 --output-file=<lock> <in>), then runscripts/check_lock_freshness.pyandscripts/lock_conflict_ratchet.py. - Any new ratchet needs monkeypatched failure-branch tests. Disabling a check or widening a tolerance must turn the suite red -- both mutations have slipped through here before.
Merging
masteris protected by a ruleset, not classic branch protection:/branches/master/protectionreturns 404, which does not mean "unprotected". Readgh api repos/{owner}/{repo}/rules/branches/master.required_review_thread_resolutionis on, so every review thread -- bots included -- must be resolved or the merge is refused. Resolve via GraphQLaddPullRequestReviewThreadReplythenresolveReviewThread.
Working over the bridge (Windows host)
/v1/exec/scripttakes a raw body; interpreter, timeout and cwd come from theX-Arena-Interpreter,X-Arena-TimeoutandX-Arena-Cwdheaders. WithoutX-Arena-Timeoutthe server applies its 60 s default and answers 408 no matter how long the client waits.- Anything running longer than about a minute drops the TLS connection. Start it
detached (
Start-Process ... -RedirectStandardOutput) and poll the log file. - Distinguish the failure modes before waiting:
gaierror/000is DNS,Network is unreachableis routing,SSLEOFErroris TLS, 502 means the tunnel is up and the bridge process itself is down. - The host publishes several A records and rotates them, and sometimes returns
only AAAA. Resolve with
AF_UNSPEC, try every address, cache the ones that answer -- and always send the hostname as SNI even when dialling an IP, or TLS closes with a bare EOF. - Quoting through PowerShell is a trap:
python -c "..."with nested quotes,gh ... | python -c, and slicingSelect-Stringresults all fail. Push a.pyfile, run it, parse in Python. gitwrites ordinary output to stderr through the bridge, raisingNativeCommandError. Checkexit_code, not stderr.ghoutput is cp1251: capture bytes and.decode("utf-8", "replace"), and set$env:PYTHONIOENCODING="utf-8".Never kill host processes by image name.
Get-Process python | Stop-Processlooks like it targets a stray test run; the bridge is itself a Python process, so it kills the bridge and every subsequent request returns 502 until an operator restarts it by hand and issues a new token. This has happened.Record the PID when you launch the run and stop exactly that PID:
# launcher writes it $PID | Out-File -Encoding ascii "$L\runner.pid" # cleanup reads it back, and stops the tree rooted at it $runner = Get-Content "$L\runner.pid" Get-CimInstance Win32_Process | Where-Object { $_.ProcessId -eq $runner -or $_.ParentProcessId -eq $runner } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }If the PID was lost, match on the worktree path the run was started in and explicitly exclude the bridge PID -- never on a bare
pytestsubstring, which matches any bridge whose command line happens to mention it:$bridge = <the bridge PID, read from /health or recorded at startup> Get-CimInstance Win32_Process -Filter "Name='python.exe'" | Where-Object { $_.ProcessId -ne $bridge -and $_.CommandLine -like "*$Worktree*" } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force }After any such cleanup, confirm
/healthstill answers 200 before continuing.- Long runs must be started detached and polled through a log file. A run left
going keeps rewriting the worktree under you: a later
git checkoutfails with "Your local changes would be overwritten", and -- worse -- you may be reading output produced by stale code while believing it describes the new commit. Always re-readgit rev-parse HEADin the worktree before trusting a result. - To restore a worktree the run dirtied, be specific about what you revert.
git checkout -- .is not "make it clean": it only restores tracked files, leaves untracked artifacts behind, and applies to the current directory rather than the whole tree -- and it will silently destroy edits someone else made while the run was going. Prefer reverting the known outputs by name (git checkout -- README.md), or run in a dedicated worktree that nobody else is editing, wheregit checkout -- :/plusgit clean -fdis safe.
Verification doctrine (v4.153.3+)
GREEN ≠ WORKS. A passing suite proves only that the tests passed; a green CI check is a sensor reading, not flight status. Success means an observer sees the real thing work.
- Verify by execution, not by reading. Before claiming "done", run the real artifact end-to-end: build the wheel, install it into a CLEAN environment, import it, run the thing. A claim without an execution transcript is a hypothesis.
- Fix the root cause, never the symptom. Silencing a check
(allowlist entries,
|| true, weakened gates, dismissed alerts) requires a written justification in the commit message of why the finding is genuinely a false positive. - Beware fail-open. Pipelines (
cmd | tail,|| true) and missing artifacts have repeatedly hidden real failures here. Scripts must fail CLOSED: missing lock entry, missing file, unexpected exit code = abort, loudly. - A gate that is always green is suspicious. Ratchets and contracts exist to be occasionally red. Negative-test the gate itself when you build one.
- A release is not done until CI is green on its tag. Preflight runs
on Linux only. v4.166.2 shipped with a passing preflight, a verified
SHA, an execution transcript from the downloaded ZIP, and a live check
on the operator's machine -- and its CI still went red on every
Windows and macOS job, because the bug #73 fix used
os.unlinkas an exclusive claim and that is POSIX-only semantics. The operator found it before the agent did. Polling the run once and seeingin_progressis not checking; wait for the conclusion, and treat a release as provisional until then. - Platform assumptions need a Linux-runnable test. When a fix rests
on filesystem or OS behaviour, simulate the hostile platform locally
(monkeypatch the syscall to behave the way Windows does) rather than
letting the CI matrix be the only detector. Bug #75 added
test_the_claim_survives_a_delete_that_does_not_remove_the_namefor exactly this: anos.unlinkthat reports success and removes nothing. Where a property genuinely cannot be observed on Linux -- a shared rename destination is atomic here and racy on Windows -- assert the property directly instead of hoping a runtime test trips over it. - Sabotage every new gate before you claim it works. Deliberately
reintroduce the bug the gate was written for, confirm the gate fails,
then restore. A gate that has never been observed failing is a
decoration. This is mandatory, not a suggestion.
- Restore with
git checkout --or a fresh write, then delete__pycache__: copying a backup file back withcppreserves its mtime, so Python happily keeps serving the mutant.pyc. That cost an hour once — the source read correctly while the imported module was still the sabotaged one. - Do not rely on this rule alone. Measured 2026-08-04: 406 test files
existed, six mentioned sabotage, and this document did not contain
the word. A rule that depends on remembering is not enforcement,
which is why
scripts/mutation_gate.pynow checks it mechanically.
- Restore with
- Scanners check pattern classes, not product invariants. The worst
historical bugs of this repo (broken
pip installduring a green CI, dropped Python-3.10 marker deps) were caught only by end-to-end execution on the real environment matrix — that is why the blockingPackaging E2ECI job and the pre-flight install ritual exist.
Static analysis before you finish (v4.157.0+)
Both debt gates are at zero as of v4.156.0. Zero is a state that has to be actively kept: the Debt visibility CI job turns red on the FIRST new finding, so a single unchecked commit undoes the whole cleanup.
Before completing any task that creates or modifies files, run the checks that apply to what you touched:
ruff check . # must print "All checks passed!"
python scripts/lint_ratchet.py --fail-on-any # must print LINT DEBT ZERO
python scripts/quality_ratchet.py --fail-on-any # must print QUALITY DEBT ZERO
python scripts/js_lint_ratchet.py # only if you touched .jsScope as of v4.157.0: ruff covers the whole tree (TARGETS = (".",)),
pyrefly covers arena, scripts, bin, and oxlint covers dashboard/assets
plus chat_extension. Every one of them is at zero.
The scope grew in two steps and the reason is worth keeping: scripts/ and
bin/ are shipped by make_release_zip.py, and unified_bridge.py is the
main entry point — a gate that excludes those is a preference, not a gate.
"." rather than a list so a new top-level file lands inside the gate by
default instead of silently outside it.
If anything is reported, fix ALL of it and run again to confirm zero. Do not
finish the task on a non-zero count, and do not buy the number with
# type: ignore, # noqa or a looser preset — see docs/pyrefly_debt.md
for why preset = "basic" is forbidden even though it would show fewer
findings.
Adopted from Pyrefly's "Adding Pyrefly Type Checking to Your Agentic Loop" (2026-08). Their point applies exactly here: the tool being installed is not the same thing as the habit of running it, and only the second one keeps the count at zero.
Ratchets and reproducibility (v4.153.3+)
- Lint ratchet:
python scripts/lint_ratchet.pyblocks growth of per-rule ruff counts vsscripts/lint_baseline.json; after cleanups, regenerate the floor with--write-baselinein the same commit. - Quality ratchet:
python scripts/quality_ratchet.pydoes the same for vulture/pyrefly per-kind counts vsscripts/quality_baseline.json. - import-linter contracts (pyproject) are BLOCKING. Never declare a
contract that does not already hold — verify with
lint-importslocally first. - Never bulk-apply ruff
F401/F841auto-fixes: imports here double as re-exports and monkeypatch-by-name targets. Analyze the importers of a name before deleting it. - Debt layers are cleared with AST-proven tools, never by hand or by
blind regex.
scripts/e702_split_statements.pyis the reference shape: it rewrites mechanically, then accepts a file ONLY whenast.dump()is byte-identical before/after — the proof that behaviour cannot have drifted. Its dry run rejected two files on the first pass and caught real damage: rows likeelse: j(...); sys.exit(1)(inline suite — splitting movessys.exitOUT of the branch) and rows that close a multi-line statement (''').strip()); print(out)). Rows that cannot be proven safe are LEFT AS DEBT, not forced. Write the tool, let it reject you, fix the tool. - Hash-locked CI installs only:
requirements-ci.lock(tests + tools),requirements-lint.lock(ruff),requirements-packaging.lock(build/twine/check-wheel-contents). Regenerate with theuv pip compile --universal ...command documented in each.inheader, then ALWAYS verify:python scripts/check_ci_lock.py(uv 0.12.1 has dropped marker-guarded pins on cold caches) followed by a realpip install --require-hashes -r requirements-ci.lockin a fresh venv on the OLDEST supported Python (3.10). No unpinnedpip installin workflows (Scorecard gate). - No system-Python installs on any machine that runs the bridge — isolated venvs only. Bridge connectivity: record ALL transports (Tailscale Funnel / cloudflared / bore) and fail over; Tailscale is fast but flaky, re-probe periodically.
Debt visibility (v4.153.3+)
The blocking ratchets gate growth, so their green checkmark can coexist with the full legacy backlog — and people/AI read checkmarks, not logs. Therefore the backlog owns a dedicated signal:
- CI job debt-visibility (
continue-on-error: true, NON-blocking) runslint_ratchet.py --fail-on-anyandquality_ratchet.py --fail-on-any. It is red BY DESIGN while any debt remains and turns green only at zero. This is the sanctioned noise route for the backlog: red there ≠ broken build; red in the main pipeline = broken build. Never "fix" this job's redness by deleting it — lower the actual debt. - A green main pipeline therefore never means "debt-free". Check the debt-visibility job (or its step summary tables) for backlog numbers.
Tier-2 E2E (v4.153.3+)
tests/e2e/test_bridge_live.pyspawns the REAL server process and drives it over HTTP/MCP (auth, health, handshake, tool call, fs write→read round-trip, jail refusal, abuse cases, graceful teardown).- CI job e2e-installed (BLOCKING) runs the same tests against the
built wheel installed in an isolated venv (
ARENA_E2E_SERVER_CMD), i.e. it exercises the shipped artifact, not the source tree. "Unit tests green" is not evidence the server boots — this gate is.
Where things live
Core:
- HTTP app and routes:
arena/app.py,arena/routes.py,arena/route_registry/ - Request contexts:
arena/contexts/ - Service lifecycle/status/restart:
arena/service/ - Runtime composition wiring:
arena/wiring/* - Runtime dependency namespace for the facade:
arena/runtime_deps/*
Domain modules:
- Admin / tunnels / auth:
arena/admin/tunnels.py— unified multi-provider facade (tunnels_status,tunnels_active,tunnels_start,tunnels_stop)tailscale.py— Tailscale Funnel primitivescloudflared.py— Cloudflare Quick Tunnel with platform-aware hintszerotier.py— cross-platform ZeroTier (HTTP API + CLI fallback)browseract.py— cross-platform BrowserAct CLI statussync_factories.py— sync-callable factories for handler wiringhandlers.py— HTTP handlers for/v1/{sys,tunnels,zerotier,cloudflared,tailscale}/…
- Capabilities map:
arena/capabilities.py,arena/service/capabilities.py - Desktop automation:
arena/desktop/ - Browser / CDP handlers:
arena/browser/cdp/ - Low-level CDP client:
arena/browser/cdp_client/ - Inventory / hardware probes:
arena/inventory/ - Memory and recall:
arena/memory/ - Skills registry:
arena/skills/ - MCP transports/tools:
arena/mcp/ - Dashboard handlers / templates:
arena/gui/,dashboard/assets/ - CLI wrappers / implementations:
bin/*wrappers andarena/*_cli/packages
Skill packages (vendored, cross-platform):
skills/superpowers/— upstream mirror of obra/superpowers (single directory serves both Bridge/v1/skillsand IDE plugin consumers; seedocs/SUPERPOWERS.md)skills/browseract/— Arena wrapper aroundbrowser-act-cli(cross-platform Pythonrun.py+ legacy bash shimrun.sh)
Validation before pushing meaningful changes
Start here — one command, ~12 seconds:
python scripts/preflight.py # add --full before a releaseIt runs every gate that has actually reddened this project's CI, and fails closed when a tool is missing (a skipped check must never read as a pass). Measured over 25 CI runs: 12% failed, and all of them on things a local command could have caught. CI takes ~12 minutes to say the same thing, which is why half the recent commits here are one-line follow-ups.
Notably it runs actionlint -shellcheck shellcheck. Plain actionlint is
not enough: CI runs it with shellcheck, which is how SC2012 slipped through
once and cost a build.
One class it genuinely cannot catch: platform differences. A test that reads
/proc passes everywhere locally and fails the whole macOS matrix. Two
automated heuristics for this were tried and both produced only false
positives (documented in scripts/preflight.py); the defence is the CI
matrix plus writing tests against portable properties.
The longer form, still valid:
python -m py_compile scripts/*.py bin/*.py arena/**/*.py
python -m ruff check . --select F821,F811
pytest -q
# NON-NEGOTIABLE: security gate. Same three checks CI enforces on push.
make security-scanSkipping make security-scan because "it's just docs" is a common
temptation and always wrong -- the scan is fast (~30 s bandit + ~30 s
semgrep + ~5 s pip-audit on a warm cache) and it catches new nosec
markers that need rationales, plus fresh CVEs in deps that landed
between your last pull and now.
For live bridge changes, also run endpoint smoke and
dev/stress-test-v4.py --restart.
For remote-access / provider work, verify the live surface:
curl -sH "Authorization: Bearer $(cat token.txt)" \
http://127.0.0.1:8765/v1/tunnels/status | jq
curl -sH "Authorization: Bearer $(cat token.txt)" \
http://127.0.0.1:8765/v1/capabilities | jq '{network, browser}'Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

