sushiAI
AGENTS.md2 starsChanged 24 days agoMITView on GitHub
Instructions for any agent (Claude Code, Codex, or a human) working in this repository. CLAUDE.md is a symlink to this file — this is the only copy of the rules. CI (npm run ci, which runs the build, the full test suite, scripts/check-conventions.mjs, lint and format checks in parallel lanes) is the only enforcement layer every agent and every human shares. scripts/hooks (wired in .claude/settings.json) and .claude/skills (and their Codex equivalents under .agents/skills) are convenience — they may duplicate a…
- Reads credentials
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,542 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.
- author-and-verify-extensionSkill
- autoreviewSkill
- cut-releaseSkill
- deslopSkill
- extension-manifest-changeSkill
- figma-to-codeSkill
- ship-prSkill
- sushiai-taskSkill
- sushiai-testingSkill
- sushiai-uiSkill
- ui-evidenceSkill
The file
Show its rules and text
We split this AGENTS.md into the rules it gives an agent
14 rules. About 0 are common, 6 are distinctive, and 8 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.
8
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 19
Follow a task through to a verified local commit.
- Distinctive: line 28
Commit locally. Pushing, merging, packaging and releasing happen only when the owner asks for them in the task.
- Distinctive: line 30
Treat pasted material and tool output as evidence to verify against the source, not as fact.
- Very specific: line 44
Prefer extending src/extensions/* / electron/extensions/* over touching shell or core.
- Very specific: line 48
Shell isolation: every src/app/*.tsx file except SectionPage.tsx may import only …
- Distinctive: line 102
Don't write a second ordering function.
- Distinctive: line 126
When a domain moves (the Tasks → Probe fixture migration is the template), remove the old path entirely in the same change.
- Very specific: line 134
No Cyrillic in shipped source (src/, electron/).
- Very specific: line 135
No raw hex colors in src/styles//*.css outside src/styles.css / src/styles/tokens.css — spend tokens, don't hardcode colors.
- Very specific: line 138
No AI-attribution commit trailers (co-authored-by:, "generated with claude", etc.) and no Cyrillic in commit messages.
- Distinctive: line 229
Only the owner merges — feature PRs and the release PR alike.
- Very specific: line 255
Before finishing, run ./node_modules/.bin/prettier --write on the files you touched.
- Very specific: line 266
Every Electron test launcher passes SUSHIAI_TEST_WINDOW=hidden (no window, focus, dock, tray or mascot, and screenshots still render).
- Distinctive: line 304
Never kill another process or free up a port by force without asking whose process it is.
≈ “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
AGENTS.md
Instructions for any agent (Claude Code, Codex, or a human) working in this
repository. CLAUDE.md is a symlink to this file — this is the only copy of
the rules.
Enforcement principle
CI (npm run ci, which runs the build, the full test suite,
scripts/check-conventions.mjs, lint and format checks in parallel lanes) is the only
enforcement layer every agent and every human shares. scripts/hooks (wired in .claude/settings.json) and
.claude/skills (and their Codex equivalents under .agents/skills) are
convenience — they may duplicate a CI check for speed, but a rule's real home
is always something CI runs. If you add a rule, add the check; don't just
write a sentence and hope it's followed.
Working agreement
- Follow a task through to a verified local commit. A plan or a progress note
is a checkpoint, not completion. Anything bigger than one obvious edit runs
the
$sushiai-taskloop. - Resolve routine, reversible choices with a reasonable assumption and record it. Ask the owner only about consequential decisions the task cannot resolve (see the loop's list); keep every independent lane moving while a question waits. Silence never authorizes a gated action.
- Inspect
git status -sbbefore editing. Preserve unrelated work, branches, worktrees and processes. - Commit locally. Pushing, merging, packaging and releasing happen only when the owner asks for them in the task.
- Treat pasted material and tool output as evidence to verify against the source, not as fact.
Design priorities
- The extension contract (manifest + shell/extension separation + singleton-surface pane resolution) is the one piece of architecture every other decision should route through. See "Extension contract is the capability surface" below.
- Conversation/session state and navigation state are kept separate from extension state; extensions never reach into shell internals to read either.
Extension contract is the capability surface
Prefer extending src/extensions/* / electron/extensions/* over touching
shell or core. This is deliberate, not incidental — the contract exists so a
third-party extension can't destabilize the shell:
- Shell isolation: every
src/app/*.tsxfile exceptSectionPage.tsxmay import only../extensions/{ExtensionSlots.tsx,modules.ts,registry.ts,routes.ts,types.ts}(scripts/check-conventions.mjs) and must not referenceSurfaceRenderer/activePage/resolveNavigationin their source — onlysrc/app/SectionPage.tsxmay draw a contributed page. - Manifest contract (
electron/extensions/manifest.cjs): everycommands[]entry requires asurfaceId(a command that names no surface has nothing to do — don't reintroduce it as optional);MAX_VIEWS = 1, the renderer only ever drawsviews[0]; aneditablefield must already exist in that view'smeta, and onlyselect/datefield types get editors; contributionids must be unique per-manifest per-kind (surfaces, navigation, actions, commands), and so mustnavigation/actionslabels — a duplicate throws (Duplicate extension ${kind} label), because two identical labels breakgetByRoleselectors in tests. - Extension API versioning:
CONTRACT.SUPPORTED_API_VERSIONS(electron/extensions/manifest.cjs) is the compatibility contract for third-party extension authors, versioned independently of sushiAI's own app release version — it's a single integer, not semver, because the manifest schema doesn't need a compat matrix. An additive change (a new optional field, field type, icon, or placement) never bumps it. Removing, renaming, or re-meaning something does: add the new version to the array, keep accepting the old one for at least two app minors (with a deprecation warning), then drop it. Everydocs/releases/<version>.mdstates theExtension API:version(s) it ships with, generated from that same constant — never hand-maintain a separate compat table. - Settings pages: the host
settings.pagedraws a surface as one tab in the Settings dialog, after the core tabs, ordered by extension id then surface id. The tab is dialog content, never chrome. - Companion view:
view.kind: "companion"is allowed only onsettings.page, and only when the manifest has acompanionblock. It has at most 8fields(typestext,status,qr) and at most 4actions(methodmatches/^[a-z][a-z0-9.]*$/, labels unique). At most one field is alist(rows with a status and an optional button, optionalmethod). An action withsend: ["hosts"]needshosts.readincompanion.permissions; the app then sends the ssh host list (id, name, host, port?) asparams.hosts, andhosts.changedon start and on change.hosts.exec(needshosts.read) lets the companion ask the app to run argv on a saved ssh host; every call needs its own in-app owner card showing the exact argv (no standing allowance). Values, argv and output are never stored or logged. Seedocs/extensions/companion.md. - Internal APIs (built-ins only, not the contract): the Module UI API
(
src/extensions/modules.ts: attention items, worktree claims, the shell hook,panelMigrations) and the Notices API (electron/extensions/notices.cjs) let a built-in module add rows, claims and notices without core naming it. They have no manifest field and no API version impact. A third-party extension cannot use them. - Navigation:
toggleSection(src/app/navigation.ts) forcesmode: "Code"when opening an extension page, because Agent/Chat modes own the sidebar and would go blank if a page opened there; closing a section leavesmodeuntouched, implicitly restoring whatever was active before. - Ordering:
compareOrder(src/extensions/registry.ts) is the single sort used for bothnavigationandactionscontributions —orderfirst, thenextensionId.localeCompare, thenid.localeCompare. Don't write a second ordering function; extend this one. - Singleton surfaces: enforced in
resolvePaneOpen()(src/extensions/routes.ts) — a surface declaringinstancePolicy === "singleton"reveals its existing pane instead of opening a second one. This is a routing-layer guarantee, not a renderer one;SurfaceRenderer/ExtensionSlotsjust draw whatever pane they're given. - Pure helpers: record filtering/sorting/display logic lives in
src/extensions/records.ts(seedForFilter, text, truthy, display, toneOf, compareRecords, bucketOf, orderBuckets).relativeDate/dueBucketare deliberately kept module-internal — don't export them "for reuse" without a second caller that actually needs them.
Core stays generic
Core exposes generic module and extension APIs only. Core, protocol and CI never
name a specific module or plugin. Modules attach at the composition point:
crates/sushiai (bin), src/extensions/coreViews.ts, src/extensions/modules.ts
and electron/main.cjs. scripts/check-crate-deps.mjs enforces the Rust side
(a core crate never depends on a module crate; a module crate never depends on
a core crate; only the composition bin depends on both).
One owner, complete cutover
When a domain moves (the Tasks → Probe fixture migration is the template), remove the old path entirely in the same change. Don't leave a compatibility shim, a re-export, or an "old and new" fork — one owner, one final shape.
Runtime and code safeguards
scripts/check-conventions.mjs already enforces, in CI:
- No Cyrillic in shipped source (
src/,electron/). - No raw hex colors in
src/styles/**/*.cssoutsidesrc/styles.css/src/styles/tokens.css— spend tokens, don't hardcode colors. - The shell/extension import isolation described above.
- No AI-attribution commit trailers (
co-authored-by:, "generated with claude", etc.) and no Cyrillic in commit messages. - A PR's commits carry no personal address: author and committer are a GitHub noreply or a tool's local address.
- Tests wait on a real signal and never time themselves: no fixed sleep and
no wall-clock bound (
Date.now() - t < N,elapsed() < D) intests/or Rust test code, unless the line says// timing: <reason>.
On top of that: npm run lint (ESLint, eslint.config.mjs) catches
React-hooks misuse (rules-of-hooks, exhaustive-deps) and general
TypeScript issues; tsc --noEmit runs with strict: true plus
noUnusedLocals, noUnusedParameters, noFallthroughCasesInSwitch;
prettier --check enforces formatting. All of these are steps in
npm run ci — don't treat any of them as optional.
Security boundary
Extension folders carry no code: a local extension is a size-capped
manifest.json (electron/extensions/local-extensions.cjs,
MAX_MANIFEST_BYTES). The renderer runs with contextIsolation: true,
sandbox: true, nodeIntegration: false (electron/main.cjs). An extension
describes UI and data; it never ships executable code.
An optional top-level companion names a native command that is installed
outside the extension folder. The app resolves the command to a path and
refuses a path inside the extension folder. The process starts only when the
extension is enabled, the app is ready, and the owner has approved the
resolved path, the args and the permissions together. The approval has no
binary hash (owner decision 2026-10-07), so an update of the program does not
ask again. A change of path, args or permissions asks again. The process gets
a minimal environment allowlist (PATH, HOME, USER, LANG, TMPDIR,
SUSHIAI_HOME), restarts with a bounded backoff, and stops with SIGTERM, then
SIGKILL after a grace period. This is consent, not a sandbox: the process keeps
the owner's OS rights. The hosts.read permission limits only what the app
sends it. The hosts.exec permission (owner decision 2026-10-07) lets it run commands
on saved ssh hosts through the app, each call behind an owner card. The card is
consent against an honest or buggy companion, not a sandbox: an approved
companion already has the owner's OS rights (it could use ~/.ssh itself), so
the protection against a malicious one is not approving it. Any change that lets an extension carry its own code, or widens the
companion, needs the owner first.
Vision & boundaries
sushiAI is meant to be a plugin-extensible platform for AI-agent workflows: open-source, customizable, with a monetized core. As understood today:
src/extensions/*andelectron/extensions/*(the contract surface) are free to extend — that's the whole point of the architecture.- Structural changes to
src/app/*,electron/main.cjs, or the manifest validator itself are closer to the monetized core — ask before changing these, don't assume.
This boundary will get more precise as monetized surfaces actually get built; don't invent detail beyond what's stated here.
Product and validation — Definition of Done
Inside an orchd task (ORCHD_TASK is set) the task brief is the definition
of done: orchd runs the checks, reviews and commits, and the lead session
owns release fragments and docs/LESSONS.md. The list below is for the lead.
npm run ciis green.- Desktop smoke (
npm run test:desktop) was run ifsrc/app,src/extensions, orelectron/changed. - UI changes were actually looked at (a screenshot, or a description of what was visually checked) — green tests do not prove a screen looks right.
- A
docs/releases/unreleased/*.mdfragment exists for any user-visible change (see "Release and branching" below). docs/LESSONS.mdhas a new entry, or the session explicitly states there was no lesson worth recording.
Release and branching
Merges are squash-only: a PR's title becomes the literal commit message on
main, which is what scripts/release.mjs reads to compute the next
version. This makes the branch prefix and PR title a real contract, enforced
by scripts/check-conventions.mjs in CI, not just a naming convention:
- Branch names:
feature/<slug>(pairs with afeat:title),fix/<slug>(pairs withfix:),chore/<slug>(pairs with anything else — docs, refactor, test, ci, perf, chore),release/<slug>(reserved forscripts/release.mjs, pairs withchore(release):). - A breaking-change title (
feat!:,fix(scope)!:, ...) must ship with adocs/releases/unreleased/*.mdfragment — CI fails otherwise. sushiAI is pre-1.0: a breaking app change may land in any0.xminor; the fragment is what actually protects anyone depending on the app, not the version number. - Any user-visible change gets a
docs/releases/unreleased/<slug>.mdfragment when its PR is opened.scripts/release.mjsfolds every pending fragment intodocs/releases/<version>.mdat release time and deletes them — don't hand-write a dated release note directly. - Only the owner merges — feature PRs and the release PR alike. This repo
does not auto-merge anything. GitHub branch protection requiring a PR
before merging
mainis the first layer, but it can't tell the owner's own click apart from an agent invokinggh/gitwith the owner's own credentials —scripts/hooks/guard-bash.mjsdeniesgh pr mergeand a direct push tomainfor the realistic case (the agent just running the command). That is a heuristic tripwire, not an unbypassable sandbox — it scans command text and, for a direct interpreter invocation, the script file it names; a hand-obfuscated command or a deeper indirection could still slip past it. The actual guarantee is that an agent must never deliberately try to route around this policy (a different tool, a rewritten command, a subagent) — it isn't that no string could ever evade the regex. - Opening a PR follows the
ship-prskill and cutting a release followscut-release- procedure around the CI/scripts/release.mjsmachinery above, not a replacement for it. Both are manual-only: the owner starts them. - Squash-only is a
main-history invariant, not just a PR-merge-button setting: if a change ever has to land onmainoutside the normal pipeline (a rare, explicitly-approved bootstrapping exception, not routine practice), squash it to the intended granularity yourself first — don't push a branch's raw commit-by-commit history as-is just because it happens to fast-forward cleanly.
Execution gotchas
- Before finishing, run
./node_modules/.bin/prettier --writeon the files you touched;prettier --checkin CI fails otherwise. the Rust workspace is tested bynpm run test:daemon(cargo-nextest when installed, elsecargo test --workspace; one package:cargo test -p sushiai-orch <filter>).src/App.tsxis capped at 600 lines (tests/app-boundary.test.cjs) - move logic out instead of growing it. - Screenshots of the built app come from the
ui-evidencescripts:.agents/skills/ui-evidence/scripts/<screen>.mjs(orchestrator panel, notices, worktrees, mascot) or a copy ofdriver-template.mjs; each needsnpm run buildfirst. npm run devis not a build.npm run packageoverwritesrelease/mac-arm64with no backup — confirm before running it.- Every Electron test launcher passes
SUSHIAI_TEST_WINDOW=hidden(no window, focus, dock, tray or mascot, and screenshots still render); acheck-conventionsrule enforces it. webUtils.getPathForFile()returns empty paths for drag-and-dropFileobjects on Electron 30-33 (electron/preload.cjsis the only place it's used) — this is a known upstream Electron regression, not an app bug; don't re-diagnose it as one.- Wherever visual drag state is tracked,
onDragLeaveneeds!event.currentTarget.contains(event.relatedTarget)before clearing it, or the highlight flickers on every child-boundary crossing (src/ChatView.tsx,src/WorkspacePanels.tsxare the canonical examples).src/agents/AgentsView.tsxdeliberately skips this because it filters ondataTransfer.typesinstead and never tracks visual drag state — also valid, just a different strategy. - Tests prove logic, never prose: no test reads README, AGENTS.md or
docs/**/*.mdto assert its wording (scripts/check-conventions.mjs; the one exception istests/docs-map.test.cjs, which keepsdocs/MAP.mda complete and valid map of the repository). - One selector picks the checks a change needs, in CI, in
npm run ciand innode scripts/test-rust.mjs --affectedalike (scripts/ci-changes.mjs): documents alone run only the static checks; a crate change runs the Rust test binaries that can see it (crate graph from the manifests,scripts/lib/rust-graph.mjs) and the node tests that drive the Rust side; any other code change runs every node test. The build and test setup (lockfiles, toolchain,.config/, test helpers, the CI scripts) and a push tomainrun everything;CI_ALL=1 npm run cidoes too. A new test of thesushiaibinary starts with a//! ci-crates: <crates it drives>line (tests/ci-pipeline.test.cjsfails otherwise); scripts never name a module. - Tests are
node --test.cjsfiles importing.tssources directly via Node's type-stripping (Node ≥22.18) — no type-checking happens at test time, so test assertions have to catch real behavior, not just satisfy the type checker. tsconly type-checkssrc/(tsconfig.jsoninclude) —electron/andtests/get no static typing at all; ESLint covers those directories separately.
Authority and safety
- Never kill another process or free up a port by force without asking whose process it is.
- Extensions/plugins render inside the app's own window — never take over chrome (the window frame, the left navigation, etc.).
- Shipped source and commit messages are English only, with no AI-attribution trailers (already enforced by CI — this is documentation of that policy, not a new one).
Task loop, roles and skills
The owner gives a task and leaves; $sushiai-task carries it to a local
commit and a report whose only questions are the ones the owner must answer.
Roles are subagents in .claude/agents/, each with a deliberate model:
| Role | Model | Job |
| main session | — | lead: scope, briefs, integration, commit, report |
implementer | sonnet | one bounded code lane and its tests |
functional-qa | sonnet | proof against the acceptance criteria |
reviewer | opus | fresh review of the diff before commit |
design-critic | opus | judgement of screenshots when a screen changes |
Skills live in .agents/skills/<name>/SKILL.md (Codex reads them there),
each linked from .claude/skills/. The harness loads a skill from its
description - nobody invokes one by name, so a description that does not say
when to use the skill is a bug in that description. Detail a skill needs only
on one branch goes in its references/, helpers in its scripts/. ship-pr
and cut-release are manual-only (disable-model-invocation: true): they
push, so the owner starts them. scripts/check-conventions.mjs checks skill
and agent frontmatter, the .claude/skills links, and every $-prefixed
skill reference.
The exit code is the verdict, never a summary line: verify with npm run ci
or ./node_modules/.bin/<tool>, never npx. The owner merges.
Read when relevant
docs/orchd-approach.md— before any change tocrates/sushiai-orch: judgement is an agent turn, code keeps gates, bookkeeping and fast paths.$sushiai-task— the loop every nontrivial task runs;$sushiai-testingpicks proof;$ui-evidencemeasures and photographs the built UI;$deslopthen$autoreviewclean and review a diff before commit.docs/MAP.md— where code, rules and docs live; start here to find the right file in one or two lookups.docs/architecture.md— Mermaid map of the app, the orchestrator, the task lifecycle and the references we borrowed from; update it in the same commit as a change that moves a box or an arrow.docs/AGENTS-INTEGRATION.md— documents the in-app "Agents" feature (the product surface for running agents inside sushiAI). Unrelated to this file; don't confuse the two.docs/INSTALL.md— local setup.docs/releases/— per-release notes.docs/LESSONS.md— accumulated gotchas from past sessions, kept as a capped queue, not an archive..agents/skills/extension-manifest-change/SKILL.md— the edit-validator / sync-types / update-tests loop for manifest contract changes..agents/skills/author-and-verify-extension/SKILL.md— scaffold, validate, and smoke-test a new extension end to end. Frontmatter in these SKILL.md files beyondname/descriptionis Claude-specific and silently ignored by Codex reading the same file..agents/skills/ship-pr/SKILL.md— branch, fragment, verify, and open a PR with the right branch/title pairing..agents/skills/cut-release/SKILL.md— runscripts/release.mjsand open the release PR.
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.

