🤖 Contributions by AI Agents #
Since October 2026, a growing share of the pull requests landing in kit-fixups and in some kit repositories is generated by AI coding agents: an agent session plans the work, spawned subagents (one per issue) do it, and a human reviews every public artifact before and after it lands on GitHub. In a single two-day cycle this loop produced 17 merged PRs and processed around 15 issues from the mark-issues queue.
This chapter explains how that workflow is built — how the agent node reproduces failures, runs tests, generates PRs and survives review — so that maintainers know what to expect when reviewing an agent-authored PR, and contributors can reproduce the setup themselves. The examples are from the MARK stack, but the loop itself is stack-agnostic: the same sandbox-plus-playbook model applies to the Anise/Sambuca stack once its binaries are available. Everything here complements, and never replaces, the classic Became a Contributor guide: agent PRs are held to exactly the same standards as human ones.
🧩 The architecture #
The loop is made of four components:
- Workstation agent +
ghCLI — an OpenCode session on the maintainer’s workstation keeps only the policy: it walks the issue queue top-down, hands each issue to a fresh subagent (sequentially — they share one build sandbox), and folds every convention learned from review into the next subagent’s briefing. All GitHub operations go through an OAuth-authenticatedghCLI and git forks. Nothing is ever built on the workstation itself. - Incus container sandbox — all package work happens in a throwaway container running the
macaroni:macaroni/mark-unstableimage: the live meta-repo under/var/git/meta-repo(each kit is its own git repo),mark-devkit,emerge, and an archive of build logs. Snapshots before every risky phase plusgit checkout -- <kit path>make any test state revertible in seconds — the container is a scratchpad, not an asset. - The kit-fixups autogen loop — a fix is first proven in the meta-repo copy (edit ebuild →
ebuild … manifest→ rebuild → proof), then ported to the autogen spec inkit-fixups, and the generated ebuild is reviewed withmark-devkit autogen-thinbefore the final rebuild. What ships is the spec, but what gets verified is always the ebuild it renders. - The review loop — every batch is followed by a sweep of PR/issue comments; each review thread is answered in place with fresh captured proof, the branch is updated as a single squashed commit, and the maintainer’s request is turned into a standing rule for all future subagents.
🔁 How a typical issue flows #
- Pick & check —
gh issue view N --comments, then a duplicate search over open/merged PRs. - Reproduce — rebuild the package in the sandbox and capture the exact failing lines. Several open issues are already fixed by tree drift; this step rejects those honestly instead of filing a cosmetic PR.
- Fix — pick a known fix class: a C23 pin (
append-flags -std=gnu17), an upstream patch, a version-tracking bump, or an autogen spec/API correction. - Verify —
emergeto zero errors,qlistproof, then a functional smoke test: run the binary, the upstream test suite, or rebuild one cheap reverse dependency. - Port & generate — express the fix as a kit-fixups spec, run
mark-devkit autogen-thinand review the rendered ebuild (SRC_URI,post_src_unpack(), dependencies), then build from the generated ebuild. - PR & review — one branch on the fork, one squashed commit, a body starting with
Fixes https://github.com/macaroni-os/mark-issues/issues/Nfollowed by the evidence, a link comment on the issue, and per-thread replies to review feedback.
Concretely, this is how recent merges came to be: raising the meson autogen selector cap (bounded
by meson’s own Python floor) unblocked packages needing newer meson; fixing asset matchers after the
pnpm upstream renamed its release assets; guarding a cython parallel-build race with a -j 1
serialization while citing the open upstream issue; and splitting opencode into a -bin package
plus a tracked source build across two review rounds.
🛡 Guardrails #
The point of the harness is not to make review easier for the agent — it is to make review meaningful for the maintainer:
- Sandboxed everything — real builds only in a disposable, snapshot-revertible container; the host never touches an ebuild.
- Reproduce before you fix — and check for duplicates before you file anything.
- Evidence or it didn’t happen — only captured command output goes into PR bodies and comments.
When something cannot be verified (license-walled downloads, missing toolchains), the PR says so
explicitly: an honest
BLOCKEDreport with evidence is a valid outcome; a faked green run is not. - A human reviews all public output — every PR, comment and reply is the contributor’s own responsibility, before and after posting.
- Delegating has limits — version policy changes, toolchain migrations and package removals are not auto-PR’d; the loop reports findings and options and lets the maintainers decide.
🚀 Getting started #
The concrete setup behind this chapter, reproduced on any workstation that can run Incus.
Nothing package-related is ever installed on the host itself — the host only runs the agent
session, the gh CLI and the sandbox hypervisor-equivalent.
1. Incus + the Macaroni image server #
# Ubuntu/Debian: `sudo apt install incus`; Fedora: `sudo dnf install incus`
sudo usermod -aG incus-admin $USER # re-login afterwards
# a wide subuid/subgid range, otherwise the Macaroni image fails to unpack:
echo "root:1000000:10000000" | sudo tee -a /etc/subuid /etc/subgid
# official Macaroni OS image server:
incus remote add macaroni https://images.macaronios.org/lxd-images
incus launch macaroni:macaroni/mark-unstable mark-contrib
2. Prepare the build sandbox #
Inside the container (incus exec mark-contrib -- bash) the meta-repo already lives at
/var/git/meta-repo — each kit is its own git repo, so git checkout -- <path> per kit is the
revert tool. Add your fork of kit-fixups and tune the build:
git clone https://github.com/<you>/kit-fixups.git /kit-fixups
echo 'MAKEOPTS="-j$(nproc)"' >> /etc/portage/make.conf
mark-devkit --help # sanity check — comes with the image; if absent, emerge app-misc/mark-devkit
Then snapshot the clean state from the host, and re-snapshot before every risky phase:
incus snapshot create mark-contrib clean-base
3. Host side: gh + OpenCode
#
gh auth login -p https -w # browser OAuth — API + push rights for your forks
curl -fsSL https://opencode.ai/install | bash # or via mise/npm, e.g. `mise use -g opencode`
opencode --version # v2.x
When the agent’s host is a Macaroni OS system itself, OpenCode is already in the tree — install it with the native tooling instead of the upstream installer:
emerge opencode-bin # prebuilt, or: emerge opencode (tracked source build)
anise i opencode # Anise/Sambuca side, once the binary packages are published
4. Boot the loop #
Clone the playbook repository (below), open an OpenCode session inside that directory, and
paste KICKOFF.md as the first message. Then keep the human half of the loop: review everything
the agent is about to post — and everything it already posted.
🚀 Try it yourself #
The full, generalized setup — sandbox provisioning, the exec/credential patterns, the autogen conventions learned from review, the verification checklist and a paste-ready briefing template — lives in the agent-contributor playbook repository:
👉
github.com/macaroni-os/ai-agent-playbook
(README.md to start, PLAYBOOK.md for the rules, KICKOFF.md as the subagent prompt template).
In brief, the conventions every agent PR follows: [<kit>] <category>/<pn>: <short fix> titles with
one squashed commit; prefer builtin-github (or builtin-dirlisting) over builtin-noop so the bot
can auto-track releases; let simple-github.tmpl handle S; move autogen atoms into
base_autogen.yml and remove the stale entry from base.yml; use template macros rather than shell
variables in specs; no multilib, no FreeBSD in mark-* branches.
📎 Lessons we learned (the hiccups, too) #
Two public mistakes happened during the first cycles, and both are worth repeating as advice:
- Token hygiene — an OAuth token briefly leaked into a shell transcript through sloppy quoting.
Agents should never interpolate a token inside printable strings, should scrub command output, and
should treat rotation (
gh auth refresh) as a zero-cost reflex since everything reads the token live. - Never guess CLI semantics —
gh api -f body=@filedoes not expand@file, so a literal local path got posted publicly. The fix was transparent: delete, repost with--body-file, and document the rule. Agents should verify tool semantics instead of assuming them.
Both were caught by a human reviewing the agent’s public output — which is exactly why the human stays in the loop. Everything else the loop got wrong early (a couple of duplicate PRs, stale issues) became a mandatory step in the checklist, and the whole cycle keeps getting cheaper: the eighth fix took a fraction of the effort of the first, because every review comment taught the loop something permanent.