adventure: author Pete's dispatches in his voice with the local LLM

emitFact now runs the final fact through authorDispatch, which asks the local
Ollama model for a warm-reporter headline and lede and ships them on the Fact.
Pete guards and publishes them, falling back to its own templates on anything
it rejects — so authoring is best-effort by design: LLM off, a timeout, a
malformed generation, or an over-length pair all return an empty prose pair and
Pete templates the fact. The names allowed in the prose are the fact's Actors,
built from the post-opt-out subject/opponent, so what the model may say and what
Pete's guard permits are the same list. Synchronous like the holdem tip rewrite,
but on a tight 15s budget: news facts are infrequent and a template now beats a
voiced dispatch late. With no route for Pete to call back into this box, the
voice lives in the prompt here rather than in a Pete-owned inference endpoint.
This commit is contained in:
prosolis
2026-07-17 09:28:13 -07:00
parent fbed45fc96
commit 22b7949791
4 changed files with 295 additions and 0 deletions

View File

@@ -183,6 +183,12 @@ func emitFact(f peteclient.Fact, subjectUser, opponentUser id.UserID) {
}
}
f.Actors = actors
// Author the prose in Pete's voice from the FINAL fact, so the names in the
// dispatch match the Actors allow-list Pete guards against. Best-effort: an
// empty pair (LLM off or authoring failed) just means Pete templates the
// fact. Synchronous, like the holdem tip rewrite — news facts are infrequent
// and the call is tightly bounded (dispatchLLMTimeout).
f.Headline, f.Lede = authorDispatch(f)
peteclient.Emit(f)
}