Splitting this out of a comment on #1660, which is closed — so today this is only tracked in prose on a closed issue, and the daily report has been silently off since 2026-08-22.
Current state
LLM_DAILY_REPORT_ENABLED=false on the live hp-llm-worker. The session and payload pipelines run normally against real captured data; the daily report does not run at all.
The failure
analyze_daily_report() passes DailyReport.model_json_schema() as Ollama's format. Ollama answers:
400 "Failed to initialize samplers: failed to parse grammar"
Deterministic, and independent of prompt content — it reproduces directly against /api/chat with a trivial prompt. PayloadAnalysis and SessionAnalysis use the same code path and work fine.
What is known
An earlier bisect found that a summary string plus a single enum field is enough to reproduce it, while the full 7-field PayloadAnalysis schema — more fields, larger arrays — is not. That rules out "too many fields" and points at a specific construct rather than anything wrong with DailyReport's field types.
Comparing the two schemas, the differences are narrow:
|
PayloadAnalysis (works) |
DailyReport (fails) |
summary |
maxLength: 1200 |
maxLength: 2000 |
| lists |
10 / 20 / 50 items |
three × 20 items |
Ollama compiles the JSON schema into a GBNF grammar, and bounded-length strings become repetition rules. A large maxLength is a plausible way to blow past a limit in that compiler — which would explain why the trigger is a size, not a field.
What a fix needs
Either shape the schema so Ollama's grammar compiler accepts it, or stop constraining this one call site and validate the reply in Python instead. Whichever it is, the bound belongs in the Pydantic validator regardless — the schema is what asks the model to behave, the validator is what actually enforces it.
Then re-enable LLM_DAILY_REPORT_ENABLED and confirm a real report is written to the llm-analysis index.
Why it matters
This is the only one of the three annotation pipelines that has never produced a single real output. It was never exercised before captured-data mode was turned on, so it has been broken from the start rather than regressed.
Splitting this out of a comment on #1660, which is closed — so today this is only tracked in prose on a closed issue, and the daily report has been silently off since 2026-08-22.
Current state
LLM_DAILY_REPORT_ENABLED=falseon the livehp-llm-worker. The session and payload pipelines run normally against real captured data; the daily report does not run at all.The failure
analyze_daily_report()passesDailyReport.model_json_schema()as Ollama'sformat. Ollama answers:Deterministic, and independent of prompt content — it reproduces directly against
/api/chatwith a trivial prompt.PayloadAnalysisandSessionAnalysisuse the same code path and work fine.What is known
An earlier bisect found that a
summarystring plus a single enum field is enough to reproduce it, while the full 7-fieldPayloadAnalysisschema — more fields, larger arrays — is not. That rules out "too many fields" and points at a specific construct rather than anything wrong withDailyReport's field types.Comparing the two schemas, the differences are narrow:
PayloadAnalysis(works)DailyReport(fails)summarymaxLength: 1200maxLength: 2000Ollama compiles the JSON schema into a GBNF grammar, and bounded-length strings become repetition rules. A large
maxLengthis a plausible way to blow past a limit in that compiler — which would explain why the trigger is a size, not a field.What a fix needs
Either shape the schema so Ollama's grammar compiler accepts it, or stop constraining this one call site and validate the reply in Python instead. Whichever it is, the bound belongs in the Pydantic validator regardless — the schema is what asks the model to behave, the validator is what actually enforces it.
Then re-enable
LLM_DAILY_REPORT_ENABLEDand confirm a real report is written to thellm-analysisindex.Why it matters
This is the only one of the three annotation pipelines that has never produced a single real output. It was never exercised before captured-data mode was turned on, so it has been broken from the start rather than regressed.