Imported from michiTrader/arxi (
AGENTS.md). Install upstream withnpx skills add michiTrader/arxi. Copyright stays with the author.
arxi
Agent orchestration system in Go. The whole project rests on one thesis:
A single pure reducer produces run, simulation, replay and diagnosis as the same machinery.
Decide(State, Event, Config) -> (State', []Effect)
Pure: no clock, no network, no filesystem. Everything it wants to happen in the
world it describes as an Effect and returns; something else carries it out.
That constraint is why arxi run, arxi run --sim, arxi run replay and
arxi run why are one body of logic instead of four programs that drift apart.
Before touching code, read docs/adr/. Thirty-four records, each one stating what was
decided, what alternative was rejected, and what breaks if someone reverts it
without reading. docs/design/10-execution.md has the execution model;
spec/events.md has the event catalog and the blocked_ref contract.
Language policy
EVERYTHING is written in English. No exceptions. No Spanish anywhere.
The user of this project speaks Spanish and will often address you in Spanish. That changes nothing about what goes into files. Conversation with the user may be in Spanish; the repository is English-only. This is the single most common way this rule gets broken — the language of the request leaks into the language of the artifact.
This covers, without exception:
- Identifiers — variables, functions, types, constants, struct fields, packages, file names, directory names.
- Comments — including doc comments (godoc) and inline notes.
- Test names and table-driven test case descriptions, plus every
t.Fatalf/t.Errorffailure message. - User-facing strings — CLI output, usage text, error messages, diagnostics
emitted by the reducer (
diagnosispayloads,whyoutput, remediation hints). - JSON field names and event type names in the wire format and payloads.
- Commit messages, branch names, PR titles and PR descriptions.
- All documentation — ADRs, design docs, specs, README, and this file.
Before writing a single line, re-read this rule. If a comment, identifier, string or commit message you are about to write would naturally come out in Spanish, translate it before it goes in the file. Never write it in Spanish "to fix later" — that is how the mixed state got here in the first place.
Migration status
The migration is done. The repository was originally written in Spanish; a dedicated pass translated all of it — code comments, diagnostic strings, CLI usage text, test names and failure messages, ADRs, design docs and the README. There is no grandfathered Spanish content and no file is exempt.
So the rule is no longer transitional, it is simply the steady state: every line you add or edit is English. If you find Spanish anywhere, it is a regression — report it and fix it in a focused commit.
One warning drawn from how that pass went wrong the first time around, because it is the failure mode to watch for. Translation must be read and rewritten, not substituted word by word. A mechanical pass produced two classes of damage that compiled cleanly and kept every test green, so nothing caught it:
- Comments replaced by a placeholder. 540 argued comments were collapsed
into
// Implementation note.— the entire body of reasoning about why each decision is right and what breaks otherwise was deleted. Those comments are the primary defense against someone later "simplifying" a load-bearing decision. Losing them is worse than leaving them in Spanish. - Word-for-word substitution.
nobecamenotandesbecameisregardless of context, yielding text like "with not runtime" and "nadie is working and nadie can empezar". Even flag names were corrupted:go build -oturned intogo build -or.
If you are ever asked to translate anything here again: read the paragraph, understand the argument, and write the English that makes the same argument with the same force. Never map token to token, and never drop content you have not replaced with an equivalent.
Do not translate unrelated content as a side effect of some other task — that turns a small reviewable change into an unreviewable one.
Why this matters beyond style
The failure messages in this project are load-bearing. Go does not give
exhaustive match, so a switch over Effect missing a variant compiles fine
and the test suite is the only net that catches it (ADR-0007). Those test
messages are the current documentation of the decision they protect — they must
name the consequence and the remedy, and they must be readable by every
contributor and every tool that ingests them.
Comment policy
Comments explain why a decision is right and what breaks otherwise. Never what the code does — the code already says that.
// BAD: increments the counter
// GOOD: Only MemberIdle counts as runnable. Including MemberSubmitted here
// would break quiescence detection in the worst way: a member that already
// delivered would look wakeable forever, so the run would never be reported
// as stuck and the user would wait all night for nothing.
If a comment would still be true after the surrounding logic was rewritten differently, it is describing behavior, not defending a decision. Rewrite it or delete it.
Test policy
Every test protects a decision, and its failure message names the
consequence and the remedy. t.Fatal("mismatch") does not satisfy this
contract.
t.Fatal("two folds of the same log produced different states: replay is worthless")
Because the type system does not cover exhaustiveness or immutability here, the
test suite is part of the architecture, not a recommendation. Nothing merges
without go test ./... passing.
Architectural boundaries
The kernel (internal/kernel) must stay pure. internal/arch_test.go runs
go list and fails if it imports time, net, net/http, os, os/exec,
math/rand, crypto/rand, database/sql, io or bufio. Do not work around
that test — it guards the assumption the entire design rests on. If you need the
clock, the answer is an event, not an import.
Commit policy — MANDATORY, not exceptions
Commit after every file you create or modify. Push after every commit.
This is not process hygiene, it is data-loss prevention with a track record. This sandbox has already been reset three times on this project. The first time destroyed an entire turn of work that was committed locally but never pushed. The times after that cost nothing, because the work was on the remote. A commit that exists only in the local working copy is exactly as fragile as an uncommitted change.
Concretely:
-
After every
Write/Edit/MultiEditcall — or a very small, tightly coupled group of files forming one atomic change (a source file and the fixture it needs) — immediatelygit add+git commitwith a descriptive conventional-commit message in English. -
Push after every commit, or at worst after every small batch, so the remote branch is always close to current.
-
Do not batch a whole feature into one commit at the end. If something goes wrong mid-task, the last pushed commit is the only thing that survives.
-
Open the pull request early — as soon as there is one meaningful commit — and keep updating it. Do not wait for the whole step to be done.
-
Before ending a turn, verify
git statusis clean and thatgit log origin/<branch>..HEADshows nothing unpushed. -
A pull request reporting "merged" is not evidence that the code shipped. Verify against
origin/mainitself:git merge-base --is-ancestor <pr-merge-commit> origin/main && echo in-main ./scripts/audit-merged-prs.sh # audits every merged PR; exits 1 if any is stranded
Never leave a stacked base branch alive after merging it
Stacking a pull request on another is fine; leaving the base branch alive after merging it is not. GitHub re-points a stacked PR onto main only when its base branch is deleted. If the base is merged but kept, the PR above it stays pointed at a branch that no longer leads to main. Merging it then writes a merge commit onto that dead branch: GitHub reports MERGED, CI is green, and main receives nothing.
A full audit of all 73 merged pull requests found this has happened three times, not twice as first recorded:
- #30 — merged into
feat/exec-event-cause. Found only after the audit stopped truncating; it sits at position 45, past the original default of 40. - #33 / #34 — stranded this way. Rescued later by #35, which diagnosed the mechanism precisely in its own description.
- #73 — stranded the same way regardless, after that diagnosis existed. Its ADR (#72) was already published on main, so the decision was visible while the defect it forbids stayed live in the code. The worst available shape.
#30, #33 and #34 are benign today: their content reached main through later pull requests, leaving only dangling merge nodes. Only #73 cost the product working code. The script reports that difference as ORPHANED versus STRANDED rather than flattening the two.
Prose did not prevent the recurrence, which is why the check above is a script. Either merge a stack top-down, or delete each base branch as it merges. Both are safe; keeping a merged base branch is not.
Recovering from a sandbox reset
If the working tree looks empty or reverted, the work is not lost — it is on the remote. Do not rebuild from scratch:
git fetch origin
git checkout -B genspark_ai_developer origin/genspark_ai_developer
Go is not preinstalled and does not survive a reset:
cd /tmp && curl -sSLO https://go.dev/dl/go1.22.5.linux-amd64.tar.gz
sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf go1.22.5.linux-amd64.tar.gz
export PATH=$PATH:/usr/local/go/bin
Working rules
One step at a time, in order. Do not start the executor while blueprint loading is half done.
No new dependencies without justification. The project is standard library only. A CLI that ships as a single static binary with no runtime is a feature, and every dependency is a claim against it. Justify it against that or leave it out.
Respect the frozen surface. internal/surface declares 50 capabilities;
34 are exposed as agent tools. That gap is deliberate — some things a human may
do from a terminal an agent must not be able to do to itself. Adding a command
means implementing something already promised, not inventing a new promise.
The clearest example is trigger run, and it is worth knowing because it is the
only transitive exclusion: it is withheld from agents not for what it does
directly, but because it starts whatever every stored trigger's --then names.
Granting it would grant the union of every scheduled action.
These two numbers are checked by TestEveryDocumentStatingTheSplitIsCurrent.
They were stale for two capabilities before that test existed.
Verify, do not assume. Before reporting a number, measure it. Before saying tests pass, run them. Counts, file contents and git state have all been wrong from memory on this project.
Check a claim at its widest point, not its narrowest. The recurring failure here is not an unverified claim — it is a claim verified once, in one place, and then generalised to a whole capability. It has now happened ten times. Three worth knowing, because they look different and are the same:
- "CI is blocked, the App lacks
workflowspermission." True, and reported for five turns. The refusal covers.github/workflows/only; a checked-in script anywhere else was never blocked. The runner was missing, not the verification. - "Memory arrives on the user channel." True in one assembler, false in the two others ADR-0020 named, for four ADRs (ADR-0025).
- "Phase 7 is not started; the store, retrieval and deletion lineage do not exist." All three existed, and the same status block said so one hundred lines below. The guard nearest to it checked that a status line exists, never what it claimed.
So when a check passes or a blocker is confirmed, state the scope you actually measured. If the claim is about a capability, probe the capability, not the one path you happened to try. A guard whose subject is enumerated by hand goes stale in the cases nobody enumerated — derive the subject from the corpus when you can, and when you cannot, make an unrecognized case fail closed.
A guard must be broken before it is trusted. A test that has only ever passed is decoration: it is indistinguishable from one asserting nothing. Break each check in isolation, confirm it fails, and confirm the message names the offending file and the consequence. Mutation found a "derived" roadmap guard that passed over an empty set, and a vacuity check that would have forced a clean document to fail — the shape that gets a test relaxed instead of a document fixed.
Correct the code, not the test. When a test fails, the default assumption is that the test is right. Three real reducer bugs were found this way, including a broadcast steer that opened a billed turn for an advisory member nobody had activated. Weakening the test would have hidden a bug that costs real money.
Build and test
Requires Go 1.22.
./scripts/verify.sh # gofmt, vet, build and the suite -- the merge gate
./scripts/verify.sh --race # also under the race detector
./scripts/verify.sh --audit # also audit merged pull requests against main
Run verify.sh before proposing a merge. It collects failures rather than
stopping at the first, so one run reports everything that is wrong, and it names
the offending files and failing tests instead of a count.
The individual commands, when you need one in isolation:
go build -o arxi ./cmd/arxi
go vet ./... && gofmt -l .
go test -count=1 ./...
UPDATE_GOLDEN=1 go test ./internal/kernel # regenerate golden fixtures
There is no GitHub Actions workflow, and that is a permission, not a choice
The GitHub App this project is developed through cannot write
.github/workflows/. Measured, not assumed — the push is refused with
refusing to allow a GitHub App to create or update workflow ... without 'workflows' permission.
The refusal covers that directory only. A checked-in script elsewhere was never
blocked, which is why the gate is scripts/verify.sh: the runner was missing,
not the verification. That distinction went unreported for five turns while
every check stayed manual — a blocker verified at its narrowest point and then
generalised to the whole capability, which is the failure shape this repository
keeps finding in its own documents.
Nothing runs automatically. Until somebody with workflows permission adds
the YAML, a green result exists only if a human or an agent ran the script and
said so. When that YAML is added, it should call scripts/verify.sh rather than
restate the checks: two copies of a check list is how the list drifts.