agentleFS
ElcanoTekElcanoTek/fleet/AGENTS.md

fleet

AGENTS.md7 starsChanged 37 days agoMITView on GitHub

Operating guide for AI coding agents (Claude Code, Codex, Cursor, opencode, Goose, Gemini CLI, …) working in the fleet repository. It follows the agents.md convention; Claude Code and every other AGENTS.md-aware tool read this file directly. Do not add a CLAUDE.md, .claude/CLAUDE.md or CLAUDE.local.md here, or a CLAUDE.md above the checkout: Claude Code then loads those instead and skips this file. Humans, start with README.md and CONTRIBUTING.md; this file is the agent-facing distillation, not a replacement for them. Every Elcano…

  • 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,439 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 repository2 other files it gives its agents.

The file

We split this AGENTS.md into the rules it gives an agent

8 rules. About 0 are common, 2 are distinctive, and 6 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈2

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 6

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  • could be automatic: a setting could enforce it
  1. Very specific:

    Every Elcano web product follows one of two tiers.

    line 14
  2. Distinctive:

    No third-party CDNs: every script, stylesheet and font is served by our own binary.

    line 50
  3. Very specific:

    Caching: Only content-hashed /assets/* (Vite output: JS, CSS, fonts) are served public, max-age=31536000, immutable.

    line 107
  4. Very specific:

    Every other non-HTML file is served at a stable URL that something outside the bundle hard-codes, so it is no-cache (revalidated on every …

    line 109
  5. Very specific:

    When you touch web/ (the Vite SPA — details in web/AGENTS.md):

    line 177
  6. Very specific:

    Run tests in the foreground.

    line 338
  7. Very specific:

    Ship features with honest scope: Every feature lands with a design note recording what shipped, what deviated from the issue, and what was …

    line 371
  8. Distinctive:

    Prefer the standard library or a small piece of owned code.

    line 388

≈ “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.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
AGENTS.md

AGENTS.md

Operating guide for AI coding agents (Claude Code, Codex, Cursor, opencode, Goose, Gemini CLI, …) working in the fleet repository. It follows the agents.md convention; Claude Code and every other AGENTS.md-aware tool read this file directly. Do not add a CLAUDE.md, .claude/CLAUDE.md or CLAUDE.local.md here, or a CLAUDE.md above the checkout: Claude Code then loads those instead and skips this file.

Humans, start with README.md and CONTRIBUTING.md; this file is the agent-facing distillation, not a replacement for them.

<!-- BEGIN:elcano-frontend-standard v2 (shared verbatim by every Elcano repo; scripts/stack-check.sh pins its hash; change it everywhere or nowhere) -->

Elcano frontend standard (v2)

Every Elcano web product follows one of two tiers. stack.conf at the repo root declares which, and scripts/stack-check.sh (run by CI) enforces it.

The server is Go and serves everything. The browser talks to one origin: the Go binary serves the HTML, the static assets and the JSON API, and it owns sign-in (the elcano_auth cookie from the auth service), sessions, CSRF and security headers. There is no Node, Python or other JavaScript server in production. Static files ship inside the binary with go:embed; Caddy only terminates TLS and reverse-proxies to Go.

Tier A — server-rendered (the default). Go html/template pages, plain CSS that reads Flag tokens, and small plain-JavaScript modules (no framework, no bundler, no transpiler, no npm runtime dependencies). npm may appear only as a dev dependency for browser tests (Playwright, axe). Pick Tier A unless the page is a long-lived, state-heavy application. Components use Flag's CSS component classes, which mirror the names and states of Flag's shadcn/ui components so both tiers look and behave alike.

Tier B — interactive app. React + TypeScript built by Vite into a static bundle that the Go server embeds and serves (same origin, so no CORS). No server-side rendering, no Next.js, no runtime server code in TypeScript; data comes from the Go JSON API. Base UI components come from Flag's shadcn/ui registry (React + Radix + Tailwind, themed by Flag tokens; registry URL in Flag's README): npx shadcn add copies them into the app, where they live and are reviewed like our own code. Build a component by hand only when the registry lacks it, then upstream it to Flag. Keep other runtime npm dependencies few and justified in AGENTS.md. Use Tier B only when Tier A would mean re-implementing a client-side state framework by hand (streaming chat, multi-step builders).

Both tiers:

  • Design comes from Flag (ElcanoTek/flag). Vendored Flag files are listed in flag.lock with their hashes; scripts/flag-sync.sh check fails CI on any hand edit. Update with scripts/flag-sync.sh sync, never by editing in place. Product styling lives outside the vendored directories and reads tokens.
  • No third-party CDNs: every script, stylesheet and font is served by our own binary. Fonts are Flag's Nebula Sans and Hack only.
  • A strict Content-Security-Policy (no unsafe-inline scripts, no remote script hosts), X-Content-Type-Options: nosniff, and frame-ancestors.
  • Hashed or versioned asset URLs with long-lived caching; HTML is no-cache.
  • Accessibility is tested (Playwright + axe where a browser suite exists).
  • Exceptions (a non-Go backend, an extra runtime dependency) are written down in this repo's AGENTS.md with the reason, not left implicit.
<!-- END:elcano-frontend-standard -->

How fleet applies the standard (Tier B)

stack.conf declares Tier B: the web UI (web/) is a React + TypeScript SPA that Vite builds into internal/webui/bundle/dist, and the fleet binary embeds it (go:embed) and serves it, together with every browser /api/* route, on FLEET_WEB_ADDR (default 127.0.0.1:3000) (ADR-0091). Tier B because the chat is a streaming, state-heavy app (SSE turns, approval cards, generative UI) that Tier A would mean re-implementing by hand.

  • internal/webui is the browser gateway: auth gate + public allowlists, the HMAC elcano_session / Ed25519 elcano_auth cookies (internal/websession, shared with the orchestrator), OIDC, CSRF, CSP and the security headers, X-App-Version, theme/brand, SSE pass-through and streamed uploads. It calls the chat and orchestrator handlers in-process. It renders index.html per request (metadata + a JSON bootstrap), so the CSP needs no inline script. A browser API route belongs here.
  • web/ owns the client only — see web/AGENTS.md for its commands, the in-house router (justified there and in ADR-0091: a dozen routes do not earn a router dependency), and the justified runtime dependencies.
  • Runtime dependencies. dependencies in web/package.json ship in the browser bundle; everything else is a devDependency (Vite, TypeScript, Vitest, Playwright, axe, type packages) and never reaches production.

    Runtime dependencyWhy it is needed
    react, react-domThe UI framework and its browser renderer — the reason fleet is Tier B (streaming chat, approval cards, generative UI).
    react-markdownRenders assistant replies, run logs, shared transcripts and the /help guides (AssistantContent.tsx, LogMarkdown.tsx, GuideMarkdown.tsx, …) as React elements — no HTML strings, no dangerouslySetInnerHTML, and no Markdown parser of our own to maintain.
    remark-gfmGitHub-flavoured Markdown for react-markdown: tables, task lists, autolinks and strikethrough, which agents and the guides use and plain CommonMark renders as text.
    @tanstack/react-virtualVirtualises the chat transcript (ChatTranscript.tsx) so a conversation with thousands of messages renders only what is on screen.

    Adding another runtime dependency needs a row here with the reason (make stack-check enforces it); prefer a small in-house module.

  • New or replaced base UI components come from Flag's shadcn/ui registry (npx shadcn add <registry-url>/r/<item>.json; the URL is in Flag's README). Existing components are not being migrated wholesale.
  • Vendored Flag files (fonts, the core icon sprite, the fleet mark, Flag's MIT licence) are pinned in flag.lock. Update them with scripts/flag-sync.sh sync ../flag (a clean sibling checkout of ElcanoTek/flag; scripts/flag-sync.sh status ../flag previews), never by hand. The PR gate is the offline make stack-check (CI job stack-check); the weekly .github/workflows/flag-drift.yml reports when upstream Flag has moved past the lock. scripts/flag-sync.sh and scripts/stack-check.sh are vendored too: MIT-licensed copies of Flag's tools/product/ scripts, kept byte-identical (their SPDX header names the canonical copy) — replace them from Flag, never edit them here.
  • Caching. Only content-hashed /assets/* (Vite output: JS, CSS, fonts) are served public, max-age=31536000, immutable. HTML is no-store. Every other non-HTML file is served at a stable URL that something outside the bundle hard-codes, so it is no-cache (revalidated on every use, with an ETag where the gateway computes one) — never long-cached: sw.js (a service worker must keep its URL), manifest.webmanifest, theme-init.js (named in index.html's pre-paint script tag), the icon sprite and logos under icons/ and logos/, app-icons/, icon.svg, apple-icon.png and share.png (named by the manifest, <use href>, favicons and share metadata), the backgrounds/ images (stable URLs a client bundle's theme may name), /api/theme (the bundle palette) and /api/brand/* (the bundle's images). An error on an asset URL (404, 405) never carries the immutable header. TestCachingPolicy (internal/webui) pins all of this.
  • Builds always carry the bundle. go build / go test work without it (pages then answer 503), but make build runs make web first and ends with fleet version --web, which fails on a binary without one — as do the CI web job, release.yml (which also refuses a binary built with fleet_host_executor) and fleet release fetch on every box (ADR-0100).

What fleet is (one paragraph)

fleet is a self-hosted, general-purpose agent platform. One Go process runs interactive chat and a scheduling engine, driven by one unified agent runtime (internal/agentcore). Model-authored local execution — bash, Python, and file I/O — runs inside a mandatory sandbox; fixed host-side brokers handle MCP credentials/network and the small control-plane exception set enumerated in ADR-0036. The sandbox backend is pluggable (ADR-0049): FLEET_SANDBOX_BACKEND=podman — the default, rootless Podman co-located with the process, which is the single-box install — or kubernetes, one ephemeral pod per sandbox exec'd over the apiserver, for the split control-plane deployment. "On one box" is the default shape, not the only one, so do not write code (or docs) that assumes podman is the only executor. See the README "Architecture at a glance" for the full picture.

Build · test · lint (run before opening any PR)

make build        # compile-check ./..., build the web bundle, emit ./fleet with it embedded
make web          # the web bundle only (npm ci + vite build → internal/webui/bundle/dist)
make dev          # fleet serve + the Vite dev server (hot reload) at http://127.0.0.1:3000
make compile      # go build ./...   (release config — see the build tag below)
make test         # scripts/go-test.sh (tagged; one parallel run) — FOREGROUND
make test-race    # the same, with -race   (use when touching concurrency)
make test-cover   # the same, with -coverprofile/-covermode=atomic (writes coverage.out)
make lint         # golangci-lint + ruff check/format (Python) + migration DDL lint
                  #   + actionlint & shellcheck (workflows + shell) + stack-check — must pass clean
make stack-check  # the Elcano frontend standard: AGENTS block, flag.lock, no CDNs, Tier B rules
make govulncheck  # call-graph-aware CVE scan of the dependency tree
make fmt          # gofmt -w .
make tidy         # go mod tidy
make ci-go        # the whole Go gate locally: compile, vet, lint, test, -race, govulncheck
make ci-web       # the Web CI job verbatim (see below)
make ci-local     # ci-go + ci-web — the fast PR gates, locally

-tags fleet_host_executor is load-bearing, not decoration. The unsandboxed host executor is fenced behind that build tag (#159) so it is not compiled into a release binary: make compile deliberately omits it, and host_disabled.go then stubs newHostSandbox out and rejects MockMode at boot. Tests opt in — every go test/go vet target above carries the tag, and .golangci.yml sets build-tags so the linter agrees — so a bare go test ./... builds a different tree than CI does (host.go unvetted, untested). Use the Makefile targets.

make lint's lint-python (ruff) and lint-actions (actionlint/shellcheck) skip loudly when the tool is missing, printing the install command. So a green local make lint is not proof — read the output, and remember CI enforces both regardless.

When you touch web/ (the Vite SPA — details in web/AGENTS.md):

make ci-web                                        # the Web CI job, verbatim — prefer this
cd web && npm ci && npm run lint && npm run typecheck && npm run test && npm run build
cd web && npx playwright test --project=mocked     # mocked e2e (builds the bundle + cmd/web-e2e-server)

web/ is the only npm tree (the second one, scripts/rampart-service/, was removed in ADR-0063), and CI audits it (npm audit --audit-level=low, lockfile-only) before the install. make ci-web runs every step; the hand-rolled line above skips the audit, which is how a clean local run turns into a red PR.

CI mirrors all of this in one workflow on one branch. fleet is trunk-based (ADR-0062): main is the only long-lived branch, every PR targets it and is squash-merged, and CI (ci.yml) runs the full gate on every PR into main and every push to it — Go build/vet/lint/test (including a -race lane) plus a govulncheck dependency-CVE scan, a Grype container-image CVE scan (fail on a fixable CRITICAL/HIGH) of the sandbox image, a Python lint (ruff), a workflow + shell lint (actionlint & shellcheck), web lint (oxlint) / typecheck (TS 7) / test / build, Playwright (mocked and live, against a real backend + sandbox), a Helm chart lint, a migration DDL lint, and a gitleaks secret scan. CI gate is the single required status check: it needs every other job and always reports, so a docs-only PR (heavy jobs skipped by the changes classifier) still merges, while a code PR cannot go green over a skip. There is no integration branch, no "fast lane", and no promotion step: the PR is the first and last time CI sees the change, and every green push to main is a release (release.yml, ADR-0059).

Every job must be green before merge, and nothing merges itself — auto-merge was removed, so every PR, dependency bumps included, waits for a human decision. An agent may perform the squash merge only when a human has explicitly told it to merge that PR (or that specific set of PRs) in the current conversation, and on the PR's current head the required CI gate check is green (a docs-only PR's classifier-approved skips count as green), every review thread (bot or human) is resolved or answered with evidence, and Codex has finished rather than "Running". A standing preference, a memory note, or authorization given for a different PR does not carry over; absent that explicit instruction the PR waits for a human. Tests are deterministic without a live model: use the fake-LLM seam (internal/fakellm via OPENROUTER_BASE_URL), never a real key.

CodeQL (security queries, security-extended) and Semgrep (Go/JS/Python SAST + Actions supply chain) run on every PR: codeql.yml and semgrep.yml are reusable workflows that ci.yml calls as jobs, so their results roll up into CI gate like any other job. npm audit (the web/ tree, lockfile-only, any severity) and ruff (check and format --check) gate the same way.

Their thresholds differ, and the difference is load-bearing:

  • Semgrep, ruff and npm audit: zero findings, gating on any finding.
  • CodeQL: zero blocking findings. It gates on the High band (security-severity >= 7.0, or level error/warning for a rule publishing no security-severity), minus a reviewed register of accepted (rule, file) pairs in .github/codeql-accepted-findings.json — each with a written reason. Below the band is advisory: printed in the job log and uploaded to the Security tab, not blocking. So "CodeQL is green" means "no unwaived High-band finding", not "no findings". The reasoning is ADR-0048.

One fact an agent must not get wrong here. A pull_request CodeQL run is diff-informed — it evaluates every query over the full database, then reports only results inside the PR's diff — so it certifies a diff, never a tree; only the push run on main and the scheduled runs give a tree-wide verdict. See docs/SCANNING.md.

Repository map

The top-level tree: cmd/ (the one fleet binary — fleet serve runs the server, every other verb is the operator CLI (the fleet-admin shim was removed in ADR-0060); plus the fleet-bench, fake-llm, sandbox-probe and web-e2e-server harness binaries), internal/ (agentcore the one run loop, sandbox, mcp, creds, clientconfig, store, sched, httpapi, webui the browser gateway, websession, …), web/ (the Vite + React SPA the binary embeds: /chat, /orchestrator, /settings, /admin, /help), deploy/ (the systemd units + Caddyfile for the single-box install and the deploy/helm/fleet chart for the Kubernetes one), and config/default/ (the generic client bundle baked in so fleet runs bare).

Non-negotiable invariants — do NOT weaken these

These are the security and design guarantees the whole project rests on. A change that breaks one is wrong even if tests pass. The why behind several of them is recorded as Architecture Decision Records in docs/adr/ — a change that adds, weakens, or reverses an invariant must add or supersede an ADR in the same PR.

  • The sandbox is mandatory — the backend is pluggable, the sandbox is not. The agent loop runs in the fleet process, but every agent tool call's data-plane execution — bash, Python, and file I/O (view_file/write_file/edit_file, via the sandbox FileOp seam, #784) — runs inside the sandbox (rootless Podman by default; an ephemeral Kubernetes pod under FLEET_SANDBOX_BACKEND=kubernetes, ADR-0049); there is no fast path that skips it and no host-execution fallback (they fail closed without a sandbox). The unsandboxed host executor is compiled in only behind the fleet_host_executor build tag (#159), which is what makes "it cannot ship enabled in a production build" a property of the artifact rather than a runtime flag — do not widen that fence, and do not add a path that reaches host.go from an untagged build. Selecting the kubernetes backend runs a fail-closed boot preflight (apiserver + credentials, the exact RBAC verbs, the workspace claim, the sealed-egress NetworkPolicy object) and refuses podman-only knobs rather than ignoring them: no degrade to podman, none to host execution. The loop holds no privileged local executor of its own: each tool call is handed to the sandbox under host policy. A small set of native tools are host-side control-plane / broker operations by design (host network fetch, brokered credentials, governed datastore writes) — enumerated and threat-modelled in ADR-0036, not a silent exception. MCP credentials are brokered out-of-process (issue #167) and never enter the sandbox — the broker injects them only when it runs a delegated MCP call host-side.
  • Credentials stay host-side. MCP/connector credentials are brokered on the host and never enter the sandbox, the agent container, the model context, or logs. Never ship a secret into a container or print one.
  • The broker authorizes, it does not just transport. The credential-owning child re-derives each server's tool allowlist and enabled-server set from its OWN bundle and treats the parent's gates as narrowing only, so a parent-side gating bug restricts a run instead of unbounding it (ADR-0042). Do not add a broker path that binds or dispatches without going through that gate.
  • Governance is one core. agentcore.Run is the single governed loop (policy, cost/token ceilings, audit, notes). New entrypoints adapt I/O around it — they must not fork a second, weaker governance path.
  • No secrets in the repo. gitleaks gates CI. Use the fake-LLM seam and obvious placeholders in tests; the real OPENROUTER_API_KEY lives outside the repo.
  • Honesty in docs. Claim only shipped, tested capabilities. If you add a capability, document what it actually does — and what it does not. (Example: a skill's allowed-tools frontmatter is parsed and surfaced for review (skills library UI, /skills API) but not enforced as an authorization boundary — the docs say so plainly rather than implying a boundary that isn't there. It structurally can't be one: skills are read on-demand mid-turn with no single "active skill" to gate a roster against, and a skill can never exceed the turn's existing sandbox/MCP/approval limits. See docs/SKILLS.md.)
  • Client content is external. Branding, the MCP catalog, personas, protocols, skills, and the sandbox Containerfile live in an out-of-repo client-config bundle (FLEET_CLIENT_CONFIG_DIR). fleet ships only the generic config/default bundle — do not add client-specific content to this repo. Upstream Fleet maintainer and licensing contacts (hello@elcanotek.com, licensing@elcanotek.com) belong to the core distribution and are not client-specific bundle content.

Conventions

  • Single Go module github.com/ElcanoTek/fleet, Go 1.27. Keep it go vet- and golangci-lint-clean — lint failures block CI.
  • Coverage (advisory, not a merge gate): CI runs the plain go test step with -coverprofile=coverage.out -covermode=atomic, then prints the project total to the log and writes the per-package go tool cover -func table to the Actions job summary. There is no external coverage service and no coverage threshold anywhere in the pipeline — the Codecov upload and codecov.yml were removed because the repo has no CODECOV_TOKEN, so the upload only ever produced a missing-token warning. Treat coverage as a quality signal, not a gate: add tests that catch real behavior, not to chase a number. (The merge gates are build/vet/lint, ruff — check and format --check — actionlint + shellcheck, the test suites, the -race lane, govulncheck, Grype, npm audit, CodeQL, Semgrep, the Helm chart lint, both Playwright suites, the migration linter, and gitleaks — all rolled up into the one required CI gate check.)
  • Match the surrounding code: naming, idioms, and comment density. The internal/agentcore package comments explain why each governance invariant holds — preserve that level of explanation when you extend it.
  • Run tests in the foreground. Do not background go test, and do not pkill -f 'go test' (it can kill the shell). Prefer make test (it sets the host-executor tag — see scripts/go-test.sh). The DB suites need only FLEET_TEST_DATABASE_URL (a server whose role can CREATE DATABASE): every DB test clones a database of its own from a migrated template (internal/testdb; internal/testdb/fleetdb for the stores), so never share one, and call t.Parallel() unless the test sets env or package globals — docs/TESTING-STRATEGY.md#database-tests.
  • New task fields thread one way. A new per-task column is one migration plus one row in taskColumnRegistry (internal/sched/db/task_columns.go, #1126) plus the models.Task field — and, for a read column, its taskScanBuf field (the compiler enforces that one). The registry derives the SELECT/scan, insert, upsert and UpdateTaskTx statements from per-row flags; flag export (and add the matching models.TaskExportRecord field) when the column belongs to the portable definition. Result-like columns written after the terminal transition (e.g. error_analysis) stay excluded from the insert/upsert so a status write can never clobber them — every exclusion needs a reason string on its row, and the sched/db registry tests fail on schema↔registry drift.
  • A user-visible change updates the user guides. The two guides in internal/clientconfig/builtin_skills/fleet-guide/ are what /help renders AND what the fleet-guide skill hands the assistant, so a stale sentence there is a wrong answer given to a user in their own words. If your change adds, renames, moves or removes something a person can see or press — a control, a status badge, a card, a menu item, a shortcut, a limit, a default — update the guide in the same PR and run make sync-guides. Two rules keep this cheap: describe only what shipped (the same honesty bar as the rest of this file), and say "by default" for anything an operator can configure. Where a guide claim can be checked mechanically, check it — scripts/ already pins the shortcut tables against the shell's real bindings and the two guide copies against each other, and that pattern is the reason to add a row rather than trust a reviewer to notice. docs/USER-GUIDES.md has the full contract.
  • Ship features with honest scope. Every feature lands with a design note recording what shipped, what deviated from the issue, and what was deliberately deferred — in a dedicated docs/<FEATURE>.md (plus an ADR when an invariant is touched). The PR body's "What changed, and why" is the record of the change itself; there is no changelog file to update (ADR-0061). Do not append per-feature design notes to this file — that is how it grew past 300 lines once already; the historical notes live in docs/FEATURE-NOTES.md.
  • One focused branch + PR per change; keep diffs scoped. Don't refactor unrelated code in a feature PR. .github/PULL_REQUEST_TEMPLATE.md asks for exactly the three things above — what/why, what you actually ran to verify it, and scope-and-deviations — so fill it in rather than deleting it. See CONTRIBUTING.md for branch/PR conventions.

Dependencies and in-housed code

Prefer the standard library or a small piece of owned code; add a dependency only when it earns its keep (a protocol, a large or security-critical body of code we should not own). Dependabot opens one grouped version-update PR per ecosystem a month (7-day cooldown; majors separate); a human merges that routine refresh once it is green, so drift stays small. Out of cycle, a bump is merged only when it earns it — a specific commit we need, a security scanner update (such as CodeQL), or a CVE whose code path we actually reach (govulncheck or npm audit turns CI red) — and goes in its own PR. Dependabot security updates arrive whenever an advisory does. The web UI's runtime npm dependencies are few and each is justified in web/AGENTS.md (the frontend standard's rule); the router is in-house for the same reason. In-housed code is not tracked by Dependabot. On a CVE, check reachability first (make govulncheck is call-graph aware); delete the affected code if it is unused, adapt the upstream patch if it is used.

In-housedLives inUpstream (version)Licence
cron parser + DST-safe Nextinternal/cronrobfig/cron v3.0.1MIT
sched migration runnerinternal/sched/db/migrate.gogolang-migrate v4.20.1 (state-compatible re-implementation)MIT
Web Push (RFC 8291/8292)internal/webpush/protocol.goreplaces webpush-go v1.4.0 (own code)—
HTML → Markdowninternal/tools/htmlmdhtml-to-markdown v1.6.0MIT
rotating log fileinternal/logging/rotatelumberjack v2.2.1MIT
ClientIPFromXFF, Recoverer, WrapResponseWriterinternal/httpmwchi middleware: v5.4.0 (ClientIPFromXFF), v5.3.2 (Recoverer, WrapResponseWriter); chi stays the routerMIT
code highlightingweb/src/app/chat/ui/highlight/prismjs 1.30.0, refractor 5.0.0, react-syntax-highlighter 16.1.2MIT

Each copied file names its upstream, version and path and carries the licence beside it; none of them ships a data file to refresh. What was kept byte-for-byte, what changed and the tests pinning it: docs/IN-HOUSED-DEPENDENCIES.md. A fix there is ours to make — do not re-add the package to pick one up.

Where to look

The docs index is docs/README.md: one bullet per design note, runbook and ADR, by question. The entry points an agent needs most:

  • Driving a PR to green after it is opened — the follow-up loop for any coding agent or human. Posture: we can fix everything; if you are driving the PR, every red check and every open thread on it is yours, whoever wrote the code. Procedure: .agents/skills/steward/SKILL.md (.claude/skills/steward is a symlink to it). Reference: docs/PR-STEWARDSHIP.md.
  • Why the invariants are the way they are: docs/adr/.
  • Agent runtime mechanics (per-turn sandbox seal, cost/token ceilings, compaction, the MCP credential allowlist): docs/AGENT-RUNTIME.md.
  • Contributor workflow and releases: CONTRIBUTING.md, docs/VERSIONING.md.
  • Documentation for the people USING fleet (the /help guides, the fleet-guide skill, and what to update when a surface changes): docs/USER-GUIDES.md.
  • Testing strategy and the scanning stack: where a new test belongs and the chat-stream contract: docs/TESTING-STRATEGY.md; the CI lanes: docs/TESTING.md; docs/SCANNING.md.
  • Reporting a vulnerability: SECURITY.md.

Repo Boundaries & Coupling Doctrine (owner direction, 2026-07-11)

fleet is an engine: a client-agnostic harness. Everything customer- or deployment-specific arrives as a bundle (FLEET_CLIENT_CONFIG_DIR) — never as fleet code. To keep the v2 consolidation from becoming a maintenance tarpit:

  1. Bundles are data, fleet is engine. Per-customer MCP servers, personas, protocols, env/tool contracts, and critical-tool lists live in the *-config repos. A new customer is a new bundle, not a fleet change. The bundle manifest.yaml is the complete contract for what a server needs (env keys, tool allowlist, critical_tools, identity_env) — if fleet can't express something a server needs, extend the schema here (as with ${FLEET_WORKSPACE}), don't special-case a customer.
  2. Engines never depend on intake apps. fleet MUST NOT import, clone, or gate CI on manifest (or any future front door). Intake apps integrate by calling fleet's API and receiving webhooks — one create seam in, one outcome callback out — and they own their contract checks as data fixtures in their own repos.
  3. MCP source of truth: the bundle. Each *-config bundle fully owns the MCP servers under its mcp/. Fix a server in the bundle that ships it — that is the only place a fix reaches a customer. The bundles are peers: there is no upstream among them and no sync between them, so a fix that matters to more than one is hand-ported into each, as its own reviewed PR. cutlass is deprecated; its mcp/ copy serves the legacy v1 stack only and is not authoritative for any bundle. The cutlass → bundle sync was deleted on 2026-08-13 (it had silently reverted a bundle-side fix; see cutlass mcp/docs/CONSUMER_SYNC.md) — do not re-introduce an automated sync, and do not tell anyone to "fix it upstream and re-sync".
  4. Identity/credentials are harness-side. Host-side broker + suffixed account vars + identity_env refusal; servers read plain env and stay customer-agnostic. Bundle manifests name the variables, never values.
  5. Additive-first schema evolution. New ServerDef fields must fail loud on old manifests only when actually used (strict decoding is the backstop); bundles adopt new fields only after the fleet release that understands them ships.

Download now (ZIP)

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.