curated_feed.policy

Assembling the prompt from the policy documents and the day’s candidates.

No judgement of its own: this module concatenates files and numbers a list. What the model is told lives in two documents, and the split between them is the split between the engine and a runner.

The scoring prompt ships with the package. It is not taste but contract: its response section is the schema models.Response parses, and its consequence scale is the range models.Graded validates, so a prompt and a parser that disagree fail at six in the morning with a malformed day. The two version together or not at all.

The editorial policy is named by the runner’s configuration and is loaded at runtime, so that a change of taste is a commit against a change in output quality rather than a code change.

docs/scoring-spec.md is deliberately absent. It names the score floor and the item cap, and a model that knows them pre-filters or pads.

The assembled prompt is kept in two parts. The documents are identical on every run and are most of the tokens, so they are the half worth caching; the candidate list is the half that changes.

Attributes

DOCUMENT_SEPARATOR

CANDIDATES_HEADING

PACKAGE

PROMPT_FILENAME

NO_SUMMARY

UNKNOWN_DATE

EXAMPLE_CANDIDATE

A neutral candidate, so that the doctests below have something to render.

Classes

Prompt

An assembled prompt, split at the boundary between stable and variable.

Functions

default_prompt_path(→ pathlib.Path)

The scoring prompt that ships with the package.

read_documents(→ str)

Concatenate the policy documents, in the order given.

format_candidate(→ str)

One candidate as two lines: the reference line, then the summary.

render_candidates(→ str)

The day's candidates as a numbered list, one item per two lines.

build_prompt(→ Prompt)

Assemble the prompt for one day.

Module Contents

curated_feed.policy.DOCUMENT_SEPARATOR = Multiline-String
Show Value
"""

---

"""
curated_feed.policy.CANDIDATES_HEADING = '# Candidates'
curated_feed.policy.PACKAGE = 'curated_feed'
curated_feed.policy.PROMPT_FILENAME = 'prompt.md'
curated_feed.policy.NO_SUMMARY = '(no summary)'
curated_feed.policy.UNKNOWN_DATE = 'date unknown'
curated_feed.policy.EXAMPLE_CANDIDATE

A neutral candidate, so that the doctests below have something to render.

class curated_feed.policy.Prompt(/, **data: Any)[source]

Bases: pydantic.BaseModel

An assembled prompt, split at the boundary between stable and variable.

documents: str
candidates: str
as_text() → str[source]

The whole prompt, for a client that cannot cache a prefix.

curated_feed.policy.default_prompt_path() → pathlib.Path[source]

The scoring prompt that ships with the package.

Resolved through the package rather than the working directory, because the engine is installed rather than checked out wherever it runs: an image, a virtual environment, an editable tree. A runner’s configuration cannot name it, which is the point — see this module’s docstring.

>>> default_prompt_path().name
'prompt.md'
>>> default_prompt_path().is_file()
True
curated_feed.policy.read_documents(*paths: pathlib.Path) → str[source]

Concatenate the policy documents, in the order given.

curated_feed.policy.format_candidate(candidate: curated_feed.models.Candidate, number: int, *, with_id: bool) → str[source]

One candidate as two lines: the reference line, then the summary.

The model refers to articles by number, so the numbering is the contract. Ids are for a human tracing a pick back to the corpus and are left out of the prompt, being long opaque hashes that models mangle.

>>> print(format_candidate(EXAMPLE_CANDIDATE, 1, with_id=False))
[1] A title — A source — 2026-08-08T09:14:00Z
    A summary.
curated_feed.policy.render_candidates(candidates: collections.abc.Sequence[curated_feed.models.Candidate], *, with_ids: bool = False) → str[source]

The day’s candidates as a numbered list, one item per two lines.

>>> print(render_candidates([EXAMPLE_CANDIDATE]))
[1] A title — A source — 2026-08-08T09:14:00Z
    A summary.
curated_feed.policy.build_prompt(candidates: collections.abc.Sequence[curated_feed.models.Candidate], *, policy_path: pathlib.Path, prompt_path: pathlib.Path | None = None) → Prompt[source]

Assemble the prompt for one day.

prompt_path overrides the packaged scoring prompt. Nothing in the pipeline passes it: it exists so that a candidate prompt can be tried against a saved day without reinstalling, which is the only way to evaluate a prompt change once the corpus and the engine live in different repositories.