Live Sync Overview

Google Docs, but for code — at the function level, not the keystroke.

Live Sync is Aura's real-time collaboration layer. It continuously streams function bodies between teammates and AI agents working on the same repository, via the self-hosted Mothership. Instead of waiting for someone to push a branch, you see their functions appear in your working copy within seconds of them saving. Unlike Google Docs, Live Sync does not operate on characters — it operates on AST nodes, the smallest unit of code that is actually meaningful. That one design decision is why Live Sync does not destroy your syntax, does not explode into merge conflicts, and does not cost a fortune in bandwidth.

Overview

The pitch is simple: you and your colleagues should not be blind to each other's edits until the next PR. In a world where half of new code is written by AI agents and the other half is written by humans racing AI agents, ten minutes of blindness is enough for three people to rewrite the same function in three incompatible ways. Live Sync closes that window.

Here is the one-paragraph mental model. Every few seconds, Aura walks the dirty set of your working tree, identifies the functions whose AST hashes have changed, and pushes those function bodies to the Mothership. The Mothership fans them out to every peer subscribed to the repository. On each peer, Aura applies the incoming bodies at the AST level — replacing node auth::login with the new version, leaving the surrounding file untouched. If both sides edited the same function within the same sync window, a conflict is flagged and the user (or agent) is asked to pick a side. If only one side edited, the update lands silently.

You can watch it happen:

aura live sync status --watch

Output:

mothership:     online (peers: 4)
outbound queue: 0 functions
inbound queue:  0 functions
last push:      2.1s ago (3 functions)
last pull:      4.8s ago (1 function)
wal:            healthy (entries: 412, size: 1.2 MB)

recently synced:
  <- alice     billing::compute_tax        3s ago
  -> (self)    auth::login                 8s ago
  <- claude-1  billing::format_invoice    11s ago

That is the feature. Everything else in this module is depth on how it works and how to trust it.

The "not Google Docs" story

Real-time collaboration on documents is a solved problem. CRDTs, OT, Google Docs, Figma. Every engineer has used them. So the reasonable question is: why not just apply those techniques to source code?

We tried. It does not work.

Character-level CRDTs on code produce, for every keystroke, a partial parse tree. Ninety-nine percent of the time, that tree is syntactically invalid — you have half-typed an identifier, an unclosed brace, a dangling comma. Broadcasting that to your peers means their language servers explode, their autocomplete flickers, and their AST-aware tools (including Aura) refuse to index the file. Character-level sync is fine for prose because prose has no grammar that tools depend on. Code has grammar that everything depends on.

Line-level sync is no better. A line is a typographic unit, not a semantic one. Moving a function up ten lines produces a diff the size of the function. Reformatting a line — breaking an argument list across three lines instead of one — produces conflicts where there is no actual disagreement.

Live Sync's insight is that the right granularity was sitting under our noses the whole time: the function. A function is the smallest unit that is both (a) typically complete when the user hits save, and (b) independently meaningful. Sync at that unit and the whole thing clicks into place.

| Property | Google Docs | VS Code Live Share | Cursor cloud sync | Aura Live Sync | |---|---|---|---|---| | Unit | character | file buffer | file buffer | function (AST node) | | Cadence | per keystroke | continuous stream | continuous stream | ~5 seconds | | Conflict granularity | character | line | line | function | | Works offline | partial | no | no | yes (WAL queues) | | Self-hosted | no | no | no | yes (Mothership) | | AST-aware | no | no | no | yes | | Bandwidth | moderate | high | high | low (changed bodies only) |

How It Works

The sync loop is boringly simple, which is on purpose — simple loops are easier to make crash-safe.

   every 5 seconds on each peer:
   ┌──────────────────────────────────────┐
   │ 1. scan working tree dirty set       │
   │ 2. compute AST hash per function     │
   │ 3. diff against last-pushed hash     │
   │ 4. for each changed fn:              │
   │      append to WAL                   │
   │      push body to Mothership         │
   │ 5. poll Mothership for inbound fns   │
   │ 6. for each inbound fn:              │
   │      if local unchanged -> apply     │
   │      if local changed   -> conflict  │
   └──────────────────────────────────────┘

The loop is built on four guarantees that we discuss in depth elsewhere:

  1. Function-level granularity. See function-level sync for why this is the right unit and how the cadence was chosen.
  2. WAL-backed durability. See WAL architecture. If your laptop dies mid-push, nothing is lost.
  3. Cross-branch impact surfacing. See impact alerts. If a teammate on another branch modified a function you depend on, you find out within seconds.
  4. Conflict ceremony only when it matters. See sync conflicts. Most edits land silently. Real conflicts are rare and meaningful.

When to use it

Live Sync is designed for two specific workloads, and you should turn it on for both:

  • Paired human + AI work on the same repo. You are refactoring, Claude is writing tests, Cursor is fixing lints. Without Live Sync, the three of you drift apart in seconds. With it, you converge.
  • Small teams (2–15) inside the same Mothership. You do not need a PR to know Alice renamed a helper. You see it land.

When not to use it

Live Sync is not magic, and there are workloads where it is a net negative:

  • Very large repos (>250k functions) on slow disks. The scan pass becomes the bottleneck. Use selective sync to narrow the set, or run Live Sync only for the module you are actively inside.
  • Secrets-heavy codebases without a clear public/private split. Secrets are masked by default (see Live Sync privacy), but if your entire codebase is secrets-adjacent, you probably want manual push instead.
  • Open-source repositories with untrusted drive-by contributors. Live Sync is for your inner circle of peers. It is not a drop-in replacement for pull requests from strangers.

Gotcha: Live Sync is not a substitute for commits. Your WAL is durable, but WAL entries eventually roll off. Commit, push, and review like normal. Live Sync closes the short gap between saves; Git closes the long gap between features.

Config

Live Sync is off by default on a fresh install. To turn it on:

aura live sync enable
aura live sync status

The config lives in .aura/live.toml:

[live]
enabled = true
cadence_ms = 5000            # 5 second sync window
mothership = "ws://mothership.local:7070"

[live.selective]
include = ["src/**/*.rs", "src/**/*.ts"]
exclude = ["**/test/**", "**/*.gen.rs"]

[live.privacy]
mask_secrets = true
skip_private_functions = false

See selective sync and Live Sync privacy for a full field-by-field walkthrough.

Troubleshooting

If aura live sync status shows mothership: offline for more than a minute, Live Sync has quietly degraded to local-only mode — changes still go to the WAL, but nothing leaves your machine. Run aura doctor live to diagnose, then see sync troubleshooting for the common failure modes.

A day in the life

Concretely, here is what using Live Sync looks like on a small team.

At 09:00 Alice opens the repo and starts on a billing refactor. Bob is at the airport on his phone. Claude (a Claude Code agent) is running in Alice's terminal helping her write tests. The Mothership is on a box in the office.

  • 09:02 Alice edits billing::compute_tax. Five seconds later, her push hits the Mothership. Bob's phone sees nothing (he is not running Aura), but Claude's aura_status on the next tool call includes the new body. Claude updates the test it was writing to match the new signature.
  • 09:04 Claude adds a new test function test_tax_region. It pushes. Alice's status banner updates: "claude-1 added test_tax_region 2s ago".
  • 09:07 Bob lands, opens his laptop, aura live sync pull runs automatically on first save. He pulls 14 function updates Alice pushed while he was in the air and 3 from Claude. The apply is silent — no conflicts, because Bob had not edited any of them.
  • 09:15 Bob edits billing::generate_invoice — a function that calls compute_tax. At the same moment, Alice edits compute_tax again, this time changing its signature. Five seconds later, Bob gets an impact alert: "signature of compute_tax changed on Alice's branch; your caller generate_invoice depends on it." He updates and pushes. No PR ceremony. No rebase.
  • 09:30 A collision. Alice and Bob both edit billing::compute_tax within the same 5-second window. Live Sync raises a conflict on Bob's side (he pushed second). The interactive picker shows both bodies. Bob picks the merged option, reviews the result, saves. That function converges.

The whole flow is designed so that the boring cases are invisible (silent apply, zero ceremony) and the interesting cases are surfaced clearly (conflict picker, impact alert). There is no middle ground of "maybe something happened; better check."

Relationship to Git

Live Sync is not a replacement for Git. Git is still where commits, branches, PRs, and long-term history live. Live Sync is the intra-session layer — the minutes-to-hours scale where Git is too heavy. You commit to Git when you are done with a logical unit of work. You Live Sync continuously so that "done with a logical unit of work" is a coordinated moment across the team.

Think of it this way: Git is for the work you want to remember. Live Sync is for the work you want others to see now.

See Also