rob thijssen 3c7d95edf9
Some checks failed
deploy / deploy (push) Failing after 5m31s
fix(deploy): correct the runner label, the vhost listen line and the cert paths
Three faults, any one of which would have failed the first deploy — found by
reading the house conventions in ~/git/architecture rather than by running it.

`runs-on: fedora-43-rust` is not a registered runner label. The catalogue in
gitea-runners.md §2 has `rust`; the job would never have been scheduled. Also
collapse build and deploy into one job: the `rust` image descends from
runner-fedora-44 and carries node, ssh and rsync, so the artifact upload and
download — on actions/*-artifact@v3, EOL upstream — bought nothing but a
round-trip and a directory-structure assumption. rpm.lair.cafe's working deploy
is single-job for the same reason.

The nginx vhost bound `listen 443 ssl`. TCP 443 on hanzalova belongs to the
stream SNI router (reverse-proxies.md §4-5, and the live bench.internal.conf
confirms it), so the vhost would never have been reached — the router answers
first with whichever certificate its default branch holds. `nginx -t` passes
either way, which is what makes this worth catching by reading rather than by
deploying. Now `listen 127.0.0.1:14443 ssl proxy_protocol;`.

Cert paths pointed at the host identity cert under /etc/pki/tls. A per-service
name needs its own cert (internal-tls.md §2): the host cert's SAN is bob's FQDN,
not tireless.internal, so verification would have failed. Now
/etc/nginx/tls/{cert,key}/tireless.internal.pem, minted with --san and renewed
by step@tireless.timer.

infra-setup.sh now provisions the ingress rather than describing it: mints the
cert through the JWK provisioner (removing the credential even on failure),
installs the vhost via sites-available + symlink, and registers the
split-horizon record on BOTH routers — a record on one router NXDOMAINs at the
other site. opn-cli has no reconfigure verb, so the apply is a direct API POST;
without it the name resolves only whenever Unbound next happens to reload.

Also fold the stale-bindings check into the workflow (closes the CI half of #8)
and let the runner unit fail without failing the deploy, since it correctly
refuses to start until the interactive login exists.

Refs #9

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

tireless

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 — 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, against the self-hosted helexa fleet. Never Anthropic; enforced at startup.

The rule of thumb: Claude Code gets judgement, OpenCode gets specification.

Full design, constraints and the staged implementation plan: doc/plan/design.md.

Status

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

cargo test --workspace
cargo clippy --all-targets --all-features -- -D warnings
cargo fmt --all

cd dashboard && npm ci && npm run lint && npm run build

Run locally

cargo run -p tireless-api -- --config ./config.toml
cargo run -p tireless-worker -- --config ./config.toml poll
cargo run -p tireless-worker -- --config ./config.toml run

cd dashboard && npm run dev     # proxies /v1 to 127.0.0.1:23296

tireless preflight verifies configuration and credentials without starting a service: it reports which billing mode a Claude Code run would use and asserts the OpenCode lane is not pointed at Anthropic.

Deploy

CI-driven via Gitea Actions on merge to main (architecture/deployment-gitea-actions.md). One-time host provisioning — including the interactive Claude Code login and the Gitea bot account — is script/infra-setup.sh.

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; 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 the CLI. (§1)
  • 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.
Description
Autonomous issue-to-PR development driver for Claude Code and OpenCode
Readme 618 KiB
Languages
Rust 83.5%
Shell 8.6%
TypeScript 7%
JavaScript 0.5%
CSS 0.3%
Other 0.1%