All work AI marketing coworker · product, brand & engineering

Desklight

I designed and built Desklight, an AI marketing platform, solo. Allie takes a brief through brand-aware planning, copy, imagery, video and audio into an editable campaign and publishing workflow. Underneath: an agent with persistent memory, a scene-graph graphics engine, model orchestration and metered billing. Live, invite-only.

Role
Founder · solo product design & engineering
When
May 2026 — present
Built with
React · TypeScriptNode / ExpressSupabase · pgvectorClaude tool-useCanvas · HarfBuzzOKLCHStripe
The live Desklight site: the Spark identity, Ember accent and a sample Noon campaign across photography, typography and motion

The current Desklight identity and public site, captured September 2026. Noon is a sample brand; the campaign is real Desklight output.

The problem

A small business needs a consistent creative direction and a working content operation. A blank prompt box still leaves the owner acting as strategist, art director, editor and scheduler.

My role

Founder, product designer and engineer. I built the brand, React application, Node backend, agent, memory system, creative editor, Lumen design system and billing. I own the product decisions and the production code.

The outcome

A live, invite-only platform that carries brand context from brief to campaign: planning, generation, editing, scheduling and publishing. Text, imagery, video and audio share one workspace; the owner can refine the work and decide what goes out.

Explore the product

See the work. Try the workflow.

Desklight is live and invite-only. Explore the public campaigns and Lumen, or request a walkthrough of the working product.

Engineering write-ups — the decisions behind the product
33agent tools in one Claude tool-use loop
8context sources, loaded in parallel per message
42registered models across text, image, editing, video and audio
One message, end to end
  1. 01 Claim An atomic Postgres claim, so two workers never answer one message. Email and chat share the path.
  2. 02 Gather Eight sources in parallel: brand rules, memories, learned skills, recent work, reference images.
  3. 03 Reason Claude tool-use with 33 tools. Read-only tools run together; anything that changes state runs in order.
  4. 04 Charge Paid tools open a chargeGuard gate first and settle once, for the model that actually ran.
  5. 05 Remember Facts and outcomes are extracted after the reply. Engram compresses them overnight.

Generic AI gets you generic.

At my last role I looked after several brands. The big marketing pushes took the time, and the smaller design work for those brands kept slipping. Desklight is what I built for that work. A brand needs a photographic register, a typographic voice and a point of view that survives the next brief, so Allie carries that context from email or chat into her tools and brings the work back to an editor and a calendar. The owner changes the copy, refines the design and picks the schedule. I designed the workflow and built every layer under it, so a problem in the interface can become a change to the system.

The current React workspace with local Noon sample data. Campaign assets are real Desklight output; activity counters are illustrative.

One product identity. Many creative voices.

Desklight’s identity is the Spark D, a solid D with a four-point sparkle cut through it, set with an Ember accent and a Syne wordmark. Lumen’s Contour mark belongs to the same family. I carried it through the public site, the app and the design system’s documentation. Inside the product the job flips: every customer brand needs its own direction. Noon, Marrow, Forma and Loam are sample brands with their own photography, colour, type and tone, and their campaigns below show the range.

Lumen’s live identity studio: Contour for the system, Spark for the product. September 2026.

Spend the model call where judgment matters.

Email and chat enter the same agent pipeline. An atomic claim in Postgres keeps two workers from answering one message, then eight context sources load in parallel: brand rules, memories, learned skills, recent work and reference images. The calendar and the account used to load on every message too; now they arrive only when the message needs them. Allie has 33 tools. Read-only tools run together, and anything that changes state runs in sequence. In chat, a deterministic reflex layer handles approvals and scheduling commands without a model round-trip. I once handed the turn after an image to Haiku for speed. That turn also judges the render, may re-render and targets the calendar, and the smaller model re-rendered posts nobody asked for and praised its own work. Every turn now runs on the workspace’s Sonnet.

Send the model only what changed.

Allie was paying to re-read context she had already seen. Cache writes were 59% of her model bill, because the clock and the latest headlines sat ahead of the conversation and changed with every message. I reordered her prompt by how often each part changes. Tools and instructions come first, cached for an hour and shared by every workspace. The brand follows, and anything that changes per message rides inside the message itself.

Then I put Jev, a small judgment model from TypeSafe, in front of her. It reads each message in about 150 ms and answers four yes-or-no questions: does this need the calendar, the brand profile, the help center or the account? When it’s sure, the server runs that lookup before Allie starts, so she can answer in one call instead of two. On 45 real asks, 28 of its 30 confident calls matched the lookup she would have made. Jev only chooses and never writes, so a wrong guess costs one extra lookup, never a wrong fact. On the same real chat, a warm message now costs 39% less.

−39%cost per message, same real chat
−53%
tokens paid for again
6 → 4
model calls
150ms
for Jev to read one
01 Anatomy of one message
  • Shared, cached
  • This chat, cached
  • Paid for again · 25× a cached read
Before

The clock lived in the brand block, so brand, history and message were paid for again every time.

After

Only the new message is paid for. The clock, memories and Jev’s lookups ride inside it.

02 Cost per message
  • Before
  • After
  1. Remind me how our brand voice is supposed to sound. 3.9¢ 2.7¢
  2. How do I connect our Instagram account? 3.0¢ 1.6¢
  3. Give me three headline ideas… 2.7¢ 1.8¢
  4. What time is it for me right now? 2.2¢ 1.1¢
Jev by
One real six-message chat in Desklight’s demo workspace, run before and after on October 15, 2026. Costs from the usage log at Sonnet 5.5 prices; the graphic message is left out. In 01, the tools and the system prompt are drawn to scale on wide screens; the chat’s own parts are widened to fit their labels.

A useful memory also needs to forget.

Engram is the three-level memory I built for Allie. L0 stores facts and corrections. L1 compresses dense, brand-scoped clusters into reusable heuristics, and a weekly L2 pass synthesizes strategy, flags drift and switches off guidance the evidence has outgrown. Reprocessing the same evidence shouldn’t multiply the same rule, so when an existing heuristic already covers 60% of a candidate’s source memories, the system reinforces that rule instead of writing a new one. Older memories lose weight daily. At recall, brand, sender and age filters keep another brand’s direction, a teammate’s private preferences and stale instructions out of the current brief.

Encode taste without washing it out.

Each brand’s visual DNA (photo register, named references, banned visuals, type rules) loads with every message. When Allie writes a photo brief that hits the banned list, it goes back to her for a rewrite before any paid model runs. Colour gets the same care. The OKLCH engine turns a primary and an accent into tonal scales and semantic tokens. A scraped accent is often the greige wall behind the product, so low-chroma yellow-browns are rebuilt from the brand’s own hue, and monochrome brands keep a separate path so black and white never pick up a made-up green. Text and surface pairs are checked against 4.5:1; for accents, the selector tries the brand’s own colour first, then its tonal stops, and reports the contrast it measured.

The editor needed a different foundation.

The editable templates nearly made me stop. I came close to paying for a white-labeled editor instead of finishing my own. The first version described a design in HTML and CSS and measured it in the browser, so the server and the editor could disagree about the same post. After about two and a half rewrites I changed the foundation: a typed scene graph and one pure layout function that takes the scene, its resources and a time tick. HarfBuzz shapes the text, the canvas paints the resolved layout, and a still image is a video with one frame. It lives in eleven engine packages, with a gate suite that checks shaping, per-glyph placement and colour against the layout’s own guarantees. On September 30 I deleted the old HTML renderer: every graphic now bakes through the engine, and a layout it can’t draw refuses instead of falling back.

A retry must not become a second charge.

Model calls cost real money, and a request can be retried while the first run is still alive. chargeGuard is the one door to paid work: check the expected spend before generating, then settle against the model that actually ran. It replaced eleven hand-rolled charge sites, and nothing outside billing calls the charge functions directly. Request-keyed routes claim a settlement position before the wallet moves. That position is built from the resource, a stable charge slot and a counter local to the gate, so a replay rebuilds the same identity and dedups instead of charging twice. A failed settlement is recorded for reconciliation, because the credit balance has to stay understandable when a provider fails.

Live, invite-only.

Desklight went live on September 26, 2026. It supports planning, creation, editing, scheduling and downloads. LinkedIn and X connect today, and the other six platform integrations open as each platform approves the app. The campaigns here use sample brands and real product output. I’m glad to walk you through the working app, the decisions behind it and the code.

Construction

The AI is the negative space.

Construction drawing of the Spark D on a 64-unit grid: a 48-unit silhouette, a bowl of radius 24, a four-point sparkle of radius 15 cut through as an opening, and the lockup with its ascenders flush to the D’s top edge

The four-point sparkle has become the shorthand for AI. In the Spark D it’s cut out of the letter as a real opening, so it takes on whatever ground the mark sits on. The mark is drawn on a 64-unit grid: a 48-unit silhouette with a single-radius bowl. The wordmark is Syne Semibold, outlined, with its ascenders set flush to the top of the D. Favicons, app icons and the share card come from the same vector source.

The identity

One mark, every surface.

Production source — selected excerpts

Where the decisions become code.

packages/layout/src/index.ts
export const layout = (  sceneInput: DeepReadonly<Scene>,  resources: DeepReadonly<LayoutResources>,  at: TimeTick,): LayoutResult => {  const parsed = safeParseScene(sceneInput);  if (!parsed.success)    return { ok: false, errors: [{ code: "INVALID_SCENE", path: "$", message: parsed.error.message }] };  const scene = parsed.data;  const timeError = validateTime(scene, at);  if (timeError !== null) return { ok: false, errors: [timeError] };

The one layout function. The scene is validated at the boundary, time is an input, and failure comes back as typed data that the editor and the server read the same way.

services/billing.js
settleOrdinal += 1;const settlePos = { resource, chargeSlot, ordinal: settleOrdinal };if (!chargeModelId) return { ok: true, charged: 0, skipped: 'no_paid_model_ran' };let fenceUnverified = false;if (idemKey) {  const fence = await claimSettle(idemKey, settlePos, tenantId);  if (fence.claimed === false) {    return { ok: true, charged: 0, deduped: true };  }  if (fence.unavailable) fenceUnverified = true;}const charge = await chargeForGeneration({  tenantId, resource, modelId: chargeModelId, preferredModelId: modelId,  costOpts: actualCostOpts === undefined ? costOpts : actualCostOpts,  metadata: fenceUnverified ? { ...metadata, fence_unverified: true } : metadata,});

chargeGuard builds a repeatable settlement position before charging the model that ran. A claimed position returns without charging again; an unavailable fence is flagged for reconciliation. Excerpt from settle(); comments and diagnostic logging omitted.

services/consolidation.js
const candidateSet = new Set(sourceIds);let dup = null;for (const existing of eligible) {  const existingSet = new Set(existing.source_memory_ids || []);  if (existingSet.size === 0) continue;  let intersect = 0;  for (const id of candidateSet) if (existingSet.has(id)) intersect++;  const overlap = intersect / Math.max(candidateSet.size, 1);  if (overlap >= 0.6) { dup = { skill: existing, overlap }; break; }}

Engram deduplicates by evidence coverage, within the same brand and domain. Dividing by the candidate set is deliberate: a smaller cluster already covered by a larger rule should not create a duplicate. Excerpt; comments omitted.

lib/color-inference.js
const isMonochrome = pri.c < 0.025 && acc.c < 0.045;const isMuddyAccent = !isMonochrome && (  acc.c < 0.04 ||  (acc.c < 0.085 && acc.h >= 50 && acc.h <= 100));// …} else if (isMuddyAccent) {  let popH = pri.c >= 0.015 ? pri.h : (acc.h + 150) % 360;  if (popH >= 50 && popH <= 100) popH = 38;  effAcc = { l: 0.62, c: ACCENT_TARGET_C, h: popH };}

A scraped brand colour is often the wall behind the product. Low-chroma yellow-browns get no usable hue, so the engine builds a vivid accent from the brand’s own primary; true monochrome brands skip this path entirely. Excerpt.

The system underneath — live & public

Lumen — the product’s working design system.

I built Lumen alongside the application. Its September refresh brings spatial recipes, semantic surfaces, component states, motion, response and recovery into a public studio. The spacing workbench exposes how inset and nested radii relate; the agent studio makes scope, autonomy and permission visible. Change the controls and inspect the resulting behavior below.

2.0.0-rc.1 · 14 foundations · 32 components · 4 patterns. Live inventory verified September 27, 2026. The studio includes interactive demonstrations of proposed recipes and agent behavior.

Desklight · live, invite-only

Off your desk.On brand.

Visit desklight.ai Request a walkthrough See the Noon campaign

The closing section from desklight.ai: native SVG curves that draw themselves in and bend around your pointer.