- inbox.py (§4.2+§5): VerdictDocument med verbatim-id-regel; write_verdict
authoring-primitiv (deterministisk JSON); load_inbox tolerant (skip, aldri
raise; sortert på filnavn); merge_inbox_into_store first-write-wins,
idempotent, skriver aldri (rolle-splitt §3 steg 7)
- promotion.py (§6): promote fail-closed mot {approved,
approved_with_adjustment}; eksplisitt påkrevd timestamp; minimal frontmatter
(rationale → description, aldri strukturerte læringsfelt); path-safe token
med content-hash-fallback; idempotent index-lenking med fast nøytral label
- persona.py (§4.3): load_persona_example fail-fast (run-path-vokabular,
marker ⊆ rationale); drop_persona_verdict artefakt-sourced ved kalltid mot
delt shared/-artefakt
- experience.py (kirurgisk): seeding leser verdict_id VERBATIM fra frontmatter
— re-minting ville kollidert distinkte promoterte kandidater
- 43 nye load-bearing tester (step7/step8/persona), 164/164 uten API-nøkkel;
to-runs-bevis med fersk store + tom-inbox-kontroll; fire detach-bevis kjørt
røde og revertert grønne
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QdSfQdND84oeq2mbjueLTS
70 lines
2.7 KiB
Python
70 lines
2.7 KiB
Python
"""The persona example artifact loader (method-spec §4.3).
|
|
|
|
The expert-reviewer persona is a shared skill artifact: one canonical example
|
|
verdict JSON (``decision``, ``marker``, ``rationale``). The persona judgement
|
|
is sourced from that artifact AT CALL TIME — never from an inlined copy — and
|
|
loading is fail-fast: the artifact is required input (contrast the tolerant
|
|
inbox, §5). In simulation the persona plays the human on the WRITE side of the
|
|
inbox role split: ``drop_persona_verdict`` authors a §4.2 verdict file through
|
|
the same folder interface a production expert uses.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
from typing import Literal
|
|
|
|
from pydantic import BaseModel, Field, model_validator
|
|
|
|
from portfolio_optimiser_claude.experience import CandidateFeatures
|
|
from portfolio_optimiser_claude.inbox import VerdictDocument, write_verdict
|
|
|
|
|
|
class PersonaVerdict(BaseModel):
|
|
"""The canonical persona example (§4.3): a run-path decision + traceable payload."""
|
|
|
|
# §4.3: MUST be a run-path value (§4.1) — the seed-only vocabulary is rejected.
|
|
decision: Literal["approved", "rejected"]
|
|
marker: str = Field(min_length=1)
|
|
rationale: str = Field(min_length=1)
|
|
|
|
@model_validator(mode="after")
|
|
def _marker_is_traceable(self) -> PersonaVerdict:
|
|
# The marker is the payload a simulation follows from the persona's
|
|
# judgement into a later run's prompt — it must live in the rationale.
|
|
if self.marker not in self.rationale:
|
|
raise ValueError(
|
|
f"persona example: marker {self.marker!r} is not a substring of the rationale"
|
|
)
|
|
return self
|
|
|
|
|
|
def load_persona_example(artifact_path: Path) -> PersonaVerdict:
|
|
"""Load the shared artifact — FAIL-FAST on a missing or malformed file (§4.3)."""
|
|
return PersonaVerdict.model_validate(json.loads(artifact_path.read_text(encoding="utf-8")))
|
|
|
|
|
|
def drop_persona_verdict(
|
|
inbox_dir: Path,
|
|
artifact_path: Path,
|
|
features: CandidateFeatures,
|
|
*,
|
|
description: str,
|
|
) -> VerdictDocument:
|
|
"""The persona judges a candidate and drops a verdict file into the inbox.
|
|
|
|
The judgement (decision + rationale) is read from the artifact at call
|
|
time; the id is minted from the candidate features (§4.2); the file is
|
|
written through the authoring primitive (§5) — the same interface a
|
|
production expert uses.
|
|
"""
|
|
persona = load_persona_example(artifact_path)
|
|
verdict = VerdictDocument.from_candidate(
|
|
features,
|
|
decision=persona.decision,
|
|
rationale=persona.rationale,
|
|
description=description,
|
|
)
|
|
write_verdict(inbox_dir, verdict)
|
|
return verdict
|