Ctrl K
ComunidadGuías…

AGENTS.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# AGENTS.md

Concise entrypoint for AI agents and new contributors working in this repo.
For contributor process see [`CONTRIBUTING.md`](CONTRIBUTING.md).

**Assistant sessions:** this file and `CONTRIBUTING.md` are the public entrypoints.
For optional workstation instructions, discover private context as described below.

**Design context and working records.**
[`docs/README.md`](docs/README.md) is the map — from there, current contracts
are [`docs/specs/`](docs/specs/README.md) (held to the code by
`docs-inventory -strict`), decided architecture is
[`docs/decisions/`](docs/decisions/README.md), operational procedures are
[`docs/runbooks/`](docs/runbooks/README.md). Existing public proposals are
indexed in [`docs/prd/`](docs/prd/README.md). New internal analyses, handoffs
and experiments belong in the separate private context repository, discovered
with `git config --get crewship.internalContext`; read its `AGENTS.md` and topic
index when available. See the [boundary and migration rules](docs/development/private-context.md).
Public builds and contributions must not require it. Read the specification
that matches your task, not the whole archive.

**Placement and tooling:** use the [repository layout](docs/development/repository-layout.md)
and [script catalog](scripts/README.md) before adding a directory or running an unfamiliar helper.

## What Crewship is

Open-source AI agent orchestration platform. A single Go binary embeds a Next.js
static export; agents run inside Docker containers behind a credential-injecting
sidecar proxy. Orchestration is **queue + trigger** based (cron schedulers,
webhooks, an autonomous assignment queue, durable pipeline waitpoints), not
infinite loops.

## Before you touch an issue: claim it

Ten sessions work this repo at once and all push as the same GitHub account, so
the assignee field cannot distinguish "another session has this" from "that's
me". #1481 duplicated #1471's fix and both sides lost the work.

```bash
scripts/claim-issue.sh <issue>          # checks for a live claim, then claims
scripts/claim-issue.sh <issue> --check  # read-only: who holds it, which PRs touch it
scripts/claim-issue.sh --list           # every open claim in the repo
scripts/claim-issue.sh <issue> --release "why you stopped"
```

Claim **before the first commit**, release in the same thread when you stop —
including when you failed, so the next session starts from your evidence. It
exits 3 without posting if someone else holds it. Full convention and failure
modes: [CONTRIBUTING.md → Claiming an issue](CONTRIBUTING.md#claiming-an-issue-before-you-work-it).

## Build, run, test

```bash
./dev.sh start          # dev: Go :8080 + Next.js :3001 (hot reload). `make up` is an alias.
make build              # prod: pnpm build → static export → embedded → ./crewship
make build:go           # Go binary only (+ sidecar)

# Verification loop — run until green before considering work done:
go test ./... -count=1 -timeout 40m  # all Go tests (Docker tests self-skip if daemon absent)
go vet ./...            # static analysis
pnpm lint               # ESLint (UI/frontend changes)
pnpm build              # confirm static export still builds (cross-cutting changes)
```

Multi-instance: a clone named `crewship_N` auto-offsets all ports/data/sockets,
so parallel agents/worktrees run conflict-free (see `dev.sh`).

**Throwaway servers** (a second, disposable instance for a test or a
screenshot pass): start them only with
`scripts/throwaway-server.sh run NAME --binary /tmp/crewship-N-dev -- CMD`
(or `start`/`stop`). It gives the server its own data directory, port,
container prefix and installation identity, and on exit removes exactly the
containers and volumes that server created before deleting its data. A server
started by hand and its data directory deleted leaves containers no
installation can find again. `restart NAME [--down-seconds N]` stops only the
server process and starts it again on the same data, port and identity (no
reseed); `scripts/acceptance-chat-recovery-throwaway.sh` uses it to prove host
chat recovery (#4824). Protected host capture needs a state root whose
ancestors are not group-writable (`CREWSHIP_THROWAWAY_STATE`); otherwise the
server silently uses the legacy exec transport.

**`web/out` embed:** `web/embed.go` embeds the Next.js export, so `web/out/`
must exist for *any* Go build to compile. It is tracked-by-one-file
(`web/out/.placeholder.html`) so `go build ./...` works in a fresh worktree
with zero setup — **do not `mkdir web/out` or hand-roll an `index.html` stub**,
and do not let a `git add -A` delete the placeholder after a build. Such a
binary has no UI (every UI route → `503` + an explanatory page); run
`make build` for a real one. Details: [`docs/development/web-out-embed.md`](docs/development/web-out-embed.md).

## Before you merge: confirm the review happened

CodeQL findings can live only in a run’s annotations; inspect them as well as the aggregate check.

Review the final diff and document concrete findings, fixes and
validation in the PR. Keep required CI checks green; skipped checks are not
execution evidence. A self-review must be labelled as such, not presented as
an independent review. See [CONTRIBUTING.md](CONTRIBUTING.md#review-before-merge).

**Merge queue:** after review, submit with `gh pr merge <PR> --auto --squash`
(and `--match-head-commit <SHA>` for the reviewed head). Let the queue check the
current base; do not repeatedly update branches to chase main. Do not use
`--admin` or a REST bypass except a recorded emergency with the responsible
operator, reason, reviewed SHA and successful exact-SHA required checks.
See [CONTRIBUTING.md → Merge through the queue](CONTRIBUTING.md#merge-through-the-queue).

**Risk tiers:** low-risk changes (docs, tests, additive code behind a default-off
flag) merge on green required checks and an automated review by another
session; authorization, privacy, data deletion, migrations, CI workflows,
branch rules and infrastructure need a deep independent review of the exact
head SHA by a separate session plus a regression test. Only irreversible actions
(production, destructive data operations, secrets, history rewrites, removing
required checks) wait for the maintainer. Cap review at three rounds, then merge with recorded debt or
close. Fix a red main by revert first. Details:
[CONTRIBUTING.md → Review scaled by risk](CONTRIBUTING.md#review-scaled-by-risk).

## Architecture map (`internal/`)

```text
api/            HTTP API (stdlib http.ServeMux, "METHOD /path" patterns)
orchestrator/   agent execution engine — container exec + streaming + env build
sidecar/        credential-injecting forward proxy (127.0.0.1:9119)
keeper/         credential gating + gatekeeper + Phase-2 self-improvement routines
scheduler/      robfig/cron engine — fires scheduled agent runs
pipeline/       pipeline runner: schedules, webhooks, durable waitpoints (HITL)
harbormaster/   approval gate for dangerous actions (approvals_queue)
policy/         per-crew autonomy matrix (strict|guided|trusted|full × warn|block)
paymaster/      cost ledger + budgets + per-model pricing
journal/        durable run/event journal (the shared audit + activity spine)
episodic/ consolidate/ memory/   agent memory: recall before runs, consolidate after
manifest/       declarative `crewship apply` (plan/validate, ~20 resource kinds)
skills/         SKILL.md parser + importer + bundled skills
auth/           JWE (NextAuth v5 compatible) validation
database/       SQLite via database/sql (modernc.org/sqlite, pure Go, no CGO)
provider/       infra interfaces: Container (Docker/Apple), Storage, State
scrubber/ httpsafe/ safepath/   credential scrubbing, SSRF + path-traversal guards
ws/             WebSocket hub (pub/sub channels)
config/         defaults → YAML → env vars (env wins)
```

Frontend: `app/` (Next.js App Router), `components/` (`ui/` = shadcn), `hooks/`,
`lib/` (Zod schemas, CASL permissions).

## Conventions

- **Go**: no ORM — raw `database/sql` with `QueryRowContext`/`ExecContext`. Provider
  interfaces for all infra. Imports stdlib → external → internal. Table-driven
  tests; `t.Skip()` for optional deps (Docker).
- **Errors**: two envelopes, and the split is real rather than aspirational.
  `replyError` writes `{"error": "…"}` and is the overwhelming majority (~2200
  call sites); `writeProblem` writes RFC 7807 (~450). Both send
  `Content-Type: application/json` — `writeProblem` routes through `writeJSON`,
  which hard-sets it, so an RFC 7807 body does **not** arrive as
  `application/problem+json` unless a handler sets that header itself.
  Match the surrounding file rather than converting one; `cmd/gen-openapi`
  documents whichever the handler uses, so a mismatch shows up as a contract
  finding rather than a surprise. (This line used to read "RFC 7807 Problem
  Details for errors", which described about 17% of the code — #1919.)
- **DB**: `snake_case`, plural tables; CUID ids (`internal/api/cuid.go`); soft delete
  via `deleted_at`; timestamps TEXT (RFC 3339).
- **Frontend**: Next.js 16 App Router (static export), Tailwind 4 + Radix/shadcn,
  Zod, CASL (`lib/permissions/`). ES modules only. `@/*` path alias.
- **Responsive**: two axes, and picking the wrong one is how tablets broke
  (#2483). *Layout* keys on width — `md:` for the phone/desktop split, matching
  `useIsMobile`'s 768. *Touch sizing* keys on the pointer — `coarse:`
  (`@media (pointer: coarse)`, defined in `app/globals.css`), so a 44px target
  applies to every touch device at any width instead of stopping at some
  breakpoint. Note `--spacing` is `0.23rem`, so `h-12` is the 44px class, not
  `h-11`. Navigation is defined once in `lib/nav-sections.ts` and read by both
  the rail and the phone sheet.
- **Commits**: conventional (`feat:`, `fix:`, `refactor:`, `docs:`, `test:`,
  `chore:`). Branch from `main`.

## Migrations

**One `.sql` file per migration** in `internal/database/migrations/`. The
directory *is* the registry — there is no central list to edit, which is the
point: two people adding a migration add two files and cannot conflict.

```bash
date -u +%Y%m%d%H%M%S    # the version stamp — filenames are
                         # <YYYYMMDDHHMMSS>_<snake_case_name>.sql
```

Versions must be strictly ascending, so **append, never insert** a stamp older
than one already committed. Sequential numbers are gone: `v1..v169` were
allocated that way and are frozen in the `legacyMigrations` slice in
`migrate.go` — **nothing at or below v169 may move or change**, they are
applied in databases nobody controls (`migrate_version_scheme.go` enforces
this at startup).

Migrations that genuinely need Go (schema discovery at apply time, SQLite
table rebuilds) still go in that slice with a timestamp version. Everything
expressible as plain SQL belongs in a file. `migrations/post_deploy/` runs
after the server is serving instead of blocking boot — read its README first,
it is a contract about what the running code must tolerate, not a free win.

Runs at startup, tracked in `_migrations`. Check your work with
`go run ./scripts/lint-migrations`, which fails if an already-shipped
migration's SQL changed under it.

**Prisma is for TypeScript types only — never run `prisma migrate`.**

Full detail: [`internal/database/migrations/README.md`](internal/database/migrations/README.md)
and [`docs/guides/migrations.mdx`](docs/guides/migrations.mdx).

## NEVER DO

Five of these are checked, not promised — `go run ./scripts/agents-invariants`,
which the `Shell` CI job runs. They are marked. The rest are about what a person
does and cannot be, so they are prose and say so.

**Checked:**

- Never add API routes under `app/` — static export silently drops them.
- Never use `"sqlite3"` as the driver name — `modernc.org/sqlite` registers `"sqlite"`.
- Never use `npm`/`yarn` — `pnpm` only (a stray lockfile is the evidence).
- Never change sidecar UID (1002) or agent UID (1001) — it's a security boundary.
- Never track root assistant instructions (`CLAUDE.md`, `CODEX.md`, `GEMINI.md`),
  workspaces (`.claude/`, `.codex/`, `.cursor/`),
  `.github/copilot-instructions.md`, private-context aliases, internal mockups
  or captured run reports. The invariant reads the
  Git index; ignored local files remain allowed. Public test fixtures stay public.

**Prose — nothing enforces these but you:**

- Never skip the verification loop (`go test` + `go vet`) — or ship without tests.
- Never change the GCM byte layout in `internal/encryption/` — breaks all stored creds.
- Never discard WIP — `git stash` before switching branches, never `git checkout .`.
- Never start an issue without checking it for a live claim — the assignee is not
  a lock when every session is the same account.
- Never commit secrets (`.env.local`, real keys) — gitleaks runs on every PR, so
  this one has a net, just not a preventative one.

## When unsure

Find the right category from [`docs/README.md`](docs/README.md), read the
spec that matches the task ([`docs/specs/`](docs/specs/README.md)), then the
proposal that motivated it ([`docs/prd/`](docs/prd/README.md)) — before
assuming design or requirements. Keep documents current after significant
changes — and prefer pointing at the gate that enforces a fact over restating
the fact, because prose with no check behind it is the thing that rots.