feat(learning): S9 — D7 læringssløyfe: verdict-inbox, fail-closed promoteringsgate, artefakt-sourced persona
- 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
This commit is contained in:
parent
9a4caeb419
commit
22bfc80dda
7 changed files with 947 additions and 2 deletions
70
src/portfolio_optimiser_claude/persona.py
Normal file
70
src/portfolio_optimiser_claude/persona.py
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
"""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
|
||||
Loading…
Add table
Add a link
Reference in a new issue