From ddb2574bbe1ebd21b0d3710ec3c0802b985aada3 Mon Sep 17 00:00:00 2001 From: rob thijssen Date: Fri, 7 Aug 2026 15:36:54 +0300 Subject: [PATCH] docs: state the purpose, the autonomy boundary and the dogfooding plan MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit design.md described what tireless does to an issue but never what it is for, and in one place said the opposite of the intent: §1 assigned "identifying what should be worked on" to the operator, which is the thing discovery automates. A reader — human or agent — would have concluded the system is human-triggered only and never built the lane. Rewrite §1 around continuous multi-repo development. Add §2.5, the autonomy boundary, as a table of which transitions are automatic and which are human, with the alternatives considered and why they lose. Add §2.6 on anchoring discovery to a tracking issue rather than making Job.issue a sum type, and what that buys. Add stage 6 for the lane, renumbering scheduling to 7 and hardening to 8, with the reasoning for placing a cheap stage late. Add §10 on tireless working on tireless: that a merged PR restarts the runner that opened it and why that is survivable but not free, which areas must be routed to the stronger lane because they are constraint-bearing, and the four questions dogfooding is expected to answer. Two new invariants. Admission is inherited (§2.5), and every constraint must be reachable from a binary's startup path — the rule the previous commit's orphaned guards violated. AGENTS.md symlinks CLAUDE.md: both agents look for their own filename, the implementation prompts tell them to, and a symlink is the only version of this that cannot drift. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_013TxK1CWPkFXqdcXMJ4hVe6 --- AGENTS.md | 1 + CLAUDE.md | 26 +++- doc/plan/design.md | 332 +++++++++++++++++++++++++++++++++++++++++---- readme.md | 44 ++++-- 4 files changed, 361 insertions(+), 42 deletions(-) create mode 120000 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 120000 index 0000000..681311e --- /dev/null +++ b/AGENTS.md @@ -0,0 +1 @@ +CLAUDE.md \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index 63e4ee1..26bca60 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,6 +8,11 @@ House conventions live in [`~/git/architecture`](https://git.lair.cafe/lair/arch (`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 @@ -44,11 +49,11 @@ tests. If one seems redundant, read design.md §3 before touching it. coding agent. `SYSTEM_PROMPT_FLAG` is `--append-system-prompt` for this reason. -8. **The three prompts in `prompt/` are one contract — edit them together.** +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 three plus `SYSTEM_PROMPT_CONTRACT_VERSION` + `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 @@ -57,6 +62,18 @@ tests. If one seems redundant, read design.md §3 before touching it. 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: @@ -65,9 +82,12 @@ Before considering a change complete: cargo fmt --all cargo clippy --all-targets --all-features -- -D warnings cargo test --workspace -cd dashboard && npm run build +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). + ## Commits Conventional Commits (`type(scope): subject`), imperative, under ~70 chars. diff --git a/doc/plan/design.md b/doc/plan/design.md index 2d7955f..5fd5a4f 100644 --- a/doc/plan/design.md +++ b/doc/plan/design.md @@ -1,6 +1,7 @@ # tireless — design and staged implementation plan -**Status:** planning. Nothing in stages 1+ is built yet. +**Status:** stage 0 built (workspace, domain model, policy, config, prompts, +deployment). Stages 1+ are specified but not implemented. **Conventions:** [`~/git/architecture`](https://git.lair.cafe/lair/architecture) — `generic.md` is the baseline; deviations are flagged inline below and in `readme.md`. @@ -8,14 +9,44 @@ ## 1. What tireless is -A daemon that watches labelled issues on Gitea (and, for legacy repos, GitHub), -claims them, and either **plans** them (decomposing an issue into an epic and -child issues) or **implements** them (producing a branch and a pull request). -A human reviews the output. Nothing merges itself. +**A system for keeping several repositories moving without an operator driving +each change by hand.** -The point is to move the operator's attention up a level: from writing -implementation plans and implementations, to identifying what should be worked -on and reviewing what came back. +It watches Gitea (and, for legacy repos, GitHub), and runs three kinds of work +against the repos it is given: + +- **Discover** — survey a repository and *propose* issues worth opening. +- **Plan** — decompose an admitted issue into an epic and child issues, each one + specified well enough to be implemented by a model that cannot ask questions. +- **Implement** — produce a branch and a pull request. + +Those three compose into a loop: proposals become issues, issues become specs, +specs become pull requests, and pull requests become the next thing a human +looks at. A human reviews the output. Nothing merges itself. + +### The point + +Each stage moves the operator's attention up a level. + +Without tireless, the operator writes the plan and the implementation. With +planning and implementation automated, they identify what to work on and review +what came back. With discovery automated too, they are handed a ranked list of +*candidate* work and decide what deserves to exist at all. + +That last step is the one that makes this worth building across several repos +rather than one. Reviewing a pull request is bounded work. Noticing that a repo +has drifted from its own design document, or that a stated guarantee has no test +behind it, is unbounded work that scales with the number of repositories and +gets skipped first when there are six of them. It is also exactly the kind of +reading a model does tirelessly and a person does once a quarter. + +### Where the human stays + +Automating discovery is the point at which a system like this can start +generating its own work indefinitely, so the boundary is drawn explicitly and +enforced in code rather than left to good intentions — see §2.5. In short: +**a human decides what enters the system, and a human decides what merges.** +Everything between those two points runs unattended. ### What tireless is not @@ -23,6 +54,8 @@ on and reviewing what came back. *driving agents*; its task/project/board model is deliberately absent here. The forge's issues are the work list. - **Not a merge robot.** It opens PRs. Review and merge stay human. +- **Not an autonomous backlog.** It proposes; it does not admit its own + proposals. A discovered issue sits inert until a person opts it in (§2.5). - **Not a model provider client.** It never speaks to Anthropic or any inference API directly. This is load-bearing — see §3. - **Not a multi-tenant service.** One operator, one subscription, one fleet. @@ -34,17 +67,48 @@ on and reviewing what came back. ### 2.1 The loop +At the level of one job: + ``` poll ──▶ enqueue ──▶ claim ──▶ prepare clone ──▶ run agent ──▶ deliver ──▶ report ▲ │ └──────────────────────────── reconcile ◀──────────────────────────────────┘ ``` +At the level of a repository, the three job kinds chain — with the human gates +marked, because they are the whole reason this is a supervised system: + +``` + ┌──────────── survey, on a cooldown ────────────┐ + │ │ + ▼ │ + ┌─────────┐ proposes ┌────────────┐ │ + │ Discover│─────────────▶ │ proposed │ │ + └─────────┘ (unadmitted)│ issues │ │ + └─────┬──────┘ │ + ★ human opts in │ + ▼ │ + ┌───────────┐ │ + │ Plan │ │ + └─────┬─────┘ │ + validated, children │ + inherit admission │ + ▼ │ + ┌───────────┐ │ + │ Implement │ │ + └─────┬─────┘ │ + ▼ │ + pull request │ + ★ human reviews & merges ─────┘ +``` + +Two `★` gates, and only two. Everything between them is unattended. + Two systemd units, one binary: | Unit | Role | Spends tokens? | | --- | --- | --- | -| `tireless-poller` | discovers opted-in issues, enqueues, mirrors labels | no | +| `tireless-poller` | finds opted-in issues, enqueues, mirrors labels | no | | `tireless-runner` | claims jobs, prepares clones, drives agents, opens PRs | **yes** | Splitting them means the discovery loop can run continuously while the @@ -59,15 +123,21 @@ tireless reports back the same way. | Label | Written by | Meaning | | --- | --- | --- | -| `tireless` | human | **Opt-in.** Without it tireless ignores the issue entirely, whatever else is present. | +| `tireless` | human¹ | **Opt-in.** Without it tireless ignores the issue entirely, whatever else is present. | +| `tireless/discover` | human | Survey this repository and propose work. Applied to a tracking issue (§2.6). | | `tireless/plan` | human | Decompose into an epic and child issues. | | `tireless/implement` | human | Implement and open a PR. | | `tireless/agent:cc` | human | Force the Claude Code lane. | | `tireless/agent:oc` | human | Force the OpenCode lane. | +| `tireless/proposed` | tireless | Opened by a discovery run. **Awaiting a human decision** — deliberately not opted in. | | `tireless/claimed` | tireless | A job holds this issue. | | `tireless/blocked` | tireless | Needs a human; tireless has stopped. | | `tireless/done` | tireless | Delivered — children created, or PR opened. | +¹ And by tireless in exactly one case: a child issue decomposed from a parent a +human already opted in. See §2.5 — that exception is the whole autonomy design, +and it is enforced by `tireless_entities::may_opt_in` rather than by convention. + The opt-in label is separate from the mode labels on purpose. Removing one label (`tireless`) disables the issue without destroying the operator's expressed intent about *how* it should be handled, and a single unlabelled repo full of @@ -84,6 +154,7 @@ Implemented in `tireless-core::routing`, with tests. | Job | Lane | Why | | --- | --- | --- | +| `Discover` | Claude Code (Opus) | Deciding what is worth building is the highest-judgement task in the system, and the lowest volume. Its output sets what everything downstream spends its budget on. | | `Plan` | Claude Code (Opus) | Decomposition is the high-judgement, low-volume task. Getting a plan wrong is expensive downstream; getting it right is worth the strong model. | | `Implement`, descended from a tireless plan | OpenCode (helexa) | A tireless plan *is* a spec. Executing a written spec is what a local model on the GPU fleet does well, at no subscription cost. | | `Implement`, human-written issue | Claude Code | No plan behind it means the issue needs interpretation before it needs code. | @@ -91,9 +162,14 @@ Implemented in `tireless-core::routing`, with tests. The general rule: **Claude Code gets judgement, OpenCode gets specification.** -This also produces a pleasing economic shape. The subscription is spent on the -scarce thing (planning, and interpreting under-specified work), while the bulk -of mechanical implementation runs on hardware already sitting in the office. +This also produces a pleasing economic shape. The two lanes that *create* work — +discovery and planning — are the expensive ones, and they are also the two +bounded to a handful of runs per window. The lane that *consumes* work, which is +the bulk of it by volume, is free. The subscription is spent on the scarce thing, +while mechanical implementation runs on hardware already sitting in the office. + +Spending is highest exactly where a mistake is cheapest to notice, which is the +same ordering the staged plan uses (§7). ### 2.4 The plan contract @@ -185,6 +261,84 @@ package versions: an alias that silently starts resolving to a different model would change implementation behaviour between one job and the next with no deploy and no signal. +### 2.5 The autonomy boundary + +A system that proposes its own work and then acts on it has no natural stopping +point. This section says exactly where the human is, and why there. + +| Transition | Automatic? | Why | +| --- | --- | --- | +| survey → proposed issues | **yes** | Output is text. A bad proposal costs an issue nobody opens. | +| proposed issue → planned | **no — human** | The only place anyone decides the work is worth doing at all. | +| plan → child issues | **yes** | Decomposing admitted work is not a new decision about scope. | +| child issue → implemented | **yes** | The child descends from something a human admitted. | +| pull request → merged | **no — human** | Review is the point. Nothing merges itself. | + +The rule that produces this, stated once: + +> **Admission is inherited, never invented.** +> tireless may opt an issue in only when it descends from an issue a human opted +> in. Discovery output has no admitted ancestor, so it is created unlabelled and +> waits. + +This is `tireless_entities::may_opt_in`, with tests. It is deliberately a +function and not a config flag: an operator who wants more autonomy should have +to change code and pass review, because the failure mode is not a bad pull +request but an unbounded one. + +**Why this specific gate and not another.** The alternatives were considered: + +- *Gate every transition* — a human labels each plan child too. This makes + `parent_job_id`-based routing (§2.3) nearly dead code, and turns a plan of + eight children into eight relabelling chores, which is the work the system + exists to remove. +- *Gate only at merge* — discover → plan → implement runs unattended. One + enthusiastic survey then consumes an entire window budget on work nobody + agreed to, and the operator discovers this from their review queue. + +Gating at ideation puts the single human decision at the only point where the +question is *"should this exist?"* rather than *"is this done correctly?"* — and +leaves the second question to code review, where it already lived. + +**What this costs.** Proposals accumulate if nobody triages them. That is the +intended failure mode: a backlog of unadmitted suggestions is inert and free, +whereas a backlog of in-flight jobs is neither. Discovery is rate-limited +(§2.6) partly so the inert pile grows slowly enough to stay readable. + +### 2.6 Anchoring the discovery lane + +Discovery differs structurally from the other two kinds: planning and +implementation act on *an issue*, but a survey acts on *a repository*. It +recurs, and it has no natural subject. + +tireless anchors it to a **long-lived tracking issue** in each repo, carrying +`tireless` + `tireless/discover`. That issue is the survey's subject, and each +run comments its findings there before opening anything. + +The alternative — making discovery a repo-level scheduled activity, with +`Job.issue` becoming a sum type over issue-or-repo — was rejected. It is +structurally purer, and it costs: every layer that handles a job (claiming, +leasing, label mirroring, the API, the dashboard) would need to handle a job with +no issue behind it, in order to serve one job kind. The tracking issue buys the +same recurrence for free and brings two things the repo-level design would have +had to invent: + +- **A place to report.** A survey that proposes nothing still has something to + say, and comments on the tracking issue are a readable history of what has + been considered and dismissed. +- **The usual controls.** Removing a label pauses discovery; the claim, lease and + reconciliation machinery all apply unchanged. + +Because it recurs against a terminal job, a **cooldown** (`discover.cooldown_hours`, +default weekly) governs re-enqueue. Without it the tracking issue would +re-enqueue on the next poll after every run, and the most expensive lane in the +system would run continuously against a repo that had not changed. + +A per-run proposal cap (`discover.max_proposals_per_run`, default 8) bounds the +output. A survey wanting to file forty issues has misunderstood the job, and the +operator should learn that from a truncated list and a note, not from their +notification inbox. + --- ## 3. Constraints @@ -374,13 +528,24 @@ job), so §1's "extract when the second consumer appears" test is met. | Concern | Value | | --- | --- | -| Host | `bob.hanzalova.internal` | +| Host | `bob.hanzalova.internal` — binaries, units, config, job trees | | API port | **23296** — derived per `port-allocations.md` §3, registry updated | -| Ingress | `tireless.internal` on the hanzalova proxy, mesh-only, per-service internal cert | -| Dashboard | static, `/var/www/tireless`, served by nginx | +| API bind | `0.0.0.0:23296`, reachable across the mesh only | +| Ingress | `tireless.internal` on the **hanzalova proxy**, mesh-only, per-service internal cert | +| Dashboard | static, `/var/www/tireless` **on the proxy**, served by nginx | | Database | `magrathea.kosherinata.internal:5432`, mTLS | | Deploy | Gitea Actions, build-and-rsync, musl static | +**Ingress is not on bob.** nginx runs on the office proxy: it serves the built +dashboard from its own web root and reverse-proxies `/v1` to bob across the +mesh. Three artefacts encode that single decision and must agree — +`asset/nginx/tireless.hanzalova.conf`, the `0.0.0.0` bind in +`asset/config/config.toml.tmpl`, and `asset/firewalld/tireless-api.xml` opening +the port. If ingress ever moves onto bob, all three change together: loopback +bind, no firewalld service, vhost relocated. They were briefly inconsistent +during stage 0 — a loopback bind with a remote proxy — which is a configuration +that deploys green and then serves nothing. + bob was chosen because it already hosts vibe-kanban and helexa-bench, and sits on the same site as cortex (`hanzalova.internal:31313`) — so the highest-volume path, OpenCode implementation runs, stays local rather than crossing the @@ -430,14 +595,24 @@ Each stage is independently deployable and independently verifiable, per without spending tokens is proven first**, and the first token-spending capability produces text (issues), not code. -### Stage 0 — Foundations *(scaffolded)* +### Stage 0 — Foundations *(built)* -Workspace, entities, routing and budget logic with tests, binaries that start and -stop cleanly, dashboard shell, deployment assets, CI. +Workspace and domain model; routing, budget, plan validation, policy and config +with tests; the four system prompts; binaries that load and validate their +configuration, run preflight and stop cleanly; dashboard shell; deployment +assets; CI. -*Done when:* `tireless-api` answers `/v1/ready` on bob:23296, the dashboard loads -at `tireless.internal`, all three units are active, and the deploy workflow is -green end to end. +Every constraint in §3 is enforced by code reachable from a binary's startup +path — `Config::validate` calls the Anthropic guard, `PromptSet::resolve` checks +the prompt set, and the runner refuses to start without a credential store. +There is no invariant here that is merely written down. + +*Done when:* `tireless-api` answers `/v1/ready` from the proxy, the dashboard +loads at `tireless.internal`, all three units are active, `tireless preflight` +reports the expected billing mode, and the deploy workflow is green end to end. + +*Not done yet:* nothing in this stage has been deployed to bob. The workflow has +never run. ### Stage 1 — Forge ingestion (read-only) @@ -525,15 +700,40 @@ end-to-end by OpenCode on the GPU fleet, with zero subscription usage; the run demonstrably respected its `Out of scope` section; and a captured upstream request shows `implement.oc.md` in the winning position. -### Stage 6 — Scheduling and dashboard control +### Stage 6 — Discovery lane + +The tracking-issue anchor (§2.6), `prompt/discover.cc.md` applied via +`--append-system-prompt`, proposal issues created with `tireless/proposed` and +**without** the opt-in label, the cooldown and the per-run proposal cap. + +*Done when:* a survey of a real repository proposes issues an operator agrees +are worth reading; none of them are opted in; a second poll within the cooldown +does not re-enqueue; and an artificially small `max_proposals_per_run` +demonstrably truncates and says so. + +*Why last among the capability stages, despite being cheap.* Its output is text, +so by the §7 ordering it looks like it belongs beside planning. But a proposal +has nowhere to go until the loop below it closes — a discovery lane running +against a system that cannot yet plan or implement just produces issues the +operator must triage by hand, which they could have written themselves. The +constraint here is not risk, it is that the value only exists once stages 3–5 +work. + +It is also the stage that makes tireless *continuous* rather than *on-demand*, +which is why §1 describes it as the point and §7 schedules it last. Those are not +in tension: it is the capstone, not the foundation. + +### Stage 7 — Scheduling and dashboard control Schedule editing, repo add/remove, lane pause/resume, budget and limit-signal -display, run history with per-run billing mode. +display, run history with per-run billing mode, and the proposal triage view — +the list of `tireless/proposed` issues awaiting a human, which is the operator's +main working surface once discovery runs. -*Done when:* the operator can add a repo, change its cadence, and pause the -Claude Code lane without touching a shell. +*Done when:* the operator can add a repo, change its cadence, admit or dismiss a +proposal, and pause the Claude Code lane without touching a shell. -### Stage 7 — Hardening +### Stage 8 — Hardening Dead-letter semantics for repeatedly failing jobs, Prometheus metrics, alerting on tripped breakers, retention and cleanup of job directories, and the optional @@ -580,7 +780,7 @@ falling back to an API key. **Unattended agents with commit rights are a real exposure.** Bounded by: a bot account that cannot push to any default branch; hardened units; per-job clones; -and human review before merge. Stage 7's container backend tightens this further, +and human review before merge. Stage 8's container backend tightens this further, and the executor interface is shaped so it can drop in without touching agent-driving code. @@ -620,3 +820,79 @@ rather than forcing an eviction to find out. **Not yet decided:** whether a failed implementation should automatically open a `tireless/blocked` issue describing what it could not do, or simply comment on the original. Deferred to stage 4, when there is real failure data to look at. + +--- + +## 10. Dogfooding: tireless on tireless + +`lair/tireless` is the first tracked repo, and its own backlog is maintained as +issues in the format this system consumes. That is deliberate: the plan contract +(§2.4) asserts that an Opus-authored spec is executable by a 27B model, and the +cheapest place to find out whether that is true is a repository whose conventions +are already written down and whose reviewer wrote the contract. + +It also means the failure modes below are not hypothetical, and are worth +knowing before the first self-directed run. + +### 10.1 A merged pull request restarts the thing that opened it + +`deploy.yaml` runs on merge to `main` and restarts `tireless-runner`. If the +runner is mid-job — quite likely, since merging a tireless PR is exactly when +other tireless work is in flight — that job's agent is killed after +`TimeoutStopSec=120`, well inside a run ceiling of an hour. + +This is survivable by design: the claim lease expires and the job returns to the +pool (§4.2), so nothing is lost except the tokens already spent. It is *not* +free, and it gets worse as concurrency rises. + +Left as-is for now, because the alternatives all have a cost that is currently +larger than the problem: draining properly means a deploy that can block for an +hour, and skipping the restart means running a stale binary silently. Revisit in +stage 8, when metrics say how often it actually happens. + +### 10.2 Some changes must not go to the cheap lane + +The 27B implementation lane follows a specification faithfully — including +following it off a cliff. Certain files in this repo are constraint-bearing in a +way a literal implementer cannot be expected to infer: + +| Area | Why | +| --- | --- | +| `CLAUDE.md` invariants | Each looks like redundant defensive code in isolation. That is precisely what makes them look like cleanup. | +| `prompt/*.md` | Behavioural specs, versioned as a set. A weakened instruction becomes a bad PR hours later, unattended. | +| `doc/plan/design.md` | The reasoning that makes the code make sense. | +| `crates/tireless-core/src/policy.rs` | Terms of service as code. | + +Label issues touching these `tireless/agent:cc`. The override exists for exactly +this (§2.3), and the general rule holds: **work whose risk is "a capable model +tidies away something load-bearing" belongs on the lane that can read the +reasoning and weigh it.** + +### 10.3 The quality gate is the acceptance command + +Every child issue planned against this repo should carry the gate from +`CLAUDE.md` as its runnable acceptance: + +```sh +cargo fmt --all +cargo clippy --all-targets --all-features -- -D warnings +cargo test --workspace +cd dashboard && npm run lint && npm run build +``` + +This is the stopping condition §2.4 requires, and it is unusually good at being +one: it is fast, it is total, and it fails loudly. A plan for this repo that +omits it has failed to do the easy part. + +### 10.4 What dogfooding is expected to reveal + +Named in advance, so the answers are evidence rather than rationalisation: + +- **Does a plan written for a 27B model actually work?** §9's open question. The + first real answer arrives in stage 5. +- **Is the discovery prompt able to say "nothing this week"?** The failure mode + is manufactured findings — a survey that proposes work to look productive + costs the operator the exact attention the system exists to protect. +- **Does the two-gate boundary hold in practice, or does the operator start + rubber-stamping?** If admission becomes reflexive, the gate is decorative and + the honest response is to make discovery propose less, not to move the gate. diff --git a/readme.md b/readme.md index c10f81b..b16d781 100644 --- a/readme.md +++ b/readme.md @@ -1,12 +1,20 @@ # tireless -Watches labelled issues on Gitea (and GitHub, for legacy repos), claims them, and -either decomposes them into an epic with child issues or implements them and -opens a pull request. A human reviews everything; nothing merges itself. +Keeps several repositories moving without an operator driving each change by +hand. It watches Gitea (and GitHub, for legacy repos) and does three things: + +- **discovers** — surveys a repo and proposes issues worth opening; +- **plans** — decomposes an issue into an epic and child issues, each specified + well enough for a model that cannot ask questions; +- **implements** — produces a branch and a pull request. + +Those chain into a loop with exactly two human gates: **a person decides what +enters the system, and a person decides what merges.** Discovery proposes but +never admits its own proposals; nothing merges itself. Two coding agents do the work, each spawned as the vendor's own binary: -- **Claude Code** — planning, and implementation of issues that need +- **Claude Code** — discovery, planning, and implementation of issues that need interpretation. Uses the operator's Claude subscription by default, or pay-as-you-go if an API key is supplied. - **OpenCode** — implementation of issues that a tireless plan already specified, @@ -20,8 +28,18 @@ Full design, constraints and the staged implementation plan: ## Status -Stage 0 (foundations) is scaffolded. Ingestion, claiming and the agent lanes are -not built yet — see §7 of the design document for what lands when. +**Stage 0 (foundations) is built and passes its gate; nothing is deployed yet.** + +Working: the domain model, routing, budgets, plan validation, the policy guards, +configuration loading and validation, the four system prompts, and preflight. +`tireless preflight` runs today. + +Not built: Postgres persistence, the forge clients, the poll loop, and every +agent executor. Stages 1–8 in §7 of the design document say what lands when. + +tireless is its own first tracked repo — see design.md §10 for what that +implies, including which parts of this repo are deliberately routed to the +stronger lane. ## Build @@ -30,7 +48,7 @@ cargo test --workspace cargo clippy --all-targets --all-features -- -D warnings cargo fmt --all -cd dashboard && npm ci && npm run build +cd dashboard && npm ci && npm run lint && npm run build ``` ## Run locally @@ -56,15 +74,16 @@ including the interactive Claude Code login and the Gitea bot account — is | | | | --- | --- | -| Host | `bob.hanzalova.internal` | -| API port | `23296` (registered in `architecture/port-allocations.md`) | -| Dashboard | `https://tireless.internal` (mesh only) | +| Host | `bob.hanzalova.internal` (binaries, units, job trees) | +| API port | `23296` (registered in `architecture/port-allocations.md`), bound `0.0.0.0`, mesh-only | +| Ingress | nginx on the hanzalova proxy — **not** on bob | +| Dashboard | `https://tireless.internal` (mesh only), served from the proxy | | Database | `magrathea.kosherinata.internal:5432`, mTLS | ## Conventions Follows [`lair/architecture`](https://git.lair.cafe/lair/architecture); -`generic.md` is the baseline. Two deliberate deviations: +`generic.md` is the baseline. Three deliberate deviations: - **`tireless-agent` crate** beyond the standard entities/core/data split. Process orchestration is not data access, and it is shared by the runner and @@ -72,3 +91,6 @@ Follows [`lair/architecture`](https://git.lair.cafe/lair/architecture); - **`MemoryDenyWriteExecute=false` on `tireless-runner`.** Both agents are Node programs and V8's JIT needs write-then-execute pages. The API and poller keep the setting. (§8) +- **`AGENTS.md` is a symlink to `CLAUDE.md`.** Both agents look for their own + filename and the instructions are identical; a symlink is the only version of + this that cannot drift.