Application code lives in this repository. Runtime user data never does.
Everything the application writes at runtime lives under the Windows per-user application-data directory:
%APPDATA%\Local-Agent\
which on a typical machine is:
C:\Users\<you>\AppData\Roaming\Local-Agent\
src/main/paths.ts (resolveUserDataPaths) resolves this as
join(app.getPath('appData'), APP_DATA_DIR_NAME) — the platform's per-user
application-data root, joined with the constant folder name — rather than
Electron's app.getPath('userData'), whose folder name instead follows
app.name. Deriving it explicitly means the folder is always exactly
Local-Agent, regardless of how app.name resolves. Nothing here is ever a
user-supplied or renderer-supplied path.
Current state. Milestone 3 implements
settings.json: path resolution, defaults on first launch, strict validation, fail-safe fallback on a corrupt or unreadable file, and atomic writes. Milestone 4 implementslogs\audit\: an append-only, redacting, UTC-daily-rotating JSONL writer. Milestone 5 implementspermissions\policy.jsonloading: the same fail-safe pattern as settings, plus a pure decision engine and executor gate that consult it. Milestone 6 implementsstate\emergency.json: the same fail-safe pattern again, with one deliberate difference from the other two — a missing file and a corrupt one resolve to different states (disengaged vs. engaged), not the same safe default — plus atomic engage and reset writes. Milestone 7 implementssecrets\secrets.enc: encrypted with the synchronoussafeStorageAPI (Windows DPAPI), never a plaintext fallback. Phase 2 Milestone 8 implementsmemory\: one file per scope, each written with the same fail-safe load and atomic write pattern, plus the session scope which is never written at all. Its loader distinguishes a missing file from a corrupt one internally, the same waystate\emergency.json's does, but — unlike the emergency state — both collapse to the identical safe answer for every caller (no key present), since there is no "fails engaged" equivalent for a credential store. Milestone 7 also wiressettings.jsonandsecrets.encto real IPC channels for the first time:settings:get/settings:updateandsecrets:status/secrets:write/secrets:clear, all through the unmodified Milestone 5 permission pipeline.permissions\policy.jsonandstate\emergency.jsonare still read fresh on every one of those calls but still have no writer or channel of their own reachable from the running application. No directory or file is created merely by reading; a directory is created lazily, only on the first write.
%APPDATA%\Local-Agent\
├── settings.json non-secret settings
├── permissions\
│ └── policy.json permission policy
├── agents/
│ └── profiles.json agent profiles (no credential, ever)
├── workflows\
│ └── workflows.json workflow definitions (manual trigger only)
├── secrets\
│ └── secrets.enc encrypted credentials
├── logs\
│ └── audit\
│ └── audit-YYYY-MM-DD.jsonl append-only audit trail, one file per UTC day
├── state\
│ └── emergency.json emergency-stop state
└── memory\
├── personal.json personal memory (never a credential)
└── projects\
└── <key>.json one file per approved project
| Path | Contains | Notes |
|---|---|---|
settings.json |
Assistant name, user name, language, provider selection, hasApiKey flag |
[implemented, M3, wired to IPC in M7] Never a credential. Strict schema; unknown keys rejected; written atomically; hasApiKey reconciled against secrets.enc on every read and write. |
permissions\policy.json |
Permission rules | [implemented, M5] Human-readable and human-editable. Cannot widen the model beyond the code-enforced floor, even if hand-edited to try. |
agents\profiles.json |
Agent profiles | [implemented, P2 M7] Human-readable and human-editable. Holds user profiles only — built-ins are read from reviewed source on every load and are never written here, so a hand edit cannot redefine one. Cannot grant a permission: its decision vocabulary has no allow. Declares no field capable of holding a credential. |
workflows\workflows.json |
Workflow definitions | [implemented, P2 M9] Human-readable and human-editable. Cannot grant a permission, cannot widen the agent profile it selects, and cannot start itself: its trigger enum has one member, so a hand edit claiming a schedule fails validation and the whole document is discarded. Declares no field capable of holding a credential. |
secrets\secrets.enc |
API keys | [implemented, M7] Encrypted with the synchronous safeStorage API (Windows DPAPI). Never leaves the main process in plaintext; presence, not content, is all any IPC channel reports. |
logs\audit\ |
One JSON object per line | [implemented, M4] Append-only writer, called for real since M7's IPC channels. Records denials and rejected confirmations exactly like a success. |
state\emergency.json |
Emergency-stop state | [implemented, M6] Missing file on first launch means disengaged. Malformed or unreadable existing file means engaged. Written atomically; not called from the running app yet. |
memory\personal.json |
Personal memory records | [implemented, P2 M8] Human-readable and human-editable. Plain JSON, deliberately — but it holds notes about a person, so see docs/security-model.md limitations 0.5 and 0.6. Declares no field capable of holding a credential, and credential-shaped values are screened at the write boundary on a best-effort basis. |
memory\projects\<key>.json |
One approved project's memory records | [implemented, P2 M8] The file name is a truncated SHA-256 of the project's canonical root path, never the path itself: a directory listing of %APPDATA% should not disclose which projects someone has opened. Reading or writing one requires that project to be approved in the current session. |
| (none — Windows automation) | — | [implemented, P2 M10] Windows automation adds no file here. Its tool registry is fixed in reviewed source (shared/automation/registry.ts), not user-editable configuration, so there is nothing for it to persist. See docs/phase-2-automation.md. |
Session memory has no entry above, and that is the point. The third memory scope is held in the main process for the lifetime of one run of the application and is never written anywhere. Closing Local Agent ends it; there is no file to find afterwards, and no code path in
src/main/memory-store.tsthat could create one.
Keeping these apart means no single file mixes concerns, and a mistake in one area cannot leak into another. Settings can be shared or inspected without exposing credentials. The audit log can be read without touching the policy that produced it. Backing up settings does not back up secrets that would be useless on another machine anyway.
- Nothing under
%PROGRAMDATA%or any machine-wide location. Local Agent is per-user. - No registry keys.
- No temporary files outside the application-data directory.
- No data in the repository working tree.
Do not create runtime user data inside the repository. .gitignore covers the
common accidents (/data/, /user-data/, /logs/, /secrets/,
/credentials/, .env, *.db), but the rule is the point, not the safety
net.
Those directory rules are anchored to the repository root on purpose. An
unanchored secrets/ would also match src/main/secrets/, a real module of
the application, and would silently drop it from version control. Key files
(*.key, *.pem, *.pfx, *.p12) stay unanchored and are ignored wherever
they appear.
If you need a scratch file while developing, put it outside the repository or in a git-ignored path, and never put a real credential in it.