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:
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.
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.
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 (*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 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) WriteError ¶
WriteError sends a {"type":"error","error":"..."} event.
func (*SSEWriter) WriteEvent ¶
WriteEvent marshals v as JSON and writes it as an SSE data frame.