server

package
v0.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 28, 2026 License: MIT Imports: 39 Imported by: 0

Documentation

Overview

Server-side callbacks the commander invokes for write-tool execution. These functions are passed to commander.DefaultConfig as method values so the commander package doesn't need to import server internals.

Package server — orchestrate.go

WHY: Multi-agent project chat orchestration

This file implements the routing and handoff protocol for project chat. The design evolved through several iterations:

  1. ORIGINAL: Fan-out to ALL agents on every user message, with cross-agent sync (N-1 hidden LLM calls per message). This was slow, expensive, and produced chaotic multi-agent responses.

  2. MIDDLE: Single-respondent with [LISTENING]/[PASS] directives. [PASS] chained to the next agent in a priority list. Removed because @mention forwarding made it redundant and the priority-chain semantics were confusing for agents.

  3. CURRENT: Single-respondent routing with [LISTENING] + @mention handoff. Only ONE agent responds per user message. If it @mentions another agent, that agent gets forwarded the message automatically (same SSE turn). [LISTENING] claims the user's next message. No [PASS].

WHY this priority ordering:

@mention > [LISTENING] agent > commander (first msg) > captain (default)

- @mention: Explicit user intent always wins.
- [LISTENING]: Agent asked a question and is awaiting the answer. Routing
  elsewhere would break conversational flow.
- Commander first on first message: The commander introduces the project
  and hands off to the captain via @mention.
- Captain as default: After handoff, the captain owns execution. All
  non-directed messages go to the captain.

WHY inline role instructions:

Previously, agents were briefed in separate sessions during provisioning.
The problem: briefing instructions didn't carry over to the project chat
session. Inline injection on first message ensures agents always have their
instructions when the conversation starts. Templates live in briefings/.

WHY context injection:

Instead of cross-agent sync (sending the full conversation to every agent
after each turn), we prepend the last 20 messages as labeled context when
addressing an agent. This is simpler, cheaper (no hidden LLM calls), and
more reliable (no fire-and-forget goroutines that silently fail).

WHY detached context:

Agent LLM calls can take 30-60 seconds. If the user closes the browser
tab, the HTTP request context cancels, which would abort the agent call
mid-response. The detached context (5-min timeout) ensures the agent
finishes and the response is persisted to chat storage even if the SSE
connection drops. The user sees the response on their next poll/refresh.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type BriefingContext

type BriefingContext struct {
	ProjectName string
	ProjectID   string
	Goal        string
	Description string
	CaptainName string
	AgentName   string // The name of the agent being briefed (so it can self-identify in API calls)
}

BriefingContext holds the template variables available to all briefing templates.

type ChatOrchestrator

type ChatOrchestrator struct {
	// contains filtered or unexported fields
}

ChatOrchestrator encapsulates the multi-agent orchestration logic for project chat and intake conversations, decoupled from HTTP concerns.

func (*ChatOrchestrator) RunProjectChat

func (o *ChatOrchestrator) RunProjectChat(ctx context.Context, proj *project.Project, message string, sse *SSEWriter) error

RunProjectChat executes the core project-chat loop: stores the user message, resolves participants, fans out to each agent sequentially, streams events via SSE, and syncs turn history across agents.

type CommanderInfo

type CommanderInfo struct {
	Name          string `json:"name"`
	DisplayName   string `json:"display_name"`
	Status        string `json:"status"`
	HierarchyRole string `json:"hierarchy_role"`
}

CommanderInfo represents the commander. Since Phase 5, the commander is Eyrie itself — a built-in LLM loop, not a separate agent instance. This struct is still used in the hierarchy response for UI display.

type DashboardMetrics

type DashboardMetrics struct {
	ActiveProjects int `json:"active_projects"`
	PausedProjects int `json:"paused_projects"`
	RunningAgents  int `json:"running_agents"`
	BusyAgents     int `json:"busy_agents"`
	StoppedAgents  int `json:"stopped_agents"`
	TotalInstances int `json:"total_instances"`
}

DashboardMetrics holds aggregate stats for the mission control dashboard.

type EventBus

type EventBus struct {
	// contains filtered or unexported fields
}

EventBus is a simple pub/sub hub for project events. API handlers publish events; SSE endpoints subscribe to receive them. Thread-safe.

func NewEventBus

func NewEventBus() *EventBus

NewEventBus creates an empty event bus.

func (*EventBus) Publish

func (eb *EventBus) Publish(event ProjectEvent)

Publish sends an event to all subscribers watching the given project. Non-blocking: if a subscriber's channel is full, the event is dropped for that subscriber (slow consumers don't block publishers).

func (*EventBus) Subscribe

func (eb *EventBus) Subscribe(projectID string) chan ProjectEvent

Subscribe creates a channel that receives events for the given project. The caller must call Unsubscribe when done to avoid leaks.

func (*EventBus) Unsubscribe

func (eb *EventBus) Unsubscribe(projectID string, ch chan ProjectEvent)

Unsubscribe removes a subscriber channel and closes it.

type HierarchyTree

type HierarchyTree struct {
	Commander *CommanderInfo `json:"commander,omitempty"`
	Projects  []ProjectTree  `json:"projects"`
}

HierarchyTree is the full tree response: commander → projects → agents.

type ProjectActivityEvent

type ProjectActivityEvent struct {
	adapter.ActivityEvent
	Agent     string `json:"agent"`
	AgentRole string `json:"agent_role,omitempty"`
}

ProjectActivityEvent is an ActivityEvent tagged with the agent that produced it.

type ProjectEvent

type ProjectEvent struct {
	Type      string    `json:"type"` // agent_created, agent_started, agent_stopped, agent_busy, progress_updated, goal_changed, message_sent, agent_removed
	ProjectID string    `json:"project_id"`
	Agent     string    `json:"agent,omitempty"`      // agent name involved
	AgentRole string    `json:"agent_role,omitempty"` // commander, captain, talon
	Detail    string    `json:"detail,omitempty"`     // human-readable description
	Data      any       `json:"data,omitempty"`       // arbitrary payload (instance info, etc.)
	Timestamp time.Time `json:"timestamp"`
}

ProjectEvent represents a structural change in a project that the UI should reflect in real-time. Published by API handlers when agents or users make changes; consumed by SSE subscribers on the project workspace.

type ProjectTree

type ProjectTree struct {
	Project project.Project     `json:"project"`
	Captain *instance.Instance  `json:"captain,omitempty"`
	Talons  []instance.Instance `json:"talons"`
}

type SSEWriter

type SSEWriter struct {
	// contains filtered or unexported fields
}

SSEWriter wraps an http.ResponseWriter for Server-Sent Events. It handles JSON marshaling, framing, and flushing in a single call.

func NewDiscardSSEWriter

func NewDiscardSSEWriter() *SSEWriter

NewDiscardSSEWriter returns an SSEWriter that discards all output. Used when the orchestrator must run without a client connection — for example, when the commander sends a message into a project chat via its send_to_project tool. The captain still needs to be invoked, but there is no SSE consumer for its stream.

func NewSSEWriter

func NewSSEWriter(w http.ResponseWriter) (*SSEWriter, error)

NewSSEWriter validates flushing support, sets SSE headers, and returns a writer. The flusher check happens before setting headers so callers can safely fall back to writeJSON on error without stale SSE headers in the response.

func (*SSEWriter) Sent

func (s *SSEWriter) Sent() bool

Sent returns true if at least one event has been flushed.

func (*SSEWriter) WriteDone

func (s *SSEWriter) WriteDone() error

WriteDone sends a {"type":"done"} event.

func (*SSEWriter) WriteError

func (s *SSEWriter) WriteError(msg string) error

WriteError sends a {"type":"error","error":"..."} event.

func (*SSEWriter) WriteEvent

func (s *SSEWriter) WriteEvent(v any) error

WriteEvent marshals v as JSON and writes it as an SSE data frame.

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server is the Eyrie web dashboard backend.

func New

func New(cfg config.Config) (*Server, error)

func (*Server) Shutdown

func (s *Server) Shutdown(ctx context.Context) error

func (*Server) Start

func (s *Server) Start() error

type SessionDestroyer

type SessionDestroyer interface {
	DestroySession(ctx context.Context, sessionKey string) error
}

SessionDestroyer is optionally implemented by adapters that support fully removing a session (transcript + registry entry).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL