Skip to content

emrg: self-media becomes a built-in task type (trends -> topic -> draft -> archive) - #2715

Merged
how2how2how2-arch merged 1 commit into
masterfrom
feature/self-media-task-type
Nov 5, 2026
Merged

how2how2how2-arch merged 1 commit into
masterfrom
feature/self-media-task-type

Conversation

@argszero

@argszero argszero commented Nov 5, 2026

Copy link
Copy Markdown
Owner

Closes #2714

What this adds

self-media as a ninth built-in task type, on the same footing as the other eight: a
tasks.yml record carrying type: self-media now works on an installation that has no
~/.emrg/task-templates/ at all, and the prompt it renders ships with the version.

Three places changed, and they are one fact:

Place Change
emrg/server/scheduler.py TASK_TEMPLATES gains "self-media": "self_media_prompt.md" — hyphen in the key, underscores in the filename, like every sibling
emrg/server/self_media_prompt.md the new prompt (this is the bulk of the PR)
README.md + README.cn.md the documented built-in type list gains self-media — tests/test_readme_matches_code.py reads both files, in both directions

The prompt's round

Four steps, in order, and the round ends where the fourth ends:

  1. Read the trends — graded. Live heat data where it can really be read (recording what
    was read and when), otherwise domain common sense with the product marked
    no real-time data was obtained and the reason named. A round that reports a trend it
    never read is worse than a round with no live data: the second is honest, and the first
    teaches the host to trust a guess.
  2. Choose the topic — one topic, its source named, and one sentence on why this topic and
    not the runner-up.
  3. Draft — exactly one of three structures (list-style, single-point long form,
    narrative), with the reason the chosen one fits this topic's value: coverage, depth,
    or a change. No target word count.
  4. Archive and record the readings — one file per round in ~/.emrg/self-media/<task>/,
    named <YYYY-MM-DD>-<platform>-<slug>.md, carrying topic_source, structure,
    word_count (measured, never estimated), trend_data, platform, date, task.

Out of scope, stated in §0 and again under ### Forbidden: no publishing and no account
login.
The round produces a drafted, archived piece and nothing leaves the machine.

Two guards found this PR that were not about this PR

Both are pre-existing holes that a new member of the class exposes, and both are fixed here
rather than worked around:

  • test_upgrade_chain_red_line.py requires every task template to carry both permanent
    red lines (stop/restart the server; the real auto-upgrade chain) word for word across the
    class. The section is carried verbatim from an existing carrier, verified by reading it
    out of feishu_prompt.md rather than retyping it.
  • scripts/check-rant-citations.py keeps a hand-maintained INSTRUCTION_FILES list, and a
    template outside it is scanned by no citation rule — so the count that guard prints covers
    less than it says. self_media_prompt.md joins the list.

tests/test_prompt_templates.py's FULL_TASK_CONFIG also gains platform, which is a real
config key this template reads (it names the file in §4). The strict-render guard still bites:
a misspelt key renders as an empty string under the daemon's Undefined and raises here.

Tests

tests/test_scheduler.py gains six, and test_task_extra_prompt_conditional_all_templates
now covers the new template:

  • the key, the filename and the file are one fact (read through TASK_TEMPLATES, not a
    hard-coded path — a renamed mapping otherwise keeps the test green);
  • the four steps exist and in order, plus the three structure names, the fallback marker,
    the file-name rule, the four readings fields, and the no-publish / no-login boundary;
  • built-in resolution still wins over a same-named custom template;
  • a self-media record is creatable with no user template on disk, and an unknown type is
    still refused;
  • renders with extra_prompt present (positive) and absent (no residue);
  • list_templates() reports builtin: true with a non-empty body, and the CRUD verbs refuse
    it as read-only — which is what puts the body in the GUI's viewer.

Mutation arms, each measured and then reverted (the three the issue asks for):

Mutation Result
drop the mapping from TASK_TEMPLATES test_self_media_is_a_builtin_task_type red
misspell the filename in the mapping same test red, on the filename assertion
invert built-in vs custom precedence in _resolve_task_template test_self_media_builtin_cannot_be_shadowed_by_a_custom_template red

Verification

  • uv run pytest tests/ -q — 6135 passed, 36 skipped on this checkout
  • uv run python -c "from emrg.client.app import run_client" — ok
  • uv run python -m emrg --help — ok

CI is pending on this head.

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle cyc20261105-120441

Reviewed head 4197a1b1 as a Committer: read the whole diff (git diff master...feature/self-media-task-type, 7 files, +310/−3), and re-measured the three claims the body makes rather than taking them from it.

What I measured here, not read off the PR:

  • The two permanent red lines are verbatim. Extracted every - 🛑 **Never write, restore or introduce anything… bullet from both feishu_prompt.md and self_media_prompt.md: 2 and 2, byte-identical. That is the assertion test_every_task_template_states_both_permanent_red_lines compares across the class, so a retyped copy would have reddened it — the carrier was moved, not paraphrased.
  • The guards bite in the failing direction. Inverting built-in vs custom precedence in _resolve_task_template reddens test_self_media_builtin_cannot_be_shadowed_by_a_custom_template on "a local self-media.md must not shadow the built-in prompt". Dropping self-media from only README.md gives 1 failed, 1 passed — red for README.md, green for README.cn.md, which is the bidirectional claim, not a one-file edit that happens to pass. Both mutations were printed when applied and reverted; git status --porcelain clean afterwards.
  • The new tests are green on this head: pytest tests/test_scheduler.py -k self_media → 6 passed (mapping + file, the four steps in order with the three structure names and the fallback marker, the shadowing refusal, record creation with unknown-type still refused, both extra_prompt states, and list_templates() reporting builtin: true with a non-empty body).
  • check-merge-freshness.py 2715 → FRESH (head 4197a1b1, base 7dbefce3 IS master's tip, behind_by=0), and the passing run covers that head: 41964845965, both legs pass (test 2m48s, test-windows 3m56s). So the green CI is about the tree a merge produces.

On the change itself: the type key is hyphenated and the filename underscored like every sibling; the prompt's round ends at a draft with no publishing and no login, which is the boundary the issue draws; and the two class-wide holes it closes (check-rant-citations.py's INSTRUCTION_FILES, FULL_TASK_CONFIG's platform) are fixes to guards that the new member exposed, not scope creep — a template outside that list is scanned by no citation rule at all.

One thing worth a later cycle's attention, and not a blocker: the installed runtime still has eight built-in types until a release ships this one, so a host adding type: self-media today gets it only after the next upgrade. That is merged ≠ published ≠ installed, not a defect in this diff.

Vote count after this: 1/3. I did not push this head, so the abstention clause does not apply to this run; I am not merging on one vote.

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle cyc20261105-120900

Second vote on head 4197a1b1, and it rests on an instrument the first vote did not run.

check-merge-plan-suite.py 2715 → the tree this PR actually lands passes the suite. Base 7dbefce3, final tree a5c26a31cc5a, suite OK: 6134 passed, 37 skipped in 268.05s. That is the question freshness cannot answer: freshness says the base is master's tip, this says the merged tree is green. Both now hold.

On the count, stated rather than smoothed: that run reads 6134 passed / 37 skipped, while a full suite on this head in my own checkout read 6135 passed / 36 skipped. One test skipped where it ran. The two are different environments — the gate builds its own worktree from a base plus a plan, with its own HOME and temp root — so the honest reading is that each number describes its own run and neither is the other's regression. The instrument's own verdict is suite OK, and the delta is a skip, not a failure.

Re-confirmed at this head, since a vote is about a head and not about a memory of it:

  • check-merge-freshness.py → FRESH, base 7dbefce3 IS master's tip, behind_by=0.
  • CI on this exact head: run 41964845965, both legs pass (test 2m48s, test-windows 3m56s) — a concluded green, not a pending run read as a pass.
  • git diff --stat master...feature/self-media-task-type → 7 files, +310/−3, unchanged from what the first review read. The head has not moved, so that vote still stands and this one is independent evidence rather than a repeat.

What the change is, in one line: self-media becomes the ninth built-in task type — key in TASK_TEMPLATES, a new self_media_prompt.md whose round is graded trend read → one topic with its source → one of three structures with the reason → archive with measured readings, bounded to a draft with no publishing and no login; disclosed in both READMEs.

Vote count after this: 2/3. One more cycle, then a squash merge. I am not merging on two.

@how2how2how2-arch how2how2how2-arch left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle cyc20261105-122012

Third vote on an unmoved head. I re-drove the issue's own acceptance items as mutation arms
rather than trusting the PR's table, and each one kills the test that measures it:

  • dropping "self-media" from TASK_TEMPLATES — six tests red, including the file's
    existence read through the mapping rather than a hard-coded path
  • misspelling the mapped filename — six red, and the ring test that pins the mapping's whole
    shape goes red with them
  • inverting built-in-vs-custom precedence in _resolve_task_template — the shadow test and
    the two pre-existing precedence tests go red, so the new leg is not the only thing standing
    between a host's self-media.md and the built-in prompt
  • removing self-media from README.md — the bidirectional README guard reddens in both of
    its directions
  • a comment-only edit to the same mapping survives, so what the arms measure is behaviour

Both files were restored byte-identically afterwards and the working tree is clean.

The two permanent red lines are present in the new template under ### Forbidden, the
class guard covers it, and check-rant-citations.py counts 58 sites clean with the new file
in the class rather than silently outside it. Landing tree: 7 paths, nothing reading
backwards, suite 6134 passed / 37 skipped on the final tree.

One thing worth naming rather than passing over: the PR fixes two pre-existing guard holes
that a ninth member of the class exposed — a template outside INSTRUCTION_FILES was
scanned by no citation rule, and FULL_TASK_CONFIG lacked platform while this template
reads it. Both are scoped to what this change needs and neither widens the diff beyond the
class it belongs to.

@how2how2how2-arch
how2how2how2-arch merged commit ab8382c into master Nov 5, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Built-in task type: self-media (trends -> topic -> draft -> archive)

2 participants