Files
tireless/prompt/readme.md
rob thijssen 581e6ae738 feat(discover): add the discovery lane and the autonomy boundary
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
2026-08-07 15:36:35 +03:00

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.