Skip to content

IPC and Communication

Magia has four communication layers between its parts:

  1. Tauri invoke() — frontend → backend request/response
  2. Tauri events — backend → frontend fire-and-forget (and vice versa)
  3. Unix sockets — external processes → backend (agents, hook-handler, CLI)
  4. WebSocket — IDE extensions → backend

The frontend calls invoke(commandName, args) from @tauri-apps/api/core. Each command is a Rust function annotated with #[tauri::command] and registered in the Tauri builder.

// Frontend
import { invoke } from "@tauri-apps/api/core";
const sessions = await invoke<Session[]>("get_active_sessions");
// Backend (src-tauri/src/…)
#[tauri::command]
fn get_active_sessions() -> Vec<ActiveSession> { … }

Tauri translates between camelCase (JavaScript) and snake_case (Rust) automatically. By default, Rust snake_case parameter names become camelCase on the JS side. Commands annotated with #[tauri::command(rename_all = "snake_case")] expect snake_case argument keys from JavaScript — be careful to match this when calling such commands.

Mismatched naming causes silent failures (the command succeeds but receives None / default values for every parameter). Always add error logging to invoke calls during development.

GroupExample commands
Session managementget_active_sessions, create_session, update_session, delete_session, set_session_title
Agent lifecyclestart_agent, stop_agent, send_input, get_agent_events
PTY / terminalcreate_pty, write_pty, resize_pty, kill_pty
Discoveryget_projects, get_sessions_for_project, rescan_project
Settingsload_settings, save_settings, get_otel_port
Provider configread_claude_settings, update_claude_settings, read_gemini_settings, inject_gemini_hooks
Provider accountsget_provider_accounts, connect_provider, disconnect_provider
File operationsread_file, write_file, list_directory
Searchsearch_sessions, search_files
Auth / cloudstore_auth_token, get_auth_token, get_current_user, api_get, api_request
Systemstart_background_sync, is_first_launch, factory_reset, get_api_url
Stats / metricsget_session_metrics, get_project_stats
Pluginslist_plugins, install_plugin, enable_plugin, disable_plugin

Events flow in both directions but are predominantly backend → frontend. The backend calls app_handle.emit(event_name, payload). The frontend listens with listen(event_name, handler) from @tauri-apps/api/event.

import { listen } from "@tauri-apps/api/event";
const unlisten = await listen<AgentEvent>("agent:event", (event) => {
useAgentEventsStore.getState().addEvent(event.payload);
});
// Call unlisten() to deregister
EventDirectionDescription
agent:eventBackend → FrontendA new hook event from a running agent (assistant message, tool call, tool result, permission request, etc.)
live-sessions:updateBackend → FrontendThe map of currently running agent sessions has changed
claude-data:projects-invalidatedBackend → FrontendThe session cache has changed; frontend should refetch discovery data
sessions:changedBackend → FrontendSession metadata changed (title, status, hidden flag, etc.)
otel:metrics-updateBackend → FrontendNew OTel metrics (token counts, latencies) for a session
settings:changedBackend → FrontendSettings file changed on disk (e.g. by a watcher)
providers:changedBackend → FrontendA provider binary was installed or removed
live-sessions:updateBackend → FrontendActive sessions map changed (PID check, new session, session ended)
notification-session-clickedBackend → FrontendUser clicked a native OS notification; payload is the session ID
tray:open-projectBackend → FrontendUser opened a project from the tray menu
tray:new-sessionBackend → FrontendUser clicked “New Session” in the tray
tray:minimized-first-timeBackend → FrontendApp was minimised to tray for the first time
tray:theme-changedFrontend → BackendDark/light theme toggled; tray panel window syncs
tray:sync-completedBackend → FrontendSession cache periodic sync finished
session:hijackedBackend → FrontendAnother process has taken control of the active session
factory-reset-completeBackend → FrontendFactory reset completed (dev mode only; prod uses app.restart())

Unix sockets — external process communication

Section titled “Unix sockets — external process communication”

The Rust backend listens on several Unix sockets in $TMPDIR/magia-{uid}/. All socket paths are managed by the paths module.

Purpose: Receives lifecycle events from agent CLI processes (Claude Code, Gemini, Codex) as they run.

Protocol: The agent CLI is configured to call magia-hook-handler (a small helper binary) for each hook event. magia-hook-handler connects to the hook socket and writes a newline-delimited JSON payload. The hook listener reads the payload, passes it through the event_normalizer, and emits an agent:event Tauri event to the frontend.

Agent CLI
→ exec magia-hook-handler (per event)
→ Unix socket (hook path)
→ hooks::start_hook_listener
→ event_normalizer::normalize
→ app.emit("agent:event", event)
→ useAgentEventsStore.addEvent(event)

Purpose: Receives commands from the magia CLI binary and from scripts that want to control the app programmatically (e.g. open a project, create a session).

Protocol: Line-delimited JSON commands over the CLI socket.

Purpose: Intercepts tool permission requests from agent processes before they are executed.

Protocol: The agent writes a permission request to the permission socket. The Rust listener holds the request and emits a Tauri event to the frontend. The frontend renders the permission dialog; the user approves or denies; the frontend calls an invoke() command to respond; the Rust listener unblocks the agent.

Agent (awaiting permission)
→ Unix socket (permission path)
→ agent::start_permission_listener
→ app.emit("agent:permission-request", request)
→ Frontend PermissionDialog
→ invoke("respond_to_permission", { id, approved })
→ agent::respond_to_permission
→ unblocks agent

Purpose: Receives OpenTelemetry spans and metrics from agent CLIs that export telemetry data.

Protocol: gRPC (tonic) over a Unix socket, with automatic fallback to HTTP/TCP on a configurable port (default 4318). Metrics are stored in SharedMetricsStore and forwarded to the frontend via otel:metrics-update events.

Purpose: Allows IDE extensions (e.g. the Claude Code VS Code extension) to connect to Magia and exchange session events in real time.

Protocol: WebSocket on a dynamically-assigned TCP port. The port is stored in IdeServer managed state and can be queried via invoke("get_ide_server_port"). The protocol mirrors the Claude Code IDE integration protocol.

VS Code extension
→ WebSocket (localhost:{port})
→ ide_server::IdeServer
→ session events forwarded bidirectionally

The ipc module (src-tauri/src/ipc.rs) wraps the interprocess crate to provide a single API that works on all platforms:

  • macOS / Linux: file-based Unix domain sockets.
  • Windows: named pipes (\\.\pipe\magia-{name}).

All socket creation in the hook, CLI, permission, and OTel listeners goes through ipc::socket_name() so the rest of the codebase stays platform-agnostic.