The docs described a reactive executor: every entry point was a human label, and the only issue tireless ever created was a plan child. Nothing surveyed a repo or proposed work, which is the half that makes this continuous rather than on-demand. Add JobKind::Discover, routed always to Claude Code (proposing work is the highest-judgement, lowest-volume task), the tireless/discover and tireless/proposed labels, and prompt/discover.cc.md as a fourth member of the versioned prompt set. The contract version does not move: the plan structure is unchanged, and bumping for less than a shape change trains people to bump reflexively. With discovery comes the question of where the loop closes, which was previously unspecified — routing inferred that plan children are auto-admitted, but nothing said so. State it as a rule and enforce it: Admission is inherited, never invented. may_opt_in() lets tireless label a plan child, because a human admitted its parent, and refuses to label a discovered issue, because nothing has been admitted. It is a function rather than a config flag on purpose: the failure it prevents is unbounded, not merely wrong, so relaxing it should require review. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013TxK1CWPkFXqdcXMJ4hVe6
6.8 KiB
System prompts
Four prompts, one set. They are versioned together and must be edited together.
| File | Surface | Used for |
|---|---|---|
discover.cc.md |
Claude Code --append-system-prompt |
Discover jobs |
plan.cc.md |
Claude Code --append-system-prompt |
Plan jobs |
implement.oc.md |
OpenCode AgentConfig.prompt |
Implement jobs descended from a tireless plan |
implement.cc.md |
Claude Code --append-system-prompt |
Implement jobs on unplanned, human-written issues |
The contract
plan.cc.md tells Claude Code what a child issue must contain.
implement.oc.md tells OpenCode to execute exactly that and nothing more. Both
describe the same structure — tireless_entities::ChildSpec — from opposite
ends:
plan.cc.md ──emits──▶ ChildSpec ──validated by──▶ tireless_core::plan::validate
│
└──consumed by──▶ implement.oc.md
Change the shape and all three must move together, or the planner emits something the implementer is not expecting and the failure surfaces as a puzzlingly bad pull request rather than as an error.
contract-version: on the first line of each file guards this.
PromptSet::load refuses a mismatched set, and SYSTEM_PROMPT_CONTRACT_VERSION
in tireless-core/src/prompt.rs is the version this build expects. Bump all five
when the structure changes.
discover.cc.md does not emit a ChildSpec — its output is prose proposals that
a human admits and a later Plan job decomposes. It is in the versioned set
anyway, because it writes the issue bodies plan.cc.md later reads, and because
a prompt outside the set is a prompt nobody remembers to update. Adding it did
not bump the version: the plan structure did not change, and bumping for
anything less than a shape change trains people to bump reflexively.
The autonomy boundary lives in the prompts too
discover.cc.md tells the model its proposals are created without the opt-in
label and wait for a human. That is not decoration — a model that believes its
output will be acted on immediately writes differently from one that knows it is
making a case to a reader. tireless_core::prompt has a test asserting the
sentence is still there.
Tests in tireless-core::prompt assert that the prompts actually mention every
section ChildSpec requires — so adding a field without updating the prompts
fails the build rather than every future planning run.
Append, don't replace (Claude Code)
Both Claude Code prompts are appended via --append-system-prompt. Claude
Code also offers --system-prompt, which replaces its default entirely — do not
use it. The default carries the tool-use and repository-navigation scaffolding
that makes Claude Code a coding agent at all. Replacing it produces a less
capable agent, not a more obedient one.
Faithful passthrough (OpenCode)
implement.oc.md is the whole system prompt for the OpenCode agent. It travels
OpenCode → helexa cortex → neuron, and depends on helexa's passthrough guarantee:
no injection, no rewriting, no defaults
(helexa/helexa#179).
That issue is closed (2026-08-02) and the guarantee holds. It was verified
live through cortex on all three surfaces — /v1/chat/completions,
/v1/responses, /v1/messages — streaming and non-streaming, with a negative
control confirming nothing is injected when the caller sends no system prompt.
Regression tests assert on what cortex forwarded upstream, captured from a mock
neuron, rather than on the reply: a gateway that silently dropped the prompt
would still return a convincing answer, so asserting on the response would not
catch the regression.
Two consequences for this prompt, both worth knowing before debugging one.
Multiple system messages: the last one wins
cortex forwards several system messages unmerged and in order, and does not editorialise; the model resolves precedence, and observably the last one wins.
This is the failure mode to check first, because OpenCode sends its own preamble
alongside an agent's configured prompt. If implement.oc.md arrives before
OpenCode's own system content, its instructions lose. The symptom is not an error
— it is an agent that behaves like a generic coding assistant: helpful,
plausible, and ignoring Out of scope.
So the stage 5 check is not "did the prompt arrive" (settled) but "did it arrive last". Verify by asserting on what OpenCode sends upstream, not on how the model behaves — the same reasoning helexa used for its own tests.
Thinking models on /v1/responses can return nothing
helexa#223, open: /no_think
is honoured on /v1/chat/completions but ignored on /v1/responses. On the
Responses surface a small max_output_tokens can be spent entirely on the
reasoning block, and the caller gets "" with status: "incomplete".
That matters here because the natural upgrade path for this lane is a thinking
model. An unattended run receiving an empty string looks like agent failure
rather than a token-budget artifact, and would burn a retry. Prefer
chat/completions, budget output tokens generously, and treat
status: "incomplete" as a distinct outcome rather than an empty success.
The qwen3_next family is unverified, but not unhandled
Qwen3-Coder-Next and Qwen3-Next-80B-A3B-Thinking were not live-tested: both
are catalogue-feasible only on beast, where the pinned 27B is resident, so
testing them means displacing a production model for a multi-minute cold load.
What makes that a gap rather than a hole: the system slot is not
arch-branched. Rendering goes through the shared chat_template.rs path,
applying the template the model ships in its own tokenizer_config — there is no
qwen3_next-specific code for a system prompt to fall down, and template tests
exercise the shared path directly. If tireless ever points this lane at a Next
model, probe it while it is warm for other reasons rather than forcing an
eviction.
Editing guidance
These prompts are behavioural specifications, and they are load-bearing in a way ordinary documentation is not — a weakened instruction here becomes a bad pull request three hours later, in a repository, unattended.
- Prefer stating the constraint and the reason for it. Both models follow a rule better when the rationale is present, and the rationale is what lets a model resolve a case the rule did not anticipate.
- Keep the "stop and report" paths prominent. The most expensive failure mode is not an agent that gives up; it is an agent that guesses confidently and produces plausible, wrong work.
- Test changes against a real issue before merging. There is no unit test for whether a prompt produces good plans.