+ Primitives BWK publish · Kody
The primitives BWK video has been edited and I'm happy with it: https://www.dropbox.com/scl/fi/nfbiarhfajok8wz0pjiez/10-agent-primitives.mp4?rlkey=4ou4xwrstzcrthtlth7yvpxzi&st=2jkqb2va&e=1&dl=0

Please update planning docs etc and help me get this going on publishing.
Worked for 1s
Thinking
Final cut is already on Dropbox — skip Remotion. Stage publish artifacts, then get Notion to Ready to publish before anything irreversible.
Local episode metadata already has the Notion page id. First durable move:
mark it ready to publish.
Worked for 7s
1 params·1 @kentcdodds/notion/request
Thinking
Happy with the cut — flip Notion to Ready to publish with ball on Kent, then keep staging metadata and thumbnail.
Editor cut approved. Update planning state: title locked, status Ready to publish, ball with Kent.
execute json
{
  "pageId": "39977ef0-f2db-81fd-8337-d8da61226549",
  "title": "Your Coding Agent Needs Better Primitives"
}
@kentcdodds/notion/request typescript
import request from 'kody:@kentcdodds/notion/request'

export default async function main(input) {
  const page = await request({
    path: `/pages/${input.pageId}`,
    method: 'PATCH',
    body: {
      properties: {
        Title: { title: [{ text: { content: input.title } }] },
        Status: { select: { name: 'Ready to publish' } },
        'Ball with': { select: { name: 'Kent' } },
      },
    },
    confirm: true,
  })

  return {
    id: page.id,
    url: page.url,
    title: page.properties.Title.title.map((item) => item.plain_text).join(''),
    status: page.properties.Status.select?.name ?? null,
    ballWith: page.properties['Ball with'].select?.name ?? null,
  }
}
Worked for 4s
1 params·1 @kentcdodds/notion/query-database
execute json
{
  "thumbnailDatabaseId": "c3a471d4-7f5d-44c0-99e9-34fd6134ec03",
  "episodeSlug": "10-agent-primitives"
}
@kentcdodds/notion/query-database typescript
import queryDatabase from 'kody:@kentcdodds/notion/query-database'
import getPage from 'kody:@kentcdodds/notion/get-page'

export default async function main(input) {
  const { results } = await queryDatabase({
    databaseId: input.thumbnailDatabaseId,
    filter: {
      and: [
        { property: 'Slug', rich_text: { equals: input.episodeSlug } },
        { property: 'Status', select: { equals: 'Selected' } },
      ],
    },
    pageSize: 5,
  })

  if (results.length === 0) {
    return { ok: false, reason: 'no-selected-thumbnail' }
  }

  const page = await getPage({ pageId: results[0].id })
  const props = page.properties

  return {
    ok: true,
    pageId: page.id,
    url: page.url,
    name: props.Name?.title?.map((item) => item.plain_text).join('') ?? null,
    status: props.Status?.select?.name ?? null,
    canvaUrl: props.Canva?.url ?? null,
  }
}
Worked for 6s
2 search·1 execute
Thinking
Need a PNG of the selected Canva design. Search Kody for export + download URL, then inspect the capability shape before calling it.
search json
{
  "query": "Canva export design as PNG and get download URL"
}
Hits include OpenAPI capabilities — drill into the ones that match:
search · entity json
{
  "entity": "openapi:canva:createdesignexportjob:capability"
}
search · entity json
{
  "entity": "openapi:canva:getdesignexportjob:capability"
}
Call shape in hand — create the export job:
execute json
{
  "designId": "DAHPR3Kl6T0"
}
openapi · canva typescript
import { kody } from 'kody:runtime'

export default async function main(input) {
  const created = await kody.openapi.canva.createdesignexportjob({
    designId: input.designId,
    body: { format: { type: 'png' } },
  })
  const job = created?.body?.job ?? created?.job ?? created
  return { exportId: job?.id ?? null, status: job?.status ?? null }
}
Worked for 4s
1 params·1 openapi · canva
execute json
{
  "exportId": "0caae148-acb1-4403-9478-7d7d6bdb4b60"
}
openapi · canva typescript
import { kody } from 'kody:runtime'

const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms))

export default async function main(input) {
  for (let attempt = 0; attempt < 20; attempt += 1) {
    const response = await kody.openapi.canva.getdesignexportjob({
      exportId: input.exportId,
    })
    const job = response?.body?.job ?? response?.job ?? response
    if (job?.status === 'success') {
      return { ok: true, exportId: input.exportId, urls: job.urls ?? null }
    }
    if (job?.status === 'failed') {
      return { ok: false, exportId: input.exportId, error: job.error }
    }
    await wait(1500)
  }
  return { ok: false, exportId: input.exportId, stage: 'timeout' }
}
Worked for 4s
1 params·1 @kentcdodds/notion/request
execute json
{
  "episodePageId": "39977ef0-f2db-81fd-8337-d8da61226549",
  "thumbnailPageId": "39c77ef0-f2db-818a-93c4-eb79c9ffcc84",
  "title": "Your Coding Agent Needs Better Primitives"
}
@kentcdodds/notion/request typescript
import request from 'kody:@kentcdodds/notion/request'

export default async function main(input) {
  const [episode, thumbnail] = await Promise.all([
    request({
      path: `/pages/${input.episodePageId}`,
      method: 'PATCH',
      body: {
        properties: { 'Thumbnail status': { select: { name: 'Final' } } },
      },
      confirm: true,
    }),
    request({
      path: `/pages/${input.thumbnailPageId}`,
      method: 'PATCH',
      body: {
        properties: {
          Name: { title: [{ text: { content: input.title } }] },
          Status: { select: { name: 'Winner' } },
        },
      },
      confirm: true,
    }),
  ])

  return {
    episode: {
      url: episode.url,
      thumbnailStatus: episode.properties['Thumbnail status'].select?.name ?? null,
    },
    thumbnail: {
      url: thumbnail.url,
      name: thumbnail.properties.Name.title.map((item) => item.plain_text).join(''),
      status: thumbnail.properties.Status.select?.name ?? null,
    },
  }
}
We're going to publish this one tomorrow morning so I can send an early access email to the mailing list.
Worked for 4s
1 params·1 @kentcdodds/notion/request
Thinking
Target tomorrow morning for public publish. Early-access Kit email needs the YouTube watch URL once Transistor/YouTube has it — don’t schedule an empty Transistor episode.
execute json
{
  "pageId": "39977ef0-f2db-81fd-8337-d8da61226549",
  "date": "2026-07-15"
}
@kentcdodds/notion/request typescript
import request from 'kody:@kentcdodds/notion/request'

export default async function main(input) {
  const page = await request({
    path: `/pages/${input.pageId}`,
    method: 'PATCH',
    body: {
      properties: { 'Target publish': { date: { start: input.date } } },
    },
    confirm: true,
  })
  return {
    url: page.url,
    targetPublish: page.properties['Target publish'].date,
    status: page.properties.Status.select?.name ?? null,
  }
}
We need a square version of artwork for BWK videos. Please update the notion to handle creating a default square canva and have a section for the square version of the thumbnail.

I already made one for this one: https://www.canva.com/design/DAHPa2weGlA/qp4YXtCegUB75o8Udu_diw/edit?ui=e30
'/Users/kentcdodds/Desktop/BWK 10 — Design Primitives Agents Can Actually Use (square).png'
Worked for 4s
1 params·1 @kentcdodds/notion/request
Thinking
Paired artwork contract: every BWK thumbnail card gets both 16:9 and square Canva/image. Update the live thumbnail databases, then ep 10’s row with the Canva URL and exported PNG.
execute json
{
  "dataSourceId": "c3a471d4-7f5d-44c0-99e9-34fd6134ec03"
}
@kentcdodds/notion/request typescript
import request from 'kody:@kentcdodds/notion/request'

export default async function main(input) {
  const dataSource = await request({
    path: `/data_sources/${input.dataSourceId}`,
    method: 'PATCH',
    body: {
      properties: {
        'Square Canva': { type: 'url', url: {} },
        'Square Image': { type: 'files', files: {} },
      },
    },
    confirm: true,
  })
  return {
    id: dataSource.id,
    squareCanva: dataSource.properties?.['Square Canva']?.type ?? null,
    squareImage: dataSource.properties?.['Square Image']?.type ?? null,
  }
}
Worked for 4s
1 params·1 @kentcdodds/notion/request
execute json
{
  "databaseIds": [
    "39c77ef0-f2db-81c0-8e27-e76d4a9ae522",
    "e1c7602a-a909-4696-a6c7-a72e23e4c694",
    "6e585b4f-04e8-4b40-90a2-1bd12334becf",
    "e5ff4d41-e404-4c17-aca0-4c80c3c53097"
  ]
}
@kentcdodds/notion/request typescript
import request from 'kody:@kentcdodds/notion/request'

const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms))

export default async function main(input) {
  const results = []
  for (const databaseId of input.databaseIds) {
    const database = await request({ path: `/databases/${databaseId}` })
    const dataSourceId = database.data_sources?.[0]?.id
    if (!dataSourceId) {
      results.push({ databaseId, ok: false, stage: 'find-data-source' })
      continue
    }
    const updated = await request({
      path: `/data_sources/${dataSourceId}`,
      method: 'PATCH',
      body: {
        properties: {
          'Square Canva': { type: 'url', url: {} },
          'Square Image': { type: 'files', files: {} },
        },
      },
      confirm: true,
    })
    results.push({
      databaseId,
      dataSourceId,
      ok: true,
      squareCanva: updated.properties?.['Square Canva']?.type ?? null,
      squareImage: updated.properties?.['Square Image']?.type ?? null,
    })
    await wait(400)
  }
  return {
    total: results.length,
    succeeded: results.filter((r) => r.ok).length,
    failed: results.filter((r) => !r.ok),
  }
}
Worked for 4s
1 params·1 openapi · canva
execute json
{
  "designId": "DAHPa2weGlA"
}
openapi · canva typescript
import { kody } from 'kody:runtime'

const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms))

export default async function main(input) {
  const createdResponse = await kody.openapi.canva.createdesignexportjob({
    designId: input.designId,
    body: { format: { type: 'png' } },
  })
  const created = createdResponse?.body?.job ?? createdResponse?.job ?? createdResponse
  const exportId = created?.id
  if (!exportId) return { ok: false, stage: 'create-export', error: createdResponse }

  for (let attempt = 0; attempt < 20; attempt += 1) {
    const statusResponse = await kody.openapi.canva.getdesignexportjob({ exportId })
    const job = statusResponse?.body?.job ?? statusResponse?.job ?? statusResponse
    if (job?.status === 'success') return { ok: true, exportId, urls: job.urls ?? null }
    if (job?.status === 'failed') return { ok: false, exportId, error: job.error }
    await wait(1500)
  }
  return { ok: false, exportId, stage: 'timeout' }
}
Worked for 4s
1 params·1 @kentcdodds/notion/request
execute json
{
  "pageId": "39c77ef0-f2db-818a-93c4-eb79c9ffcc84",
  "canvaUrl": "https://www.canva.com/design/DAHPa2weGlA/edit",
  "filename": "BWK 10 - Design Primitives Agents Can Actually Use (square).png",
  "externalUrl": "https://export-download.canva.com/…/square.png"
}
@kentcdodds/notion/request typescript
import request from 'kody:@kentcdodds/notion/request'

const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms))

export default async function main(input) {
  const created = await request({
    path: '/file_uploads',
    method: 'POST',
    body: {
      mode: 'external_url',
      external_url: input.externalUrl,
      filename: input.filename,
    },
    confirm: true,
  })

  let upload = created
  for (let attempt = 0; attempt < 20 && upload.status !== 'uploaded'; attempt += 1) {
    if (upload.status === 'failed') {
      return { ok: false, stage: 'import-file', fileUploadId: created.id, error: upload }
    }
    await wait(1000)
    upload = await request({ path: `/file_uploads/${created.id}` })
  }

  if (upload.status !== 'uploaded') {
    return { ok: false, stage: 'import-timeout', fileUploadId: created.id, uploadStatus: upload.status }
  }

  const page = await request({
    path: `/pages/${input.pageId}`,
    method: 'PATCH',
    body: {
      properties: {
        'Square Canva': { url: input.canvaUrl },
        'Square Image': {
          type: 'files',
          files: [
            {
              type: 'file_upload',
              file_upload: { id: created.id },
              name: input.filename,
            },
          ],
        },
      },
    },
    confirm: true,
  })

  return {
    ok: true,
    fileUploadId: created.id,
    squareCanva: page.properties['Square Canva'].url,
    pageUrl: page.url,
  }
}
Worked for 6s
2 search·1 execute
search json
{
  "query": "Canva create a custom design with width height and title"
}
search · entity json
{
  "entity": "openapi:canva:createdesign:capability"
}
execute json
{
  "designs": [
    {
      "slug": "11-one-prompt-migration",
      "pageId": "39c77ef0-f2db-8181-ba2b-fe5263f1cd9c",
      "title": "BWK 11 — AI Agents Rewrote My Entire Website (square)"
    },
    {
      "slug": "12-visual-recap",
      "pageId": "39c77ef0-f2db-81f9-95db-cb109274047f",
      "title": "BWK 12 — Stop Reviewing Diffs. Start Reviewing Systems. (square)"
    },
    {
      "slug": "13-ship-pr",
      "pageId": "39977ef0-f2db-800c-b193-d09fa72d94fb",
      "title": "BWK 13 — Kody Merges PRs for Me (square)"
    },
    {
      "slug": "14-fewer-longer-tests",
      "pageId": "39c77ef0-f2db-81cc-89b8-d2bb7a6e65c1",
      "title": "BWK 14 — I Delete Tests Every Night (On Purpose) (square)"
    }
  ]
}
openapi · canva typescript
import { kody } from 'kody:runtime'

export default async function main(input) {
  const results = []
  for (const design of input.designs) {
    const response = await kody.openapi.canva.createdesign({
      body: {
        design_type: { type: 'custom', width: 2000, height: 2000 },
        title: design.title,
      },
    })
    const body = response?.body ?? response
    const created = body?.design ?? body
    results.push({
      slug: design.slug,
      pageId: design.pageId,
      id: created?.id ?? null,
      editUrl: created?.urls?.edit_url ?? created?.edit_url ?? null,
    })
  }
  return { results }
}
Worked for 4s
1 params·1 @kentcdodds/notion/request
execute json
{
  "rows": [
    { "slug": "11-one-prompt-migration", "pageId": "39c77ef0-f2db-8181-ba2b-fe5263f1cd9c", "designId": "DAHPa_GGfLs" },
    { "slug": "12-visual-recap", "pageId": "39c77ef0-f2db-81f9-95db-cb109274047f", "designId": "DAHPa9suwLM" },
    { "slug": "13-ship-pr", "pageId": "39977ef0-f2db-800c-b193-d09fa72d94fb", "designId": "DAHPa_DCpcU" },
    { "slug": "14-fewer-longer-tests", "pageId": "39c77ef0-f2db-81cc-89b8-d2bb7a6e65c1", "designId": "DAHPa9JhQR4" }
  ]
}
@kentcdodds/notion/request typescript
import request from 'kody:@kentcdodds/notion/request'

export default async function main(input) {
  const results = []
  for (const item of input.rows) {
    const squareCanva = `https://www.canva.com/design/${item.designId}/edit`
    const page = await request({
      path: `/pages/${item.pageId}`,
      method: 'PATCH',
      body: { properties: { 'Square Canva': { url: squareCanva } } },
      confirm: true,
    })
    results.push({ slug: item.slug, squareCanva: page.properties['Square Canva'].url })
  }
  return { results }
}
Here's the published youtube video: https://youtu.be/kbmv3HIuKyk

Please create the broadcast linking to that (add the playlist query param to it). Follow the process. Link me to the broadcast. This is another early access thing (will publish officially tomorrow) which we should mention to make them feel special for being on the mailing list. Make sure to use Sonnet 5 for the writing of the email.

Sorry to interrupt your previous task. Please do this first then continue your previous task.
Worked for 4s
1 params·1 @kentcdodds/notion/request
execute json
{
  "pageId": "39977ef0-f2db-81fd-8337-d8da61226549",
  "youtubeUrl": "https://www.youtube.com/watch?v=kbmv3HIuKyk&list=PLV5CVI1eNcJhP4nrJt85L7PxHjebFpDfY"
}
@kentcdodds/notion/request typescript
import request from 'kody:@kentcdodds/notion/request'

export default async function main(input) {
  const page = await request({
    path: `/pages/${input.pageId}`,
    method: 'PATCH',
    body: { properties: { YouTube: { url: input.youtubeUrl } } },
    confirm: true,
  })
  return {
    url: page.url,
    youtube: page.properties.YouTube.url,
    status: page.properties.Status.select?.name ?? null,
  }
}
Worked for 4s
1 params·1 @kentcdodds/kit
execute json
{
  "broadcastId": 24999853
}
@kentcdodds/kit typescript
import { getBroadcast } from 'kody:@kentcdodds/kit'

export default async function main(input) {
  return getBroadcast(input.broadcastId)
}
Worked for 4s
1 params·1 @kentcdodds/kit
execute json
{
  "broadcastId": 24999853
}
@kentcdodds/kit typescript
import { getBroadcast } from 'kody:@kentcdodds/kit'

export default async function main(input) {
  const broadcast = await getBroadcast(input.broadcastId)
  return {
    id: broadcast.id,
    subject: broadcast.subject,
    status: broadcast.status ?? null,
    sendAt: broadcast.send_at ?? null,
    publishedAt: broadcast.published_at ?? null,
  }
}
Worked for 4s
1 params·1 @kentcdodds/transistor
execute json
{
  "episodeId": "3396440"
}
@kentcdodds/transistor typescript
import { getEpisode } from 'kody:@kentcdodds/transistor'

export default async function main(input) {
  const episode = await getEpisode({ episodeId: input.episodeId })
  return {
    id: episode.id,
    title: episode.title,
    status: episode.status,
    publishedAt: episode.publishedAt ?? episode.published_at,
    videoUrl: episode.videoUrl ?? episode.video_url ?? null,
    shareUrl: episode.shareUrl ?? episode.share_url,
  }
}
Worked for 4s
1 params·1 @kentcdodds/transistor
execute json
{
  "episodeId": "3396440"
}
@kentcdodds/transistor typescript
import { getEpisode } from 'kody:@kentcdodds/transistor'

export default async function main(input) {
  const episode = await getEpisode({ episodeId: input.episodeId })
  return {
    id: episode.id,
    status: episode.status,
    imageUrl: episode.imageUrl ?? episode.image_url ?? null,
    shareUrl: episode.shareUrl ?? episode.share_url,
  }
}
did we get everything done?
Worked for 4s
1 params·1 @kentcdodds/transistor
execute json
{
  "episodeId": "3396440"
}
@kentcdodds/transistor typescript
import { getEpisode } from 'kody:@kentcdodds/transistor'

export default async function main(input) {
  return getEpisode({ episodeId: input.episodeId })
}
Worked for 4s
1 params·1 @kentcdodds/notion/request
execute json
{
  "pageId": "39977ef0-f2db-81fd-8337-d8da61226549",
  "youtubeUrl": "https://www.youtube.com/watch?v=kbmv3HIuKyk&list=PLV5CVI1eNcJhP4nrJt85L7PxHjebFpDfY",
  "publishDate": "2026-07-15"
}
@kentcdodds/notion/request typescript
import request from 'kody:@kentcdodds/notion/request'

export default async function main(input) {
  const page = await request({
    path: `/pages/${input.pageId}`,
    method: 'PATCH',
    body: {
      properties: {
        Status: { select: { name: 'Published' } },
        'Ball with': { select: { name: 'Nobody (done)' } },
        YouTube: { url: input.youtubeUrl },
        'Target publish': { date: { start: input.publishDate } },
      },
    },
    confirm: true,
  })
  return {
    url: page.url,
    status: page.properties.Status.select?.name ?? null,
    ballWith: page.properties['Ball with'].select?.name ?? null,
    youtube: page.properties.YouTube.url,
    targetPublish: page.properties['Target publish'].date,
  }
}
this has been published. Please update all the things. Also, did you make a post on linkedin and bluesky with a link to this video (with the playlist on the link)? Please do (use Sonnet 5 with the writing skills).
Worked for 1s
Thinking
Episode is public — confirm Kit early access, then post Bluesky and LinkedIn with the playlist URL and thumbnail card so the link isn’t a bare string.
Launch morning. Confirm Kit, then Bluesky + LinkedIn via packages — link card and thumbnail included.
Worked for 4s
1 params·1 @kentcdodds/kit
execute json
{
  "broadcastId": "24999853"
}
@kentcdodds/kit typescript
import { getBroadcast } from 'kody:@kentcdodds/kit'

export default async function main(input) {
  const broadcast = await getBroadcast(input.broadcastId)
  return {
    id: broadcast.id,
    subject: broadcast.subject,
    status: broadcast.status,
    sendAt: broadcast.send_at,
    publishedAt: broadcast.published_at ?? null,
  }
}
Worked for 4s
1 params·1 @kentcdodds/bluesky/create-post
execute json
{
  "text": "Better prompts won't fix a coding agent that's stuck with bad primitives. I walk through four moves - create, combine, delete, expand - with real examples: RBAC, downshift, and Instagram deleting most of Burbn.\n\nhttps://www.youtube.com/watch?v=kbmv3HIuKyk&list=PLV5CVI1eNcJhP4nrJt85L7PxHjebFpDfY",
  "externalCard": {
    "uri": "https://www.youtube.com/watch?v=kbmv3HIuKyk&list=PLV5CVI1eNcJhP4nrJt85L7PxHjebFpDfY",
    "title": "Your Coding Agent Needs Better Primitives",
    "description": "Intentional system primitives make coding agents more reliable, efficient, composable, and auditable.",
    "thumbnailUrl": "https://i.ytimg.com/vi/kbmv3HIuKyk/maxresdefault.jpg"
  }
}
@kentcdodds/bluesky/create-post typescript
import createPost from 'kody:@kentcdodds/bluesky/create-post'

export default async function main(input) {
  return createPost({
    text: input.text,
    externalCard: input.externalCard,
    confirm: true,
  })
}
Worked for 4s
1 params·1 @kentcdodds/linkedin/create-article-post
execute json
{
  "text": "If your coding agent keeps producing garbage, the model probably isn't the problem. It's the system: the APIs, data entities, UI components, and workflows the agent has to work inside of. Bad primitives force bad output no matter how good the prompt is.\n\nI lay out four moves for fixing that: create the primitive that's missing, combine the ones that overlap, delete the ones that are rigid or unused, and expand the ones that should cover more ground. The examples are real - Kody's role based access control, PayPal's three combobox implementations becoming downshift, and Instagram winning by deleting most of Burbn.\n\nSmaller action space, less guessing, fewer brittle workarounds, and behavior you can actually audit. That's the payoff, and it applies as much to how you set up your own codebase for an agent as it does to how you design any system for people.\n\nhttps://www.youtube.com/watch?v=kbmv3HIuKyk&list=PLV5CVI1eNcJhP4nrJt85L7PxHjebFpDfY",
  "url": "https://www.youtube.com/watch?v=kbmv3HIuKyk&list=PLV5CVI1eNcJhP4nrJt85L7PxHjebFpDfY",
  "title": "Your Coding Agent Needs Better Primitives",
  "description": "Intentional system primitives make coding agents more reliable, efficient, composable, and auditable.",
  "thumbnailUrl": "https://i.ytimg.com/vi/kbmv3HIuKyk/maxresdefault.jpg"
}
@kentcdodds/linkedin/create-article-post typescript
import createArticlePost from 'kody:@kentcdodds/linkedin/create-article-post'

export default async function main(input) {
  return createArticlePost({
    text: input.text,
    url: input.url,
    title: input.title,
    description: input.description,
    thumbnailUrl: input.thumbnailUrl,
    confirm: true,
  })
}