From d45fb64eff04fb075a43e547fee0eb0a75d8e769 Mon Sep 17 00:00:00 2001 From: Smokey <150986568+jacisjake@users.noreply.github.com> Date: Wed, 28 Oct 2026 05:33:52 -0600 Subject: [PATCH] feat(conspiracy): invite N crows, each from its own persona lens (#24) --- nestapi.go | 72 ++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 62 insertions(+), 10 deletions(-) diff --git a/nestapi.go b/nestapi.go index 5af1c3c..8affd54 100644 --- a/nestapi.go +++ b/nestapi.go @@ -82,6 +82,7 @@ func (s *Server) postNestMessage(w http.ResponseWriter, r *http.Request) { Text string `json:"text"` Kind string `json:"kind"` Mode string `json:"mode"` + Invited int `json:"invited"` To string `json:"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, 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 // conspiracy, which expects multiple claims. if kind == "claim" && body.RefersTo != "" { @@ -153,6 +161,7 @@ func (s *Server) postNestMessage(w http.ResponseWriter, r *http.Request) { TaggedBy: taggedBy, Mode: mode, ModeTagged: modeBy, + Invited: invited, Timestamp: time.Now().UTC().Format(time.RFC3339), } record, err := s.msgs.Append(msg) @@ -163,10 +172,16 @@ func (s *Server) postNestMessage(w http.ResponseWriter, r *http.Request) { s.emitNestMessage(record) - // Event-driven trigger: a request addressed to a member crow gets one - // response (must-respond) or a Sentry flag. - if kind == "request" && slices.Contains(nest.Crows, record.To) { - go s.respondToRequest(nest, record) + // Event-driven trigger (no idle loops): + // conspiracy -> several invited crows, each from its own lens + // 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) + } } writeJSON(w, http.StatusCreated, nestRecord(record)) @@ -215,15 +230,52 @@ func (s *Server) emitNestMessage(m Message) { s.hub.PublishNest(m.NestID, "nest.message", rec) } -// respondToRequest is the must-respond rule: the addressed crow answers with -// claim, deliver or decline. A reply that isn't one of those is retried once -// with a stricter ask; still nothing responsive and the crow gets a flag from -// the Sentry (no crow reply is posted). +// inviteCount is how many crows a conspiracy asks to take the one task, capped +// by MurderSize, the flock's in-flight limit, and the nest's own size. +func (s *Server) inviteCount(nest Nest, requested int) int { + 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) { - crow := req.To - budget := &Budget{name: "nest-message", limit: s.p.G.TokenBudgetPerItem} + s.respondOne(nest, req, req.To, false) +} + +// 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) 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) if !ok {