Files
tireless/CLAUDE.md
rob thijssen 6545d1980b
All checks were successful
deploy / deploy (push) Successful in 5m35s
feat(data): add the initial schema and migrations
Closes #2. Three tables mirroring tireless-entities, plus the sqlx
compile-time-checking decision the rest of stage 1 inherits.

RUNTIME QUERIES, NOT `query!`. No .sqlx metadata, no DATABASE_URL to build.
lairball, the other house project on this cluster, does the same. The deciding
argument is specific to tireless: compile-time checking makes a database a build
dependency, and this crate is meant to be modified by a 27B model working
unattended. A build that fails without a database it cannot provision is one
that model cannot fix, and its documented failure mode is to improvise. The cost
is named in store.rs — a malformed query is caught by a test, not by cargo.

Enums are text with a check constraint, not Postgres enum types: adding a
JobKind variant should be a migration, not an ALTER TYPE holding a lock. Unit
tests assert every serde variant appears in the schema, so adding a variant
without a migration fails the build rather than the first job of that kind.

That surfaced a spelling that would have been permanent: `rename_all =
"snake_case"` turns Forge::GitHub into `git_hub`, across the database, the JSON
API and the generated TypeScript. Renamed to `github` now, while nothing is
persisted and GitHub support is still disabled.

The live-issue index is PARTIAL, and both directions of getting it wrong are
silent. A plain unique constraint on (forge, owner, repo, number) would forbid
re-running a terminal job, and would forbid the discovery lane outright, since
discovery recurs against one tracking issue on a cooldown. Partial on the
non-terminal states gives at most one live job per issue and unlimited history.

The claim index orders by created_at alone and leaves kind as a filter, because
the claim takes LIMIT 1 and can stop at the first match. Leading with kind sorts
every pending row on every claim: measured at 720 buffers versus 4, and the gap
grows with the backlog rather than staying fixed. INCLUDE (kind) was measured
too and dropped — FOR UPDATE visits the heap regardless.

Verified against Postgres 18, the same major as the house cluster: migrations
apply to an empty database and are a no-op on the second run; all eight
constraints reject what they should and admit what they should; and two
concurrent claimers of one pending job produce exactly one winner, with the
loser skipping rather than blocking.

Those live tests are #[ignore]d, not skipped on a missing variable, so a green
`cargo test` never implies the schema was exercised. CLAUDE.md says how to run
them and that an applied migration must never be edited.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013TxK1CWPkFXqdcXMJ4hVe6
2026-08-07 18:47:16 +03:00

5.3 KiB

tireless — agent instructions

Read doc/plan/design.md before making changes. It carries the constraints, the staged plan, and the reasoning behind decisions that look arbitrary in isolation.

House conventions live in ~/git/architecture (generic.md is the baseline). This project's deliberate deviations are listed at the bottom of readme.md.

tireless works on tireless. This repo is its own first subject, so changes here are both the product and the test of it. If you are an agent working from a tireless-authored issue, design.md §10 says what that implies — in particular, that some areas of this repo are constraint-bearing and are routed deliberately.

Invariants — do not "clean these up"

These are terms-of-service and safety constraints expressed as code. Each has tests. If one seems redundant, read design.md §3 before touching it.

  1. Never construct a request to a model provider. Agents are spawned as the vendor's own binary and authenticate themselves. Adding an HTTP client that talks to api.anthropic.com would break the arrangement that lets a subscription back this app.

  2. Never read or forward agent credentials. has_credentials() stats ~/.claude.json and nothing more. Do not parse it, copy it, or pass its contents anywhere.

  3. Never set ANTHROPIC_API_KEY. It reaches Claude Code only if an operator put it in the unit environment. Its presence selects pay-as-you-go; its absence selects the subscription. That choice is the operator's.

  4. Never point the OpenCode lane at Anthropic. assert_not_anthropic is checked at startup against both provider id and base URL. Local gateways that merely serve an Anthropic-compatible surface (helexa cortex) are fine and are tested for.

  5. Never let a lane run unbounded. Every agent invocation passes through tireless_core::budget::Governor. Provider rate-limit signals are checked first and are authoritative.

  6. Postgres is the authority on claims, not forge labels. Labels are a best-effort mirror. Do not make a decision by reading a label that could be made by reading the database.

  7. Never use --system-prompt for Claude Code; append instead. Replacing Claude Code's default discards the tool-use scaffolding that makes it a coding agent. SYSTEM_PROMPT_FLAG is --append-system-prompt for this reason.

  8. The four prompts in prompt/ are one set — edit them together. plan.cc.md emits the structure implement.oc.md consumes and tireless_core::plan::validate enforces. Changing one alone breaks the handoff silently, as a bad pull request rather than an error. Bump contract-version: in all four plus SYSTEM_PROMPT_CONTRACT_VERSION together; PromptSet::load refuses a mismatched set.

  9. A plan is validated, not trusted. Never enqueue implementation work from a plan that has not passed plan::validate. The Acceptance (runnable stopping condition) and Out of scope (boundary) requirements exist because a small model needs them; do not relax them because a plan looks fine to a human reader.

  10. Admission is inherited, never invented. tireless may apply the opt-in label only to an issue descended from one a human opted in. may_opt_in(JobKind::Discover) is false and must stay false: it is the only thing standing between "proposes work" and "generates its own work indefinitely". See design.md §2.5. This is not a policy to relax once the system is trusted — the failure it prevents is unbounded, not merely wrong.

  11. Every constraint above is reachable from a binary's startup path. A guard that exists as a tested function nobody calls is not a guard. If you add one, wire it into Config::validate or tireless_agent::preflight::run so it cannot be bypassed by a caller that forgot.

Quality gate

Before considering a change complete:

cargo fmt --all
cargo clippy --all-targets --all-features -- -D warnings
cargo test --workspace
cd dashboard && npm run lint && npm run build

This exact block is what a plan targeting this repo should use as its runnable Acceptance (design.md §10.3).

Touching crates/tireless-data/migrations/ or any SQL also means running the database tests, which the gate above deliberately skips — CI has no Postgres, so they are #[ignore]d rather than silently passing:

podman run -d --name pg -e POSTGRES_PASSWORD=test -e POSTGRES_DB=tireless_test \
    -p 55432:5432 docker.io/library/postgres:18-alpine
export TIRELESS_TEST_DATABASE_URL=postgres://postgres:test@127.0.0.1:55432/tireless_test
cargo test -p tireless-data -- --ignored

Queries are checked at runtime, not compile time — there is no .sqlx offline metadata and no DATABASE_URL needed to build. store.rs says why. The consequence is that these tests are the only thing standing between a malformed query and production.

Never edit an applied migration. sqlx records a checksum per version, so an edit makes every deployed database refuse to start. Add a new numbered file.

Commits

Conventional Commits (type(scope): subject), imperative, under ~70 chars. Commit autonomously when the work is a coherent, complete unit; hold off when follow-ups on the same topic are likely. See generic.md §12.