feat(persona): build the shared expert-reviewer persona as a framework-neutral Agent Skill
The expert reviewer was only a hardcoded verdict_input dict inside the offline simulation. Build it as the real, shared artifact target picture §8 calls for: shared/skills/expert-reviewer/ — a SKILL.md persona prompt (energy-advisor / M&V role + the realization-gap methodology the validator cannot compute) plus a canonical references/example-verdict.json. shared/ stays pure data; the MAF side reads it via portfolio_optimiser.persona.load_persona_example (call-time, fail-fast) and the Claude-SDK sibling reads the same JSON with its own loader. This de-stubs the simulation: its persona judgement (decision + rationale + traced marker) is now sourced from the artifact at call time, not an inline literal — so the shared persona is genuinely consumed and cannot rot silently. decision is binary (approved/rejected, the FeedbackContract the run path accepts); approved_with_adjustment is rejected there and lives only in the bundle seed frontmatter + the promotion gate, so the realization correction is carried in the rationale prose. Load-bearing trio (tests/test_persona_skill_loadbearing.py), each proven RED on its own detach: structure + framework-neutrality, the example is valid pipeline input (incl. FeedbackContract, on a throwaway copy), and the simulation's marker follows the artifact file. Suite 149->152. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MHR8iKxJRxDiDfNw8HZmWE
This commit is contained in:
parent
a9144cb9bb
commit
6f861a0078
8 changed files with 281 additions and 13 deletions
52
src/portfolio_optimiser/persona.py
Normal file
52
src/portfolio_optimiser/persona.py
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
"""Loader for the shared expert-reviewer persona artifact (målbilde §8).
|
||||
|
||||
The persona lives in ``shared/skills/expert-reviewer/`` as a framework-neutral Agent Skill (SKILL.md
|
||||
+ a canonical example verdict). ``shared/`` stays pure DATA — this loader is the MAF-side reader of
|
||||
it; the Claude-SDK sibling reads the same JSON with its own loader. This is what de-stubs the
|
||||
offline simulation: its persona judgement is sourced from the artifact instead of a hardcoded
|
||||
literal, so the shared persona is genuinely consumed (and cannot rot silently).
|
||||
|
||||
Fail-fast on purpose: the example is REQUIRED input (contrast the tolerant async verdict inbox,
|
||||
``verdicts.load_verdicts_from_dir``). A missing or malformed file raises rather than degrading —
|
||||
a broken shared artifact must surface, not pass silently.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
# Module-global so tests can monkeypatch it; read at CALL time inside the loader (never frozen into
|
||||
# a default argument), which is what makes the simulation's persona genuinely artifact-driven.
|
||||
_EXAMPLE_PATH = (
|
||||
Path(__file__).resolve().parents[2]
|
||||
/ "shared"
|
||||
/ "skills"
|
||||
/ "expert-reviewer"
|
||||
/ "references"
|
||||
/ "example-verdict.json"
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PersonaExample:
|
||||
"""The expert-reviewer persona's canonical example verdict: the judgement the persona renders for
|
||||
the reference measure. ``marker`` is a substring of ``rationale`` (the realization-rate payload
|
||||
the simulation traces across runs)."""
|
||||
|
||||
decision: str
|
||||
rationale: str
|
||||
marker: str
|
||||
|
||||
|
||||
def load_persona_example() -> PersonaExample:
|
||||
"""Read the persona's canonical example verdict. Fail-fast: a missing file raises
|
||||
``FileNotFoundError`` and a missing key raises ``KeyError`` (required input). Reads
|
||||
``_EXAMPLE_PATH`` at call time so the path is patchable."""
|
||||
data = json.loads(_EXAMPLE_PATH.read_text(encoding="utf-8"))
|
||||
return PersonaExample(
|
||||
decision=data["decision"],
|
||||
rationale=data["rationale"],
|
||||
marker=data["marker"],
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue