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.
- web/AGENTS.mdAGENTS.md
- stewardSkill
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
- Very specific: line 14
Every Elcano web product follows one of two tiers.
- Distinctive: line 50
No third-party CDNs: every script, stylesheet and font is served by our own binary.
- Very specific: line 107
Caching: Only content-hashed /assets/* (Vite output: JS, CSS, fonts) are served public, max-age=31536000, immutable.
- Very specific: line 109
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 …
- Very specific: line 177
When you touch web/ (the Vite SPA — details in web/AGENTS.md):
- Very specific: line 338
Run tests in the foreground.
- Very specific: line 371
Ship features with honest scope: Every feature lands with a design note recording what shipped, what deviated from the issue, and what was …
- Distinctive: line 388
Prefer the standard library or a small piece of owned code.
≈ “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
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 inflag.lockwith their hashes;scripts/flag-sync.sh checkfails CI on any hand edit. Update withscripts/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-inlinescripts, no remote script hosts),X-Content-Type-Options: nosniff, andframe-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/webuiis the browser gateway: auth gate + public allowlists, the HMACelcano_session/ Ed25519elcano_authcookies (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 rendersindex.htmlper request (metadata + a JSON bootstrap), so the CSP needs no inline script. A browser API route belongs here.web/owns the client only — seeweb/AGENTS.mdfor 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.
dependenciesinweb/package.jsonship in the browser bundle; everything else is a devDependency (Vite, TypeScript, Vitest, Playwright, axe, type packages) and never reaches production.Runtime dependency Why 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 /helpguides (AssistantContent.tsx,LogMarkdown.tsx,GuideMarkdown.tsx, …) as React elements — no HTML strings, nodangerouslySetInnerHTML, 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-checkenforces 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 withscripts/flag-sync.sh sync ../flag(a clean sibling checkout ofElcanoTek/flag;scripts/flag-sync.sh status ../flagpreviews), never by hand. The PR gate is the offlinemake stack-check(CI jobstack-check); the weekly.github/workflows/flag-drift.ymlreports when upstream Flag has moved past the lock.scripts/flag-sync.shandscripts/stack-check.share vendored too: MIT-licensed copies of Flag'stools/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 servedpublic, max-age=31536000, immutable. HTML isno-store. Every other non-HTML file is served at a stable URL that something outside the bundle hard-codes, so it isno-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 inindex.html's pre-paint script tag), the icon sprite and logos undericons/andlogos/,app-icons/,icon.svg,apple-icon.pngandshare.png(named by the manifest,<use href>, favicons and share metadata), thebackgrounds/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 testwork without it (pages then answer 503), butmake buildrunsmake webfirst and ends withfleet version --web, which fails on a binary without one — as do the CI web job, release.yml (which also refuses a binary built withfleet_host_executor) andfleet release fetchon 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 levelerror/warningfor 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 underFLEET_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 thefleet_host_executorbuild 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 reacheshost.gofrom 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.Runis 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_KEYlives 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-toolsfrontmatter is parsed and surfaced for review (skills library UI,/skillsAPI) 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 genericconfig/defaultbundle — 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 itgo vet- andgolangci-lint-clean — lint failures block CI. - Coverage (advisory, not a merge gate): CI runs the plain
go teststep with-coverprofile=coverage.out -covermode=atomic, then prints the project total to the log and writes the per-packagego tool cover -functable to the Actions job summary. There is no external coverage service and no coverage threshold anywhere in the pipeline — the Codecov upload andcodecov.ymlwere removed because the repo has noCODECOV_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 —checkandformat --check— actionlint + shellcheck, the test suites, the-racelane, govulncheck, Grype,npm audit, CodeQL, Semgrep, the Helm chart lint, both Playwright suites, the migration linter, and gitleaks — all rolled up into the one requiredCI gatecheck.) - Match the surrounding code: naming, idioms, and comment density. The
internal/agentcorepackage 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 notpkill -f 'go test'(it can kill the shell). Prefermake test(it sets the host-executor tag — seescripts/go-test.sh). The DB suites need onlyFLEET_TEST_DATABASE_URL(a server whose role canCREATE DATABASE): every DB test clones a database of its own from a migrated template (internal/testdb;internal/testdb/fleetdbfor the stores), so never share one, and callt.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 themodels.Taskfield — and, for areadcolumn, itstaskScanBuffield (the compiler enforces that one). The registry derives the SELECT/scan, insert, upsert andUpdateTaskTxstatements from per-row flags; flagexport(and add the matchingmodels.TaskExportRecordfield) 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 thesched/dbregistry 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/helprenders AND what thefleet-guideskill 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 runmake 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.mdhas 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 indocs/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.mdasks 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. SeeCONTRIBUTING.mdfor 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-housed | Lives in | Upstream (version) | Licence |
cron parser + DST-safe Next | internal/cron | robfig/cron v3.0.1 | MIT |
| sched migration runner | internal/sched/db/migrate.go | golang-migrate v4.20.1 (state-compatible re-implementation) | MIT |
| Web Push (RFC 8291/8292) | internal/webpush/protocol.go | replaces webpush-go v1.4.0 (own code) | — |
| HTML → Markdown | internal/tools/htmlmd | html-to-markdown v1.6.0 | MIT |
| rotating log file | internal/logging/rotate | lumberjack v2.2.1 | MIT |
| ClientIPFromXFF, Recoverer, WrapResponseWriter | internal/httpmw | chi middleware: v5.4.0 (ClientIPFromXFF), v5.3.2 (Recoverer, WrapResponseWriter); chi stays the router | MIT |
| code highlighting | web/src/app/chat/ui/highlight/ | prismjs 1.30.0, refractor 5.0.0, react-syntax-highlighter 16.1.2 | MIT |
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/stewardis 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
/helpguides, thefleet-guideskill, 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:
- 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.yamlis 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. - 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.
- 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; itsmcp/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 cutlassmcp/docs/CONSUMER_SYNC.md) — do not re-introduce an automated sync, and do not tell anyone to "fix it upstream and re-sync". - Identity/credentials are harness-side. Host-side broker + suffixed
account vars +
identity_envrefusal; servers read plain env and stay customer-agnostic. Bundle manifests name the variables, never values. - Additive-first schema evolution. New
ServerDeffields 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.
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.

