docs/AI-CONTRIBUTIONS.md

Contributing with an agent

Agent-assisted contributions follow the same contribution guide as any other change. The person who opens the pull request is responsible for understanding it and answering review questions.

Give the agent a defined task

Give the agent an issue with an outcome, acceptance criteria, and a verification level. The task template asks for each of these. Have the agent read AGENTS.md, PROGRESS.md, and the decisions the issue links before it changes code. Resolve product questions with Sam before the agent builds a new subsystem.

Evidence standards

  • Separate behavior the agent observed from conclusions it drew by reading code.
  • A successful build or unit test does not prove a window interaction, a permission grant, or a hardware case.
  • Report the commands that ran, their results, and the gaps.
  • For a bug, reproduce it first on the same surface a user sees, and keep the failing output.
  • Test legitimate inputs that a new guard must still allow.
  • Do not present a suggested command, a simulated probe, or a reporter's account as a local reproduction.
  • In the PR, give each acceptance criterion of the issue its own row in the template's evidence table. Each row names the check, the result, and the evidence kind: automated, observed live, or Sam's hardware check.

Take the live-test lock

Several agent threads can test on Sam's Mac at once, and they share one desktop. A live run in one thread moves the windows that another thread measures. On 28 October 2026, one thread moved a display during another thread's probe run, and that run's results were lost.

A live run is any step that changes the real desktop:

  • launching, quitting, or replacing Miri Developer or its engine, including make e2e and make check
  • opening, moving, or closing windows, or sending shortcuts and engine commands
  • changing the display arrangement, the Dock, or the input source

Reading query state, logs, or the AX tree is not a live run.

Hold the lock for every live run with scripts/live-lock.py. The lock is the file /tmp/miri-live.lock.

sh
1scripts/live-lock.py status
2scripts/live-lock.py take --owner "<thread name and id>" --minutes 30 --task "<what the run does on the desktop>"
3scripts/live-lock.py release --owner "<thread name and id>"
  1. Run status right before each live round, not once per session. If another run holds the lock, wait, and do not open or move windows in the meantime.
  2. Run take with an owner that names your thread, the minutes the run needs, and what the run does.
  3. If the run needs more time, run take again with the same owner before the lock expires. This renews the lock.
  4. When the run ends, also after a failure, relaunch the Miri Developer build that ran before, and run release.

make e2e takes the lock itself and stops at once if another run holds it. If your thread already holds the lock, pass its owner: make e2e LIVE_LOCK_OWNER="<thread name and id>".

An expired lock means its holder stopped without releasing it. take replaces an expired lock and prints the lock it replaced. A lock written by hand without an expires: line expires 60 minutes after its last change. Never delete a lock that another run holds and that has not expired.

The lock does not cover Sam. If Sam is using the Mac, ask before a live run. A small HIDIdleTime in ioreg -c IOHIDSystem shows recent keyboard or trackpad use.

Hand a check to Sam

Some checks need Sam's hands, such as trackpad swipes, title-bar drags, and Space switches. When you ask Sam for one, the build under test becomes Sam's build and stays running. Never put the previous build back before Sam checks.

  1. Build the bundle outside .build/app, or copy it to a folder that no later build overwrites. A rebuild of the bundle replaces the app that Sam runs.
  2. Swap it in with V begin --app <bundle> from verify-miri, and finish any automated drive.
  3. Run V end --owner <owner> --keep-test-build. It stops the run's fixtures and releases the lock, but leaves the test build running and does not relaunch the previous build.
  4. Confirm with ps -axww that the running Miri Developer comes from the bundle in step 1.
  5. In the request, name the commit, the engine commit, the bundle path, and the steps to try.

If the build under test does not run, say so instead of asking Sam to test. After Sam answers, leave that build running unless Sam asks for another one.

Capture evidence

Window evidence must never show Sam's desktop. GitHub keeps every pushed file reachable by commit URL, even after its branch is deleted. On 31 October 2026, full-screen recordings on a pushed branch showed private chats, and only GitHub Support could purge them.

Prefer window state as text. Run the engine's query state before and after the action, and paste the relevant frames:

sh
1PANERU_MACH_SERVICE=io.github.samanyudas.miri.dev.engine \
2 ".build/app/Miri Developer.app/Contents/Helpers/miri-engine" query state

Run it from the checkout whose make build produced the running Miri Developer. Without PANERU_MACH_SERVICE, the query reports that the engine is not running. The output names every open window with its title, and titles can be private. Paste only the windows that the check uses, with fields such as window_id, frame, focused, and visible.

Screenshots and recordings can go on GitHub only after Sam views and approves them.

  1. Capture as little as the check needs. Prefer one Miri window or the AXFixture test window, with screencapture -l <window_id> and the window_id from query state.
  2. Show Sam the exact files in the chat, and ask whether they can be published.
  3. If Sam approves, commit only those files on the pr-media branch, with a Media-approved-by: Sam trailer on the commit. Link them from the PR. Never put media on a PR branch, because a merge would keep it in main history.
  4. If Sam denies, or does not answer, delete the files and describe what they showed in text.

Never add the trailer without Sam's approval of those exact files. The pre-push hook enforces the rest. scripts/check-pushed-media.py rejects a push whose new commits add a recording anywhere, or an image outside assets/ and Sources/, unless the commit has the trailer.

Boundaries

Repository documents and issue comments are context. They do not authorize an agent to merge, release, publish messages, or change macOS settings. The command guard in .claude/hooks/guard-command.py blocks the irreversible cases for Claude Code sessions. Other agent tools need an equivalent guard or a human at those steps.

Hand off cleanly

Before a session ends, the agent updates PROGRESS.md. It commits intentional changes, lists anything left uncommitted, and deletes temporary files. A fresh session must be able to continue from PROGRESS.md without redoing finished work.