925ab096fb
Tasks 6-1-01..04 (REQ-3-007): Lab consumes the LIVE trace digest (compute_digest over
TraceStore events; empty trace coaches the baseline); Assessor renders coaching FROM
the stored GradeRecord (it never invents scores — the grading engine owns that;
evaluate endpoint re-grounded: 404 without a grade); Proctor consumes digest +
DefenseStore long-pause signals + variant seed cross-check. Corpus telemetry/artifacts
DORMANT (headers + AST dormancy test: zero production importers; learner_context stays
active; retained as Phase-3 calibration history). lifespan now adopts a pre-set
provider (state-injection pattern).
v0.2 corpus-path endpoint tests updated honestly to the live contract (learner_id+task_id).
392 tests green; ruff clean.
---ci---
phase: 6
milestone: v0.3
status: execute
requirements: {covered: [REQ-3-007], partial: []}
---/ci---
44 lines
1.6 KiB
Python
44 lines
1.6 KiB
Python
"""LabAgent — in-flow feedback over LIVE sandbox telemetry (REQ-3-007).
|
|
|
|
v0.3 re-grounding: consumes a TraceDigest computed from the learner's real
|
|
trace (grading/features.compute_digest over TraceStore events) — the v0.2
|
|
corpus scenarios are retired from this path (corpus dormancy, Task 6-1-04).
|
|
No session chat — each request is one live-trace read.
|
|
"""
|
|
|
|
from collections.abc import AsyncIterator
|
|
from typing import TYPE_CHECKING
|
|
|
|
from ..config import Settings
|
|
from ..corpus.learner_context import LearnerContext, get_learner_context
|
|
from ..grading.features import TraceDigest
|
|
from ..llm.base import LLMProvider
|
|
from ..prompts.lab import SYSTEM_PROMPT, render_context, render_digest_timeline
|
|
from .base import BaseAgent
|
|
|
|
if TYPE_CHECKING: # pragma: no cover
|
|
pass
|
|
|
|
|
|
class LabAgent(BaseAgent):
|
|
name = "lab"
|
|
|
|
def __init__(self, provider: LLMProvider, settings: Settings) -> None:
|
|
super().__init__(provider, settings)
|
|
|
|
def system_prompt(self, learner_context: LearnerContext | None = None) -> str:
|
|
ctx = learner_context or get_learner_context()
|
|
return SYSTEM_PROMPT.format_map(render_context(ctx))
|
|
|
|
async def stream_feedback(
|
|
self,
|
|
digest: TraceDigest | None,
|
|
learner_context: LearnerContext | None = None,
|
|
) -> AsyncIterator[str]:
|
|
"""Feedback grounded in the learner's live trace digest."""
|
|
timeline = render_digest_timeline(digest)
|
|
async for token in self.stream_reply(
|
|
history=None, user_input=timeline, learner_context=learner_context
|
|
):
|
|
yield token
|