Handoff: CreatorTrack as a general dev-capable system, task primitives + token scoping + Forge bridge¶
URL: https://mkdocs.justinsforge.com/memory/handoffs/creatortrack-dev-tool-primitives-tokens-forge-bridge-2026-07-11/
Date: 2026-07-11 Author: [Claude Code] Status: Approved-vision, not yet built. Spec-level. Nothing implemented.
North Star (the framing that governs every choice below)¶
CreatorTrack should be usable as a full software-development tool without ever looking like one. The way this works: CT's foundation (tasks are nodes with a Notion-style configurable prop/relation model) is already general enough that "software dev" is just one configuration of universal primitives (custom fields, statuses, views, relations, links). Every primitive we add must also make obvious sense for a content creator. We never build a "dev mode," a "software" label, or a dev-only preset. Same code, same UI, configured differently by the user.
How Justin actually works (load-bearing, do not forget): Justin does not run scripts. He opens chat.justinsforge (the Session Launcher), picks a project + account + model + scenario/modifiers, and types a natural-language prompt. That spawns a persistent interactive Claude Code session with full tools/skills, and the agent runs the scripts. So every deliverable here is a tool/skill the spawned agent reaches for when Justin types something like "pull my Send to Forge dev queue and build them", not a CLI Justin invokes.
BUILD STATUS (read first)¶
Target workspace for ALL of this: "Forge / Creator Track", workspace id 90, slug forge-creator-track-mrgi0bir (on Justin's Personal account). Every task, status, and the dev queue live here. The dev-queue and the Forge token must target only workspace 90, not Justin's other workspaces (67 Personal, 85 Gus The Bass, 77 krystal playground, 78 Test).
| Item | Status | What remains |
|---|---|---|
| B: token scoping | BUILT on branch feat/api-token-scoping (not merged) |
merge to main, prod migration, restart creatortrack-prod |
| Forge PAT (token itself) | EXISTS - CREATORTRACK_API_TOKEN in ~/.forge-secrets/creatortrack-bot.env (legacy full-access) |
after B deploys, re-mint scoped to tasks_app + ws 90 |
C: forge_creatortrack_client.py |
NOT built | build it (endpoints all verified below) |
D: dev-queue skill |
NOT built | build it |
| A: task primitives (Type, group-by, task->page relation, URL field, bulk-paste) | NOT built | build; each independent |
| Board statuses "Send to Forge" / "In Development" in ws 90 | NOT created | Justin (or an agent with write) adds them |
| E: chat.justin dev-queue option | NOT built | add launcher modifier/scenario (now in scope, below) |
Confirmed live endpoint for list_workspaces(): GET /api/workspace/list -> {activeId, workspaces:[{id,name,slug,icon}]} (this is how the workspace ids above were resolved).
Workstream A: The primitive model (THIS is what makes CT dev-capable; the rest is plumbing)¶
Goal: Add the few missing primitives that turn CT into a real project/dev tracker, all framed generically so nothing reads as "software".
The foundation already in place (why this is cheap): A task is not a bespoke table. It is a row in the generic nodes table (db/schema.ts:99, type="task") whose fields live in a Notion-style configurable collection: collection_props (field defs), collection_values (per-row values), collection_relations (links between nodes) (db/schema.ts:149-183). Logic in lib/tasks.ts; defaults seeded in lib/system-apps.ts (~line 253). Because the system already supports arbitrary fields/relations/views, "make CT a dev tool" is never a schema rewrite, it is adding a few defaults + generalizing two hardwired spots. Every primitive below is universal: a dev configures it one way, a creator another, same code, same UI, no "dev mode" label anywhere.
Notes vs Pages (the model, since it confused you): a note is quick, atomic, hangs off something, disposable working memory; a page is a durable block-editor document that stands alone and can nest. Key point: a task IS a node, so it already opens as its own page at /n/{id}, which is exactly where dev-queue writeback notes land. "Task", "note", and "page" are three views of one node primitive, not three systems.
The capabilities, each with a dev and a creator use (identical feature, different config):
| Capability | Status today | Dev use (never labeled) | Creator use |
|---|---|---|---|
| Status + kanban | HAVE (drag-drop, custom statuses; KanbanBoard.tsx -> ProjectBoardView.tsx) |
Backlog / In Dev / Review / Done | Idea / Scripting / Filming / Posted |
| Priority | HAVE (Low/Med/High) | triage | which video ships first |
| Tags | HAVE (multi) | area tags | topic / platform tags |
| Subtasks | HAVE (parentTaskId, self-relation) |
break a feature into steps | shots in a video |
| Dependencies | HAVE (blockedBy, self-relation) |
"blocked by #12" | "needs thumbnail first" |
| Attachments | HAVE (node_attachments) |
logs / screenshots | reference images |
| Views | HAVE (list / board / matrix / database / search) | any | any |
| Type field | ADD (custom select, ~free) | bug / feature / chore | Reel / Long-form / Sponsored |
| Group-by any field | ADD (board groups by Status only today) | group by Milestone / Type / Assignee | group by Campaign / Series / Month |
| Milestone | ADD = a select field + group-by, NOT a new primitive | sprint / release | Launch / Series |
| Task->page relation | ADD (relations are self-only today) | link issue to its spec page | link task to script / brand doc |
| External-URL field | ADD | PR / commit link | published video / Drive asset / deal |
| Bulk paste->tasks | ADD (paste newline list -> one task per line) | paste a bug list | paste a content-idea dump |
What is genuinely NEW code (everything else above is config on the existing system):
1. Generalize the board group-by to any select/status field (today: Status only). This one change is "milestones", "swimlanes", and creator-campaign-columns all at once.
2. Generalize collection_relations so a task can relate to a type="page" node. The self-relation machinery for blockedBy/parentTask is the exact pattern; just un-hardwire the target type.
3. External-URL field type (new prop kind + render as a link).
4. Bulk-paste handler that splits pasted lines into task nodes (extends ConvertToTaskModal.tsx).
Type and Milestone need zero code, they are just default select fields.
"Milestones/sprints" is deliberately NOT its own concept. Today grouping is only Lists + Projects; there is no time-box. Do not build a sprint/cycle primitive, it falls out of #1 for free.
Files: db/schema.ts, lib/tasks.ts, lib/system-apps.ts (default-prop seed), components/apps/shared/KanbanBoard.tsx, components/apps/projects/ProjectBoardView.tsx, tasks *View.tsx, relation-field UI, components/apps/tasks/ConvertToTaskModal.tsx.
Full current task schema (CleanTask, lib/tasks.ts:21-74): id, workspaceId, workspaceName, title, status, done, due, priority, tags[], projectId, projectTitle, listIds[], listTitles[], projectRelId, projectRelTitle, reminder, dueEnd, assignedTo, assignees[], archived, createdAt, start, blockedBy[], fields{}, recurrence, parentTaskId.
Workstream B: API token scoping - ALREADY BUILT (branch feat/api-token-scoping, NOT deployed)¶
Built while this handoff was being written, so this is a status note, not a build spec. Branch feat/api-token-scoping (commits df6f079..444fbfd): ctk_ PATs now carry per-app scopes (e.g. tasks_app, calendar_app) + workspace binding + expiry; enforced in currentUser via the x-ct-pathname header; NULL scopes = legacy full access (backward compatible, so today's tokens keep working); settings UI has scope/workspace/expiry pickers (a8066bf); owner-gated mint is rejected without owner (444fbfd); live end-to-end scope-enforcement test matrix (957d448). Detail: reference_creatortrack_api_token_scopes.
Remaining: merge to main, run the prod migration, restart creatortrack-prod.
Impact on Workstream C: mint the Forge PAT with the tasks_app scope bound to the target workspace (least-privilege) instead of a full-access token, once this is deployed. Until then, C works fine against a legacy (NULL-scope) token.
Workstream C: Forge<->CreatorTrack reusable client (Forge-side, the "pull things easily" tool)¶
Goal: A token-frugal client any Forge agent/skill imports to work with CT tasks/notes cheaply. This is the foundation Justin asked for ("Forge should have more ability to work with these tasks and notes, minimize token cost").
Deliverable: scripts/forge_creatortrack_client.py
- Auth: reuse the EXISTING token CREATORTRACK_API_TOKEN (+ CREATORTRACK_BASE_URL) already in ~/.forge-secrets/creatortrack-bot.env; header Authorization: Bearer ctk_.... No new secret needed. (NOTE: curl/wget are blocked in this environment by the context-mode hook; the client must use python urllib/requests, not shell curl.)
- Always pass workspace_id=90 (Forge / Creator Track) so it never reads other workspaces. list_tasks(workspace_id, status=None, include_completed=False) -> GET /api/tasks?workspace=<id>[&includeCompleted=1], filter by status client-side (CT has no server-side status filter). Return only id/title/status/project (minimal, to save tokens - this is the whole point).
- get_task_notes(task_id); append_note(task_id, text) -> POST /api/node/<id>/append {"text":...} (the purpose-built bot write door); set_status(task_id, status) -> PATCH /api/tasks/<id> {"status":...}; list_workspaces() -> GET /api/workspace/list.
- Fail loud (raise on non-2xx / bad token / network), idempotent callers.
Verified CT API facts: bearer exemption in proxy.ts:50-99 (^Bearer\s+ctk_ cookie-exempt); resolution lib/auth.ts + lib/api-tokens.ts; app/api/tasks/route.ts (list; params view/list/project/tag/workspace/appWorkspace/includeCompleted); app/api/tasks/[id]/route.ts (PATCH {status} or {done}); app/api/node/[id]/append/route.ts ({text} -> one paragraph block per line, returns {ok, blockIds}).
Note: C works TODAY against the current full-access ctk_ token. It does not depend on Workstream B; B just makes the token least-privilege later.
Workstream D: dev-queue skill (Forge-side, agent-invoked via natural language)¶
Goal: When Justin (in a chat.justin session with project=creatortrack or forge) types "work the dev queue" / "build my Send to Forge tasks", the agent runs a consistent procedure.
Deliverable: .claude/skills/dev-queue/SKILL.md (trigger phrases: "dev queue", "send to forge"). Flow:
1. Via the client, pull tasks whose status = "Send to Forge" in workspace 90 (Forge / Creator Track) only.
2. Flip each to "In Development" (this is the idempotency guard; a re-run won't reprocess).
3. Do the work in the selected repo. The spawned agent IS the orchestrator (single planner -> /fanout sonnet workers for a batch, preserving token efficiency + cross-item reasoning).
4. Append what was done to each task's notes via append_note.
5. Leave status at "In Development". Never auto-mark Done - Justin marks Done manually.
Repo routing: reuse the launcher's PROMPT_BLOCKS["targets"] map (single source of truth; creatortrack -> /home/justinwieb/creatortrack, forge -> /home/justinwieb/forge, other -> free text). The project Justin picked in chat.justin is the default target; a task's CT project can override.
CT-side setup (config only): in workspace 90 (Forge / Creator Track), create two custom statuses on the tasks board, "Send to Forge" and "In Development". No CreatorTrack code change for the bridge. There is deliberately NO orchestrate button inside CreatorTrack; chat.justin stays the entry point.
Workstream E: chat.justinsforge - add a dev-queue option (IN SCOPE, small)¶
Audited so the picture is complete. chat.justinsforge = the Session Launcher. URL https://chat.justinsforge.com (Cloudflare Access), backend scripts/forge_session_launcher_api.py (FastAPI :7365), systemd forge-session-launcher; SPA at sites/justinsforge.com/chat/{index.html,launcher.js,launcher.css} served at /ui/; Chrome ext chrome-extension-forge-chat/ is a thin iframe over /ui/. (Separate surface: codex.justinsforge.com = OpenAI Codex, :7366, ignore.)
Options: model (opus/fable/sonnet/haiku), account (1[email protected] ~/.claude, 2=justinwieb.com ~/.claude-sub2, via CLAUDE_CONFIG_DIR), project (creatortrack/forge/other), scenario (feature/bugfix/integrator/deploy/explore), modifiers (devserver/plan/push/fanout/coordinate...), session-weight, target=vector, autorun. Picking a project does not set cwd; it injects a <session_setup> text header naming the repo path (PROMPT_BLOCKS["targets"], api lines 663-685) and the agent cd's/worktrees there itself (explicit comment forge_session_launcher_api.py:662: "the launcher only injects this text; the spawned agent runs any scripts itself"). Spawn: POST /api/sessions -> warm pool claim (forge_spawn_pool.sh) or cold forge_spawn_session.sh, prompt via file, boots claude in tmux, returns a claude.ai/code URL. Persistent interactive session, full tools/skills.
What to add this round (Justin asked for it): a launcher option so he doesn't retype the dev-queue prompt. Concretely, in scripts/forge_session_launcher_api.py:
- Add a modifier (or a scenario) e.g. dev_queue to PROMPT_BLOCKS whose text tells the agent: "Work the CreatorTrack dev queue in workspace 90 via the dev-queue skill: pull 'Send to Forge' tasks, move to 'In Development', build in the selected repo, append what you did to each task's notes, leave Done to Justin."
- Surface the toggle in the front-end sites/justinsforge.com/chat/launcher.js (the modifiers list, ~line 620-700).
- It composes with the existing project picker (creatortrack/forge) so the agent knows which repo, and with account/model as usual.
Because the agent auto-invokes the dev-queue skill on the phrase anyway, this modifier is convenience, not a dependency; build after D exists.
Sequencing¶
- A (primitives) is the actual product value and is mostly small: Type + Milestone are free (default select fields); the real code is board group-by-any-field (unlocks milestones/swimlanes/campaign columns at once), task->page relation, URL field, bulk-paste. Ship each independently.
- C (client) + D (dev-queue skill) in parallel - fast, works against today's (legacy NULL-scope) token, no CT code change beyond Justin creating two statuses + minting one PAT.
- B (token scoping) is built on branch, just needs merge + prod migration + creatortrack-prod restart; then re-mint the Forge PAT scoped to
tasks_app+ ws 90. - E (chat.justin dev-queue modifier) last, after D exists; pure convenience.
Open decisions¶
- A: confirm "milestone = group-by a custom field" instead of a new primitive (recommended).
- A: which of the four new primitives to build first (recommend board group-by, highest leverage).
- D: agent works tasks inline vs always
/fanout(recommend: inline for a few,/fanoutfor a batch).
Prerequisites¶
Mint aDONE -ctk_PATCREATORTRACK_API_TOKENalready in~/.forge-secrets/creatortrack-bot.env(legacy full-access; re-mint scoped totasks_app+ws 90 after Workstream B deploys).- Create the "Send to Forge" and "In Development" statuses on the tasks board in workspace 90 (Forge / Creator Track). (Justin by hand, or an agent with write access to ws 90.)
Out of scope¶
- No "software/dev mode" label anywhere in CT (North Star).
- No orchestrate button inside CreatorTrack.
- No cron poller for the dev queue (on-demand via chat.justin only, this round).