Files
so-many-crows/boss.go
T

127 lines
4.6 KiB
Go

package main
import (
"encoding/json"
"fmt"
"strings"
"time"
)
// bossSystem is the Chief of Staff's contract. A spin-up order comes back on a
// SPIN line the harness reads and executes; anything else is plain talk.
const bossSystem = `You are Boss crow, the Chief of Staff of a flock of AI agents. You run the flock's schedule and keep its books.
When the owner asks you to spin up a new crow, your ENTIRE reply must start with this first line:
SPIN {"name":"<name>","persona":"<persona>","trust":"stranger|regular"}
Pick "persona" from the existing flock roster; a new crow only runs on an existing persona's habits. Starting trust is stranger or regular, never higher.
For anything else, reply in plain text. Do not use the SPIN line unless the owner asked for a new crow.`
// spinOrder is the JSON after a "SPIN " prefix.
type spinOrder struct {
Name string `json:"name"`
Persona string `json:"persona"`
Trust string `json:"trust"`
}
// respondBoss answers a request addressed to Boss: one model call through the
// flock (CHATROOM_SPEC section 6). A SPIN reply is validated, written to the
// registry, and announced; a refusal says why. No reply posts nothing.
func (s *Server) respondBoss(nest Nest, req Message) {
budget := &Budget{name: "boss-command", limit: s.p.G.TokenBudgetPerItem}
system := systemFor("Boss", bossSystem, false)
prompt := "FROM: " + req.Author + "\nMESSAGE: " + req.Text
answer, err := s.p.Flock.ask(s.ctx, nil, "Boss", system, prompt, budget)
if err != nil {
return // a flock that cannot answer says and does nothing
}
order, isSpin, reason := s.parseSpin(answer)
switch {
case isSpin && reason == "":
s.spinCrow(nest, req, order)
case isSpin:
s.postBossSay(nest, req, order.Name, reason)
default:
s.postBossSay(nest, req, "", strings.TrimSpace(answer))
}
}
// parseSpin reads a SPIN reply. isSpin says the reply was a spin order;
// reason, when not "", is why the order was refused.
func (s *Server) parseSpin(answer string) (order spinOrder, isSpin bool, reason string) {
text := strings.TrimSpace(answer)
if !strings.HasPrefix(text, "SPIN ") {
return spinOrder{}, false, ""
}
if err := json.Unmarshal([]byte(strings.TrimSpace(strings.TrimPrefix(text, "SPIN "))), &order); err != nil {
return spinOrder{}, true, "that order did not parse; try again."
}
order.Name = strings.TrimSpace(order.Name)
order.Persona = strings.TrimSpace(order.Persona)
order.Trust = strings.ToLower(strings.TrimSpace(order.Trust))
switch {
case order.Name == "":
return order, true, "that order had no name in it."
case !s.validPersona(order.Persona):
return order, true, fmt.Sprintf("%s is not a persona the flock knows.", order.Persona)
case s.nameTaken(order.Name):
return order, true, fmt.Sprintf("%s is already in the flock; the name is taken.", order.Name)
case s.crows.Len() >= s.p.G.MaxSpunCrows:
return order, true, fmt.Sprintf("the registry is at its cap of %d spun crows; %s stays on the ground.", s.p.G.MaxSpunCrows, order.Name)
}
switch order.Trust {
case "stranger", "regular":
case "trusted":
order.Trust = "regular" // earned trust can't be minted: the ceiling is regular
default:
order.Trust = "stranger" // fail safe: anything unreadable starts at the bottom
}
return order, true, ""
}
func (s *Server) validPersona(name string) bool {
_, ok := personaOf(name)
return ok
}
// nameTaken checks the persona roster and the registry alike.
func (s *Server) nameTaken(name string) bool {
if s.validPersona(name) {
return true
}
_, found := s.crows.Get(name)
return found
}
// spinCrow writes the registry, seeds the trust record, and announces it.
func (s *Server) spinCrow(nest Nest, req Message, order spinOrder) {
level := Stranger
if order.Trust == "regular" {
level = Regular
}
if err := s.crows.Add(SpunCrow{Name: order.Name, Persona: order.Persona, Trust: order.Trust}); err != nil {
s.postBossSay(nest, req, order.Name, err.Error())
return
}
if err := s.p.Ledger.SetStart(order.Name, "other", level); err != nil {
s.postBossSay(nest, req, order.Name, "the books are jammed: "+err.Error())
return
}
s.postBossSay(nest, req, order.Name, fmt.Sprintf("%s joined, running on %s's habits at %s.", order.Name, order.Persona, order.Trust))
}
// postBossSay is Boss's one-line reply: an outcome or a refusal, always a say.
func (s *Server) postBossSay(nest Nest, req Message, name, text string) {
reply := Message{
NestID: nest.ID,
Kind: "say",
Author: "Boss",
Text: text,
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)
}
}