sessions

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Aug 14, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

Documentation

Overview

Package sessions owns the daemon's tab-level session registry: the ordered list of live sessions, per-tab metadata (name, agent kind, cwd), the active pointer, and the maxSessions guard. It wraps a *pty.Manager — the PTY process + ring buffer live in package pty; this package layers the REST/UX-facing metadata on top.

Layering:

sessions.Manager (tab metadata, ordering, active, maxSessions)
  └── pty.Manager (PTY process + ring, ID→Session map)
        └── pty.Session (one PTY: readLoop, ring, fan-out, C1 stubber)

The WS handler (/ws/{id}) takes the underlying *pty.Manager directly — streaming bytes doesn't need tab metadata. The REST handlers (/api/sessions/*) take *sessions.Manager for create/list/select/destroy.

In-memory create + list + active pointer + maxSessions + RMOTE_SESSION_ID env injection. Step 1b adds state.json persistence + restore; Step 1c adds per-session routes (select/leave/rename/destroy/swap/exec/scrollback).

Index

Constants

View Source
const AgentExitSentinelShell = " ; printf '\\e]rmote-agent-exit\\a'"

AgentExitSentinelShell is the shell snippet appended to every agent launch line. Emits an OSC sequence (`ESC ] rmote-agent-exit BEL`) that iOS's client-side detector recognizes to clear activeAgent when the agent exits and returns to the shell. Codex/grok have no SessionEnd hook; claude's is unreliable — the sentinel is the universal exit signal. Matches Mac's NativePtySession.agentExitSentinelShell.

View Source
const MaxSessions = 30

MaxSessions caps the number of live sessions a daemon will host. 30 matches the previous implementation (ServerConstants.maxSessions). Each session holds a 2 MB ring + a PTY FD + subscriber chans, so the bound is as much about memory/fd hygiene as UX. A create that would exceed this returns ErrTooManySessions.

Variables

View Source
var ErrNoSuchSession = errors.New("sessions: no such session")

ErrNoSuchSession is returned by Select/Destroy/ListMeta lookups when the ID is not a live session. REST handlers map this to HTTP 404.

View Source
var ErrTooManySessions = errors.New("sessions: max session count reached")

ErrTooManySessions is returned by Create when the live session count is at MaxSessions. The REST handler maps this to HTTP 503.

Functions

func ResumeLaunchLine

func ResumeLaunchLine(agent AgentKind, agentSessionID, cwd string) (string, bool)

ResumeLaunchLine builds the shell line that relaunches agent against its own saved session id, appending the exit sentinel. Returns ok=false when the agent is not resumable (KindShell or unknown) or the id fails validation, so callers can skip silently. cwd is only used by grok (which takes --cwd); claude and codex launch in the PTY's cwd and ignore it.

The returned line is written into a PTY, not exec'd, which is why the id is validated here rather than trusted — state.json is user-writable and an injection-shaped id would run arbitrary shell.

func ShellQuote

func ShellQuote(s string) string

ShellQuote wraps a path in single quotes for safe interpolation into a shell command line. Handles paths with spaces/special chars. Matches the Mac's NativePtySession.shellQuote. A path containing a single quote itself is split into a concatenated pair of single-quoted strings (the standard shell idiom: 'foo' + \' + 'bar' for foo'bar).

func ValidAgentSessionID

func ValidAgentSessionID(id string) bool

ValidAgentSessionID reports whether id is safe to interpolate into an agent launch line. Alphanumerics, dash and underscore only — no shell metacharacters. The id comes from a user-writable state.json (restore) or an HTTP body (resume route), and is written into a PTY, so validation is mandatory at every call site.

Types

type AgentKind

type AgentKind string

AgentKind classifies a session by what runs inside it. "shell" is the default (user's $SHELL); the others are set when iOS creates a tab intending to run that agent (the daemon doesn't enforce it — the user can type any command — but the kind drives UI: icon, skills bar, detection rules). Matches the previous implementation's agent string set.

const (
	KindShell  AgentKind = "shell"
	KindClaude AgentKind = "claude"
	KindCodex  AgentKind = "codex"
	KindGrok   AgentKind = "grok"
)

type CreateRequest

type CreateRequest struct {
	ID            string
	Name          string
	NameIsCustom  bool
	Agent         AgentKind
	Command       string
	Args          []string
	Cwd           string
	Rows          uint16
	Cols          uint16
	Profile       string // agent-profile name tag (summon dedup)
	ProfileAgent  AgentKind
	ProfileModel  string
	ProfileEffort string
	WorktreeID    string
	BindingHealth string
}

CreateRequest is the spawn spec handed to Manager.Create. Name is optional (auto-derived from the shell basename if empty). Agent defaults to KindShell. Cwd must be absolute (the REST handler validates this before calling). Rows/Cols seed the PTY winsize; zero → 24×80 default in pty.Start.

ID is optional: when non-empty, the session is created with that exact ID (used by state.json restore so iOS reconnect finds the same tab IDs). When empty, a fresh UUIDv4 is minted.

type Manager

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

Manager owns the ordered live-session list + active pointer. It wraps a *pty.Manager for the actual PTY lifecycle. Goroutine-safe.

func NewManager

func NewManager(ptyMgr *pty.Manager, log *slog.Logger) *Manager

NewManager builds a sessions.Manager wrapping the given pty.Manager. The caller (server.New) constructs one pty.Manager and shares it between the WS handler (streaming) and this manager (REST metadata).

func (*Manager) ActiveID

func (m *Manager) ActiveID() string

ActiveID returns the currently-active session ID ("" if none).

func (*Manager) ClaimProfileBoot

func (m *Manager) ClaimProfileBoot(id string) bool

ClaimProfileBoot atomically reserves the right to boot a shell-hosted profile session. It suppresses duplicate launch lines while the first boot is still waiting for foreground-agent detection. A stale claim expires so a failed launch can be retried by a later summon.

func (*Manager) ClearAgentOnExit

func (m *Manager) ClearAgentOnExit(rmoteID, agent string)

ClearAgentOnExit is the authoritative, agent-agnostic "the agent quit" reset for a tab. Called from the SessionEnd hook branch (claude/codex/grok/ antigravity/pi all reach it) it clears the per-agent session-id binding, reverts the tab's agent label back to shell, drops the stale live summary, clears the conversation topic title (ModelTitle + naming lifecycle) so the tab's displayed name reverts to the shell-derived name (folder), and reverts the transient OSC title the agent pinned on launch.

The title is tied to the LIVE agent conversation: quitting drops it (the tab shows its shell name again), and resuming re-derives it from the transcript via the SessionStart → SetSessionTitle path. TitleSessionID is intentionally LEFT intact (not cleared here) so a later SetAgentSessionID can still tell a resume (same id) from a genuinely new conversation (different id) — only the displayed title + lifecycle reset, not the detection key.

The label revert + title clear fire only when the tab currently shows THIS agent, so a stale SessionEnd (e.g. an old pi session_end arriving after the user already launched claude in the same tab) cannot clobber the newer agent's label. The session-id binding for the named agent is cleared regardless. Generic over the agent id — no per-agent hardcoding.

func (*Manager) ClearAgentSessionID

func (m *Manager) ClearAgentSessionID(rmoteID, agent string)

ClearAgentSessionID clears the agent's session id, called on SessionEnd so the skills bar disappears once the agent quits. The PTY tab itself stays alive (the user is dropped back into their shell).

func (*Manager) Close

func (m *Manager) Close()

Close permanently stops debounced persistence and flushes one final state snapshot. Unlike SaveStateNow, it rejects schedulers racing with shutdown.

func (*Manager) ConsumeSummonReply

func (m *Manager) ConsumeSummonReply(id, summonerID string) bool

ConsumeSummonReply atomically claims the active summon reply binding. The Stop-hook auto-reply calls this immediately before delivery so an explicit peer reply can consume the same binding first.

func (*Manager) Create

func (m *Manager) Create(req CreateRequest) (*Meta, error)

Create spawns a new session and registers its metadata. Enforces MaxSessions. Injects RMOTE_SESSION_ID=<id> into the child env so hook events can be attributed back to this tab (the hook ingest to fan out per-session state). Returns the new session's Meta.

func (*Manager) Destroy

func (m *Manager) Destroy(id string) error

Destroy kills and removes a session by ID. Idempotent — a missing ID returns ErrNoSuchSession (so the REST handler can 404), but the underlying pty.Manager.Delete is a no-op on missing IDs. Reclaims the slot in the order list; if the destroyed session was active, the first remaining session becomes active (or none if the list is now empty).

func (*Manager) FindByProfile

func (m *Manager) FindByProfile(profile string) *Meta

FindByProfile returns the first live session tagged with the given profile name, or nil if none exists. Returns a copy to avoid encapsulation leaks.

func (*Manager) FindOrCreateByProfile

func (m *Manager) FindOrCreateByProfile(req CreateRequest) (*Meta, bool, error)

FindOrCreateByProfile atomically finds an existing profile-tagged session or creates a new one. The profileCreateMu serializes concurrent calls for the same profile, preventing duplicate sessions from racing past each other. Returns (meta, created=true) for a new session, (meta, created=false) for an existing one.

func (*Manager) ForegroundAgent

func (m *Manager) ForegroundAgent(id string) AgentKind

ForegroundAgent returns the currently detected foreground agent kind for a session, or KindShell if no agent is running (shell prompt visible). Used by the peer-messaging endpoint to gate delivery: direct PTY injection only when an agent is foreground, never into a bare shell (red-team C1).

func (*Manager) GetMeta

func (m *Manager) GetMeta(id string) (Meta, bool)

GetMeta returns the metadata for a single session by ID. Returns ok=false if the session isn't live. Used by git routes to resolve a session's cwd (the working directory git operates in).

func (*Manager) GetOrAssignShortName

func (m *Manager) GetOrAssignShortName(id, abbrev string) string

GetOrAssignShortName returns the peer-messaging alias for a session.

Profile-summoned sessions (meta.Profile set) use a stable, meaningful alias derived from the profile name (#ccworker, #cxsol) — the tab is "the ccworker tab" and peers address it by profile name, regardless of whether its agent is currently foreground or reverted to shell. Ad-hoc sessions use the agent abbreviation + counter (#cc1, #cx2); those update when the agent type changes (shell → codex → #cx1) and clear when the agent exits to shell.

func (*Manager) Input

func (m *Manager) Input(id string, p []byte) error

Input writes raw bytes to a session's PTY master, delivering them to the child process. Used by the resume routes (write `claude --resume <id>` to a shell tab) and the future exec route (POST /api/sessions/{id}/exec). Returns ErrNoSuchSession for an unknown ID, or pty.ErrSessionClosed if the child has exited. Input writes user input to the session's PTY through the input hook (if set). Used by REST API endpoints that forward user input (e.g., iOS chat submission). The hook intercepts peer-messaging patterns (#name message).

func (*Manager) LastReply

func (m *Manager) LastReply(id string) time.Time

LastReply returns the last-reply timestamp for id (zero if none).

func (*Manager) List

func (m *Manager) List() []*Meta

List returns the ordered slice of live-session metadata. The active session has Active=true. The returned slice is a snapshot; callers may mutate freely. Sessions that exited naturally (reaper removed them from pty.Manager) are filtered here so iOS doesn't render dead tabs.

The Title field is pulled fresh from the live pty.Session (the OSC 0/2 scanner updates it in real-time from shell output). A user-set custom name (Meta.Name) takes precedence over the captured title — the title is only used when the user hasn't named the tab explicitly.

func (*Manager) LoadFromState

func (m *Manager) LoadFromState() (int, error)

LoadFromState reads state.json + re-spawns each saved session as a fresh PTY with the saved ID + cwd + name. Returns the count restored. The caller (server.New) skips auto-create-default-session when this returns ≥1.

Saved agent IDs (claude/codex/grok) are restored into Meta so iOS's isActiveClaudeSession fallback works on the first reconnect. The live PTY starts as a plain shell, and for any tab whose saved agent has a matching per-agent session id, the agent is re-launched inside that shell PTY by writing ResumeLaunchLine(id) + "\n" — asynchronously, off the startup critical path, so a slow CLI boot never blocks the listener. Tabs whose agent session id is empty (the user quit the agent before restart, or the tab was always a shell) come back as plain shells. Set RMOTE_AGENT_RESTORE_AGENTS=0 to disable agent relaunch entirely.

func (*Manager) MarkReply

func (m *Manager) MarkReply(id, targetID string)

MarkReply stamps LastReplyAt and consumes the active summon binding when the message is addressed to that summoner. Messages to other peers still satisfy `wait --until replied` but do not suppress the expected summon result.

func (*Manager) PTYManager

func (m *Manager) PTYManager() *pty.Manager

PTYManager returns the underlying pty.Manager. The WS handler uses this to look up sessions for /ws/{id} streaming without going through the metadata layer.

func (*Manager) RawInput

func (m *Manager) RawInput(id string, p []byte) error

RawInput writes directly to the PTY WITHOUT the input hook. Used by the daemon for programmatic injection (peer messages, seeding, resume launch, uploads) so injected text starting with '#' is not re-intercepted.

func (*Manager) ReleaseProfileBoot

func (m *Manager) ReleaseProfileBoot(id string)

ReleaseProfileBoot clears the in-flight boot marker after foreground detection or an immediate PTY write failure.

func (*Manager) Rename

func (m *Manager) Rename(id, name string) error

Rename sets a custom display name on a session. Persists to state.json so the name survives daemon restart. Empty name is allowed (clears the custom name; callers may then re-derive from AutoTitle). Returns ErrNoSuchSession for an unknown ID.

func (*Manager) SaveStateNow

func (m *Manager) SaveStateNow()

SaveStateNow cancels any pending debounced save and writes state.json synchronously. Used by the daemon's SIGINT/SIGTERM handler so brm never drops the latest batch of session mutations to the 200 ms debounce window.

Lock order: take stateMu only to cancel the timer, release it BEFORE calling writeState. writeState takes m.mu (the meta lock) internally and never stateMu — nesting stateMu inside m.mu would invert the documented order at manager.go:352 ("stateMu never nests inside m.mu"). Safe to call concurrently with saveStateDebounced: a just-fired timer will already have released stateMu by the time we acquire it (Stop returns false, we proceed); a saveStateDebounced call arriving during writeState schedules a fresh timer that never fires because the process is exiting.

func (*Manager) Select

func (m *Manager) Select(id string) error

Select marks id as the active session. Returns ErrNoSuchSession if id is not a live session. The active pointer drives which tab iOS lands on after a session list refresh.

func (*Manager) SetAgentSessionID

func (m *Manager) SetAgentSessionID(rmoteID, agent, agentSessionID string)

SetAgentSessionID records the agent's own session id on the tab's meta, keyed generically by agent id in AgentSessionIDs (the single source of truth). Called when a SessionStart hook event arrives — the agent has just launched and reported its session id. Exposed via /api/sessions (derived from the map) so iOS's isActiveClaudeSession/isActiveTabCodex/isActiveTabGrok fallbacks work after force-quit-reopen, when the per-tab WS 0x08 frame hasn't arrived.

func (*Manager) SetCwd

func (m *Manager) SetCwd(rmoteID, cwd string)

SetCwd updates a tab's stored cwd to live, persisting it so daemon-restart auto-resume lands the agent in its actual project directory. Called by the server-layer foreground-agent callback (Server.handleFgAgentChange) right after resolveLiveCwd reads the PTY's live process cwd — claude/codex session ids are scoped to a project path (~/.claude/projects/<encoded-cwd>/…), so a stale creation-time cwd in state.json makes claude --resume <id> look under the wrong encoded path and fail to find the session.

Matches SetForegroundAgent's mutate-under-m.mu → saveStateDebounced + notifyChange shape. No-op when the tab is unknown, live is empty, or live matches the stored value. The debounce coalesces the concurrent SetForegroundAgent + SetCwd writes from the same fg-detection tick into a single disk write, so the cwd refresh adds no extra I/O on the hot path.

func (*Manager) SetForegroundAgent

func (m *Manager) SetForegroundAgent(rmoteID, agent string)

process (codex/grok/claude found among the shell's descendants), or reverts it to shell when `agent` is "" (the agent exited). Authoritative — overrides any prior label so switching agents in one tab is reflected, and so the tab reverts to its default chip/skills bar when the agent quits. Persists + notifies so iOS updates on the next session-list refresh.

func (*Manager) SetModelTitle

func (m *Manager) SetModelTitle(rmoteID, title string)

SetModelTitle stores a model-derived session title (Line 1) WITHOUT touching Name/NameIsCustom. Read precedence (List + session row) is custom > model > OSC > first-msg, so a user /name always wins and OSC never clobbers a model title (advisory (a): the old code overwrote Meta.Name from AutoTitle). Blank is ignored (keep last good title). Persists to state.json like SetSummary. The server drives the 0x04 push separately.

func (*Manager) SetOnChange

func (m *Manager) SetOnChange(fn func())

SetOnChange registers a best-effort notification for mutations visible in the live session list. The callback runs after the manager lock is released.

func (*Manager) SetOnCmdDone

func (m *Manager) SetOnCmdDone(fn func(sessionID string, exitCode *int))

SetOnCmdDone wires the cmdDone callback. The callback receives the session ID + the shell-reported exit code (nil if the marker carried none). Called from each session's readLoop goroutine — the callback MUST be goroutine- safe (the server's implementation publishes to hooks.Bus, which is).

func (*Manager) SetOnForegroundAgent

func (m *Manager) SetOnForegroundAgent(fn func(sessionID, agent string))

SetOnForegroundAgent wires the foreground-agent-change callback. Fires with the detected agent kind ("codex"/"grok"/"claude") on launch or "" on exit. Called from the pty readLoop goroutine — the callback MUST be goroutine-safe (the server's implementation pushes a WS frame, which is).

func (*Manager) SetOnRemove

func (m *Manager) SetOnRemove(fn func(sessionID string))

SetOnRemove registers a callback for proven session removal, including natural child exit and explicit destroy. It runs after manager locks release.

func (*Manager) SetSessionTitle

func (m *Manager) SetSessionTitle(rmoteID, title string)

SetSessionTitle sets the tab's topic label from the agent's OWN session name (forwarded on SessionStart / session_info_changed) and locks the on-device naming lifecycle so the model doesn't re-derive/overwrite it on the next turn. Unlike SetModelTitle (which ignores blank to keep the last good value), a blank title clears the label and unlocks re-derivation — used when the agent reports its session is unnamed. Generic over the agent id; any agent that forwards a `title` in its hook payload reaches this. A user /name (NameIsCustom) always wins the displayed slot regardless.

func (*Manager) SetStatsModel

func (m *Manager) SetStatsModel(rmoteID, model string)

SetStatsModel records the active model for a session (from the hook stream's agent identity or a profile model tag). Convenience over UpdateStats.

func (*Manager) SetSummary

func (m *Manager) SetSummary(rmoteID, summary string)

SetSummary stores the Foundation Model's "what it's doing now" one-liner for a tab and persists it to state.json so a daemon restart keeps the panel populated until the next hook refreshes it. Matches the SetCwd mutate-under-m.mu → saveStateDebounced shape. A blank summary is ignored: the summarizer emits blank only when the model is unavailable or input is empty, and overwriting a good summary with blank would blank the panel for no reason. The server caller drives the 0x04 client push separately (markLiveSessionsChanged), so this only stores + persists. No-op for an unknown tab or an unchanged value.

func (*Manager) SetSummoner

func (m *Manager) SetSummoner(id, summonerID string)

SetSummoner marks id as summoned by summonerID and stamps the auto-reply window start. The Stop-hook auto-reply fires while the binding is active.

func (*Manager) SetWorktreeResolver

func (m *Manager) SetWorktreeResolver(resolve func(id, savedPath string) (cwd, health string))

func (*Manager) String

func (m *Manager) String() string

String is a debug aid (logs / panic messages).

func (*Manager) SummonTarget

func (m *Manager) SummonTarget(id string) (string, bool)

SummonTarget returns the summoner's session ID when id has an active summon binding (non-empty and within summonReplyWindow). A lapsed binding is cleared lazily here so the tab stops replying after the window.

func (*Manager) UpdateStats

func (m *Manager) UpdateStats(rmoteID string, fn func(*SessionStats))

UpdateStats mutates a session's Stats under the meta lock and persists on change. The fn receives a pointer to the current Stats (zero-value if none) and may set any field. Returning leaves persistence to saveStateDebounced. No-op for an unknown session.

func (*Manager) UpdateTitle

func (m *Manager) UpdateTitle(rmoteID string, fn func(*TitleState))

UpdateTitle mutates a session's Line-1 lifecycle state (TitleState) under the meta lock and persists on change. The server's title code (server_title.go) uses this to buffer user prompts, flip Meaningful on first confident title, bump Attempts on UNCLEAR, and reset on a detected topic change. Persisting TitleState means a daemon restart mid-session keeps the naming context. No-op for an unknown session.

func (*Manager) ValidateSessionToken

func (m *Manager) ValidateSessionToken(id, token string) bool

type Meta

type Meta struct {
	ID             string       `json:"session_id"`
	Name           string       `json:"name"`
	NameIsCustom   bool         `json:"name_is_custom"`
	Agent          AgentKind    `json:"agent"`
	Cwd            string       `json:"path"`
	Shell          string       `json:"command"` // the spawn command (shell path or agent CLI)
	Active         bool         `json:"active"`
	AutoTitle      string       `json:"auto_title,omitempty"`       // OSC 0/2 captured title; empty until shell emits one
	ModelTitle     string       `json:"model_title,omitempty"`      // model-derived session title (Line 1); precedence: custom > model > OSC
	Summary        string       `json:"summary,omitempty"`          // Foundation Model "what it's doing now" line; empty → fall back to auto_title
	Stats          SessionStats `json:"stats,omitempty"`            // Line 3: model · msgs · turns · tokens · last-updated (agent-agnostic)
	Title          TitleState   `json:"title,omitempty"`            // Line 1 lifecycle state (prompt buffer + flags); persisted across restart
	TitleSessionID string       `json:"title_session_id,omitempty"` // agent session id the current ModelTitle/Title belongs to; persists across quit so resume-vs-new-conversation is detectable
	ShortName      string       `json:"short_name,omitempty"`       // peer-messaging alias (#cc1); assigned once, never changed
	WorktreeID     string       `json:"worktree_id,omitempty"`
	BindingHealth  string       `json:"binding_health,omitempty"`
	Index          int          `json:"-"` // position in the tab strip (0-based); recomputed by List callers
	SessionToken   string       `json:"-"` // in-memory capability injected into this PTY only

	// AgentSessionIDs is the single canonical per-agent session-id binding
	// (SessionStart sets, SessionEnd / ClearAgentOnExit clears). Exposed on
	// the wire as agent_session_ids; the skills bar / restart auto-resume read
	// it after a force-quit-reopen when no fresh 0x08 frame has arrived yet.
	// Keyed generically by agent id — no per-agent struct mirrors.
	AgentSessionIDs map[agents.ID]string `json:"-"`
	Profile         string               `json:"profile,omitempty"` // agent-profile name tag (summon dedup)
	ProfileAgent    AgentKind            `json:"-"`
	ProfileModel    string               `json:"-"`
	ProfileEffort   string               `json:"-"`

	// Orchestration (in-memory only; not persisted — a daemon restart re-runs
	// any summon fresh, and stale reply bindings must not survive). The
	// Stop-hook auto-reply fires for a session while Summoner is set and
	// SummonedAt is within summonReplyWindow. LastReplyAt advances whenever a
	// peer message is sent FROM this session, enabling `wait --until replied`.
	Summoner    string    `json:"-"`
	SummonedAt  time.Time `json:"-"`
	LastReplyAt time.Time `json:"-"`
	// contains filtered or unexported fields
}

Meta is the per-tab metadata layered over a live pty.Session. The PTY process details (PID, ring, subscribers) live in pty.Session; Meta holds what the REST API + iOS tab strip need: display name, agent kind, cwd.

func (Meta) AgentSessionIDMap

func (m Meta) AgentSessionIDMap() map[string]string

AgentSessionIDs returns the canonical open agent→session-id map (empty values dropped). AgentSessionIDs is the single source of truth; there are no per-agent struct matches to fall back to.

type SessionStats

type SessionStats struct {
	Model         string `json:"model,omitempty"`
	Messages      int    `json:"messages,omitempty"`
	Turns         int    `json:"turns,omitempty"`
	Tokens        int64  `json:"tokens,omitempty"`
	LastUpdatedMs int64  `json:"last_updated_ms,omitempty"`
}

SessionStats is the Line-3 payload: a compact, agent-agnostic stats summary rendered as `model · msgs · turns · tokens · updated Xm ago`. All fields omitempty so a fresh session serializes to {} until the first hook event.

Counting is event-driven (works for every agent without parsing their token-schema JSON): UserPromptSubmit → Messages++, Stop/AfterModel → Turns++. Tokens stays 0 until a per-agent usage path lands (phase-05 refinement); LastUpdatedMs is the observation time (when the daemon last updated this struct), distinct from session mtime.

type TitleState

type TitleState struct {
	Prompts    []string `json:"prompts,omitempty"`
	Meaningful bool     `json:"meaningful,omitempty"`
	Attempts   int      `json:"attempts,omitempty"`
}

TitleState is the Line-1 lifecycle state, persisted to state.json so a daemon restart doesn't lose naming context for a long-running session. Driven by server_title.go: prompts accumulate as naming input until the model returns a confident title (Meaningful=true, then locked). Attempts bounds the UNCLEAR retries before a best-effort call forces a name.

Jump to

Keyboard shortcuts

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