Terminal Tab State System
Terminal Tab State System
Overview
The LifeOS system uses Kitty terminal tab colors and title suffixes to provide instant visual feedback on session state. At a glance, you can see which tabs are working, completed, waiting for input, or have errors.
State System
| State | Icon | Format | Suffix | Inactive Background | When |
|---|---|---|---|---|---|
| Inference | 🧠 | Normal | … | Purple #1E0A3C | AI thinking (Haiku/Sonnet inference) |
| Working | ⚙️ | Italic | … | Orange #804000 | Processing your request |
| Completed | ✓ | Normal | (none) | Green #022800 | Task finished successfully |
| Awaiting Input | ❓ | BOLD CAPS | (none) | Teal #0D4F4F | AskUserQuestion tool used |
| Error | ⚠ | Normal | ! | Orange #804000 | Error detected in response |
Text Colors:
- Active tab: White
#FFFFFF - Inactive tab: Gray
#A0A0A0
Active Tab Background: Always Dark Blue #002B80 (regardless of state)
Key Design: State colors only affect inactive tabs. The active tab always stays dark blue so you can quickly identify which tab you’re in. When you switch away from a tab, you see its state color.
How It Works
Two-Hook Architecture
1. UserPromptSubmit (Start of Work)
- Hook:
SessionAnalysis.hook.ts - Sets title with
…suffix - Sets background to orange (working)
- Announces via voice server
2. Stop (End of Work)
- Hook:
SessionAnalysis.hook.ts→handlers/TabState.ts - Detects final state (completed, awaiting input, error)
- Sets appropriate suffix and color
- Voice notification with completion message
State Detection Logic
function detectResponseState(lastMessage, transcriptPath): ResponseState {
// Check for AskUserQuestion tool → 'awaitingInput'
// Check for error patterns in STATUS section → 'error'
// Default → 'completed'
}
Awaiting Input Detection:
- Scans last 20 transcript entries for
AskUserQuestiontool use
Error Detection:
- Checks
📊 STATUS:section for: error, failed, broken, problem, issue - Checks for error keywords + error emoji combination
Examples
| Scenario | Tab Appearance | Notes |
|---|---|---|
| AI inference running | 🧠 Analyzing… (purple when inactive) | Brain icon shows AI is thinking |
| Processing request | ⚙️ 𝘍𝘪𝘹𝘪𝘯𝘨 𝘣𝘶𝘨… (orange when inactive) | Gear icon + italic text |
| Task completed | ✓Fixing bug (green when inactive) | Checkmark, normal text |
| Need clarification | ❓𝗤𝗨𝗘𝗦𝗧𝗜𝗢𝗡 (teal when inactive) | Bold ALL CAPS |
| Error occurred | ⚠Fixing bug! (orange when inactive) | Warning icon + exclamation |
Note: Active tab always shows dark blue (#002B80) background. State colors only visible when tab is inactive.
One session, watched through the tab
Picture five tabs open across two projects. You type a prompt into tab three and switch away to read tab one. Here is the whole system doing its job, hands-off:
- The moment you submit, tab three goes orange with an italic gear and a
…suffix — it is working. You didn’t touch it; the UserPromptSubmit hook painted it. - Mid-run the model stops to think, and the tab flips purple with the 🧠 brain — inference in flight — then back to orange when tool work resumes.
- The run finishes clean, so at Stop the tab settles to green with a ✓ and no suffix. From tab one, you see green in your peripheral vision and know tab three is done without reading a word.
- Had the run instead asked you a question, the tab would be teal, bold ALL CAPS — the one color that means you are the blocker. Teal is the tab you walk back to first.
The active tab you are actually looking at never changes color — it stays dark blue — so “which tab am I in” and “what state is everything else in” never fight for the same signal.
The state machine behind the paint
stateDiagram-v2
[*] --> Working: you submit a prompt
Working --> Inference: model starts thinking
Inference --> Working: back to tool work
Working --> Completed: response finished clean
Working --> AwaitingInput: it asks a question
Working --> Error: status reports a failure
AwaitingInput --> Working: you answer
Completed --> Working: next prompt
Error --> Working: next prompt
Every edge is driven by a hook, never by hand: the working and inference states come from PromptProcessing.hook.ts on submit, and the three terminal states — completed, awaiting-input, error — are chosen by handlers/TabState.ts at Stop from what the transcript actually shows. A green tab is a claim the run finished; a teal tab is a claim it needs you.
Text Formatting
- Working state: Uses Unicode Mathematical Italic (
𝘈𝘉𝘊...) for italic appearance - Question state: Uses Unicode Mathematical Bold (
𝗔𝗕𝗖...) in ALL CAPS
Mode/Tier Token (title prefix) — RETIRED 2026-07-11
History only. The mode/tier token was retired 2026-07-11 when mode/tier classification (MINIMAL/NATIVE/ALGORITHM, E1–E5) was abolished system-wide and
TheRouter.hook.ts— the authoritative classifier described below — was deleted. No successor stamps anE{tier}/Ntoken. Some token plumbing (setModeToken,MODE_TOKEN_RE, thenativetab color) still lingers intab-setter.ts/PromptProcessing.hook.tsbut nothing classifies into it. The Algorithm Phase Tab System below still runs (phase icons/colors), minus the tier-token prefix. The description below is kept for history.
Every tab title used to lead with a mode/tier token so you could see at a glance what kind of turn each tab was running:
N— a NATIVE turn. Rendered in a lighter, brighter orange (#C2660A, thenativestate inTAB_COLORS) so native work is visually distinct from Algorithm’s darker build/execute oranges.E1–E5— an ALGORITHM run at that effort tier.
Canonical title format: {TOKEN} {ICON} {summary} — e.g. N ⚙️ Fixing tab titles. or E3 🔨 Building phase tabs.
Single authority (2026-07-01 coordination fix; moot since the 2026-07-11 retirement). The mode/tier token was owned by ONE writer — TheRouter.hook.ts, the authoritative classifier — so the tab, work.json, and the Pulse Agents/Lattice page all projected the SAME decision. Before this fix, PromptProcessing.hook.ts stamped the token from its own 8-verb isNativeMode() shadow-classifier, which diverged from TheRouter and showed N on ALGORITHM turns (e.g. a prompt like “analyze… and fix” has none of the 8 verbs); the correct tier token only appeared once an ISA existed and its phase advanced.
Where the token came from (all historical — TheRouter deleted 2026-07-11):
- TheRouter (authority) — the instant it classified,
TheRouter.hook.tscallssetModeToken(sessionId, token)(tab-setter.ts):E{tier}for ALGORITHM,Nfor NATIVE (MINIMAL leaves the tab).setModeTokensets/replaces ONLY the leading token, preserves the live working description, and clears any prior-turn✅ completedstate — so a stale “done” can’t linger into live work, in EITHER direction (an ALGORITHM turn never showsN, a NATIVE turn after an ALGORITHM turn clears the staleE{tier}/✅). TheRouter also persists the tier intowork.json(markAlgorithmStarting(uuid, hint, tier)) so the Agents page is tier-correct before any ISA exists. - PromptProcessing (description only) — sets the working gerund description; it no longer classifies mode. It recovers the token TheRouter stamped via
extractModeToken(readTabState()), but ONLY when the tab shows live work — a stale completion/idle token is dropped (TheRouter re-stamps the authoritative one ~concurrently). This is the race contract: TheRouter owns the token, PromptProcessing owns the description, each preserves the other’s field. - AlgoPhase + ISASync (phase) (historical — AlgoPhase retired 2026-07-14) — both stamped
setPhaseTab(phase, sessionUUID, undefined, eLevel)at transitions (idempotent, sameE{tier}+phase-icon output):AlgoPhase.tson the explicit CLI phase write (the SAME write that updateswork.json, keeping tab ↔ Agents-page congruent),ISASync.hook.tson the ISA-edit phase change (catches the scaffold and manual edits).eLevelcomes from the roweffort/ ISA frontmatter viaeffortToCanonicalELevel(). - Completion —
handlers/TabState.tscallssetPhaseTab('COMPLETE', …)with noeLevel;setPhaseTabrecovers the existing token (extractModeToken), soN/E3carries through to the green done state.
stripPrefix(), extractModeToken(), setModeToken() (all in tab-setter.ts) parse/mutate the token + icon; MODE_TOKEN_RE is the shared ^(N|E[1-5])\s+ matcher.
Algorithm Phase Tab System
Separate from the State System above, Algorithm runs drive tab titles/colors via setPhaseTab() in hooks/lib/tab-setter.ts. Each phase (OBSERVE, THINK, PLAN, BUILD, EXECUTE, VERIFY, LEARN, COMPLETE) has a distinct emoji prefix and background color defined in hooks/lib/tab-constants.ts::PHASE_TAB_CONFIG. The title format is {TOKEN} {symbol} {description} — for example E3 ⚡ Fixing Algorithm State Sync..
Two drivers feed setPhaseTab (a third, LIFEOS/TOOLS/AlgoPhase.ts, was retired 2026-07-14 in the agents-dashboard deep strip — phase is now written only via ISA frontmatter):
ISASync.hook.ts(PostToolUse, Edit on ISA.md) — the primary driver: fires when the Algorithm executor edits the ISA frontmatterphase:field (catches the scaffold write and manual phase edits), writeswork.jsonAND stamps the tab, emittingE{tier}+phase.LIFEOS/PULSE/VoiceServer/voice.ts::tryPhaseCapture(out-of-process) — fires when an Algorithm phase-announcement voice call hits/notifywithphase+slug. The daemon resolves the kitty socket via the per-session file atMEMORY/STATE/kitty-sessions/{sessionUUID}.json(written byKittyEnvPersist.hook.tsat SessionStart).
Cross-process support details:
tab-setter.ts::kittenBin()resolves thekittenbinary viacommand -v, falling back to/Applications/kitty.app/Contents/MacOS/kitten— required because the Pulse daemon runs under launchd with a restricted PATH that doesn’t include/Applications/*.- All
kitten @invocations intab-setter.tspass--match="id:{windowId}"so the daemon (which has no focused kitty window) targets the correct tab instead of whichever tab happens to be focused. - Fallback chain for socket discovery: process env (
KITTY_LISTEN_ON) → per-session file → default/tmp/kitty-$USERsocket.
Terminal Compatibility
Requires Kitty terminal with remote control enabled:
# kitty.conf
allow_remote_control yes
listen_on unix:/tmp/kitty
Implementation Details
Kitty Commands Used
# Set tab title
kitty @ set-tab-title "Title here"
# Set tab colors
kitten @ set-tab-color --self \
active_bg=#1244B3 active_fg=#FFFFFF \
inactive_bg=#022800 inactive_fg=#A0A0A0
Hook Files
Tab painting was consolidated into one hook, TabState.hook.ts, on 2026-07-10 — it dispatches on hook_event_name, merging the three former painters (SetQuestionTab, QuestionAnswered, ResponseTabReset, all deleted). Working-state on prompt submit stays in PromptProcessing.hook.ts.
| File | Event | Purpose |
|---|---|---|
PromptProcessing.hook.ts | UserPromptSubmit | Set working state (italic text) |
TabState.hook.ts (← SetQuestionTab) | PreToolUse (AskUserQuestion) | Set question state (teal); save previousTitle for restore |
TabState.hook.ts (← QuestionAnswered) | PostToolUse (AskUserQuestion) | Restore working/orange state after the answer |
TabState.hook.ts (← ResponseTabReset) → handlers/TabState.ts | Stop | Set final/completion state |
Color Constants
// hooks/lib/tab-constants.ts — single source of truth for tab colors/states
working: { inactiveBg: '#804000', inactiveFg: '#A0A0A0', label: 'orange' },
thinking: { inactiveBg: '#1E0A3C', inactiveFg: '#A0A0A0', label: 'purple' },
// full state map + active-tab colors live in the same file
const INACTIVE_TEXT = '#A0A0A0'; // Gray
// In TabState.hook.ts PreToolUse branch (via lib/tab-constants.ts)
const TAB_AWAITING_BG = '#0D4F4F'; // Dark teal (waiting for input)
// In handlers/TabState.ts (via lib/tab-constants.ts)
const TAB_COLORS = {
awaitingInput: '#0D4F4F', // Dark teal
completed: '#022800', // Dark green
error: '#804000', // Dark orange
};
// Tab icons and formatting
const TAB_ICONS = {
inference: '🧠', // Brain - AI thinking
working: '⚙️', // Gear - processing (italic text)
completed: '✓', // Checkmark
awaiting: '❓', // Question (bold caps text)
error: '⚠', // Warning
};
const TAB_SUFFIXES = {
inference: '…',
working: '…',
awaitingInput: '', // No suffix, uses bold QUESTION
completed: '',
error: '!',
};
Key Point: active_bg is always set to #002B80 (dark blue). State colors are applied to inactive_bg only.
Debugging
Check Current Tab Colors
kitty @ ls | jq '.[].tabs[] | {title, id}'
Manually Reset All Tabs to Completed
kitten @ set-tab-color --match all \
active_bg=#002B80 active_fg=#FFFFFF \
inactive_bg=#022800 inactive_fg=#A0A0A0
Test State Colors
# Inference (purple) - inactive only
kitten @ set-tab-color --self active_bg=#002B80 inactive_bg=#1E0A3C
# Working (orange) - inactive only
kitten @ set-tab-color --self active_bg=#002B80 inactive_bg=#804000
# Completed (green) - inactive only
kitten @ set-tab-color --self active_bg=#002B80 inactive_bg=#022800
# Awaiting input (teal) - inactive only
kitten @ set-tab-color --self active_bg=#002B80 inactive_bg=#0D4F4F
Note: Always set active_bg=#002B80 to maintain consistent dark blue for active tabs.
Benefits
- Visual Task Tracking - See state at a glance without reading titles
- Multi-Session Management - Quickly identify which tabs need attention
- Color-Coded Priority - Teal tabs need input, green tabs are done
- Automatic - No manual updates needed, hooks handle everything
Last Updated: 2026-06-18
Status: Production - Implemented via hook system + out-of-process daemon phase updates. Mode/tier token (N / E1–E5) + lighter-orange native color added 2026-06-18.
