docs: state the purpose, the autonomy boundary and the dogfooding plan

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) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013TxK1CWPkFXqdcXMJ4hVe6
This commit is contained in:
rob thijssen
2026-08-07 15:36:54 +03:00
parent 581e6ae738
commit ddb2574bbe
4 changed files with 361 additions and 42 deletions

1
AGENTS.md Symbolic link
View File

@@ -0,0 +1 @@
CLAUDE.md

View File

@@ -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.

View File

@@ -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 35
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.

View File

@@ -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 18 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.