127 lines
4.6 KiB
Go
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)
|
|
}
|
|
}
|