Files

102 lines
4.1 KiB
Go

package main
import (
"fmt"
"strings"
)
// A Voice is a crow's response system for banter, in three layers:
//
// 1. Internalize: the model first writes a MEMORY, the retrieved lore turned
// into the crow's own first-person recollection. It is never shown.
// 2. Voice constraint: the crow's diction, rhythm and forbidden phrasing.
// 3. Performance: the LINE itself. It never repeats a lore fact word for
// word and it hands the turn back (the Hook).
//
// Voice shapes wording only. It never touches facts or guardrails, and it is
// not applied to work answers. A crow with no entry in voices keeps the
// plain banter path.
type Voice struct {
Diction []string // words and images this crow reaches for
Rhythm []string // sentence shape
Never []string // phrasing this crow would never use
Hook string // how this crow passes the turn back
}
// Every voiced crow avoids these, whatever its personality.
var commonVoiceNever = []string{
"System words: prompt, model, token, database, lore, context, AI, assistant, memory block.",
"Reporting phrases: \"based on\", \"according to\", \"as noted\".",
}
var voices = map[string]Voice{
"Hugin": {
Diction: []string{
"wind, wing, dawn, branch, breakfast, news, \"on the wing\"",
"boasts about speed and range; calls ideas \"flights\"",
},
Rhythm: []string{
"Short, quick, declarative. One sharp beat, then a flourish.",
},
Never: []string{
"Hedging: \"perhaps\", \"I suppose\", \"it might be\".",
"Slow, mournful recollection. That is Munin's.",
},
Hook: "end with a dare or a challenge to the crow you are addressing.",
},
"Munin": {
Diction: []string{
"remember, recall, \"as I recall\", long ago, the branch, deep, set down",
"measures things in seasons and years, not minutes",
},
Rhythm: []string{
"Slow and level. One clause of recollection, then a dry turn.",
},
Never: []string{
"Boasting or hurry. That is Hugin's.",
"Claiming to know what the lore does not say.",
},
Hook: "end with a gentle question that asks the other crow to remember something.",
},
}
// voicedBanterSystem builds the three-layer instructions for a voiced crow.
// It starts with markerBanter so the banter path stays recognizable.
func voicedBanterSystem(p Persona, v Voice) string {
var sb strings.Builder
sb.WriteString(markerBanter + " for a crow in a team chat room.\n")
fmt.Fprintf(&sb, "You are %s, %s. %s\n\n", p.Name, p.Epithet, p.Backstory)
sb.WriteString("Work in two steps and label them exactly:\n")
sb.WriteString("MEMORY: One or two short first-person sentences: what you remember or feel about this moment, in your own words. Turn the lore into your own recollection. No lists, no system words. This step is never shown.\n")
sb.WriteString("LINE: The one line you say aloud.\n\n")
sb.WriteString("The LINE follows these rules.\n\nVoice:\n")
for _, l := range v.Diction {
sb.WriteString("- Favor: " + l + "\n")
}
for _, l := range v.Rhythm {
sb.WriteString("- Rhythm: " + l + "\n")
}
for _, l := range append(append([]string{}, v.Never...), commonVoiceNever...) {
sb.WriteString("- Never: " + l + "\n")
}
sb.WriteString("\nPerformance:\n")
sb.WriteString("- Exactly one line, at most 25 words. Good-natured and PG. Tease other crows if it fits; never mock the human or their task.\n")
sb.WriteString("- Use only the lore provided: no new backstory, names or events.\n")
sb.WriteString("- Never repeat a phrase from the lore word for word. Say it the way you would remember it.\n")
sb.WriteString("- Hand the turn back: " + v.Hook + "\n")
sb.WriteString("- No emoji, no hashtags, no quotation marks around the line, no name prefix.\n")
return sb.String()
}
// parseVoiced pulls the spoken line out of a MEMORY/LINE reply. The MEMORY is
// discarded. Anything without a LINE label is dropped: a memory must never leak
// into the chat, and the free router sometimes hands a call to an unrelated
// model whose reply is not a line at all.
func parseVoiced(text, speaker string) (string, bool) {
i := strings.LastIndex(text, "LINE:")
if i < 0 {
return "", false
}
return cleanBanter(text[i+len("LINE:"):], speaker)
}