(#24) Conspiracy invites + lens directives #31

Merged
jacisjake merged 2 commits from task/24-conspiracy-invites into main 2026-10-28 11:34:07 +00:00
Showing only changes of commit d45fb64eff - Show all commits
+61 -9
View File
@@ -82,6 +82,7 @@ func (s *Server) postNestMessage(w http.ResponseWriter, r *http.Request) {
Text string `json:"text"` Text string `json:"text"`
Kind string `json:"kind"` Kind string `json:"kind"`
Mode string `json:"mode"` Mode string `json:"mode"`
Invited int `json:"invited"`
To string `json:"to"` To string `json:"to"`
RefersTo string `json:"refers_to"` RefersTo string `json:"refers_to"`
} }
@@ -132,6 +133,13 @@ func (s *Server) postNestMessage(w http.ResponseWriter, r *http.Request) {
// Mode (Epic C, same rule shape as kind): Jev first, header escape, "" when unsure. // Mode (Epic C, same rule shape as kind): Jev first, header escape, "" when unsure.
mode, modeBy := s.tagMode(kind, body.Mode, body.Text) mode, modeBy := s.tagMode(kind, body.Mode, body.Text)
// A conspiracy expects several crows on the one task; the invite count is
// capped by MurderSize and the flock's in-flight limit.
invited := 0
if mode == "conspiracy" {
invited = s.inviteCount(nest, body.Invited)
}
// Claim arbitration (#5, Epic C): first claim wins unless the referent is a // Claim arbitration (#5, Epic C): first claim wins unless the referent is a
// conspiracy, which expects multiple claims. // conspiracy, which expects multiple claims.
if kind == "claim" && body.RefersTo != "" { if kind == "claim" && body.RefersTo != "" {
@@ -153,6 +161,7 @@ func (s *Server) postNestMessage(w http.ResponseWriter, r *http.Request) {
TaggedBy: taggedBy, TaggedBy: taggedBy,
Mode: mode, Mode: mode,
ModeTagged: modeBy, ModeTagged: modeBy,
Invited: invited,
Timestamp: time.Now().UTC().Format(time.RFC3339), Timestamp: time.Now().UTC().Format(time.RFC3339),
} }
record, err := s.msgs.Append(msg) record, err := s.msgs.Append(msg)
@@ -163,11 +172,17 @@ func (s *Server) postNestMessage(w http.ResponseWriter, r *http.Request) {
s.emitNestMessage(record) s.emitNestMessage(record)
// Event-driven trigger: a request addressed to a member crow gets one // Event-driven trigger (no idle loops):
// response (must-respond) or a Sentry flag. // conspiracy -> several invited crows, each from its own lens
if kind == "request" && slices.Contains(nest.Crows, record.To) { // any other request addressed to a member crow -> one must-respond reply
if kind == "request" {
switch {
case mode == "conspiracy" && invited > 0:
go s.respondConspiracy(nest, record, invited)
case slices.Contains(nest.Crows, record.To):
go s.respondToRequest(nest, record) go s.respondToRequest(nest, record)
} }
}
writeJSON(w, http.StatusCreated, nestRecord(record)) writeJSON(w, http.StatusCreated, nestRecord(record))
} }
@@ -215,15 +230,52 @@ func (s *Server) emitNestMessage(m Message) {
s.hub.PublishNest(m.NestID, "nest.message", rec) s.hub.PublishNest(m.NestID, "nest.message", rec)
} }
// respondToRequest is the must-respond rule: the addressed crow answers with // inviteCount is how many crows a conspiracy asks to take the one task, capped
// claim, deliver or decline. A reply that isn't one of those is retried once // by MurderSize, the flock's in-flight limit, and the nest's own size.
// with a stricter ask; still nothing responsive and the crow gets a flag from func (s *Server) inviteCount(nest Nest, requested int) int {
// the Sentry (no crow reply is posted). cap := s.p.G.MurderSize
if s.p.G.MaxCrowsInFlight < cap {
cap = s.p.G.MaxCrowsInFlight
}
if requested > 0 && requested < cap {
cap = requested
}
if len(nest.Crows) < cap {
cap = len(nest.Crows)
}
return cap
}
// respondConspiracy invites N crows; each answers from its own persona lens so
// distinct strengths produce distinct readings.
func (s *Server) respondConspiracy(nest Nest, req Message, invited int) {
crows := s.p.Flock.pick(nest, invited)
for _, crow := range crows {
go s.respondOne(nest, req, crow, true)
}
}
// conspiracyLine is the lens directive added to every invited crow's prompt.
const conspiracyLine = "You are one lens among several on this request; answer from your own strength, not the flock's consensus."
func (s *Server) respondToRequest(nest Nest, req Message) { func (s *Server) respondToRequest(nest Nest, req Message) {
crow := req.To s.respondOne(nest, req, req.To, false)
budget := &Budget{name: "nest-message", limit: s.p.G.TokenBudgetPerItem} }
// respondOne is the must-respond rule for a single crow: answer with claim,
// deliver or decline. A non-responsive reply is retried once with a stricter
// ask; still nothing responsive and the crow gets a Sentry flag (no reply).
func (s *Server) respondOne(nest Nest, req Message, crow string, lens bool) {
budget := &Budget{name: "nest-reply", limit: s.p.G.TokenBudgetPerItem}
system := systemFor(crow, nestReplySystem, false) system := systemFor(crow, nestReplySystem, false)
prompt := "NEST: " + nest.Name + "\nREQUEST FROM: " + req.Author + "\nRespond to " + req.Author + ".\nTASK: " + req.Text prompt := "NEST: " + nest.Name + "\nREQUEST FROM: " + req.Author + "\nRespond to " + req.Author + ".\nTASK: " + req.Text
if lens {
strength := ""
if p, ok := personaOf(crow); ok {
strength = p.Strength
}
prompt += "\n" + conspiracyLine + "\nYour strength: " + strength + " (" + crow + ")."
}
kind, body, ok := s.nestReply(system, prompt, crow, budget) kind, body, ok := s.nestReply(system, prompt, crow, budget)
if !ok { if !ok {