Why this matters
The session state starts with one big immutable shape. In the source, that shape
starts with export type AppState = DeepImmutable<{, and the rest of the shell
keeps using the same AppState model while the store swaps in updated snapshots
under the hood.
getDefaultAppState() and the main.tsx initialState object are related but
not identical. They both seed the same AppState shape, but they are built at
different times for different jobs.
getDefaultAppState() is the neutral baseline. main.tsx builds the real
starting state after it has looked at CLI flags, config, and bridge mode.
The default seed
This is the snapshot from state/AppStateStore.ts.
export function getDefaultAppState(): AppState {
// Determine initial permission mode for teammates spawned with plan_mode_required
// Use lazy require to avoid circular dependency with teammate.ts
/* eslint-disable @typescript-eslint/no-require-imports */
const teammateUtils =
require('../utils/teammate.js') as typeof import('../utils/teammate.js')
/* eslint-enable @typescript-eslint/no-require-imports */
const initialMode: PermissionMode =
teammateUtils.isTeammate() && teammateUtils.isPlanModeRequired()
? 'plan'
: 'default'
return {
settings: getInitialSettings(),
tasks: {},
agentNameRegistry: new Map(),
verbose: false,
mainLoopModel: null, // alias, full name (as with --model or env var), or null (default)
mainLoopModelForSession: null,
statusLineText: undefined,
expandedView: 'none',
isBriefOnly: false,
showTeammateMessagePreview: false,
selectedIPAgentIndex: -1,
coordinatorTaskIndex: -1,
viewSelectionMode: 'none',
footerSelection: null,
kairosEnabled: false,
remoteSessionUrl: undefined,
remoteConnectionStatus: 'connecting',
remoteBackgroundTaskCount: 0,
replBridgeEnabled: false,
replBridgeExplicit: false,
replBridgeOutboundOnly: false,
replBridgeConnected: false,
replBridgeSessionActive: false,
replBridgeReconnecting: false,
replBridgeConnectUrl: undefined,
replBridgeSessionUrl: undefined,
replBridgeEnvironmentId: undefined,
replBridgeSessionId: undefined,
replBridgeError: undefined,
replBridgeInitialName: undefined,
showRemoteCallout: false,
toolPermissionContext: {
...getEmptyToolPermissionContext(),
mode: initialMode,
},
agent: undefined,
agentDefinitions: { activeAgents: [], allAgents: [] },
fileHistory: {
snapshots: [],
trackedFiles: new Set(),
snapshotSequence: 0,
},
attribution: createEmptyAttributionState(),
mcp: {
clients: [],
tools: [],
commands: [],
resources: {},
pluginReconnectKey: 0,
},
plugins: {
enabled: [],
disabled: [],
commands: [],
errors: [],
installationStatus: {
marketplaces: [],
plugins: [],
},
needsRefresh: false,
},
todos: {},
remoteAgentTaskSuggestions: [],
notifications: {
current: null,
queue: [],
},
elicitation: {
queue: [],
},
thinkingEnabled: shouldEnableThinkingByDefault(),
promptSuggestionEnabled: shouldEnablePromptSuggestion(),
sessionHooks: new Map(),
inbox: {
messages: [],
},
workerSandboxPermissions: {
queue: [],
selectedIndex: 0,
},
pendingWorkerRequest: null,
pendingSandboxRequest: null,
promptSuggestion: {
text: null,
promptId: null,
shownAt: 0,
acceptedAt: 0,
generationRequestId: null,
},
speculation: IDLE_SPECULATION_STATE,
speculationSessionTimeSavedMs: 0,
skillImprovement: {
suggestion: null,
},
authVersion: 0,
initialMessage: null,
effortValue: undefined,
activeOverlays: new Set<string>(),
fastMode: false,
}
}
The live runtime seed
This is the corresponding main.tsx state block.
const initialState: AppState = {
settings: getInitialSettings(),
tasks: {},
agentNameRegistry: new Map(),
verbose: verbose ?? getGlobalConfig().verbose ?? false,
mainLoopModel: initialMainLoopModel,
mainLoopModelForSession: null,
isBriefOnly: initialIsBriefOnly,
expandedView: getGlobalConfig().showSpinnerTree ? 'teammates' : getGlobalConfig().showExpandedTodos ? 'tasks' : 'none',
showTeammateMessagePreview: isAgentSwarmsEnabled() ? false : undefined,
selectedIPAgentIndex: -1,
coordinatorTaskIndex: -1,
viewSelectionMode: 'none',
footerSelection: null,
toolPermissionContext: effectiveToolPermissionContext,
agent: mainThreadAgentDefinition?.agentType,
agentDefinitions,
mcp: {
clients: [],
tools: [],
commands: [],
resources: {},
pluginReconnectKey: 0
},
plugins: {
enabled: [],
disabled: [],
commands: [],
errors: [],
installationStatus: {
marketplaces: [],
plugins: []
},
needsRefresh: false
},
statusLineText: undefined,
kairosEnabled,
remoteSessionUrl: undefined,
remoteConnectionStatus: 'connecting',
remoteBackgroundTaskCount: 0,
replBridgeEnabled: fullRemoteControl || ccrMirrorEnabled,
replBridgeExplicit: remoteControl,
replBridgeOutboundOnly: ccrMirrorEnabled,
replBridgeConnected: false,
replBridgeSessionActive: false,
replBridgeReconnecting: false,
replBridgeConnectUrl: undefined,
replBridgeSessionUrl: undefined,
replBridgeEnvironmentId: undefined,
replBridgeSessionId: undefined,
replBridgeError: undefined,
replBridgeInitialName: remoteControlName,
showRemoteCallout: false,
notifications: {
current: null,
queue: initialNotifications
},
elicitation: {
queue: []
},
todos: {},
remoteAgentTaskSuggestions: [],
fileHistory: {
snapshots: [],
trackedFiles: new Set(),
snapshotSequence: 0
},
attribution: createEmptyAttributionState(),
thinkingEnabled,
promptSuggestionEnabled: shouldEnablePromptSuggestion(),
sessionHooks: new Map(),
inbox: {
messages: []
},
promptSuggestion: {
text: null,
promptId: null,
shownAt: 0,
acceptedAt: 0,
generationRequestId: null
},
speculation: IDLE_SPECULATION_STATE,
speculationSessionTimeSavedMs: 0,
skillImprovement: {
suggestion: null
},
workerSandboxPermissions: {
queue: [],
selectedIndex: 0
},
pendingWorkerRequest: null,
pendingSandboxRequest: null,
authVersion: 0,
initialMessage: inputPrompt ? {
message: createUserMessage({
content: String(inputPrompt)
})
} : null,
effortValue: parseEffortValue(options.effort) ?? getInitialEffortSetting(),
activeOverlays: new Set<string>(),
fastMode: getInitialFastModeSetting(resolvedInitialModel),
...(isAdvisorEnabled() && advisorModel && {
advisorModel
}),
// Compute teamContext synchronously to avoid useEffect setState during render.
// KAIROS: assistantTeamContext takes precedence — set earlier in the
// KAIROS block so Agent(name: "foo") can spawn in-process teammates
// without TeamCreate. computeInitialTeamContext() is for tmux-spawned
// teammates reading their own identity, not the assistant-mode leader.
teamContext: feature('KAIROS') ? assistantTeamContext ?? computeInitialTeamContext?.() : computeInitialTeamContext?.()
};
verbose is the clearest example of why the two objects are different.
getDefaultAppState() has no CLI context, so it stays on the safe baseline.
main.tsx can read the user’s flags and global config, so it fills in the
session-specific value instead.
The same idea applies to replBridgeEnabled. The store default starts from
false, while the real launch path turns it on when full remote control or CCR
mirror mode is active.
The main thing to remember is simple: the two objects are related but not identical because one is the reusable default and the other is the live startup state.