Origin: rant 2026-10-11T11:23:55.136401+08:00
Part: 1/1
A record-level extra_prompt reaches nothing and reports nothing — the render site passes only config
What is wrong
TaskHandler._build_evolution_prompt hands the templates "task": self._config, and _config is the record's config sub-dictionary (scheduler.py:794). All six built-in templates read the section as a guarded block:
{% if task.extra_prompt %}
### Task-specific Instructions (extra_prompt from tasks.yml)
{{ task.extra_prompt }}
{% endif %}
The daemon renders with jinja2.Undefined, so a name that is not in the context is the empty string: the condition takes the false branch and the heading and body both disappear — no error, no warning, no log line. An extra_prompt written at the record's top level, beside interval / enabled, is therefore dropped in silence.
Measured on master 60541e39 (2026-10-11, cyc20261011-193407) through the real builder, TaskHandler._apply_record(record) → _build_evolution_prompt():
| record |
extra_prompt in the rendered prompt |
extra_prompt at the top level |
absent |
config.extra_prompt |
present |
| both |
present (config wins) |
Where the wrong position came from
The module's own schema comment advertised it:
sandbox / description / extra_prompt — optional; anything else is ignored.
Two of those three are not the same as the third, and the file says so elsewhere:
So the comment listed a field that works where it claims, beside one that does not, and a reader of the schema had no way to tell which was which. validate_task_record does not look at the position either, and the CRUD/GUI write path does not warn.
Why it matters
The host reported the cost (rant 2026-10-11T11:23:55, translated from the Chinese original): the promote task's 4601-char extra_prompt (in place since 2026-08-31) and the competition task's 3042-char one never entered a single prompt — nothing in llm.jsonl or the session histories carries the section — and one competition round was spent diagnosing the missing section before the file was hand-edited around it. The schema comment is what made the wrong position look right.
Feedback is the host's first ask, ahead of leniency: with the render silent, a task's operator gets no signal at all that their instructions are not being read.
The fix
emrg/server/scheduler.py:
_template_task_context(config, record) — the mapping the templates render against. extra_prompt is honoured in both positions; config wins when a record carries both, because it is the canonical home of the whole task namespace. Blank counts as absent on both sides, since {% if %} renders nothing for "".
- The losing copy is named in a warning — with the task's name and the size of the ignored copy — so the remaining ambiguous case is audible rather than silent. Being ignored is not the defect; being ignored quietly is.
- The schema comment is corrected:
sandbox keeps its two positions with their precedence stated, extra_prompt now says both positions are honoured and which wins, and description no longer hides inside a triple that implied all three behaved alike.
The fallback exists for the file written against the old comment, not to make the top level a second equal spelling: config is what the templates' task namespace is built from, so it stays authoritative.
Verification
- Reversed direction, through the real builder: the three new pins fail on master (
60541e39) while their two controls pass. Full suite on the fixed head: 4759 passed, 27 skipped.
uv run python -c "from emrg.client.app import run_client" and uv run python -m emrg --help — rc 0.
- Six guards rc 0:
check-doc-count, check-undefined-names, check_unbound_reads, check_nonlocal, check-citation-resolves, check-memory-index.
- Mutation arms: removing the fallback KILLED its pin; removing the ambiguity warning KILLED its pin; making a blank copy count as a value KILLED the blank-rule pin; a reworded schema comment (control) SURVIVED.
Origin: rant 2026-10-11T11:23:55.136401+08:00
Part: 1/1
A record-level
extra_promptreaches nothing and reports nothing — the render site passes onlyconfigWhat is wrong
TaskHandler._build_evolution_prompthands the templates"task": self._config, and_configis the record'sconfigsub-dictionary (scheduler.py:794). All six built-in templates read the section as a guarded block:The daemon renders with
jinja2.Undefined, so a name that is not in the context is the empty string: the condition takes the false branch and the heading and body both disappear — no error, no warning, no log line. Anextra_promptwritten at the record's top level, besideinterval/enabled, is therefore dropped in silence.Measured on master
60541e39(2026-10-11,cyc20261011-193407) through the real builder,TaskHandler._apply_record(record)→_build_evolution_prompt():extra_promptin the rendered promptextra_promptat the top levelconfig.extra_promptconfigwins)Where the wrong position came from
The module's own schema comment advertised it:
Two of those three are not the same as the third, and the file says so elsewhere:
sandboxreally is record-level (scheduler.py:773) — and is also accepted underconfig, with the record-level value winning (_resolve_sandbox,scheduler.py:853).extra_promptis honoured underconfigonly; the design that introduced it (PR emrg: task prompts: support per-task extra_prompt from tasks.yml config (rant 2026-08-24T21:59:15) #964) renderstaskas the config, and its test spells that out.So the comment listed a field that works where it claims, beside one that does not, and a reader of the schema had no way to tell which was which.
validate_task_recorddoes not look at the position either, and the CRUD/GUI write path does not warn.Why it matters
The host reported the cost (rant 2026-10-11T11:23:55, translated from the Chinese original): the
promotetask's 4601-charextra_prompt(in place since 2026-08-31) and thecompetitiontask's 3042-char one never entered a single prompt — nothing inllm.jsonlor the session histories carries the section — and one competition round was spent diagnosing the missing section before the file was hand-edited around it. The schema comment is what made the wrong position look right.Feedback is the host's first ask, ahead of leniency: with the render silent, a task's operator gets no signal at all that their instructions are not being read.
The fix
emrg/server/scheduler.py:_template_task_context(config, record)— the mapping the templates render against.extra_promptis honoured in both positions;configwins when a record carries both, because it is the canonical home of the wholetasknamespace. Blank counts as absent on both sides, since{% if %}renders nothing for"".sandboxkeeps its two positions with their precedence stated,extra_promptnow says both positions are honoured and which wins, anddescriptionno longer hides inside a triple that implied all three behaved alike.The fallback exists for the file written against the old comment, not to make the top level a second equal spelling:
configis what the templates'tasknamespace is built from, so it stays authoritative.Verification
60541e39) while their two controls pass. Full suite on the fixed head: 4759 passed, 27 skipped.uv run python -c "from emrg.client.app import run_client"anduv run python -m emrg --help— rc 0.check-doc-count,check-undefined-names,check_unbound_reads,check_nonlocal,check-citation-resolves,check-memory-index.