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
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.
-
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.comwould break the arrangement that lets a subscription back this app. -
Never read or forward agent credentials.
has_credentials()stats~/.claude.jsonand nothing more. Do not parse it, copy it, or pass its contents anywhere. -
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. -
Never point the OpenCode lane at Anthropic.
assert_not_anthropicis 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. -
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. -
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.
-
Never use
--system-promptfor Claude Code; append instead. Replacing Claude Code's default discards the tool-use scaffolding that makes it a coding agent.SYSTEM_PROMPT_FLAGis--append-system-promptfor this reason. -
The four prompts in
prompt/are one set — edit them together.plan.cc.mdemits the structureimplement.oc.mdconsumes andtireless_core::plan::validateenforces. Changing one alone breaks the handoff silently, as a bad pull request rather than an error. Bumpcontract-version:in all four plusSYSTEM_PROMPT_CONTRACT_VERSIONtogether;PromptSet::loadrefuses a mismatched set. -
A plan is validated, not trusted. Never enqueue implementation work from a plan that has not passed
plan::validate. TheAcceptance(runnable stopping condition) andOut of scope(boundary) requirements exist because a small model needs them; do not relax them because a plan looks fine to a human reader. -
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)isfalseand 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. -
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::validateortireless_agent::preflight::runso 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.