# Charles Operating System

**Status:** architecture/design proposal, 2026-07-11  
**Scope:** an evolution of the existing Charles Dashboard and iPhone PWA; additive migration, no current page replacement.

## Non-negotiable foundation

The Charles Dashboard already built in this repository is the canonical starting point and product foundation. Charles OS must not become a parallel generic dashboard or a visual reset.

Preserve and reuse:

- the warm paper/cream Met/magazine visual language, restrained purple accent, calm rounded cards, large editorial art/wisdom/music hero, and TV-readable type;
- the existing Start/Now/Planning/Year/Strategy/Coach/Mindset/Music/Art/Wisdom/World/Comms/Finances/Holiday Map/System/Tokens specialist pages and direct URLs;
- the existing Python server, live APIs, data files, Tailscale route, TV mode, launch agent, Big Reset recovery process, and screenshot contract;
- the principle that the Dashboard is a control surface Bart drives, not an invisible autopilot.

Charles OS is therefore an **operational layer inside and above the Dashboard**: shared Today/Inbox/Act/Plans/Life navigation, action/approval state, and cross-domain summaries that open the existing specialist cockpits. Migration should improve the current Dashboard incrementally. The experimental `/os.html` may prove the shell, but it is not a new standalone product and must visually and technically converge back into the Dashboard.

## 1. Product definition

Charles OS is not another dashboard page. It is one personal operating model rendered differently by device:

- **Desktop/TV:** broad awareness, planning, comparison, and deliberate control.
- **iPhone:** the next action, quick capture, approvals, and live context.
- **One state model:** both surfaces show the same resources, freshness, actions, approvals, and receipts.

The existing Dashboard implementation is the strong shell: static HTML/CSS/JS, a Python-stdlib server, no build step, loopback binding, private Tailscale access, and working APIs. It remains the canonical cockpit. Charles OS adds shared action and attention behavior to it rather than replacing it.

### Shared product model

Charles OS has four layers:

1. **Resources — what is true:** people, tasks, events, trips, accounts, health observations, devices, media, and system checks.
2. **Attention items — what needs Bart:** actions, decisions, replies, warnings, and failed operations.
3. **Intents and execution — what Charles can do:** a bounded command with a preview, safety tier, allowed operation, and result.
4. **Views — how a device presents it:** Now, Inbox, Plan, and domain Spaces. Views never own canonical data.

Core entities:

| Entity | Purpose | Minimum fields |
|---|---|---|
| `ResourceRef` | Stable pointer to a source object | `system`, `type`, `id`, `url` |
| `ActionItem` | Something Bart can do now | `id`, `kind`, `domain`, `title`, `state`, `priority`, `due_at`, `source`, `available_actions` |
| `Approval` | Exact proposed external effect | `id`, `tier`, `preview`, `effects`, `expires_at`, `state`, `idempotency_key` |
| `Receipt` | Immutable execution result | `id`, `approval_id`, `status`, `executed_at`, `effect_ids`, `error`, `undo_until` |
| `Freshness` | Trust metadata on every derived view | `mode` (`live/cache/disabled/error`), `fetched_at`, `stale_after`, `message` |

States are explicit: `open → pending_approval → executing → succeeded|failed|cancelled|expired`. Cached or failed data must never be translated into “nothing to do” or “free day.”

## 2. Life domains

Domains are stable filters over shared entities, not separate databases.

| Domain | Owns / answers | Existing foundations |
|---|---|---|
| **Family** | household commitments, children, school, shared calendar, family tasks | Planning/Year family events, Family task list |
| **People** | relationships, conversations, birthdays, promises, follow-ups | People & Social card, Comms APIs/pages |
| **Work** | JET, Highgrounds, work calendar, projects, decisions | task-list slugs, Strategy, Planning weekly strips |
| **Money** | position, movement, bills, payments requiring approval | Finances, `/api/finance/shares` |
| **Home** | maintenance, shopping, media, rooms, later home controls | Home/Shopping tasks, Sonos APIs |
| **Health** | body, training, sleep/energy, mindset, appointments | Coach, Mindset, `/api/coach` |
| **Travel** | trips, reservations, documents, packing, changes | Holiday Map, travel events/tasks |
| **Culture** | music, art, wisdom, world, saved “good stuff” | Now hero, Music/Art/Wisdom/World APIs |
| **System** | privacy, integrations, recovery, models, cost, source health | System, Tokens, `/api/system`, spend/lane APIs |

A task or event can carry one primary domain plus tags; a person remains one identity shared by Family, People, Work, and Travel. “System” is visible when action is required, otherwise quiet.

## 3. Cross-device information architecture

### Desktop IA

Use a compact persistent shell with these destinations:

1. **Now** — operational home: Do next, Needs approval, Up next, exceptions, live context.
2. **Inbox** — `Decide`, `Reply`, `Triage`, and `History` filters across all domains.
3. **Plan** — day/week/month/year and protected family/health time.
4. **Spaces** — the nine domain views above.
5. **Charles** — type/speak capture; transcript and structured preview before any mutation.
6. **System** — readiness, privacy, integrations, audit receipts, models, and cost.

Desktop has two presentation modes over the same data:

- **Working mode:** actionable cards, filters, keyboard navigation, exact approval previews.
- **TV mode:** glanceable summaries and freshness; hide or de-emphasize writes so casting cannot become an accidental control surface.

Existing page mapping:

- `index.html` remains the ambient **Start**/TV magazine until deliberately retired.
- `now.html` is the future operational **Now**, but currently remains culture-led.
- `planning.html` and `year.html` remain the deep planning surfaces.
- Strategy, Coach/Mindset, Finances, Holidays, Culture pages, System/Tokens become domain detail routes.
- Direct URLs remain valid; no redirect or page rename is required.

### iPhone IA

Target persistent tabs, following the iPhone audit:

1. **Today** — one primary action, approvals, next event, due tasks, exceptional context.
2. **Inbox** — Decide / Reply / Triage.
3. **Act** — prominent centre sheet for task, event, message draft, health log, home/music, trip, or payment preparation.
4. **Plans** — day/week, trips, routines, and goals.
5. **Spaces** — People, Money, Health, Home, Travel, Culture, and System; Family/Work appear contextually and as filters.

The current `mobile.html` shell (`Now · Plan · Charles · Coach · More`) already supplies live task add/complete and voice preview/approve. Migrate labels and grouping in place: Now→Today, Charles→Act, Coach→Health within Spaces, More→Spaces, then add Inbox. Keep action controls within the first 390×844 viewport and move music/status below urgent work unless music is the active intent.

## 4. Universal action and approval inbox

The inbox is a projection, not a new source of truth. Adapters normalize source objects into `ActionItem`s:

- open due/overdue Google Tasks → `kind: action`
- communication needing a reply → `kind: reply`
- voice/capture plan awaiting confirmation → `kind: approval`
- stale source, failed write, system warning → `kind: alert`
- future booking/payment/home proposal → `kind: approval`

Ordering is deterministic and explainable: expired/failed, approvals, overdue, due today, replies, then suggestions. Within a class use due time and explicit priority—never an unexplained AI score.

Every card shows:

- verb-first title and why it is here;
- domain, source, and freshness;
- exact primary effect and safety tier;
- one safe primary control plus More;
- after execution, a receipt rather than optimistic disappearance.

Approval rules:

- approval is for an immutable proposal hash; edits create a new proposal;
- approving one effect never approves a chain of effects;
- silence, navigation, retry, or biometric unlock is not approval;
- approvals expire and execute once via idempotency key;
- the client submits an action/approval ID, never an arbitrary executable URL or shell payload;
- both devices read the same state, so resolving on one removes it from the other.

Initially, pending voice approvals may remain in-session. Before payments, bookings, sends, or background workflows, persist approvals and append-only receipts server-side.

## 5. APIs and data contracts

### Existing compatibility APIs

Use these now, behind a small client adapter:

| Capability | Existing contract | Important behavior |
|---|---|---|
| Tasks | `GET /api/tasks` | `{lists,total,fetched,source}`; `source` may be `cache` |
| Task writes | `POST /api/tasks/add/<slug>`, `POST /api/tasks/complete/<slug>/<id>` | HTTP 200 can contain `ok:false`; always inspect JSON |
| Calendar | `GET /api/year` | may return `ok:false`; do not infer a free day |
| Voice plan | `POST /api/voice-memo` then `/api/voice-memo/execute` | existing preview/explicit-execute pattern to preserve |
| People/comms | `/api/comms/index`, `/briefing`, `/thread`, `/others` | read-only attention sources today |
| Domain/status | weather, coach, finance, Sonos/Spotify, art/wisdom/world, system/spend/lane | preserve existing page contracts |

The `CHARLES_DASHBOARD_DISABLE_GOG` brake already guards Google task/calendar reads and task writes. Keep it as a top-level kill switch. The server is loopback-bound and JSON responses currently allow `Access-Control-Allow-Origin: *`; do not publicly expose it or treat tailnet location alone as authorization for future A3 actions.

### Canonical additive contract

Do not break old endpoints. Add `/api/os/v1` later and normalize server-side:

```json
{
  "ok": true,
  "data": [],
  "freshness": {
    "mode": "live",
    "system": "google_tasks",
    "fetched_at": "2026-07-11T08:00:00Z",
    "stale_after": "2026-07-11T08:05:00Z",
    "message": null
  },
  "errors": []
}
```

Recommended routes:

- `GET /api/os/v1/now`
- `GET /api/os/v1/inbox?filter=decide|reply|triage|history`
- `GET /api/os/v1/spaces/<domain>`
- `POST /api/os/v1/intents/preview`
- `POST /api/os/v1/approvals/<id>/approve`
- `POST /api/os/v1/approvals/<id>/reject`
- `GET /api/os/v1/receipts/<id>`

### First persistent task/action slice — implemented 2026-07-11

The first additive `/api/os/v1` slice is now active without removing legacy task endpoints:

- `GET /api/os/v1/inbox?viewer=bart|elin` returns task attention items, pending approvals, receipts and freshness. The `elin` projection is server-filtered to `shared_family`; it never returns `private_bart` tasks.
- `POST /api/os/v1/tasks/<task_id>/action` accepts bounded actions only: `claim`, `assign`, `snooze`, `clarify`, `reopen`, or `complete`.
- `complete` does not mutate Google Tasks immediately. It creates a persistent A2 Approval with an exact preview, immutable proposal hash, 24-hour expiry and unique idempotency key.
- `POST /api/os/v1/approvals/<approval_id>` accepts `approve` or `reject`; execution produces a persistent Receipt and records failures rather than hiding the item.
- Task workflow metadata, approvals and receipts are stored additively in `data/tasks.db`. Assignment/snooze/clarification changes create receipts too.
- Desktop `/os.html#inbox` and iPhone `/mobile.html#inbox` use the same API and state.

Important prototype boundary: `viewer=elin` proves the privacy projection but is not authentication. The Dashboard remains tailnet-only; separate Bart/Elin identity must be added before treating the viewer selector as access control. Outbound Telegram/WhatsApp alert delivery remains disabled.

Example item:

```json
{
  "id": "task:family:abc123",
  "kind": "action",
  "domain": "family",
  "title": "Complete school form",
  "state": "open",
  "priority": "due_today",
  "due_at": "2026-07-11T17:00:00+02:00",
  "tier": "A2",
  "source": {"system": "google_tasks", "type": "task", "id": "abc123"},
  "available_actions": [{"id": "complete", "label": "Mark complete", "requires_confirmation": true}]
}
```

Contract rules: ISO-8601 with timezone, stable IDs, additive fields, `schema_version`, no domain-specific HTML, no secrets, explicit freshness, semantic HTTP status plus JSON `ok`, idempotency on writes, and a correlation ID connecting intent → approval → receipt. Polling is sufficient initially; add SSE/WebSocket only when persistent approvals genuinely need push updates.

## 6. Safety tiers

| Tier | Meaning | UX and execution rule | Examples |
|---|---|---|---|
| **A0 Observe** | read-only/private computation | automatic; show source/freshness | agenda, weather, balances, health trend |
| **A1 Reversible personal** | bounded low-risk write | one explicit tap; offer undo where possible | add task, like song, log weight |
| **A2 External or hard to undo** | affects records/people or lacks undo | exact preview and explicit confirm | complete task today, create/move event, send message later |
| **A3 Sensitive/high impact** | money, booking, access, health/reputation | fresh step-up approval every time; disclose who/what/when/cost/fees | pay, buy, book, unlock, share health data |
| **A4 Human-only boundary** | irreversible, legal, unsafe, or insufficiently observable | research/draft only; Charles never executes | trades, contracts/loans, medication change, emergency diagnosis, recovery credential changes |

Global controls: same-origin authenticated access before A3, CSRF protection, rate/amount limits, immutable audit receipts, idempotency, timeout/expiry, no bundled approvals, domain kill switches, and a visible emergency stop. Existing GET-triggered mutations are compatibility debt and must not be used by the OS action layer.

## 7. Phased migration without breakage

1. **Phase 0 — Freeze contracts and trust states.** Document current payloads; add client adapters and visible `live/cache/disabled/error` handling. Keep every current page and URL unchanged.
2. **Phase 1 — Parallel desktop prototype.** Add a directly addressable experimental `charles-os.html` only. No nav change, redirect, backend change, or service-worker dependency.
3. **Phase 2 — Shared browser model.** Extract a plain no-build `os-core.js` with fetch wrappers, source adapters, domain mapping, and action-card rendering. Existing pages adopt it one component at a time.
4. **Phase 3 — Add `/api/os/v1`.** Move normalization, persistent approvals, idempotency, and receipts to `server.py`/local storage while retaining every old endpoint as a compatibility layer.
5. **Phase 4 — Desktop shell adoption.** Make the proven prototype the new Now/Inbox shell; keep Start as ambient TV view and Planning/Year/domain pages as deep links. Add the new route to nav only after screenshot and write-safety checks.
6. **Phase 5 — iPhone regrouping.** Re-label/reorder the existing PWA into Today/Inbox/Act/Plans/Spaces. Reuse `/api/os/v1`; version the service-worker cache so old JS cannot submit stale approvals.
7. **Phase 6 — Domain consolidation.** Replace duplicated summaries with shared domain contracts, then optionally archive—not delete—obsolete presentation code after usage and restore checks.

Each phase is reversible: direct legacy URLs remain; old API shapes remain; new functionality is additive; writes stay behind explicit user gestures; TV mode is tested separately; golden screenshots and non-mutating API checks gate release.

## 8. Smallest desktop prototype buildable today

Build **one new, unlinked `charles-os.html`** using only existing APIs. This is safer and smaller than modifying `index.html` or `now.html` before the model is proven.

First viewport:

- **Do next:** normalize up to five due/overdue/open items from `GET /api/tasks`, with source badge.
- **Needs your approval:** an empty-state lane plus a confirmation card when Bart chooses task completion; execute through existing `POST /api/tasks/complete/<slug>/<id>` only after Confirm.
- **Up next:** next verified event from `GET /api/year`; show Calendar unavailable on failure.
- **Capture:** add an Inbox task through `POST /api/tasks/add/inbox` (A1).
- **Context strip:** exceptional weather and `/api/system` warning count; links open existing detail pages.

Optional second row: read-only `GET /api/comms/briefing?days=7` for Reply candidates, clearly labelled as suggestions until the adapter is validated. Do not add sending, payment, booking, generic command execution, or a new server endpoint.

Prototype constraints:

- warm existing design; 44 px controls; keyboard and TV-readable text;
- no mutation on load, refresh, or first completion click;
- disable task writes unless `source === "google"`;
- inspect response `ok`, keep failed items visible, and show a receipt/error;
- no false empty state from cached/error data;
- direct URL and deletion provide instant rollback.

This prototype proves the highest-value architectural seam—one cross-domain attention surface with trustworthy source state and explicit approval—without disturbing the current desktop dashboard or iPhone PWA.
