Documentation
¶
Index ¶
- func AppendParticipant(path, userID string) (added bool, err error)
- func AppendTranscript(path string, e TranscriptEntry) error
- func ParticipantsPath(dir, name string) string
- func ReadParticipants(path string) []string
- func ReadTranscriptLast(path string) string
- func RemoveParticipantJournal(path string) error
- func RemoveTranscript(path string) error
- func TranscriptPath(dir, name string) string
- type HomeRef
- type Session
- type State
- func (s *State) AddSession(sess Session) error
- func (s *State) ApplyDefaults(home *HomeRef, workspace, source string)
- func (s *State) FindSession(name string) (Session, bool)
- func (s *State) QualifiedName(name string) string
- func (s *State) RemoveSession(name string) error
- func (s *State) Save() error
- func (s *State) SetArchived(name string, archived bool) error
- func (s *State) SetBackendTarget(name, vendor, cmd, modelID string) bool
- func (s *State) SetBudget(name string, costCap float64, tokenCap uint64, cohortCostCap float64, ...) error
- func (s *State) SetHome(h HomeRef) error
- func (s *State) SetInstanceID(id string) error
- func (s *State) SetPausedReason(name, reason string) error
- func (s *State) SetResumeToken(name, token string) error
- func (s *State) SetSource(path string) error
- func (s *State) SetStatusMessageID(id string) error
- func (s *State) SetWorkspace(path string) error
- func (s *State) SnapshotSessions() []Session
- func (s *State) SourceDir() string
- func (s *State) WorkspaceRoot() string
- type TranscriptEntry
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func AppendParticipant ¶
AppendParticipant records userID in the append-only journal at path (one id per line). It is idempotent: if userID is already present, the file is left untouched and added is false. A missing parent directory is created.
Append uses O_APPEND so concurrent appenders (the bridge child) never race with the daemon's reads; the daemon only reads this file, never writes it.
func AppendTranscript ¶
func AppendTranscript(path string, e TranscriptEntry) error
AppendTranscript appends one JSON-line entry. Best-effort: O_APPEND so the daemon's single writer never races a read; a missing parent is created. When the file crosses transcriptMaxBytes it is compacted to its newest entries, so scrollback stays cheap and the transcript is bounded.
func ParticipantsPath ¶
ParticipantsPath returns the journal path for session name under dir (dir/participants/<name>.log). Both the supervisor (which tells the bridge where to append) and the handler (which reads it) call this so they agree.
func ReadParticipants ¶
ReadParticipants returns the de-duplicated user ids in the journal at path, in first-seen order. A missing file yields an empty slice (no error: the journal is best-effort observability).
func ReadTranscriptLast ¶
ReadTranscriptLast returns the timestamp of the last recorded entry, reading only the file's tail so a hot caller (the session list, polled ~1/s) never scans a long transcript. Empty only when the file is missing/empty. When the newest entry is larger than the tail window it falls back to a bounded full read (rare), so a timestamp is never lost. Kept separate from ReadTranscript, whose callers need the entries themselves.
func RemoveParticipantJournal ¶
RemoveParticipantJournal deletes the journal at path. A missing file is not an error (called on session close to avoid leaking participants/*.log).
func RemoveTranscript ¶
RemoveTranscript deletes the transcript at path. A missing file is not an error (called on real session removal to avoid leaking transcripts/*.jsonl).
func TranscriptPath ¶
TranscriptPath returns the transcript path for session name under dir (dir/transcripts/<name>.jsonl), beside participants/<name>.log.
Types ¶
type Session ¶
type Session struct {
ID string `json:"id,omitempty"` // stable logical id, decoupled from Name and ChannelID
// Incarnation is an opaque identity for this specific persisted session
// object. Closing and recreating the same Name generates a different value.
Incarnation string `json:"incarnation,omitempty"`
Name string `json:"name"`
ChannelID string `json:"channelID"`
Type string `json:"type"` // "text" | "forum"
Cmd string `json:"cmd"`
Backend string `json:"backend,omitempty"` // bridge backend ("" or "stream" = stream-json default; "oneshot" = per-message cmd)
Vendor string `json:"vendor,omitempty"` // agent backend vendor ("claude", "codex", "cursor")
// ModelID is the catalog identifier of the chosen model. Unlike Cmd, which is
// an opaque invocation string, it lets a resume look up the model's ROUTE —
// so we know whether gateway credentials need to be re-injected. Empty for
// sessions created before the catalog existed.
ModelID string `json:"modelId,omitempty"`
Worktree string `json:"worktree,omitempty"` // abs path; empty for a shared session
Dir string `json:"dir,omitempty"` // bridge working dir; empty = inherit launcher cwd (pwd fallback)
Project string `json:"project,omitempty"` // workspace sub-dir the session started from
Agent string `json:"agent,omitempty"` // durable agent this session was provisioned from ("" = none)
// ResumeToken is the backend's opaque resume id, folded in from each turn's
// reply so a restart can resume the conversation with --resume. Empty =
// start fresh.
ResumeToken string `json:"resumeToken,omitempty"`
// Archived marks a session closed-but-kept (session archive): its row,
// transcript and ResumeToken are retained so /resume can revive it, but the
// boot loop does not auto-supervise it. Absent/false = live as today.
Archived bool `json:"archived,omitempty"`
// Owned marks a channel this session created, and may therefore tidy away
// when it closes. A session started in a conversation that already existed
// binds to it instead, and that channel belongs to the people who were
// already talking in it — closing the session must leave it alone. Absent on
// a row written before this field existed, which is the safe direction: the
// worst outcome is a channel left behind.
Owned bool `json:"owned,omitempty"`
// Learning config (P1 write side, opt-in). Extractor names a registered
// curation extractor; empty keeps the plain Curator (no learning). Journal
// is the call-journal path Consolidate reads (worktree-relative is fine).
// ConsolidateEvery runs Consolidate every N turns (0 = manual only).
Extractor string `json:"extractor,omitempty"`
Journal string `json:"journal,omitempty"`
ConsolidateEvery int `json:"consolidateEvery,omitempty"`
// Gateways binds the session to a set of gateway kinds (e.g. "chat",
// "terminal"). Empty means "legacy": a pre-multi-gateway session whose
// binding the host resolves from the gateways actually built (see IsLegacy).
Gateways []string `json:"gateways,omitempty"`
Participants []string `json:"participants,omitempty"` // observed authors (cache; journal is source of truth)
// Parent names the lead session that delegated this one (result-back P3).
// Empty = no parent. The coordinator reads it to find the delivery target
// of this session's completion report (Report).
Parent string `json:"parent,omitempty"`
// Budget caps (0 = uncapped), persisted from contracts.CreateSession.
CostCap float64 `json:"cost_cap,omitempty"`
TokenCap uint64 `json:"token_cap,omitempty"`
CohortCostCap float64 `json:"cohort_cost_cap,omitempty"`
CohortTokenCap uint64 `json:"cohort_token_cap,omitempty"`
// PausedReason is non-empty when the session was halted by a budget cap:
// "cost" | "tokens" | "cohort_cost" | "cohort_tokens". Cleared on resume.
PausedReason string `json:"paused_reason,omitempty"`
}
Session is one bridged channel/post supervised by the daemon.
func (Session) BoundGateways ¶
BoundGateways returns the explicit gateway kinds this session is bound to, or nil when it is legacy (no stored set). The state package stays platform-blind: it never names a concrete gateway. A legacy session's effective binding is resolved by the host against the gateways actually built (see IsLegacy).
func (Session) IsLegacy ¶
IsLegacy reports whether this session predates explicit gateway binding: it carries a channel but no stored gateway set. The host binds such a session to the primary (non-terminal) gateways present, reproducing the original single-gateway behavior without the core naming that gateway.
type State ¶
type State struct {
Home HomeRef `json:"home"`
Repo string `json:"repo,omitempty"` // legacy single-repo root; defaults to daemon cwd
Workspace string `json:"workspace,omitempty"` // abs path to the workspace root; preferred over Repo
Source string `json:"source,omitempty"` // abs path to the herrscher source checkout (for /service update)
Sessions []Session `json:"sessions"`
StatusMessageID string `json:"statusMessageID,omitempty"` // cached id of the status embed
InstanceID string `json:"instanceID,omitempty"` // per-daemon namespace for global resources; "" = legacy
// contains filtered or unexported fields
}
State is the daemon's persisted configuration. All access is mutex-guarded.
func (*State) AddSession ¶
AddSession adds a session, erroring if the name is taken, and persists.
func (*State) ApplyDefaults ¶
ApplyDefaults seeds declarative config.json values into the in-memory state for any field not already set, WITHOUT persisting. This keeps config.json the source of truth for unset fields while a live /set (which persists to state.json) always wins: persisted state > config > empty. Because it never writes, removing a value from config.json takes effect on the next restart.
func (*State) FindSession ¶
FindSession returns the session with name (and whether it exists).
func (*State) QualifiedName ¶
QualifiedName maps a logical session name to the name used on global resources (channel title): "<InstanceID>__<name>". In legacy mode (empty InstanceID) it returns the bare logical name, preserving pre-namespacing behavior.
func (*State) RemoveSession ¶
RemoveSession drops the session named name and persists.
func (*State) SetArchived ¶
SetArchived sets a session's archived flag and persists only on change. An unknown name is a no-op (best-effort, mirrors SetResumeToken).
func (*State) SetBackendTarget ¶
SetBackendTarget re-points a live session's backend: it rewrites Vendor and the opaque Cmd string (which carries model+effort) and clears the resume token, which is backend-specific and meaningless once the vendor/model changes. The caller restarts the supervised child so the new values take effect. Returns false when no session matches name.
func (*State) SetBudget ¶
func (s *State) SetBudget(name string, costCap float64, tokenCap uint64, cohortCostCap float64, cohortTokenCap uint64, pausedReason string) error
SetBudget persists the budget caps and paused reason for the named session in one write. Mirrors SetResumeToken/SetPausedReason's locking + persistence. Used by session set-budget so raising a cap (which clears PausedReason) survives restart. A missing session is a no-op.
func (*State) SetInstanceID ¶
SetInstanceID records the per-daemon instance id and persists. The id is meant to be frozen after first resolution; callers enforce that invariant.
func (*State) SetPausedReason ¶
SetPausedReason records why a session halted on a budget cap ("" clears it, resuming the session). Mirrors SetResumeToken's locking + persistence.
func (*State) SetResumeToken ¶
SetResumeToken records the backend resume token for the named session, persisting only when it changes. Turns report the same id, so this avoids rewriting state.json every turn. A missing session or an unchanged token is a no-op.
func (*State) SetStatusMessageID ¶
SetStatusMessageID caches the status embed's message id and persists.
func (*State) SetWorkspace ¶
SetWorkspace records the workspace root and persists.
func (*State) SnapshotSessions ¶
SnapshotSessions returns a copy of the current sessions.
func (*State) WorkspaceRoot ¶
WorkspaceRoot returns the configured workspace, else the legacy Repo, else "".
type TranscriptEntry ¶
type TranscriptEntry struct {
Ts string `json:"ts"`
Role string `json:"role"` // "user" | "assistant"
Text string `json:"text"`
Cost float64 `json:"cost,omitempty"`
Kind string `json:"kind,omitempty"` // reserved (tool calls)
// Per-turn usage, present on assistant entries. TokensOut is the turn's
// output tokens; TokensIn the input; CacheRead/CacheCreate the prompt-cache
// read/creation tokens; DurMs the turn's wall-clock duration in ms. All
// omitempty so user turns and pre-usage transcripts stay compact.
TokensIn int `json:"tokens_in,omitempty"`
TokensOut int `json:"tokens_out,omitempty"`
CacheRead int `json:"cache_read,omitempty"`
CacheCreate int `json:"cache_create,omitempty"`
DurMs int `json:"dur_ms,omitempty"`
}
TranscriptEntry is one recorded turn side. Kept separate from the learning call-journal: this is the human-visible conversation, replayed as scrollback.
func ReadTranscript ¶
func ReadTranscript(path string, limit int) []TranscriptEntry
ReadTranscript returns entries in file order; when limit > 0, only the last limit. A missing file yields nil (best-effort observability, never an error). A bufio.Reader (not a Scanner) reads lines, so an oversized entry is parsed in full rather than silently dropped at a fixed token cap.