Help us shape the future of Go! Take the 2026 Go Developer Survey

teamrouter

package
v0.28.0 Latest Latest
Warning

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

Go to latest
Published: Oct 22, 2026 License: MIT Imports: 22 Imported by: 0

Documentation

Overview

Package teamrouter is the Go port of the Orca attention router (hadron-server scripts/orca-attention-router, TypeScript) — cli#829 (T7).

It nudges the Orca-hosted workers on an explicit roster when they are mentioned, by typing one short line into that worker's Orca terminal. It never reads, relays or posts a chat message, never marks anything read, and never hands its token to a worker.

The acceptance spec is the TS suite, ported 1:1 into this package's tests (at server main f0ab31a0 — #1845's per-roster terminal pin). The TS router shelled out to `hadron api`; this port speaks GraphQL natively through the Server seam and keeps exec-ing only the `orca` CLI.

Why the gates exist (finding hrn:node:hadronmemory.com:hadron-server:findings:orca-terminal-send-busy-agent-claude-queues-codex-steers):

  • A prompt typed into a BUSY Codex agent steers its turn and drops the rest of its task. So a nudge is sent only on proof the terminal is idle; an UNTRACKED terminal can never satisfy tui-idle, so its timeout is unknown, and the rendered screen decides (#7398).
  • tui-idle reports idle while a HUMAN is typing, so the screen must also prove the prompt empty (#5473), with a stale Orca draft refuted by a Claude screen (#6736).
  • Tab titles are not identity: each roster row names its worktree, and a CLI worker binding in that worktree must agree.
  • The token is adopted only when every roster worker with new attention was nudged; a ledger keyed on it stops the retry re-nudging the others.
  • Counts are recomputed tokenless immediately before the send (#1616), and both gates are re-proved after that round trip, the idle one fail-closed.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func BreakStaleLock

func BreakStaleLock(lockDir, staleOwner string) (string, error)

BreakStaleLock is breakStaleLock for a lock judged stale by its owner record staleOwner: "retry" (broken, or already gone — take it) or "busy" (another run took it under the stale judgement — stand down). The rename protocol never fails outright, so err is always nil; it is in the signature for the callers that treat a lock break as fallible.

func CheckRosterEntry

func CheckRosterEntry(c *Config, entry RosterEntry) error

CheckRosterEntry performs the same identity checks used under the writer lock.

func DefaultOrcaCli

func DefaultOrcaCli() string

DefaultOrcaCli is Orca's bundled CLI on macOS; elsewhere `orca` on PATH.

func DescribeOutcome

func DescribeOutcome(o *Outcome) string

DescribeOutcome is the one-line summary of a poll; never a token or a message body.

func LivenessNudgeText

func LivenessNudgeText(cursor, head int) string

LivenessNudgeText is the one line a FLAGGED worker receives from the liveness rule (#1826). Its own literal, never NudgeText's: it makes NO mention-count claim, names its rule (Taylor #9748), names the two positions it is true for — both read fresh immediately before the send — and anchors an unfiltered catch-up from the watermark. "Mark read" appears exactly once.

func NudgeText

func NudgeText(a AttentionItem) string

NudgeText is the one line a worker receives: fresh counts with the seq range they cover — never a message body, never a token. The seqs let the recipient check the nudge against its own read cursor (#1616).

Two anchors (#1811): at or below the large-gap span, the #1805 watermark anchor (start at the first unread, walk forward); above it, a BACKWARD walk of the mentions filter from the head THIS NUDGE NAMED (frozen as every page's --before ceiling, #1827) down to the first unread, then a mark at the highest seq actually delivered — never above the named head.

func PromptLineIsEmpty

func PromptLineIsEmpty(lines []string, family string) bool

PromptLineIsEmpty is true only on positive proof the agent's prompt holds no typed text (#5473: tui-idle reports idle while a HUMAN is typing). Both agents draw their real chrome at column 0 and indent anything typed onto a continuation line, so only column-0 lines count as chrome.

  • claude: the box between the last two column-0 rules is a bare `❯` plus blank lines, and the ONE nonblank line below it — the terminal's last content — ends with the empty-input hint (typed whitespace renders as nothing; trailing content means the box is history, #1823).
  • codex: the last column-0 `›` line shows the idle placeholder and every line below it is footer.

Anything unrecognized — no box, no marker, an unknown family — is NOT empty.

func PutRosterEntry

func PutRosterEntry(path string, entry RosterEntry) (string, error)

PutRosterEntry preserves unknown config fields and refuses a reassignment. The caller holds its onboarding lock for the complete operation sequence; the router writer lock additionally excludes an in-flight delivery here.

func RecordReload

func RecordReload(c *Config) error

func ResolveTerminal

func ResolveTerminal(entry RosterEntry, terminals []Terminal, c *Config, orca OrcaRunner) (handle, family, how string, err error)

ResolveTerminal maps a roster row to the ONE agent terminal in its worktree: (handle, family, how), how being "pin" (#1845), "identity", or "screen" (the #1823 identity-flap fallback). An unresolvable row returns how "none" and an error whose text is the hold reason.

A pin resolves first but is trusted only within the row's worktree, connected and writable. Identity is the terminal whose agentIdentity is claude/codex. Only when identity resolves NOTHING does the fallback read screens: ONE connected terminal whose screen positively shows a family. Ambiguity, an unclassifiable screen or a plain shell holds — never a guess.

func ScreenAgentFamily

func ScreenAgentFamily(sc *Screen) string

ScreenAgentFamily is the agent family a rendered screen POSITIVELY shows, or "" — the identity-flap fallback's evidence (#1823). Only the agents' own chrome counts: anchored banner lines, column-0 `✻`/`⏺`/`permissions:`, Claude's pair of rules, and Codex's `›` only WITH content after it (a bare glyph is a shell theme as easily as a composer). Both families on one screen reads "": hold, never guess.

Types

type AttentionItem

type AttentionItem struct {
	Worker         string `json:"worker"`
	WorkerName     string `json:"workerName"`
	Channel        string `json:"channel"`
	Unread         int    `json:"unread"`
	UnreadMentions int    `json:"unreadMentions"`
	FirstUnreadSeq *int   `json:"firstUnreadSeq"`
	// LastRelevantSeq is the highest seq that counts for this worker: for a
	// mention-only row, its latest mention. 0 = none.
	LastRelevantSeq int `json:"lastRelevantSeq"`
	LastSeq         int `json:"lastSeq"`
}

AttentionItem is one Worker/Channel pair from the paged attention scan (TeamAttentionPageItem). LastRelevantSeq 0 stands for the server's null: seqs start at 1, so no real mention frontier is 0.

type AttentionPage

type AttentionPage struct {
	ScanComplete   bool            `json:"scanComplete"`
	NextPage       string          `json:"nextPage"`
	AdoptableSince string          `json:"adoptableSince"`
	Items          []AttentionItem `json:"items"`
}

AttentionPage is one page of the attention scan.

type AttentionRequest

type AttentionRequest struct {
	Since string
	Page  string
}

AttentionRequest addresses the paged surface: Since passes the adopted token, Page walks pages. Both empty is a tokenless scan — the first poll, and the fresh re-read before each send (#1616).

type Config

type Config struct {
	// Fingerprint identifies the exact bytes loaded, without exposing state tokens.
	Fingerprint string        `json:"-"`
	App         string        `json:"app"`
	Channel     string        `json:"channel"`
	OrcaCli     string        `json:"orcaCli"`
	StateDir    string        `json:"stateDir"`
	Roster      []RosterEntry `json:"roster"`
	// BusyWaitMS is the idle gate's tui-idle wait (default 60 s).
	BusyWaitMS int `json:"busyWaitMs"`
	// SubmitWaitS is `orca terminal send --wait-submit` (default 15 s).
	SubmitWaitS int `json:"submitWaitS"`
	// CoordinatorWatchMin is the #1826 liveness rule's N: minutes of
	// continuous idle a flagged worker must exceed (strictly) before its
	// liveness nudge may fire. Positive and finite; default 20.
	CoordinatorWatchMin float64 `json:"coordinatorWatchMinutes"`
}

Config is the router's host-local configuration. Its JSON keys are router.ts's, so an existing ~/.config/hadron-orca-attention-router config loads unchanged (cli#829: config migrates). `hadronCli` is accepted and ignored: this port speaks GraphQL natively.

func LoadConfig

func LoadConfig(path string) (*Config, error)

LoadConfig reads and validates the router config. Validation is strict where a permissive read once passed a typo silently: roster entries need worker, name and worktree; the watch flags are JSON booleans; a pin is a non-empty string; no worker, pin or worktree appears twice; numbers are JSON numbers.

type CursorResult

type CursorResult struct {
	LastSeen  *int
	FieldErrs []string
}

CursorResult is a worker's channelReadState (#1826) — the liveness source attention cannot see. FieldErrs carries GraphQL errors that arrived WITH data: an integer alongside errors is not a verdict (#1829 P1-1).

type GraphQLServer

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

GraphQLServer is the production Server: the router's own typed reads (internal/api/queries/teamrouter.graphql) on the operator's login. Every method is a read; none takes or returns the adopted token except AttentionPage, which passes it to the server and never logs it.

func NewGraphQLServer

func NewGraphQLServer(client graphql.Client, app, channel string) *GraphQLServer

NewGraphQLServer reads app's attention for the team Channel channel (an id).

func (*GraphQLServer) AttentionPage

func (s *GraphQLServer) AttentionPage(ctx context.Context, req AttentionRequest) (AttentionPage, error)

AttentionPage reads one page of the paged attention scan.

func (*GraphQLServer) ChannelHead

func (s *GraphQLServer) ChannelHead(ctx context.Context, channelRef string) (int, error)

ChannelHead is the Channel's watermark.

func (*GraphQLServer) ReadCursor

func (s *GraphQLServer) ReadCursor(ctx context.Context, workerID string) (CursorResult, error)

ReadCursor is the worker's read cursor on the team Channel. Any GraphQL error — including errors that arrive WITH data — is an error here, so the router reads it as unknown (#1829 P1-1).

func (*GraphQLServer) RegisterRow

func (s *GraphQLServer) RegisterRow(ctx context.Context, workerID string) (RegisterRow, error)

RegisterRow is the worker's winning register row on the team Channel, or the zero row (which never counts as mention-only) when it has none there.

type Ledger

type Ledger struct {
	Base   string                 `json:"base"`
	Nudged map[string]LedgerNudge `json:"nudged"`
	// Reported is each held worker's last LOGGED reason (by name), so a hold
	// is logged once per token, not once per poll.
	Reported map[string]string `json:"reported"`
	// DraftNotes: stale-draft discards already logged under this token.
	DraftNotes map[string]string `json:"draftNotes"`
	// HeldSince: epoch ms a worker's current hold began (cleared on delivery).
	HeldSince map[string]int64 `json:"heldSince"`
	// HeldLoggedAt: epoch ms the last long-hold line was logged per worker.
	HeldLoggedAt map[string]int64 `json:"heldLoggedAt"`
}

Ledger is the durable delivery record for the adopted token: which workers were already nudged for the attention reported since that token, so a poll that must hold the token does not nudge them again.

type LedgerNudge

type LedgerNudge struct {
	// RelevantSeq is the worker's last RELEVANT (mention) seq the nudge was about.
	RelevantSeq int    `json:"relevantSeq"`
	At          int64  `json:"at"`
	Stages      string `json:"stages"`
}

LedgerNudge records a delivered (or dropped-as-read) nudge's mention frontier.

type LivenessRecord

type LivenessRecord struct {
	// IdleSince is the epoch ms the continuous idle episode began; nil while reset.
	IdleSince *int64 `json:"idleSince,omitempty"`
	// Handle is the terminal the clock is measured on; a change is a replaced
	// terminal, which resets the clock.
	Handle string `json:"handle,omitempty"`
	// CoveredFrontier is the channel head at the last DELIVERED liveness
	// nudge; re-eligibility needs head > this.
	CoveredFrontier *int `json:"coveredFrontier,omitempty"`
}

LivenessRecord is one flagged worker's liveness clock (#1826). Deliberately a SEPARATE file from the ledger: adoption deletes the ledger, but an idle episode must survive token adoption and router restarts untouched.

type LivenessStatus

type LivenessStatus struct {
	IdleMinutes     *int64 `json:"idleMinutes"`
	CoveredFrontier *int   `json:"coveredFrontier"`
}

LivenessStatus is one flagged worker's watch state.

type OrcaRunner

type OrcaRunner func(ctx context.Context, argv []string) RunResult

OrcaRunner execs the `orca` CLI. argv starts at the verb ("terminal", "list", …): the binary is the runner's business, so a fake never sees it.

func ExecOrca

func ExecOrca(bin string) OrcaRunner

ExecOrca is the production runner: it execs bin with argv and never errors on a non-zero exit — the envelope (or its absence) is the verdict.

type Outcome

type Outcome struct {
	Nudged []string `json:"nudged"`
	// Dropped: workers that read their chat themselves while the nudge waited (#1616).
	Dropped []string          `json:"dropped"`
	Covered []string          `json:"covered"`
	Held    map[string]string `json:"held"`
	Adopted bool              `json:"adopted"`
	// Liveness: flagged workers the liveness rule woke this poll (#1826).
	Liveness []string `json:"liveness"`
	// LivenessDropped: flagged workers that caught up while a liveness nudge
	// waited out its gates.
	LivenessDropped []string `json:"livenessDropped"`
}

Outcome is one poll's result. Held maps a worker's NAME to why it was not nudged; any hold keeps the token unadopted.

func PollOnce

func PollOnce(ctx context.Context, c *Config, s Server, orca OrcaRunner, log func(string), now func() int64) (*Outcome, error)

PollOnce polls attention, nudges each idle roster worker with new mentions, adopts the token only if all were — then runs the #1826 liveness pass over the flagged roster. now is the clock (epoch ms), injectable for the strict >N boundary; nil means the wall clock. A *RouterError leaves the adopted token unchanged.

type RegisterRow

type RegisterRow struct {
	NamedBy     string `json:"namedBy"`
	ChannelID   string `json:"channel"`
	MentionOnly bool   `json:"mentionOnly"`
	OwnerType   string `json:"ownerType,omitempty"`
}

RegisterRow is the WINNING attendeeRegister row for the team Channel, as the attention API resolves it (App over organization; Worker over Agent over everyone).

type ReloadReceipt

type ReloadReceipt struct {
	Fingerprint string    `json:"fingerprint"`
	At          time.Time `json:"at"`
}

ReloadReceipt confirms a loop loaded these exact config bytes between polls.

func ReadReload

func ReadReload(c *Config) (ReloadReceipt, error)

type RosterEntry

type RosterEntry struct {
	// Worker is the Worker's permanent id.
	Worker string `json:"worker"`
	Name   string `json:"name"`
	// Worktree is the Orca worktree this worker is driven in.
	Worktree string `json:"worktree"`
	// Terminal (#1845) pins the handle of this worker's MAIN terminal. A pin
	// resolves FIRST, but only ever to a terminal IN this row's worktree,
	// connected and writable — a stale pin falls through with its reason.
	Terminal string `json:"terminal,omitempty"`
	// Coordinator / Lifecycle (#1826) opt the row into the liveness rule:
	// a watch a passive wait can strand.
	Coordinator bool `json:"coordinator,omitempty"`
	Lifecycle   bool `json:"lifecycle,omitempty"`
}

RosterEntry names one worker this router may wake.

type RouterError

type RouterError struct {
	Reason string
}

RouterError is a failure that leaves the adopted token untouched: the poll changes nothing it has not already persisted, and the next poll retries.

func (*RouterError) Error

func (e *RouterError) Error() string

type RunResult

type RunResult struct {
	Code   int
	Stdout string
	Stderr string
}

RunResult is one exec'd `orca` invocation's outcome.

type Screen

type Screen struct {
	Source string
	Tail   []string
	Draft  string
}

Screen is one `orca terminal read --screen` result: the rendered tail, the optional draft field, and the source (only "screen" is a full frame — the default stream read returns repaint fragments, useless for shape checks).

type Server

type Server interface {
	AttentionPage(ctx context.Context, req AttentionRequest) (AttentionPage, error)
	RegisterRow(ctx context.Context, workerID string) (RegisterRow, error)
	ReadCursor(ctx context.Context, workerID string) (CursorResult, error)
	ChannelHead(ctx context.Context, channelRef string) (int, error)
}

Server is the typed GraphQL seam (native, never `hadron api`). Every method is a read: the router changes no server state.

type State

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

State is the token/ledger/liveness persistence (dir 0700, files 0600).

func NewState

func NewState(dir string) *State

NewState addresses the state directory dir; nothing is touched until used.

func (*State) Ledger

func (s *State) Ledger() Ledger

Ledger is the ledger for the current token; a stale (other token), unreadable or malformed one reads as empty.

func (*State) Liveness

func (s *State) Liveness() map[string]LivenessRecord

Liveness is the liveness clocks, SANITIZED on read so corruption can never fail open (#1829 local P2-2): a non-object file reads as fresh, a non-object record as reset, and only well-typed fields survive — a non-negative integer idleSince, a non-empty handle, an integer frontier within the seq ceiling. Corruption costs at most one repeated nudge, never a suppressed worker.

func (*State) SaveLedger

func (s *State) SaveLedger(l Ledger) error

SaveLedger persists l atomically (0600).

func (*State) Token

func (s *State) Token() string

Token is the adopted token, or "" when none is adopted or it is unreadable.

type StatusReport

type StatusReport struct {
	StateDir                string                     `json:"stateDir"`
	TokenAdopted            bool                       `json:"tokenAdopted"`
	NudgedUnderCurrentToken []string                   `json:"nudgedUnderCurrentToken"`
	Held                    map[string]string          `json:"held"`
	Locked                  bool                       `json:"locked"`
	Roster                  []string                   `json:"roster"`
	Liveness                map[string]*LivenessStatus `json:"liveness"`
}

StatusReport is `team router status`: local state only — token presence, ledger contents, the lock, the liveness clocks. No network, and never the token itself.

func Status

func Status(c *Config) (*StatusReport, error)

Status reads the router's local state for c.

type Terminal

type Terminal struct {
	Handle        string  `json:"handle"`
	WorktreePath  string  `json:"worktreePath"`
	AgentIdentity *string `json:"agentIdentity"`
	Connected     bool    `json:"connected"`
	Writable      bool    `json:"writable"`
	LastOutputAt  *int64  `json:"lastOutputAt"` // nil = untracked (#7398)
}

Terminal is one `orca terminal list` row.

func (*Terminal) UnmarshalJSON

func (t *Terminal) UnmarshalJSON(b []byte) error

UnmarshalJSON reads Orca's row with router.ts's defaults: connected and writable count unless explicitly false, and ANY non-null lastOutputAt is tracking (only null means it was lost).

Jump to

Keyboard shortcuts

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