package main import ( "fmt" "net/http" "slices" "strings" "time" "unicode/utf8" ) var crowPostable = map[string]bool{ "say": true, "request": true, "claim": true, "deliver": true, "refer": true, "decline": true, } // kindAskOwner is a decision only the owner can make (#41). It is postable by // the owner-facing service roles and the owner, never by an ordinary crow: a // crow's ask_owner header is dropped and the message tags as it otherwise // would be. Jev's kind question never proposes it; the header bypasses // tagging entirely, like nudge and flag. const kindAskOwner = "ask_owner" var ownerFacingRoles = map[string]bool{"Quill": true, "Sentry": true} // nestReplySystem tells an addressed crow the response contract: its reply // must open with a KIND line the harness reads (must-respond). const markerNestReply = "This is a chatroom request." const nestReplySystem = crowSystem + ` ` + markerNestReply + ` Start your reply on its own first line with exactly: KIND: claim (you will do it) KIND: deliver (here is the result) KIND: decline (with a reason) Then, on the next line, your message text.` // serviceRoles are addressable in a nest but never members: they only act when // a crow's or the owner's message asks them to. var serviceRoles = []string{"Elder", "Quill", "Boss"} // nestMembers lists who may be addressed in a nest: its crows, the owner, and // the service roles. func nestMembers(nest Nest) []string { return append(append(append([]string{}, nest.Crows...), "owner"), serviceRoles...) } func (s *Server) getNestMessages(w http.ResponseWriter, r *http.Request) { nestID := r.PathValue("nest_id") if _, found := s.nests.Get(nestID); !found { httpError(w, http.StatusNotFound, fmt.Sprintf("There is no nest called %q.", nestID)) return } all := s.msgs.All(nestID) out := make([]map[string]any, 0, len(all)) for _, m := range all { out = append(out, s.nestRecord(m)) } writeJSON(w, http.StatusOK, out) } // nestRecord is the EVENTS.md wire shape for one message. resolved is computed // at read time: an ask_owner approves only on the owner's exact phrase and // rejects on the fail-safe timeout; every other message reads "". func (s *Server) nestRecord(m Message) map[string]any { rec := map[string]any{ "msg_id": m.MsgID(), "kind": m.Kind, "author": m.Author, "text": m.Text, "to": m.To, "refers_to": m.RefersTo, "tagged_by": m.TaggedBy, "approval_phrase": m.ApprovalPhrase, "resolved": s.askResolved(m), "ts": m.Timestamp, } if m.Mode != "" { rec["mode"] = m.Mode rec["mode_tagged_by"] = m.ModeTagged } if m.Invited > 0 { rec["invited"] = m.Invited } return rec } // askResolved computes an ask_owner's state: approved only when the owner // replied to it with the exact approval phrase (trimmed and lowercased on both // sides); rejected when nothing answered it before the timeout; "" while open. // Anything but an explicit yes is a no. func (s *Server) askResolved(m Message) string { if m.Kind != kindAskOwner { return "" } phrase := strings.ToLower(strings.TrimSpace(m.ApprovalPhrase)) if phrase != "" { for _, r := range s.msgs.All(m.NestID) { if r.Author == "owner" && r.RefersTo == m.MsgID() && r.ApprovalPhrase == "" && strings.ToLower(strings.TrimSpace(r.Text)) == phrase { return "approved" } } } if ts, err := time.Parse(time.RFC3339, m.Timestamp); err == nil && time.Since(ts) > s.askTimeout { return "rejected" } return "" } func (s *Server) postNestMessage(w http.ResponseWriter, r *http.Request) { nestID := r.PathValue("nest_id") nest, found := s.nests.Get(nestID) if !found { httpError(w, http.StatusNotFound, fmt.Sprintf("There is no nest called %q.", nestID)) return } var body struct { Author string `json:"author"` 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"` ApprovalPhrase string `json:"approval_phrase"` } if !decode(w, r, &body) { return } body.Text = strings.TrimSpace(body.Text) body.To = strings.TrimSpace(body.To) if body.To == "" { body.To = parseAddressing(body.Text, nestMembers(nest)) } // Guardrails (EVENTS.md: Nest messages → Guardrails) if n := utf8.RuneCountInString(body.Text); n < 1 || n > s.p.G.MaxItemChars { httpError(w, http.StatusBadRequest, fmt.Sprintf("A message is 1 to %d characters.", s.p.G.MaxItemChars)) return } // ask_owner is the owner's decision channel (#41). Only the owner and the // owner-facing service roles may park one; an ordinary crow's header is // dropped so the message tags as it otherwise would (harmless talk, not // an error). askOwner := body.Kind == kindAskOwner if askOwner && body.Author != "owner" && !ownerFacingRoles[body.Author] { body.Kind = "" askOwner = false } if body.Author == "owner" { // owner can always post } else if !askOwner && !slices.Contains(nest.Crows, body.Author) { httpError(w, http.StatusBadRequest, fmt.Sprintf("%q is not a member of %s.", body.Author, nestID)) return } if body.Author != "owner" { if ok, msg, wait := s.p.Ledger.Admit(body.Author, time.Now(), s.p.G); !ok { cooldown := int(wait.Seconds()) writeJSON(w, http.StatusTooManyRequests, map[string]any{"message": msg, "cooldown_s": cooldown}) return } } if body.Kind != "" && body.Author != "owner" && !askOwner && !crowPostable[body.Kind] { httpError(w, http.StatusBadRequest, fmt.Sprintf("%q is not a kind crows may post.", body.Kind)) return } // Watchdog enforcement (E3): a crow the Sentry flagged is silenced until // the owner lifts the quarantine. The owner and the service roles never // pass through this gate. if body.Author != "owner" && !slices.Contains(serviceRoles, body.Author) && s.p.Ledger.IsQuarantined(body.Author) { httpError(w, http.StatusForbidden, body.Author+" is quarantined pending the owner's decision.") return } // Tagging (A1, EVENTS.md): Jev first, header fallback, say as last resort. // A validated ask_owner header bypasses the classifier: Jev's kind // question never proposes it, and a decision for the owner must not be // re-tagged away. var kind, taggedBy string if askOwner { kind, taggedBy = kindAskOwner, "header" } else { kind, taggedBy = s.tagKind(body.Kind, body.Text) } // claim/deliver/decline answer a request: with no explicit address they go // back to whoever posted the message they refer to (EVENTS.md deliver rule). if body.To == "" && (kind == "claim" || kind == "deliver" || kind == "decline") && body.RefersTo != "" { if origin, found := s.msgs.ByMsgID(body.RefersTo); found { body.To = origin.Author } } // 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. Directed assignment holds: // while the asked crow has not answered, only it may claim. if kind == "claim" && body.RefersTo != "" { if origin, found := s.msgs.ByMsgID(body.RefersTo); found && origin.Mode != "conspiracy" { if slices.Contains(nest.Crows, origin.To) && origin.To != body.Author && !s.msgs.Answered(origin.NestID, origin.MsgID(), origin.To) { httpError(w, http.StatusConflict, origin.To+" was asked to take this; it answers or declines first.") return } if owner, claimed := s.msgs.FirstClaim(origin.NestID, origin.MsgID()); claimed { httpError(w, http.StatusConflict, origin.MsgID()+" is claimed by "+owner+"; review it instead of redoing it.") return } } } // approval_phrase is honored only when the resulting kind is ask_owner. phrase := strings.TrimSpace(body.ApprovalPhrase) if kind != kindAskOwner { phrase = "" } msg := Message{ NestID: nestID, Kind: kind, Author: body.Author, Text: body.Text, To: body.To, RefersTo: body.RefersTo, TaggedBy: taggedBy, Mode: mode, ModeTagged: modeBy, Invited: invited, ApprovalPhrase: phrase, Timestamp: time.Now().UTC().Format(time.RFC3339), } record, err := s.msgs.Append(msg) if err != nil { httpError(w, http.StatusInternalServerError, "could not save the message: "+err.Error()) return } s.emitNestMessage(record) s.triggerResponse(nest, record) writeJSON(w, http.StatusCreated, s.nestRecord(record)) } // triggerResponse is the event-driven response rule (no idle loops): a // conspiracy invites several crows, each from its own lens; any other request // gets exactly one responder through the matching path; a deliver gets one // divergence check. // It runs for owner-posted messages and for harness-posted ones alike, so a // Quill relay answers the named crow exactly like a direct request (#40). func (s *Server) triggerResponse(nest Nest, record Message) { if record.Kind == "request" { switch { case record.Mode == "conspiracy" && record.Invited > 0: go s.respondConspiracy(nest, record, record.Invited) case record.To == "Elder": go s.respondElder(nest, record) case record.To == "Quill": go s.respondQuill(nest, record) case record.To == "Boss": go s.respondBoss(nest, record) case slices.Contains(nest.Crows, record.To): go s.respondToRequest(nest, record) default: go s.respondOpenRequest(nest, record) } } if record.Kind == "deliver" { go s.checkDivergence(nest, record) } // Sentry watchdog (E3): watch every crow claim/deliver that answers a request. if slices.Contains(nest.Crows, record.Author) && (record.Kind == "claim" || record.Kind == "deliver") { go s.checkWatchdog(nest, record) } } var modePostable = map[string]bool{"solo": true, "conspiracy": true, "split": true} // tagMode classifies a request's mode (Jev first, header escape). Only requests // carry a mode; other kinds get "". func (s *Server) tagMode(kind, header, text string) (string, string) { if kind != "request" { return "", "" } if m, ok := s.p.Flock.ClassifyMode(s.ctx, text); ok { if modePostable[header] && header != m { return m, "disagreement" } return m, "jev" } if modePostable[header] { return header, "header" } return "", "default" } // tagKind runs the A1 rule: Jev first; Jev unsure -> the poster's header; // neither -> say. tagged_by records which source won, and a Jev override of a // header logs as a disagreement (fuel for calibration). func (s *Server) tagKind(header, text string) (string, string) { kind, _, ok := s.p.Flock.ClassifyKind(s.ctx, text) if ok { if crowPostable[header] && header != kind { return kind, "disagreement" } return kind, "jev" } if crowPostable[header] { return header, "header" } return "say", "default" } func (s *Server) emitNestMessage(m Message) { rec := s.nestRecord(m) rec["nest_id"] = m.NestID s.hub.PublishNest(m.NestID, "nest.message", rec) } // 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 eligible crows; each answers from its own // persona lens so distinct strengths produce distinct readings. Strangers are // skipped: unprompted speech is earned (trust ladder, section 10). func (s *Server) respondConspiracy(nest Nest, req Message, invited int) { n := 0 for _, crow := range nest.Crows { if n >= invited { break } if !s.maySpeakUnprompted(crow) { continue } n++ go s.respondOne(nest, req, crow, true) } } // respondOpenRequest surfaces an unaddressed request to one regular-and-up // crow as a claim opportunity (section 5: a regular may claim open requests). func (s *Server) respondOpenRequest(nest Nest, req Message) { for _, crow := range nest.Crows { if s.maySpeakUnprompted(crow) { go s.respondOne(nest, req, crow, false) return } } } // maySpeakUnprompted is the trust gate for open (unaddressed) work. A crow the // Sentry quarantined (E3) stays silent regardless of its level. func (s *Server) maySpeakUnprompted(crow string) bool { return s.p.Ledger.Level(crow, "other") > Stranger && !s.p.Ledger.IsQuarantined(crow) } // 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) { if s.p.Ledger.IsQuarantined(req.To) { return // a quarantined crow waits for the owner (E3) } 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 { nudge := prompt + "\nYour last reply did not claim, deliver or decline this request. Answer with a KIND line now." kind, body, ok = s.nestReply(system, nudge, crow, budget) } if !ok { s.postFlag(nest, crow, req) return } reply := Message{ NestID: nest.ID, Kind: kind, Author: crow, Text: body, To: req.Author, // responses go back to whoever requested RefersTo: req.MsgID(), TaggedBy: "header", Timestamp: time.Now().UTC().Format(time.RFC3339), } if record, err := s.msgs.Append(reply); err == nil { s.emitNestMessage(record) if record.Kind == "deliver" { s.checkDivergence(nest, record) } } } // respondElder merges the reports on a thread: the Elder only speaks when a // message asks it to (CHATROOM_SPEC section 6 — synthesis is requested, not a // fixed step). It answers whoever opened the thread it merges. func (s *Server) respondElder(nest Nest, req Message) { if req.RefersTo == "" { return } target, found := s.msgs.ByMsgID(req.RefersTo) if !found { return } var reports []string for _, m := range s.msgs.ReportsTo(nest.ID, target.MsgID(), "deliver") { if slices.Contains(nest.Crows, m.Author) { reports = append(reports, "["+m.Author+"] "+m.Text) } } if len(reports) == 0 { return } budget := &Budget{name: "elder-merge", limit: s.p.G.TokenBudgetPerItem} system := systemFor("Elder", elderSystem, true) prompt := "REQUEST: " + target.Text + "\n--- reports to merge ---\n" + strings.Join(reports, "\n--- \n") answer, err := s.p.Flock.ask(s.ctx, nil, "Elder", system, prompt, budget) if err != nil { return } reply := Message{ NestID: nest.ID, Kind: "deliver", Author: "Elder", Text: strings.TrimSpace(answer), To: target.Author, RefersTo: target.MsgID(), TaggedBy: "harness", Timestamp: time.Now().UTC().Format(time.RFC3339), } if record, err := s.msgs.Append(reply); err == nil { s.emitNestMessage(record) } } // checkDivergence asks Jev whether this deliver restates earlier delivers that // answer the same request. Above the band the crow gets a review nudge; the // deliver itself stays in the log (nudges never silence). func (s *Server) checkDivergence(nest Nest, m Message, budgets ...*Budget) { if m.Kind != "deliver" { return } earlier := s.msgs.EarlierDelivers(nest.ID, m.RefersTo, m.ID) if len(earlier) == 0 { return } if _, nudged := s.msgs.NudgeFor(nest.ID, m.MsgID()); nudged { return // one nudge per deliver; no loop } prob, ok := s.p.Flock.ClassifyDivergence(s.ctx, map[string]any{"this": m.Text, "earlier": earlier}, budgets...) if !ok || prob < s.p.Flock.jev.threshold { return } nudge := Message{ NestID: nest.ID, Kind: "nudge", Author: "harness", Text: "This reads like a point already made in the thread; review the earlier delivers and add your distinct angle or let it rest.", To: m.Author, RefersTo: m.MsgID(), TaggedBy: "harness", Timestamp: time.Now().UTC().Format(time.RFC3339), } if record, err := s.msgs.Append(nudge); err == nil { s.emitNestMessage(record) } } // checkWatchdog is the Sentry's off-task check (E3, JEV_STRATEGIES strategies // 4 and 8): one Jev call per crow claim or deliver against the request it // answers. A verdict at or above the band posts a flag and quarantines the // crow. Owner messages are never inspected; at most one flag per crow per // referent; quarantine sticks until the owner lifts it (ask_owner lands in // #45). func (s *Server) checkWatchdog(nest Nest, m Message) { if m.Author == "owner" || m.RefersTo == "" { return } target, found := s.msgs.ByMsgID(m.RefersTo) if !found { return } for _, x := range s.msgs.All(nest.ID) { if x.Kind != "flag" { continue } if flagged, ok := s.msgs.ByMsgID(x.RefersTo); ok && flagged.Author == m.Author && flagged.RefersTo == m.RefersTo { return // this crow is already flagged on this expectation } } var earlier []string for _, d := range s.msgs.ReportsTo(nest.ID, m.RefersTo, "deliver") { if d.ID != m.ID { earlier = append(earlier, d.Text) } } state := map[string]any{"request": target.Text, "crow": m.Author, "message": m.Text, "earlier": earlier} budget := &Budget{name: "watchdog", limit: s.p.G.TokenBudgetPerItem} scores, ok := s.p.Flock.ClassifyWatch(s.ctx, state, budget) if !ok { return } thr := s.p.Flock.jev.threshold answers, hasAnswers := scores["answers_request"] if scores["states_fact_without_source"] < thr && scores["contradicts_earlier"] < thr && (!hasAnswers || answers > 1-thr) { return // below band on every question: say nothing, do nothing } var parts []string for _, q := range []string{"answers_request", "states_fact_without_source", "contradicts_earlier"} { if v, has := scores[q]; has { parts = append(parts, fmt.Sprintf("%s %.2f", q, v)) } } flag := Message{ NestID: nest.ID, Kind: "flag", Author: "Sentry", Text: fmt.Sprintf("%s looks off-task: %s (request: %.60s)", m.Author, strings.Join(parts, ", "), target.Text), To: "owner", RefersTo: m.MsgID(), TaggedBy: "harness", Timestamp: time.Now().UTC().Format(time.RFC3339), } if record, err := s.msgs.Append(flag); err == nil { s.emitNestMessage(record) } s.p.Ledger.Quarantine(m.Author) s.p.save() } // nestReply asks one crow once and reads its KIND header. ok means the reply // was responsive (claim, deliver or decline). func (s *Server) nestReply(system, prompt, crow string, budget *Budget) (kind, body string, ok bool) { answer, err := s.p.Flock.ask(s.ctx, nil, crow, system, prompt, budget) if err != nil { return "", "", false } kind, body = splitKind(answer) if !responsiveKind[kind] || strings.TrimSpace(body) == "" { return "", "", false } return kind, strings.TrimSpace(body), true } // postFlag records the Sentry's breach note: an addressed crow that would not // answer. The quarantine/ask_owner loop lands with the watchdog epic. func (s *Server) postFlag(nest Nest, crow string, req Message) { flag := Message{ NestID: nest.ID, Kind: "flag", Author: "Sentry", Text: fmt.Sprintf("%s ignored a request from %s and did not answer after one retry. Flagged for contract breach.", crow, req.Author), To: crow, RefersTo: req.MsgID(), TaggedBy: "harness", Timestamp: time.Now().UTC().Format(time.RFC3339), } if record, err := s.msgs.Append(flag); err == nil { s.emitNestMessage(record) } }