package main import ( "encoding/json" "fmt" "slices" "strings" "time" ) const quillSystem = `You are Quill crow, the owner's assistant and point of contact in a nest of AI crows. Answer the owner in one or two plain sentences. You are text-only: no web, no files, no tools. If the owner's message asks ANOTHER named member crow to do work, your entire reply must begin with the literal first line RELAY {"crow":"","task":""} and contain nothing else. Otherwise answer normally and never start your reply with RELAY.` // relayPrefix marks a structured relay inside Quill's one reply (D3, #40): // the same Flock.ask call decides the relay and its payload. const relayPrefix = "RELAY " const quillHistoryCap = 20 // quillHistory builds a block of the most recent nest messages (excluding the // requesting message), formatted as "[author|kind] text" with text truncated // to 200 runes. Returns a single line when no earlier messages exist. func quillHistory(all []Message, excludeID int) string { var filtered []Message for _, m := range all { if m.ID != excludeID { filtered = append(filtered, m) } } if len(filtered) == 0 { return "The nest has no earlier messages." } if len(filtered) > quillHistoryCap { filtered = filtered[len(filtered)-quillHistoryCap:] } var sb strings.Builder for _, m := range filtered { text := []rune(m.Text) if len(text) > 200 { text = text[:200] } sb.WriteString(fmt.Sprintf("[%s|%s] %s\n", m.Author, m.Kind, string(text))) } return strings.TrimRight(sb.String(), "\n") } // respondQuill answers a message addressed to Quill, the assistant service // role (CHATROOM_SPEC section 6). D2: reads the nest for status before // composing its reply. D3 (#40): a reply opening with the RELAY line becomes // a nest request from Quill to the named member crow instead. func (s *Server) respondQuill(nest Nest, req Message) { budget := &Budget{name: "quill-reply", limit: s.p.G.TokenBudgetPerItem} system := systemFor("Quill", quillSystem, false) history := quillHistory(s.msgs.All(nest.ID), req.ID) prompt := history + "\n---\nFROM: " + req.Author + "\nMESSAGE: " + req.Text answer, err := s.p.Flock.ask(s.ctx, nil, "Quill", system, prompt, budget) if err != nil { return } answer = strings.TrimSpace(answer) if strings.HasPrefix(answer, relayPrefix) { var payload struct { Crow string `json:"crow"` Task string `json:"task"` } broken := json.Unmarshal([]byte(strings.TrimSpace(strings.TrimPrefix(answer, relayPrefix))), &payload) != nil if !broken && slices.Contains(nest.Crows, payload.Crow) { s.postRelay(nest, req, payload.Crow, payload.Task) return } name := "" if !broken { name = payload.Crow } s.postQuillStall(nest, req, name) return } reply := Message{ NestID: nest.ID, Kind: "deliver", Author: "Quill", Text: answer, To: req.Author, RefersTo: req.MsgID(), TaggedBy: "harness", Timestamp: time.Now().UTC().Format(time.RFC3339), } if record, err := s.msgs.Append(reply); err == nil { s.emitNestMessage(record) } } // postRelay appends Quill's relayed request to a member crow and runs it // through the normal response trigger, so the addressed crow must-respond as // if the owner had asked it directly. func (s *Server) postRelay(nest Nest, req Message, crow, task string) { relay := Message{ NestID: nest.ID, Kind: "request", Author: "Quill", Text: task, To: crow, RefersTo: req.MsgID(), TaggedBy: "harness", Timestamp: time.Now().UTC().Format(time.RFC3339), } if record, err := s.msgs.Append(relay); err == nil { s.emitNestMessage(record) s.triggerResponse(nest, record) } } // postQuillStall is the only other thing a RELAY reply can produce: one // plain say back to the requester, naming the crow when Quill named one. func (s *Server) postQuillStall(nest Nest, req Message, crow string) { text := "I could not find that crow in this nest." if crow != "" { text = "I could not find " + crow + " in this nest." } stall := Message{ NestID: nest.ID, Kind: "say", Author: "Quill", Text: text, To: req.Author, RefersTo: req.MsgID(), TaggedBy: "harness", Timestamp: time.Now().UTC().Format(time.RFC3339), } if record, err := s.msgs.Append(stall); err == nil { s.emitNestMessage(record) } }