# Presence and Cursors > *See who is here, which branch, which function they are inside, and — if they want to share it — where their cursor is sitting.* **Presence is the ambient awareness layer on top of Live Sync.** Every peer broadcasts a lightweight heartbeat that says "I am online, I am on branch X, I am currently editing function Y." The visualization is deliberately at the function level — you do not see a cursor blinking in character space; you see a marker on the function node saying "alice is here." That is both cheaper on the wire and more useful. You rarely care exactly which character your teammate is on. You care a lot which function they are working in, because that is the function you should avoid touching for the next few minutes. ## Overview In Google Docs, the colored cursor trailing around the document is the single most recognizable feature. You cannot port that directly to code — characters are not the right unit ([function-level sync](/function-level-sync)) — so Aura ports the *feeling* instead: you always know who is here and what they are working on. The visualization lives in three places: 1. **The CLI**, via `aura live presence` and a passive banner in `aura live sync status --watch`. 2. **The MCP status payload**, so AI agents see the same information other agents and humans broadcast. 3. **The dashboard** (if you run the web UI), where presence appears as avatars pinned to the function nodes in the logic graph. Presence is ambient. It is not a notification, not a nag, not something you have to acknowledge. It is a glanceable signal, like the green dot on a chat app. ## How It Works Every peer sends a heartbeat on a 30-second cadence (independent of the 5-second sync cadence). The heartbeat payload: ```json { "type": "hb", "peer": "alice", "branch": "feat/billing-refactor", "active_fn": "billing::compute_tax", "editing_since_ms": 1740000000000, "last_save_ms": 1740000012000, "ts_ms": 1740000015000 } ``` The Mothership fans out heartbeats to all peers subscribed to the repo. On receipt, each peer updates an in-memory presence table. A peer is considered **online** if its last heartbeat is within 90 seconds; **idle** between 90s and 5 minutes; **offline** after 5 minutes. ```text peer states: online (<= 90s since last heartbeat) idle (90s .. 5m) offline (> 5m) active-function tracking: determined by the file currently focused in the editor, disambiguated to the function containing the cursor line. ``` ## What is "active function"? The active function is the function node the user's cursor is inside, as reported by the editor integration: - **VS Code, Cursor, Windsurf** — the Aura extension reads the active editor's cursor position and resolves it to the enclosing function via a local AST query. - **Neovim, Helix** — an LSP-side plugin reports the same. - **Terminal-only workflows** — Aura falls back to "last function saved" as a proxy. The resolution is debounced at ~500ms so quick scrolling past functions does not flood the heartbeat channel. If the user has no file focused, `active_fn` is `null` and presence shows "alice is online, idle." ## Visualizing presence ### CLI ```bash aura live presence ``` ```text peers in this repo (5): you feat/billing-refactor billing::generate_invoice active alice feat/billing-refactor billing::compute_tax active ◀── same fn you call! bob main http::router idle 4m claude-1 feat/tests tests::test_invoice active cursor-2 main - idle 8m ``` The `◀──` marker is automatic: if a peer is editing a function that calls or is called by your current function, Aura highlights the relationship. This is the same caller graph that powers [impact alerts](/impact-alerts), reused for presence. ### Watch mode Presence is included in the live status banner: ```bash aura live sync status --watch ``` The banner updates in place as peers move between functions. ### MCP Agents receive presence in the `aura_status` payload: ```json { "team": { "peers": [ { "id": "alice", "branch": "feat/billing-refactor", "active_fn": "billing::compute_tax", "state": "active" }, { "id": "claude-1", "branch": "feat/tests", "active_fn": "tests::test_invoice", "state": "active" } ] } } ``` The Claude Code system prompt already instructs agents to read this and **avoid editing functions a human is actively inside**, to reduce conflict thrash. It is a polite protocol, not a hard lock — if you need a hard lock, use [zone claims](/team-zones). ## Cursor on function The visualization on the dashboard places a small avatar next to the function node in the logic graph. Multiple peers on the same function stack their avatars. Hovering shows their branch and when they started editing. ```text ┌── billing::compute_tax ──────────────────┐ │ [alice] editing since 2m │ │ [claude-1] editing since 45s │ └──────────────────────────────────────────┘ │ │ called by ▼ ┌── billing::generate_invoice ─────────────┐ │ [you] editing since 4m │ └──────────────────────────────────────────┘ ``` This is the cursor visualization. You do not see character-level cursors because (as discussed in [function-level sync](/function-level-sync)) characters are the wrong unit. You see **function-level cursors** — which function each peer's cursor is currently inside. ## Privacy controls Not every team wants to broadcast which function they are in. Disable it: ```toml [live.presence] # Still heartbeat (so peers know you're online), but don't include active_fn. broadcast_active_fn = false # Also suppress last-save timestamps. broadcast_last_save = false ``` With `broadcast_active_fn = false`, your entry in the presence table shows your online state but no function. This is per-peer — each person decides what they share. For fully private mode, disable presence entirely: ```toml [live.presence] enabled = false ``` Heartbeats stop. You still receive Live Sync pushes and pulls, but you are invisible to peers. > **Gotcha:** Disabling presence does not disable Live Sync. You still push function bodies; you just do not advertise which function you are inside. If you want to go fully dark, disable Live Sync itself with `aura live sync disable`. ## Stale presence Occasionally a peer disconnects abruptly (closed laptop, killed process) and its last heartbeat is still within the 90-second window — so it appears online for up to 90 more seconds. This is by design: network hiccups are common, and we do not want peers flickering offline/online on every Wi-Fi handoff. The cost is mild staleness on true disconnects. ## Multi-agent presence AI agents are full presence citizens. Claude Code, Cursor, and Windsurf each register with a distinct peer ID (usually `claude-1`, `cursor-N`, etc.) and broadcast heartbeats like humans. This lets humans see "Claude is working on `tests::test_invoice` right now" and respect the collaboration. See [sentinel collisions](/sentinel-collisions) for the escalation path when two agents land on the same function. ## Config ```toml [live.presence] enabled = true heartbeat_ms = 30000 online_window_ms = 90000 idle_window_ms = 300000 broadcast_active_fn = true broadcast_last_save = true active_fn_debounce_ms = 500 ``` ## Troubleshooting **A teammate is online but presence does not show them.** Their editor integration is not installed or not reporting cursor position. They still sync fine; you just do not see their active function. **Your own presence shows wrong active function.** The editor integration is out of sync with the AST. Run `aura graph rebuild` on your side, then reload the editor plugin. **Ghost peers.** A peer ID appears that you do not recognize. It may be a second laptop you left on. Run `aura live presence --resolve` to see the full peer ID (hostname + user). ## Comparison to other collaborative cursors | Tool | Cursor unit | Works across editors | Agent-aware | |---|---|---|---| | Google Docs | character | N/A — browser only | no | | VS Code Live Share | character | no — VS Code only | no | | JetBrains Code With Me | character | no — JetBrains only | no | | Figma | pixel | N/A — browser only | no | | **Aura presence** | **function** | **yes — any editor with plugin** | **yes — AI agents are peers** | The function-level choice means presence works uniformly across VS Code, Neovim, and a bare terminal. Character-level cursor sharing requires deep editor integration in each tool; function-level just needs to know which file is focused and the cursor line, which every editor already exposes. ## Attribution Each push carries the author peer ID. Presence is built from the same stream. This means presence and authorship are always consistent — if `claude-1` is shown as "editing `tests::foo`", and `tests::foo` gets pushed in the next tick, the author on that push is `claude-1`. There is no separate attribution channel to get out of sync. ## Do-not-disturb For focus sessions, suppress all presence notifications without disabling the underlying heartbeat: ```toml [live.presence] dnd = true ``` With DND on, you still appear as online to peers; you just do not see their movement in your banner. Pair with `aura_msg_send` silencing for a full focus mode. ## Editor integration details The active-function resolution lives in the editor plugin, not the core. Plugins expose a small contract to the core: ```typescript interface EditorPresence { activeFile(): string | null; // absolute path or null cursorLine(): number | null; // 1-indexed isIdle(): boolean; // editor considers user idle } ``` The core polls this at most every 500ms (the debounce). The plugin decides what "idle" means — VS Code uses window focus, JetBrains uses the built-in `IdeFocusManager`, Neovim uses `CursorHold`. If no plugin is installed, the core falls back to watching `mtime` on saved files and treats "most recently saved function" as the active one. This is coarser but still useful. ## Privacy of active function "Which function is Bob in" is information. Some teams want to share it, some do not. The three settings that control this: - `broadcast_active_fn = false` — hide the function but stay online. - `broadcast_last_save = false` — hide the last-save timestamp. - `enabled = false` — go fully dark; still sync code, but no presence. There is no mode where you see others but they do not see you — presence is reciprocal by design. This is a social choice, not a technical limitation. ## See Also - [Live Sync overview](/live-sync-overview) - [Function-level sync](/function-level-sync) - [Impact alerts](/impact-alerts) - [Sync conflicts](/sync-conflicts) - [Mothership overview](/mothership-overview)