Workspace 42 — Technical Companion
April 11, 2026 · 6 min read
This is the deep-dive companion to Workspace 42 — How I Stopped Losing Context Between My Own Days. Start there if you haven't. This post covers the workflow architecture and the decisions behind it.
Infrastructure
Workspace 42 runs on a small Linux VPS in my home region, locked to a single timezone for deterministic cron behaviour. Two containers on a private Docker network: n8n (SQLite backend, bound only to the loopback interface — never publicly reachable) and Traefik as the edge router with Let's Encrypt TLS and the usual security headers injected at the middleware layer.
That's the whole stack. No Postgres, no Redis, no job queue. n8n's SQLite file doubles as the workflow database, the execution history, and the credential store.
Scripts over nodes
The most important design choice in the stack. Every active workflow is just two nodes: a scheduleTrigger and an executeCommand that shells out to a plain .mjs script. The n8n canvas is a scheduler and a shell. All business logic lives in files.
Why:
- Testable. The scripts are plain Node.js — runnable locally, mockable, unit-testable. Validating behaviour doesn't require clicking through a visual canvas.
- Greppable and diffable. When something breaks at 2 AM, I want
grepover a folder, not clicks through seventeen node configuration panels. Git diffs on a.mjsfile are readable; diffs on an n8n workflow JSON export are not. - Portable. If I ever move off n8n, the scripts work anywhere Node.js does.
The tradeoff: the scripts read n8n's internal SQLite and decrypt credentials directly, which couples them to n8n's storage format. A major n8n upgrade could break that layer. I'm carrying that risk consciously because the upside — real code, real files, real version control — is worth it.
One workflow (the AI Inbox → TickTick promotion) still runs as a full node chain. It's on the list to migrate.
Credentials
Four credentials live in n8n (no secrets reproduced here, obviously): a Notion API token used by every active workflow, a header-auth variant for direct HTTP calls, an OpenAI key for AI Inbox proposal generation, and a TickTick OAuth2 entry that's staged but not active — promotion currently writes to a Notion-side proxy that syncs externally.
Notion databases
Five databases, referenced by name in the scripts rather than UUID, all living at workspace root:
- Daily Notes — daily journaling with
Date,Status,Summary,Notes. - Meetings —
Meeting Date+ a relation back to the matching Daily Note. - AI Inbox — LLM-generated proposals, gated by
Status ∈ {Proposed, Promoted, Discarded}. - Work:TickTick — promoted tasks destined for the TickTick external sync.
- Ops Log — script execution logging.
A central dashboard page gets its "today" reference link patched by the daily creation script. All databases live at workspace root (not nested under the dashboard) so the API can manage them without parent-scope constraints.
Workflow inventory
Five active workflows, all on cron. No webhooks.
| Schedule | Workflow | Does |
|---|---|---|
00:05 daily | Create today's Daily Note | Queries for today's page; creates it if missing with a formatted title and open status. Idempotent by Date. |
00:30 daily | AI Inbox — evaluate open notes | Reads open Daily Notes + their linked meetings, hashes the content, skips if unchanged, otherwise calls gpt-4o to emit up to 10 structured proposals per note. Closes the note afterward. |
01:00 daily | Close old notes | Sweeps Daily Notes older than 7 days that are still Open and flips them to Closed. Runs after AI Inbox so nothing is archived before evaluation. |
*/5 | Link meetings to Daily Notes | Finds meetings whose Daily Note relation is empty, looks up the matching day (or creates one), and sets the relation. Self-healing via poll retries. |
*/5 | Promote AI Inbox → TickTick | Reads items flagged for promotion, normalizes title and priority, dedupes against the proxy DB, creates the entry, marks the inbox row Promoted, and archives stale discards. |
The AI Inbox workflow is the most involved. The prompt returns proposals as structured objects with category (follow-up, action-item, reminder, idea), priority, a verb-first title, a reasoning trail, and free-form notes. A [hash:XXXXXXXX] tag embedded in each proposal prevents regeneration when a note stays open across multiple nights.
Cron schedule
All times in my local timezone:
00:05 — Create today's Daily Note if missing
00:30 — AI Inbox: evaluate open notes, generate proposals
01:00 — Close Daily Notes older than 7 days
*/5 — Link unlinked meetings to Daily Notes
*/5 — Promote AI Inbox items to TickTick + cleanup
Ordering is intentional. Daily Note is created first (00:05). AI Inbox runs next (00:30) so it can process notes while they're still open. Closing runs last (01:00) so nothing is archived before being evaluated.
Architecture
Known limitations
- Scripts are coupled to n8n's storage format. Decrypting credentials by reading SQLite directly is tightly coupled to the current schema. A Postgres migration or credential format change would break the layer.
- Two export flavors can drift. The thin shims in
workflows/are the source of truth; the post-activation exports inlive-export/are artifacts that need regeneration after each deploy. - SQLite on a Docker volume. All state lives in a single file; backup is volume-snapshot-only, and you can't just
cpa WAL-mode database without quiescing first. - No replay on missed crons. A host reboot during a cron window just drops that run. At this scale and with idempotent scripts, it hasn't mattered — but it's worth naming.
- Notion API doesn't support template-based page creation. Template changes made in Notion's UI have to be mirrored in the script payload manually. The raw API simply doesn't accept a
template_id. - The TickTick credential is staged, not active. Promotion writes to a Notion-side proxy that syncs externally; n8n never calls TickTick directly.
This spec is version-controlled alongside the scripts. If something breaks, start here.