2026-10-18 08:48:57 -07:00
package agents
import (
"context"
"encoding/json"
"errors"
"fmt"
"log/slog"
"strings"
2026-10-18 15:32:07 -07:00
"g.fc.run/ai/byre/internal/api"
"g.fc.run/ai/byre/internal/mcp"
"g.fc.run/ai/byre/internal/store"
"g.fc.run/ai/byre/internal/workflow"
2026-10-18 08:48:57 -07:00
)
const boardActor = "board"
func teamLead ( sessions [] api . AgentSession ) string {
if len ( sessions ) == 0 {
return ""
}
return sessions [ 0 ]. Member
}
func memberSession ( sessions [] api . AgentSession , member string ) ( api . AgentSession , bool ) {
for i := len ( sessions ) - 1 ; i >= 0 ; i -- {
s := sessions [ i ]
if s . Member == member && s . State != api . AgentSessionStateCompleted {
return s , true
}
}
return api . AgentSession {}, false
}
func teamSessions ( sessions [] api . AgentSession ) [] api . AgentSession {
team := [] api . AgentSession {}
seen := map [ string ] bool {}
for _ , s := range sessions {
if s . Member == "" || seen [ s . Member ] {
continue
}
seen [ s . Member ] = true
live , found := memberSession ( sessions , s . Member )
if ! found {
live = latestSession ( sessions , s . Member )
}
team = append ( team , live )
}
return team
}
func latestSession ( sessions [] api . AgentSession , member string ) api . AgentSession {
latest := api . AgentSession {}
for _ , s := range sessions {
if s . Member == member {
latest = s
}
}
return latest
}
func ( m * Manager ) leadsTask ( ctx context . Context , taskID , sessionID string ) bool {
sess , found , err := m . liveSession ( ctx , taskID )
if err != nil {
slog . Error ( "find task's first agent" , "task" , taskID , "err" , err )
return false
}
return found && sess . Id == sessionID
}
func checkTeam ( members [] api . TeamMember ) error {
problems := workflow . TeamProblems ( members )
if len ( problems ) > 0 {
return fmt . Errorf ( "%w: %s" , ErrInvalid , strings . Join ( problems , "; " ))
}
return nil
}
func ( m * Manager ) startTeam ( ctx context . Context , task api . Task , creator api . User , shared string , members [] api . TeamMember , actor string ) error {
err := checkTeam ( members )
if err != nil {
return err
}
if creator . GitName == "" || creator . GitEmail == "" {
return ErrNoGitIdentity
}
sessions := [] api . AgentSession {}
for _ , mem := range members {
sess , err := m . newSession ( ctx , task , creator , mem . Name , mem . Model )
if err != nil {
return err
}
sessions = append ( sessions , sess )
}
failed := [] string {}
for i , sess := range sessions {
_ , err = m . turn ( ctx , sess , task , actor , teamPrompt ( shared , members [ i ]), nil )
if err != nil {
failed = append ( failed , fmt . Sprintf ( "%q: %v" , members [ i ]. Name , err ))
}
}
if len ( failed ) > 0 {
return fmt . Errorf ( "these agents did not start, so the team is incomplete; discard the task and create it again: %s" , strings . Join ( failed , "; " ))
}
return nil
}
func teamPrompt ( shared string , mem api . TeamMember ) string {
return fmt . Sprintf ( "%s\n\n## Your part: %s\n\n%s" , strings . TrimSpace ( shared ), mem . Name , strings . TrimSpace ( mem . Prompt ))
}
func teamGuidance ( sessions [] api . AgentSession , member string , stage bool ) string {
team := teamSessions ( sessions )
names := [] string {}
for _ , s := range team {
names = append ( names , fmt . Sprintf ( "%q (%s)" , s . Member , s . Model ))
}
lead := teamLead ( sessions )
g := fmt . Sprintf ( "\n- This task is worked by a team of agents in this one worktree: %s. You are %q. " +
"The team talks through a shared board: post_to_board sends a post to every other agent (a working agent gets it after its next tool call; an idle one starts a turn with it), and read_board reads the posts so far. " +
"The human and the lead post there too. Post only when you add something: a finding, a proposal, an objection, a decision, a request. Don't post acknowledgements or thanks, since every post wakes every other agent. " +
"Your final message goes to the human, not to the team. The other agents change the same files: re-read a file before you edit it, and say on the board when you take on a file or an area." ,
strings . Join ( names , ", " ), member )
if ! stage {
return g
}
if member == lead {
return g + " You are the team's first agent, so you call finish_stage once the team's work on this stage is done; the others tell you on the board when their parts are."
}
return g + fmt . Sprintf ( " %q, the team's first agent, finishes this stage with finish_stage; tell it on the board when your part is done." , lead )
}
type poster struct {
actor string
member string
model string
session string
}
func ( m * Manager ) Post ( ctx context . Context , task api . Task , actor , text string ) error {
return m . post ( ctx , task , poster { actor : actor }, text )
}
func ( m * Manager ) post ( ctx context . Context , task api . Task , from poster , text string ) error {
if strings . TrimSpace ( text ) == "" {
return fmt . Errorf ( "%w: text is empty" , ErrInvalid )
}
if task . Status != api . TaskStatusActive {
return fmt . Errorf ( "%w: task %s is %s" , ErrConflict , task . Id , task . Status )
}
sessions , err := m . store . ListSessions ( ctx , task . Id )
if err != nil {
return err
}
team := teamSessions ( sessions )
if len ( team ) == 0 {
return fmt . Errorf ( "%w: task %s has no team, so no board" , ErrInvalid , task . Id )
}
payload := map [ string ] any { "text" : text }
if from . member != "" {
payload [ "member" ] = from . member
payload [ "model" ] = from . model
}
ev := api . Event { Kind : "board.posted" , ProjectId : & task . ProjectId , TaskId : & task . Id , Actor : from . actor , Payload : payload }
if from . session != "" {
ev . AgentSessionId = & from . session
}
err = m . store . AppendEvent ( ctx , ev )
if err != nil {
return err
}
who := from . actor
if from . member != "" {
who = fmt . Sprintf ( "%q" , from . member )
}
msg := fmt . Sprintf ( "%s posted on the board:\n\n%s" , who , text )
for _ , s := range team {
if s . Member == from . member {
continue
}
m . deliverPost ( context . WithoutCancel ( ctx ), task , s , msg )
}
return nil
}
func ( m * Manager ) deliverPost ( ctx context . Context , task api . Task , s api . AgentSession , msg string ) {
started , err := m . store . SessionStarted ( ctx , s . Id )
if err == nil && started {
_ , _ , err = m . Message ( ctx , s . Id , boardActor , msg , "" )
}
if err == nil && started {
return
}
if err != nil && ! errors . Is ( err , ErrConflict ) {
slog . Error ( "deliver board post" , "task" , task . Id , "member" , s . Member , "err" , err )
}
err = m . store . AddTaskInput ( ctx , store . TaskInput { TaskID : task . Id , Member : s . Member , Actor : boardActor , Text : msg }, task . ProjectId , s . Id )
if err != nil {
slog . Error ( "queue board post" , "task" , task . Id , "member" , s . Member , "err" , err )
}
}
func ( m * Manager ) deliverTeam ( ctx context . Context , l * live ) {
if l . member == "" {
return
}
sessions , err := m . store . ListSessions ( ctx , l . taskID )
if err != nil {
slog . Error ( "list team sessions" , "task" , l . taskID , "err" , err )
return
}
for _ , s := range teamSessions ( sessions ) {
if s . Id == l . id || s . State != api . AgentSessionStateIdle {
continue
}
err = m . Deliver ( ctx , s . Id )
if err != nil {
slog . Error ( "deliver to team member" , "session" , s . Id , "err" , err )
}
}
}
func ( m * Manager ) boardTools ( l * live ) [] mcp . Tool {
return [] mcp . Tool {
{
Name : "post_to_board" ,
Description : "Post to this task's team board. Every other agent of the team gets the post: a working agent after its next tool call, an idle one as a new turn. The human sees the board in the task's Board tab." ,
InputSchema : json . RawMessage ( `{"type": "object", "properties": {"text": {"type": "string", "description": "Markdown."}}, "required": ["text"]}` ),
Handler : func ( ctx context . Context , raw json . RawMessage ) ( string , error ) {
args := struct {
Text string `json:"text"`
}{}
err := json . Unmarshal ( raw , & args )
if err != nil {
return "" , fmt . Errorf ( "malformed post_to_board call: %w" , err )
}
task , err := m . store . GetTask ( ctx , l . taskID )
if err != nil {
return "" , err
}
err = m . post ( ctx , task , poster { actor : agentActor , member : l . member , model : l . model , session : l . id }, args . Text )
if err != nil {
return "" , err
}
return "Posted to the board." , nil
},
},
{
Name : "read_board" ,
Description : "Read this task's team board: every post, oldest first, each with its seq and who posted it. With after_seq, only the posts after it." ,
InputSchema : json . RawMessage ( `{"type": "object", "properties": {"after_seq": {"type": "integer"}}}` ),
Handler : func ( ctx context . Context , raw json . RawMessage ) ( string , error ) {
args := struct {
AfterSeq int64 `json:"after_seq"`
}{}
err := json . Unmarshal ( raw , & args )
if err != nil {
return "" , fmt . Errorf ( "malformed read_board call: %w" , err )
}
posts , err := m . store . BoardPosts ( ctx , l . taskID , args . AfterSeq )
if err != nil {
return "" , err
}
if len ( posts ) == 0 {
return "No posts." , nil
}
b := & strings . Builder {}
for _ , ev := range posts {
fmt . Fprintf ( b , "#%d %s:\n%v\n\n" , ev . Seq , postAuthor ( ev ), ev . Payload [ "text" ])
}
return strings . TrimSpace ( b . String ()), nil
},
},
}
}
func postAuthor ( ev api . Event ) string {
member , _ := ev . Payload [ "member" ].( string )
if member != "" {
return fmt . Sprintf ( "%q (%v)" , member , ev . Payload [ "model" ])
}
return ev . Actor
}