Autonomous issue-to-PR driver for Claude Code and OpenCode, structured per lair/architecture generic.md. Workspace: entities/core/data/agent library crates plus api, worker and cli binaries. Two pieces of real logic land with tests — lane routing (cc for judgement, oc for specification) and the limit governor. Constraints encoded as code rather than comments: - agents are spawned as vendor binaries; tireless never calls a provider API - ANTHROPIC_API_KEY is never set by tireless, only passed through - assert_not_anthropic refuses to start an OpenCode lane pointed at Anthropic - every run passes the governor; provider rate-limit signals win over our own accounting Deployment assets target bob.hanzalova.internal:23296 (registered in port-allocations.md), fronted by hanzalova at tireless.internal. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DHhHtohxcdk1PL3tfnYJdH
66 lines
2.4 KiB
Desktop File
66 lines
2.4 KiB
Desktop File
[Unit]
|
|
Description=tireless job runner (drives Claude Code and OpenCode)
|
|
Documentation=https://git.lair.cafe/lair/tireless
|
|
After=network-online.target
|
|
Wants=network-online.target
|
|
|
|
[Service]
|
|
Type=exec
|
|
User=tireless
|
|
Group=tireless
|
|
|
|
# Claude Code and OpenCode both keep credentials and caches under HOME. The
|
|
# service account's home is its state directory, and it must be writable: the
|
|
# subscription OAuth token is refreshed in place, so a read-only HOME breaks
|
|
# authentication after the first expiry rather than at startup.
|
|
Environment=HOME=/var/lib/tireless
|
|
Environment=NPM_CONFIG_LOGLEVEL=error
|
|
Environment=NODE_NO_WARNINGS=1
|
|
|
|
# Optional secrets, 0640 root:tireless, never in source control:
|
|
# GITEA_TOKEN=… token for the dedicated `tireless` bot account
|
|
# GITHUB_TOKEN=… only for legacy GitHub repos
|
|
# ANTHROPIC_API_KEY=… OPTIONAL. Present -> Claude Code bills pay-as-you-go.
|
|
# Absent -> Claude Code uses the subscription login
|
|
# stored in /var/lib/tireless/.claude.json.
|
|
# tireless never sets this variable itself; the choice is the operator's, and
|
|
# the runner logs which mode is active at startup.
|
|
EnvironmentFile=-/etc/tireless/tireless.env
|
|
|
|
ExecStart=/usr/local/bin/tireless-worker --config /etc/tireless/config.toml run
|
|
Restart=on-failure
|
|
RestartSec=30s
|
|
|
|
# Give in-flight agent runs time to be cancelled cleanly, then kill the whole
|
|
# process group — npx-spawned agents leave orphans otherwise.
|
|
TimeoutStopSec=120
|
|
KillMode=control-group
|
|
|
|
NoNewPrivileges=true
|
|
ProtectSystem=strict
|
|
ProtectHome=true
|
|
PrivateTmp=true
|
|
PrivateDevices=true
|
|
ProtectKernelTunables=true
|
|
ProtectKernelModules=true
|
|
ProtectControlGroups=true
|
|
RestrictRealtime=true
|
|
RestrictSUIDSGID=true
|
|
LockPersonality=true
|
|
SystemCallArchitectures=native
|
|
|
|
# RELAXATION, deliberate: MemoryDenyWriteExecute=true is omitted here (it is set
|
|
# on tireless-api and tireless-poller). Both agents are Node programs, and V8's
|
|
# JIT requires write-then-execute pages; with it enabled the agent aborts on
|
|
# startup. Per §8 we relax only the one setting that breaks the service.
|
|
MemoryDenyWriteExecute=false
|
|
|
|
# The runner clones repos, runs builds and spawns agents, all under its state
|
|
# directory. PrivateTmp gives it an isolated /tmp for the toolchains that insist
|
|
# on one.
|
|
ReadWritePaths=/var/lib/tireless
|
|
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|