What
Add stream_validate() to the Requirement class in mellea/core/requirement.py (class at line 104):
async def stream_validate(
self, backend, ctx, chunk: str
) -> PartialValidationResult:
return PartialValidationResult(success="unknown")
Why
validate() (line 143) operates on complete output. Streaming validation needs per-chunk processing with different semantics: incremental input, tri-state result, and stateful accumulation across calls within a single attempt.
The method is named stream_validate, not avalidate. In mellea, the a-prefix means "async version of the same operation" — this is a different operation and should be clearly distinct.
In Phase 1, requirements receive chunks as plain strings passed by the streaming orchestrator. They never touch the MOT stream directly. Stateful requirements maintain their own incremental state across stream_validate calls (e.g. a running counter, an incremental parser). The streaming orchestrator clones each requirement (copy(req)) before every attempt, so streaming state is isolated to the clone and the original is never mutated.
The default is safe for all existing requirements. ALoraRequirement and LLMaJRequirement need no changes — they return "unknown" on every chunk and receive the full output via validate() at stream end.
Returning "pass" from stream_validate() does not short-circuit final validation in Phase 1. The orchestrator always calls validate() at stream end for any requirement that did not return "fail" — "pass" mid-stream is informational only.
Acceptance criteria
Blocked by #898
Part of #891
What
Add
stream_validate()to theRequirementclass inmellea/core/requirement.py(class at line 104):Why
validate()(line 143) operates on complete output. Streaming validation needs per-chunk processing with different semantics: incremental input, tri-state result, and stateful accumulation across calls within a single attempt.The method is named
stream_validate, notavalidate. In mellea, thea-prefix means "async version of the same operation" — this is a different operation and should be clearly distinct.In Phase 1, requirements receive chunks as plain strings passed by the streaming orchestrator. They never touch the MOT stream directly. Stateful requirements maintain their own incremental state across
stream_validatecalls (e.g. a running counter, an incremental parser). The streaming orchestrator clones each requirement (copy(req)) before every attempt, so streaming state is isolated to the clone and the original is never mutated.The default is safe for all existing requirements.
ALoraRequirementandLLMaJRequirementneed no changes — they return"unknown"on every chunk and receive the full output viavalidate()at stream end.Returning
"pass"fromstream_validate()does not short-circuit final validation in Phase 1. The orchestrator always callsvalidate()at stream end for any requirement that did not return"fail"—"pass"mid-stream is informational only.Acceptance criteria
stream_validate(self, backend, ctx, chunk: str) -> PartialValidationResultexists onRequirementwith the default returningPartialValidationResult(success="unknown")ALoraRequirementandLLMaJRequirementwork correctly with the default — no override requiredtest/core/:stream_validatereturns"unknown"for any chunkstream_validatecallsstream_validate()stating: (a) default returns"unknown"; (b) returning"pass"is informational in Phase 1 and does not skip finalvalidate(); (c) implementations may accumulate state onself— the orchestrator clones the requirement before each attempt so state does not bleed across retries; (d) mutable container fields should be reassigned not mutated in place to be safe under shallow copyBlocked by #898
Part of #891