offline

One window instead of forty — and you can see what is running.

A native Mac application that collapses the pile a working day accumulates — several terminals, several browser windows, PDFs, images — into one window organised by project. Coding agents run inside it, so their processes can be seen, paused and killed rather than abandoned.

Download beta Install & uninstall
macOS 12 Monterey or later · Intel & Apple Silicon
unsigned beta — see install notes
beta 0.1.0 · built 2026-08-19

The problem it solves

Claude keeps opening a million Chrome browsers and a million local servers that it then abandons.

Those processes are not only lost — they cost something, and you cannot see what. An application that owns the processes but hides their cost has solved half the problem. offline owns them and shows the bill.

Every feature is measured against one test: does it reduce the number of windows, or make what is running more visible? If it does neither, it was cut.

What is in the window

Projects are foldersAlways. Add one by choosing a path — there is nothing else to define, and no project database to lose.
Sessions are live agentsClaude, Codex, OpenCode or a shell, running in the project's folder. Several per project. None of them ever start on their own — you press the button.
Panes are things you openFiles, PDFs, images, a live web preview. A pane cannot be abandoned, because it is not alive — which is why it is not counted as a session.
Pause, close, and the billSee what offline costs this machine, broken down by which session is responsible. Pause a project to free CPU; close a session to end the process and get its memory back.
Restore on openReopening a project brings back its panes and restarts its sessions. Visible, interruptible, and honest that a restored session is a new conversation.
Home and work stay apartTwo vaults, two sets of projects, never merged. Switching is a deliberate act, never inferred.
Milo is a modeThe doing half of the system loads inside this window when it exists, with a live bridge to offline's own projects and sessions.
The chat is its ownA column on the right, present in every mode, surviving a project switch. Ask and it searches the vault; type a thought and it is captured. Terminal sessions are not this chat.
MAPOne graph over the whole vault in three renderings. Every edge is a link somebody wrote or an exact code match — nothing is inferred by similarity.

Four states, legible at a glance

A project's state is recomputed from the world, never stored as a flag. It is broken because its folder is missing right now.

running — one or more live sessions
paused — deliberately suspended
detached — its drive is unplugged
broken — its folder has moved or gone

A missing folder is searched for, then reported, then left alone. A match is offered, never assumed — same-named folders are not the same folder. Nothing is destroyed: the layout, the manifest and the history all survive. It asks once, then stops, because a nag is the fastest way to teach someone to ignore warnings.

The layout is prearranged, and rearrangeable

Each project opens with a sensible default: the agent always visible on the left, whatever you need to see on the right. Drag a tab to the other half to rearrange it, and the arrangement saves with that project — never globally.

The defaults are the feature, not a placeholder. offline reads what a folder actually contains and opens accordingly: a repository with a dev server does not want the same arrangement as a folder of documents. Detection is deliberately conservative — a wrong guess is worse than no guess, because it teaches that the app is unpredictable — and every project can be reset to its default.

In this build, and deliberately not

ShippingWhat it does
Project railFolder-backed, four states, context menu for pause, resume, reset and locate.
SessionsFour agent kinds through a login shell, so your PATH resolves the way it does in Terminal.
Restore on openBrings back panes — tree, documents, preview address, arrangement. Instant, and it starts nothing.
Nothing runs unaskedNo session starts on launch, on opening a project, or by restore. An idle offline runs no timer: the readout samples only while something is live and stops when the last session ends.
Closing a sessionEvery tab has a close control. Closing a session ends the process and returns its memory — the count going down means the process actually went away.
Pause / resumeSuspends the process group. CPU drops immediately; memory returns when a session ends — and the app says exactly that rather than implying a paused session is free.
Resource readoutLive, attributed by cause, and it warns before the fans do.
ViewersFile tree, markdown, PDF, images, live web preview.
MAPRings, force and groups over one data model. Filter by kind. Ask what connects two nodes.
Chat sidebarStandalone conversation, always present, history kept as plain markdown in the vault. Retrieval works with no network and no key. No model is connected, and none is faked.
CaptureThrough the chat; writes plain markdown into the vault's _inbox. One place to type, not two.
Publishes to MiloThe scrubbed index goes to Milo's plan tier so the phone can search the brain with this Mac asleep. Refuses without a sync code, refuses a partial index, and names the workspace rather than defaulting it.
Milo can drive itshowMode hands the window back; captureFile gives Milo's photo capture somewhere to put bytes. Filenames from a phone are treated as hostile.
Volumes remember themselvesA folder on a drive is remembered by the volume's identity, not its path — so it resolves itself whenever the disk is back, at whatever mount point macOS gives it. A same-named different disk does not match. Connecting never starts an index; it offers.
Nothing blocks on a shareEvery filesystem call that could touch a network mount has a deadline. "Did not answer" is a distinct state from "gone" — one means plug it in, the other means it is missing.
Slow sources are honestA vault on a network mount takes minutes. Live file count, and Stop actually stops. A cancelled index is discarded — a partial index is not a small one, it is a wrong one.
Tested131 checks over the scrubbed index, sensitivity inheritance, search withholding, the orphan-task rule, merge and tombstones. Packaging refuses to proceed if they fail. The first test written found a live leak.
Headless modes--scrub builds the index without a window; --find searches from the command line. How the withholding rule is tested rather than asserted.
Scrubbed indexTitles, paths, tags and links only. No contents, nothing sensitive, and it verifies itself before writing.
SynthesizeDeferred. Overlap-finding across projects needs the index first.
A model behind the chatRetrieval ships. Reasoning needs a decision about keys and endpoints that belongs to Milo, so the chat says so rather than inventing answers.
Phone clientNot built here, by agreement. The phone surface for the whole system is Milo; offline exposes the scrubbed index for it to read.

Install and uninstall

Drag offline.app to /Applications. That is the install.

Two prompts on first run. Both are expected.

1 · Gatekeeper. This beta is not notarised, so macOS refuses the first double-click. Right-click the app ▸ Open ▸ Open, once. After that it opens normally. Notarisation needs a paid Apple developer account.

2 · Documents access. macOS asks whether offline may read your Documents folder. It needs this to index a vault kept there. Nothing is uploaded — the index stays on the machine, and nothing is published to any address.

Uninstalling leaves nothing behind

Drag offline.app to the Trash, then:

  rm -rf ~/Library/Application\ Support/offline

That is everything. There is no daemon, no login item, and no background server — closing the window ends the process, and that is a design rule rather than an accident.

Your vault is untouched. offline reads and writes plain files in a folder it does not own, the way Obsidian does, so your editor and your terminal stay valid views of exactly the same data. There is no app-only datastore, which means there is nothing offline can lose that nothing else can read.

Two lives, one window

HOME and WORK are separate vaults and separate projects. They are never merged and nothing crosses between them. Switching is a button you press — offline never infers which life you are in from what you happened to open.

Each side remembers its own vault, its own projects, and the project you had open. Sessions running on the side you leave keep running; they belong to their project, not to the window, and come back exactly as they were.

Milo slots in

offline is the building half of a two-application system. Milo — the doing half — owns the plan, the timeline and the phone. It has not been built yet, and offline is already ready for it.

MILO is a mode in this window, not a link to a browser. Point it at wherever Milo lives and it loads there:

dev:http://localhost:5173     a dev server, while Milo is being built
build:/path/to/dist           a built folder, no server needed
https://milo.wokegod.world    the deployed site

Milo gets window.offline injected before its own scripts run — no integration work on either side:

CallReturns
projects()the shared Project records, with live state
sessions()what is running right now
startSession(id, agent)starts one — Milo may drive offline
pauseProject(id)frees the machine
index()titles, paths, tags, links — never contents
capture(text)writes a plain file into the vault
createTask(title, goal)queued; orphan tasks are refused
resources()what this window costs, by cause
status()live or snapshot, and how stale

A session Milo starts appears in the window. One started remotely that showed up nowhere would be precisely the abandoned process this application exists to abolish.

The scrubbed index

offline publishes an index of the vault that carries titles, paths, tags, modified dates and the link graph — and no file contents at all. Anything marked sensitive, at file level or source level, is withheld entirely rather than redacted.

{
  "schema":   "offline.scrubbed-index/1",
  "counts":   { "files": 0, "withheld": 0, "links": 0 },
  "fields":   ["path","domain","title","modified","tags"],
  "files":    [ ["path/to/doc.md","20-Projects","A Title",1755561600,[]] ],
  "links":    [ ["a.md","b.md","wikilink","[[b]]"] ]
}

Exclusion is inherited and cannot be overridden upward: a sensitive file inside a normal folder stays sensitive, and marking a source sensitive marks everything in it. The safe answer always wins. A verification pass re-reads the finished index and refuses to write it if a sensitive path appears — that check should always pass, which is exactly why it runs.

No vault index is published at this URL. The generator ships in the app and writes locally. Putting a real index on a public address is a separate decision about who may read it, and it has not been made — so it has not been done.

Built for a ten-year-old machine