Studio Notebook

Claude Code Atlas

App State Shape And Defaults

Meet the AppState object and the defaults that seed the REPL before the first turn.

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.