AI Agent Contributions

🤖 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 #

Agent-contributor loop

The loop is made of four components:

  • Workstation agent + gh CLI — 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-authenticated gh CLI 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-unstable image: 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 plus git 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 in kit-fixups, and the generated ebuild is reviewed with mark-devkit autogen-thin before 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 #

  1. Pick & check — gh issue view N --comments, then a duplicate search over open/merged PRs.
  2. 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.
  3. 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.
  4. Verify — emerge to zero errors, qlist proof, then a functional smoke test: run the binary, the upstream test suite, or rebuild one cheap reverse dependency.
  5. Port & generate — express the fix as a kit-fixups spec, run mark-devkit autogen-thin and review the rendered ebuild (SRC_URI, post_src_unpack(), dependencies), then build from the generated ebuild.
  6. PR & review — one branch on the fork, one squashed commit, a body starting with Fixes https://github.com/macaroni-os/mark-issues/issues/N followed 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 BLOCKED report 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=@file does 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.