Skip to content

Plan: Wire LifeOS + inbox-capture bots into all of CreatorTrack (prod), on a permanent Justin's Workspace

URL: https://mkdocs.justinsforge.com/memory/plans/lifeos-bots-creatortrack-capstone-2026-07-02/

Repos: bots in /home/justinwieb/forge (scripts/forge_telegram_*), app in /home/justinwieb/forge-suite (CreatorTrack Suite, RLS Postgres lifeos on CT109). Target: prod https://app.creatortrack.ai.

Goal

The Telegram inbox-capture and LifeOS coordinator bots can create/log across every CreatorTrack surface as Justin, writing into ONE permanent canonical workspace on prod. Decisions locked with Justin (2026-07-02): create a fresh pinned "Justin's Workspace"; full capstone in one build.

Reality of the surface (what "everything" resolves to)

Writable today via documented prod API (docs/API-BOTS.md) — bot just needs auth + workspace: - Tasks POST /api/tasks; Projects/lists POST /api/tasks/projects - Pages POST /api/node, append POST /api/node/{id}/append - Knowledge / inbox note POST /api/knowledge (and /api/quick-note) - Habits POST /api/apps/habits (create_habit, upsert_log, increment, clear_log, set_note, update_habit, archive_habit) - Food log POST /api/apps/meals (create_entry {name,date,meal,macros}, update_entry, goals)

Not a native write store (set expectations, don't fake it): - Calendar has no create-event endpoint. Calendar app = read overlay of Google + tasks. "Put it on my calendar" => create a timed CreatorTrack task (see companion plan tasks-due-time-calendar-reminders-2026-07-02) and/or push to Google Calendar via the existing forge_google_user client. Bot exposes both paths; no new native calendar store. - Fitness is auto-fed by Hevy + wellness-PG sync (/api/fitness read-only, only /refresh). Bot reads/reports fitness; it does not hand-log workouts.

Owner gating: meals/calendar/fitness/finances require PERSONAL_APPS_OWNER_EMAIL ([email protected]). Token minted as user id 1 (owner) clears this.

Architecture facts

  • Token auth: lib/api-tokens.ts mintToken() = ctk_ + 32 base64url random bytes; only SHA-256 stored in core.api_tokens. createApiToken(identity, name) mints + returns plaintext ONCE. Bearer ctk_ resolves to a full user identity (identityForToken), so a token acts AS Justin.
  • Workspace resolve: resolveWorkspaceId(id, slug?) -> slug match (archived=false) else user's first membership (lib/system-apps.ts:874). Bots will pass the canonical slug explicitly.
  • App seeding: installSystemApp(id, workspaceId, kind) for each SystemAppKind in SYSTEM_APP_KEYS provisions a surface (idempotent).
  • Bot brains already use a tool_* function pattern; inbox brain imports forge_workspace_capture as _spine (forge_telegram_inbox_brain.py:56). Capture client default URL is dev http://localhost:3060/api/capture (forge_workspace_capture.py:29) — must move to prod.
  • Current state: core.api_tokens = 0 rows; capture bot writes to dev; coordinator has zero CreatorTrack tools.

Non-goals

  • No native calendar-event store; no bot-written workouts (read-only fitness).
  • No change to RLS model; the token rides the same RLS identity a logged-in Justin has.
  • Finances stay out of bot scope for now (sensitive; revisit separately).

Phase 0 - Permanent canonical "Justin's Workspace"

  1. Create workspace with a stable, suffix-free slug justin (name "Justin's Workspace", kind personal), owned by user id 1. Because the app's create route auto-appends a random suffix, create it as lifeos_admin directly (INSERT core.workspaces + owner core.memberships row for user 1) so the slug is exactly justin and stable. Guard: slug='justin' unique; abort if taken.
  2. Provision ALL surfaces: loop installSystemApp over every SYSTEM_APP_KEYS kind (tasks, habits, meals, knowledge, calendar, journal, goals, crm, ...) via asUser(user1) so RLS + provisioning run exactly as the app would.
  3. Pin it against purge/dup: add a core.workspaces.pinned boolean not null default false column (idempotent migration) and set it true for this row; make core.purge_expired_workspaces / any workspace-delete path skip pinned=true. This is the "permanent" guarantee.
  4. Record the resulting id + slug in the CreatorTrack reference file.

Verify: SELECT id,slug,pinned FROM core.workspaces WHERE slug='justin' returns the row; the app's workspace list shows it with all apps; a delete/purge attempt is refused.

Commit (app repo): feat(workspace): pinned flag + canonical Justin workspace guard (migration + purge guard). Workspace-row creation itself is a data op (scripted, not a migration).


Phase 1 - Mint the machine-auth token

  1. Mint a ctk_ token as user id 1 named "LifeOS Bots" by invoking createApiToken through the app's node runtime (a one-off tsx/node script in forge-suite that calls lib/api-tokens.createApiToken), capturing the plaintext once.
  2. Store it at ~/.forge-secrets/creatortrack-bot.env (chmod 600): CREATORTRACK_API_TOKEN=ctk_..., CREATORTRACK_BASE_URL=https://app.creatortrack.ai, CREATORTRACK_WORKSPACE_SLUG=justin. Never in git.

Verify: curl -H "Authorization: Bearer ctk_..." https://app.creatortrack.ai/api/workspace/list returns the workspaces incl. justin; a meals GET with the token returns 200 (owner gate passes), a scratch non-owner token gets 403.

Commit: none (secret only).


Phase 2 - Expand + repoint the capture client to prod

  1. scripts/forge_workspace_capture.py: load creatortrack-bot.env; default base URL -> prod; auth via Authorization: Bearer ${CREATORTRACK_API_TOKEN} against the documented /api/* routes (not the legacy /api/capture dev door). Default workspace -> CREATORTRACK_WORKSPACE_SLUG. Keep existing function signatures (create_task, log_habit, save_to_inbox) so no bot edits break; ADD: create_habit, create_page, append_page, quick_note, log_food, create_project, list_workspaces. Each is a thin idempotent POST with fail-loud error surfacing (doctrine: no catch-and-ignore).
  2. Preserve a rollback: env flag CREATORTRACK_CAPTURE_URL still overrides for dev testing.

Verify: from Console shell, call each new client fn against prod; confirm rows land in the justin workspace (query core.* / app.* as admin).

Commit: feat(capture): prod Bearer client covering tasks/habits/pages/knowledge/meals


Phase 3 - Inbox-capture bot: full-surface tools

  1. scripts/forge_telegram_inbox_brain.py: add tool_* wrappers for the new client fns (tool_log_habit already exists via spine; add tool_create_habit, tool_log_food, tool_quick_note, tool_create_page, tool_create_project). Register them in the brain's tool schema so the LLM can pick them from a natural capture ("ate 2 eggs", "did my meditation", "note: idea for video").
  2. Update the brain's system prompt with the surface menu + the canonical-workspace note, and the calendar/fitness caveats (calendar => timed task; fitness read-only).

Verify (untested-until-real-msg honesty): send real Telegram captures for each surface; confirm each lands correctly and the bot replies with a reaction/confirmation per feedback_telegram_bot_ux_reactions.

Commit: feat(inbox-bot): capture into habits/meals/knowledge/pages on prod


Phase 4 - LifeOS coordinator: CreatorTrack write tools

  1. scripts/forge_telegram_brain.py (coordinator): add the same CreatorTrack tool set (tasks, projects, pages, knowledge, habits, food log) + read tools (list_tasks, habits_overview, fitness_read) using the prod Bearer client. Respect the coordinator's existing tool-catalog registration + MAX_CONCURRENT_BRAINS.
  2. Calendar tool: route "schedule X at T" to a timed CreatorTrack task (companion plan) AND/OR forge_google_user Google event, per the bot's existing calendar tools (tool_create_calendar_event). Fitness tool = read-only report.

Verify: coordinator conversation creates/reads across surfaces on prod; brain latency stays in normal band (median ~10s).

Commit: feat(coordinator): CreatorTrack read+write tools on prod


Phase 5 - E2E QA + register

  1. From Telegram, exercise every surface end-to-end against prod's justin workspace: task (+timed), project, page + append, inbox note, habit def + log, food entry. Confirm each via the app UI and via admin SQL.
  2. Confirm owner-gated meals works with the token; confirm a purge/delete cannot remove the pinned workspace.
  3. Register: memory/general/reference_agent_integrations.md (add the bot token + canonical workspace), a new reference_creatortrack_bot_api.md topic + MEMORY.md index line, and update docs/API-BOTS.md if any client-observed behavior differs. Update memory/daily/2026-07-02.md.

Commit: docs(memory): CreatorTrack bot API + canonical workspace wiring


Risks / watch-items

  • Token = full Justin identity. Store chmod 600, never log, never commit. One token, revocable via /api/tokens if leaked.
  • One-poller-per-token rule for the bots (do not start a second poller to test; feedback_telegram_bot_token_ownership).
  • Idempotency: captures must dedup (same message twice = same end state); the capture layer already keys on this — preserve it in new fns.
  • Owner gate could 403 meals/calendar if the token user isn't owner — mint as user id 1 specifically.
  • Pinned guard must cover BOTH the lazy purge and the explicit workspace-delete route, or "permanent" leaks.
  • Calendar/fitness expectations documented so the bot never claims a native calendar event or a logged workout it didn't make.

[Claude Code]