(#40) Relay — Quill posts requests on the owner's behalf #48
No files matched your search
+15
-9
@@ -181,14 +181,22 @@ func (s *Server) postNestMessage(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
s.emitNestMessage(record)
|
||||
s.triggerResponse(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" {
|
||||
writeJSON(w, http.StatusCreated, 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 mode == "conspiracy" && invited > 0:
|
||||
go s.respondConspiracy(nest, record, invited)
|
||||
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":
|
||||
@@ -200,11 +208,9 @@ func (s *Server) postNestMessage(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
if kind == "deliver" {
|
||||
if record.Kind == "deliver" {
|
||||
go s.checkDivergence(nest, record)
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusCreated, nestRecord(record))
|
||||
}
|
||||
|
||||
var modePostable = map[string]bool{"solo": true, "conspiracy": true, "split": true}
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
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.`
|
||||
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":"<name>","task":"<text>"}
|
||||
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
|
||||
|
||||
@@ -40,7 +49,8 @@ func quillHistory(all []Message, excludeID int) string {
|
||||
|
||||
// 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.
|
||||
// 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)
|
||||
@@ -50,11 +60,29 @@ func (s *Server) respondQuill(nest Nest, req Message) {
|
||||
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: strings.TrimSpace(answer),
|
||||
Text: answer,
|
||||
To: req.Author,
|
||||
RefersTo: req.MsgID(),
|
||||
TaggedBy: "harness",
|
||||
@@ -64,3 +92,45 @@ func (s *Server) respondQuill(nest Nest, req Message) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user