Polycode data lifecycle
Polycode stores conversations, files, and credentials locally on your Mac, and does not require an account. Providers (Anthropic, OpenAI, Google, OpenAI-compatible endpoints, Apple Foundation Models, plus enterprise cloud gateways: Vertex AI, AWS Bedrock, Azure OpenAI) are reached directly from your Mac using your own credentials, which are stored in your macOS Keychain. No Polycode-operated server stores or relays user content. Polycode sends anonymous usage metrics and automatic crash reports by default — see Diagnostics below for the full list of categories and the one-click opt-out.
Polycode is a sandboxed Mac App Store application that uses an App Group container — a sandbox affordance for cross-target shared storage — for its primary state (sessions, messages, model metadata, caches, indexes, and user preferences):
~/Library/Group Containers/group.com.izzo.polycode/Library/Application Support/Polycode/
A small number of macOS-managed preferences (@AppStorage toggles) live alongside the app’s per-app sandbox at ~/Library/Containers/com.izzo.Polycode/. Everything else Polycode reads or writes lives under the App Group path. That structure shapes the four workflows below. In-app controls for these actions are tracked for a subsequent release; this article describes the manual-equivalent procedure available today.
Wipe
To remove every piece of local Polycode state from your Mac:
- Quit Polycode (⌘Q).
- In Finder, open
~/Library/Group Containers/(use⇧⌘Gand paste the path). - Delete the
group.com.izzo.polycodefolder. The Finder trashes it like any other folder. - In Finder, open
~/Library/Containers/. - Delete the
com.izzo.Polycodefolder. This removes the macOS-managed Preferences plist alongside the app’s per-app sandbox. - Empty the Trash if you want the bytes gone immediately.
Alternatively, drag Polycode.app to the Trash. macOS deletes the per-app sandbox container alongside the app, but does NOT delete the App Group container — that persists across re-installs because it is shared with other targets in the developer team. To wipe the App Group data, delete ~/Library/Group Containers/group.com.izzo.polycode/ separately.
Keychain note: API keys and MCP auth tokens are stored in the macOS Keychain, not inside either container. Deleting the containers does not remove them. To clear credentials, open Polycode → Settings → Privacy → Clear all credentials (a single action that removes every provider key from the Keychain). Keychain items are also wiped when you uninstall the app via macOS’s standard uninstall flow.
Back up
Polycode’s App Group container is an ordinary directory. To back it up manually:
- Quit Polycode.
- In Finder, open
~/Library/Group Containers/(use⇧⌘G). - Copy the
group.com.izzo.polycodefolder to your backup location (external drive, cloud folder, archive utility — whichever you prefer). - (Optional) Also copy the small
~/Library/Containers/com.izzo.Polycode/folder if you want to preserve macOS-managed Preferences (@AppStoragetoggles). - Relaunch Polycode. Your session resumes unchanged.
What’s inside the copy:
Library/Application Support/Polycode/Store/— the SwiftData store and the GRDB-backed FTS5 index (which lives inside the same SQLite file).Library/Application Support/Polycode/Caches/— model-metadata caches, large-result cache, approval-session cache. Regenerated on demand if missing.- The
UIStateBlobthat holds most user preferences (pinned models, fan-out opt-outs, tool approval defaults, etc.) lives inside the SwiftData store at the path above. - The optional per-app sandbox at
~/Library/Containers/com.izzo.Polycode/— standard macOS preferences plist for@AppStorage-backed toggles. The full path is<sandbox>/Data/Library/Preferences/com.izzo.Polycode.plist.
Time Machine, Arq, Backblaze, and any other backup tool that walks the user library captures both paths automatically.
Restore
To restore a previous backup onto the same or a different Mac:
- Quit Polycode.
- In Finder, open
~/Library/Group Containers/(use⇧⌘G). - Delete the current
group.com.izzo.polycodefolder (or move it aside). - Copy your backup
group.com.izzo.polycodefolder into~/Library/Group Containers/. - (Optional) If you also backed up
~/Library/Containers/com.izzo.Polycode/for the Preferences plist, replace that folder too. - Relaunch Polycode.
Version compatibility: the SwiftData schema version is embedded in the store. Polycode runs its migration plan on launch when it detects an older schema, so restores from an older Polycode into a newer Polycode work as long as the schema remains within the supported migration window. Restoring a newer container into an older Polycode is not supported — install the matching version or newer.
Keychain credentials do not travel with the container. On a restore (or a new Mac), re-enter your provider API keys via Settings → Providers.
Migrate between Macs
Polycode has no cloud sync and no account system, so migration is exactly the backup + restore workflow above, run across two machines:
- On the source Mac: quit Polycode, copy
~/Library/Group Containers/group.com.izzo.polycode/(and optionally~/Library/Containers/com.izzo.Polycode/for the Preferences plist) to portable storage (AirDrop, external drive, file-sync folder). - On the destination Mac: install Polycode from the App Store (or your TestFlight build), launch it once to let macOS create an empty App Group container, then quit.
- Replace
~/Library/Group Containers/group.com.izzo.polycode/on the destination Mac with the copy from the source. If you brought the per-app sandbox too, replace~/Library/Containers/com.izzo.Polycode/as well. - Launch Polycode on the destination.
- Open Settings → Providers and re-enter your API keys (they live in the per-Mac Keychain and do not travel with the container).
Because nothing Polycode needs lives outside these two containers, this is the complete migration procedure.
Attachments
Polycode lets you attach files to a draft in two ways: drag-drop or paste from anywhere on disk (an inline-content attachment), or pick a file from a project you’ve added in the sidebar (a scoped-file attachment). The handle and byte semantics differ:
- Scoped-file attachments persist as a handle — Polycode stores the project bookmark + the file’s path inside the project, not its bytes. When you submit, the bytes are read fresh from disk through the bookmark and sent to the providers. When you regenerate that exchange later, the file is re-read again from disk, so the providers see whatever the current on-disk content is — not a snapshot from the original submit.
- Inline-content attachments persist as bytes — drag-dropped or pasted file content is stored locally inside the conversation’s
promptAttachmentsDatasidecar in the SwiftData store. This is the only handle Polycode has, so the bytes round-trip exactly on regenerate.
Pressing submit transmits attachment bytes off your device. Each attached file is sent to every LLM provider that is enabled in the current fan-out and to the primary provider’s synthesis call. A single chip therefore produces N provider calls + 1 synthesis call, all carrying the bytes. Anthropic / OpenAI / Google receive the file as a native content block (document / input_file / inlineData); OpenAI-compatible endpoints (Ollama / LM Studio / xAI / OpenRouter / Custom) receive a banner-wrapped text fallback and reject non-text binary attachments. Apple Foundation Models receives a text projection of the file inline in the prompt.
To remove a persisted attachment from the local store, delete the conversation that contains it (Settings → Privacy → Clear all data, or the per-session delete in the sidebar). Wiping the sandbox container per Wipe above also removes every attachment Polycode has stored.
Project context
When a session is bound to a project (the project chip on the chat titlebar is set), Polycode synthesizes a project context block and sends it as the system prompt on every chat submit and every regenerate. The block contains the project’s name, its absolute root path, today’s date, your macOS marketing version, your project’s excludedPatterns, a depth-2 directory listing, and the contents of any recognized convention files at the project root: AGENTS.md, CLAUDE.md, GEMINI.md, .cursorrules, every *.md file under .cursor/rules/ (alpha-sorted), CONVENTIONS.md, with README.md as a fallback only when none of those exist. The directory listing honors your project’s excludedPatterns; the convention files do not (a stray *.md exclusion does not silently disable an authored AGENTS.md).
The block is sent to every LLM provider you have configured — Anthropic, OpenAI, Google, OpenAI-compatible endpoints, and Apple Foundation Models — as part of the standard fan-out + synthesis flow. It is not sent to PostHog or Sentry. The block is rebuilt on every submit (no cache), so edits to your context files take effect on the very next turn. Per-model systemPromptOverride settings (Settings → Providers → Provider Detail → advanced drawer) take precedence: when you’ve set an override, the project context block is replaced by your override for that model and is not transmitted.
To opt out per-project, toggle off “Inject project context into chats” under Settings → Projects. The opt-out persists locally in the same SwiftData container as the rest of your settings; subsequent submits on sessions bound to the opted-out project send systemPrompt: nil (or the per-model override if set).
Usage
The Settings → Usage tab summarizes how much you’ve spent across providers and where that spend is concentrated. It is a read-only dashboard computed entirely on your Mac:
- Local-only computation. Cost numbers come from
ProviderResponseRecord.costUSDcolumns inside the local SwiftData store. Each fan-out peer’s cost is computed once at exchange time from the model’s listed pricing × the tokens that peer used, and persisted alongside the response. The Usage tab sums these existing columns over the selected range. Nothing leaves your Mac to render the dashboard. - Snapshot-at-exchange-time pricing. Costs reflect what the model’s pricing was when the exchange ran, not what current pricing implies. If a provider changes their pricing later, your historical exchanges keep the dollar figure they were originally quoted at — a stable record of what you actually paid.
- Reset baseline rebases display, not data. The
Reset baseline…button writes a new timestamp underUIStateBlobkeypolycode.usage.baselineDateand the dashboard’s range filters clamp their start to that timestamp. Past exchanges and per-peer cost records are not deleted. They remain in the store and stay accessible from the sidebar; only the dashboard’s totals start counting fresh from the new baseline. To actually delete prior exchanges, use Settings → Privacy → Clear all data, or the per-session delete in the sidebar. - Estimates, not bills. The dashboard’s footnote names the limitation explicitly: numbers are estimates from your provider’s listed pricing at exchange time. Actual provider billing may differ (cache pricing tiers, enterprise discounts, billing-period rounding). The dashboard is a directional view, not a substitute for your provider’s billing console.
Cloud gateways
Polycode supports three enterprise cloud gateways alongside the direct provider APIs: Vertex AI (Google Cloud), AWS Bedrock (Amazon), and Azure OpenAI Service (Microsoft). These are configured under Settings → Providers → Cloud Gateways with one row per per-account / per-project / per-resource instance. A user may configure many instances per gateway.
Credential flow. The credential you provide (Vertex service-account JSON, AWS access-key + secret + optional session-token, or Azure resource api-key) is read once at add-instance time, JSON-encoded, and stored in your macOS Keychain under a per-instance slot (cloud.<uuid>.credential). The credential is never logged, never sent to a Polycode-operated server, and only used to mint per-request auth headers against the cloud you configured:
- Vertex AI uses your service-account JSON to mint short-lived OAuth2 access tokens via
oauth2.googleapis.com/token(RFC 7523 JWT-bearer assertion grant). The minted token is cached in memory for ~1 hour and refreshed before expiry. - AWS Bedrock uses your access-key pair to sign each request via AWS SigV4 (HMAC-SHA256 chain). Nothing is cached server-side; the signature is recomputed per request.
- Azure OpenAI Service uses your resource api-key as the
api-keyheader on each request. No token exchange — the api-key IS the auth material.
Where prompts go. Cohort prompts flow only to the cloud gateway you configured. Vertex AI prompts go to <region>-aiplatform.googleapis.com. Bedrock prompts go to bedrock-runtime.<region>.amazonaws.com. Azure prompts go to <resource>.openai.azure.com. Polycode does not relay cohort prompts through any Polycode-operated server.
Diagnostics scope. Cohort traffic is out of scope for the diagnostics pipeline. Disabling diagnostics under Settings → Privacy → Diagnostics has no effect on cohort prompt routing, because cohort prompts never go through PostHog or Sentry to begin with — they go directly to the cloud you configured. See Diagnostics for what diagnostics actually does cover.
Removing a cohort instance. Settings → Providers → Cloud Gateways → Remove deletes both the CloudInstance row from your local config and the matching cloud.<uuid>.credential slot from your Keychain. A launch-time orphan-reconciliation pass also runs every time Polycode starts, so a credential whose CloudInstance was deleted by another path is cleaned up on next launch.
Diagnostics
Polycode sends two kinds of anonymous diagnostics by default to help improve the app:
- Product interaction events (PostHog). A curated allowlist of events — app launch, provider added, submit started, submit failed with an error category, tool invocation with an approval outcome, and similar. Each event carries only bucketed or categorical data (provider kind as one of
anthropic/openai/google/openaiCompat/foundationModels, latency bucket, error code, token counts). Chat content, model responses, file contents, file paths, session titles, custom endpoint names, custom endpoint URLs, custom model IDs, and personal identifiers are never emitted. - Crash and performance diagnostics (Sentry). Crash stacks, hang diagnostics, and CPU exceptions delivered by the system’s MetricKit subsystem. Personal identifiers and file paths are removed before transmission; basic-auth URLs, API-key shapes, emails, and home-directory prefixes are scrubbed by a pre-send hook.
Neither pipeline collects your IP address, your macOS username, your email, a device serial, or any account identifier. The product-interaction pipeline uses an anonymous install UUID generated on first launch so retention can be measured; that UUID is scoped to PostHog only — it is never passed to Sentry, and it never appears in crash payloads.
Opt out. Open Settings → Privacy → Diagnostics. Two toggles — “Share anonymous usage metrics” and “Share crash reports” — each default to on and can be flipped off independently. Turning a toggle off flushes in-flight events and stops the corresponding SDK from sending further data; turning it back on resumes collection with the existing install UUID.
Reset diagnostics ID. The “Reset diagnostics ID” button in the same section generates a new anonymous UUID, discarding the prior one. Use this if you want to break continuity of anonymous analytics for this install — the SDKs re-initialize and the next event carries the new UUID.
Debug builds never phone home. Development builds running under Xcode short-circuit both SDKs regardless of toggles, so engineer activity does not pollute production diagnostics.
Data residency (current release-runner configuration). The v1.0 release runner injects PostHog US cloud (https://us.i.posthog.com, the default POLYCODE_POSTHOG_HOST in Config/Shared.xcconfig) and Sentry US cloud as the diagnostic ingest endpoints. Both DSNs ship empty in the committed Shared.xcconfig and are CI-injected per build, so other release-runner configurations — or self-hosted builds — may target different clouds (TelemetryConfig.swift accepts US or EU Sentry DSNs and any PostHog host); this paragraph describes what the v1.0 release runner does, not a hard product guarantee. The retention and deletion policies of PostHog and Sentry govern already-shipped data; the in-app Reset diagnostics ID button changes future identifier association, not historical data. EU-cloud migration is tracked as a follow-up item under 2026-04-22-telemetry-posthog-sentry in TODOS.md.