A HAPI session represents a single conversation with an AI coding agent. Sessions can be controlled locally from the terminal or remotely via web/PWA/Telegram, with seamless handoff between modes.
1
Session Creation
User runs hapi, hapi codex, or other agent command. CLI generates a unique session ID and connects to Hub.
2
Registration
CLI registers session with Hub via REST API (POST /cli/sessions). Hub creates entry in SQLite and broadcasts to web clients.
3
Active Phase
Session enters active state. Messages flow between user, agent, and Hub. Permission requests are handled in real-time.
4
Completion
Session ends when agent completes or user aborts. CLI sends session-end event. Session becomes inactive but remains in history.
5
Archival (Optional)
User can archive session via web UI or manually delete inactive sessions.
# Example: Starting a Claude session in local mode$ hapi✓ Connected to hub✓ Session started: ses_abc123┌─ Claude Code Session ─────────────────────────┐│ Mode: Local ││ Path: /home/user/project ││ Permission: Default │└───────────────────────────────────────────────┘You: Help me refactor this component
Automatic switch when message arrives from web/phone:
1
Web Message Sent
User sends message from phone: “Add error handling”
2
Hub Routes to CLI
Hub receives REST request, emits Socket.IO event to CLI
3
CLI Switches Mode
CLI detects remote message, switches to remote mode
4
Terminal Updates
Terminal UI shows: Remote mode - waiting for input
5
Agent Processes
Agent receives message and starts processing
┌─────────────────────────────────────────────┐│ Remote mode - waiting for input ││ Press space twice to regain control │└─────────────────────────────────────────────┘
Manual switch by pressing space twice in terminal:
// Web establishes SSE connectionconst eventSource = new EventSource(`${hubUrl}/api/events?token=${token}`)eventSource.addEventListener('message', (event) => { const syncEvent: SyncEvent = JSON.parse(event.data) switch (syncEvent.type) { case 'session-added': // New session appeared break case 'session-updated': // Session metadata or state changed break case 'message-received': // New message in session break case 'machine-updated': // Machine status changed break case 'toast': // Show notification break }})
// From shared/src/schemas.tstype Session = { id: string namespace: string seq: number createdAt: number updatedAt: number active: boolean activeAt: number metadata: Metadata | null metadataVersion: number // Incremented on metadata update agentState: AgentState | null agentStateVersion: number // Incremented on agent state update thinking: boolean thinkingAt: number todos?: TodoItem[] permissionMode?: PermissionMode modelMode?: ModelMode}
Always include expectedVersion when updating metadata or agent state. The Hub rejects stale updates to prevent race conditions.
// Web: POST /api/sessions/:id/resume// Hub routes to machine's runner via RPC// Runner spawns new CLI process with session ID// Session becomes active again
Resume only works if the original machine is online and has a runner daemon. Use hapi runner start to enable remote resume.