Run 5 shipped everything and started all three units, which then failed
identically:
configuration is invalid: /etc/tireless/config.toml: Permission denied
The config went out 0640 root:root, but the services run as `tireless`, which
is not in the root group. `--chown root:tireless` is not available as a fix:
on a fresh host that group does not exist until systemd-sysusers runs later in
the same deploy — the same ordering trap that produced the StateDirectory
change. So 0644 root:root, which is defensible precisely because this file
carries no secrets by design; the one that does, tireless.env, stays
0640 root:tireless and is installed by hand.
Testing that on the host turned up a second, quieter fault: `--chmod` only
applies to files rsync actually transfers, so a redeploy whose config is
byte-identical leaves the old mode in place. A mode fix would have appeared to
work on the deploy that introduced it and silently not applied afterwards. Add
-p to the shared options and switch to --chmod=F644/F755 so every push asserts
the mode it wants rather than hoping the content changed.
Also give tireless-runner an explicit start limit. Its preflight fails
permanently without an agent login, and Restart=on-failure with RestartSec=30s
never trips the default 5-starts-per-10s limiter, so it would have retried
forever. Three attempts in ten minutes, then failed, where systemctl status
says why.
Verified on bob: api and poller active, /v1/ready returns
{"config":"ok","database":"not_implemented","forge":"not_implemented"}, and
`tireless preflight` reports Subscription billing with the OpenCode lane
asserted non-Anthropic.
Refs #9
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013TxK1CWPkFXqdcXMJ4hVe6
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 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
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-agentcrate 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=falseontireless-runner. Both agents are Node programs and V8's JIT needs write-then-execute pages. The API and poller keep the setting. (§8)AGENTS.mdis a symlink toCLAUDE.md. Both agents look for their own filename and the instructions are identical; a symlink is the only version of this that cannot drift.