Better with Kent

I Built a Runtime Where AI Agents Can Save Their Work

Extend the agent you already use · search · execute · packages

Kent

I asked X

x.com/kentcdodds/status/2067635082173489159 · 70+ replies

Why don't you have a personal AI assistant?

  • Already pay for Claude / Codex / Cowork — enough for now
  • No use case · life isn't complicated enough
  • Maintenance · tinkering · privacy

I asked X · reply

Kevin Swiber

"I use Claude Chat and Claude Cowork with integrations. Cowork has scheduled tasks now. I already pay a lot for the subscription, and I don't have to maintain it myself. It works for me."

@kevinswiber · x.com/kevinswiber/status/2067664036872040811

Your agent already exists

  • Brain — Cursor · Codex · Claude Code — the agent you already use all day
  • Kody — thin plumbing: search · execute · saved packages to APIs, home, jobs
  • Not a second assistant app · extension for the weird edges

Other harnesses exist — today is how I wired Kody.

Two tools on the host

  • search — progressive disclosure (capabilities, packages, secrets metadata)
  • execute — write and run code in a sandbox

Not dozens of tool schemas loaded into every context window.

Architecture

flowchart LR
  archHost["MCP host\nCursor / Codex · two tools"]
  archSearch["search\ncatalog"]
  archExecute["execute\nplayground"]
  subgraph archRuntime["Kody runtime · persists"]
    direction TB
    archPackages["packages\nsaved exports"]
    archCaps["capabilities\nmemory · retrievers"]
  end
  archConnectors["connectors\nhome:default · remote"]
  archHome["home\nBond · Lutron · Sonos"]
  archWorld["OAuth · APIs · Workers"]
  archHost --> archSearch
  archHost --> archExecute
  archSearch -->|"surfaces"| archCaps
  archSearch -->|"finds"| archPackages
  archExecute -->|"imports"| archPackages
  archExecute -->|"codemode"| archCaps
  archPackages <-->|"fetch & act"| archWorld
  archCaps --> archConnectors
  archConnectors --> archHome
  archCaps --> archWorld

Connectors bridge your LAN — shades, lights, speakers — without forty home MCP tools on the host.

Packages

  • Saved modules with exports, optional jobs, and retrievers
  • Retrievers surface package data in search — not just exports to import
  • Checked, versioned surfaces — not markdown skills or gists
  • Next agent imports kody:@scope/pkg/export

When exploratory execute code works, I publish a package.

The pipeline

your prompt ad hoc execute publish package

You don't have to automate — sometimes a saved export is the whole win.

The pipeline

Triggers & invocations

  • Triggers — scheduled jobs · one-shot workflows · webhooks · event subscriptions
  • Package invocationspackages.invoke from another package, a job, or an external webhook into Kody

Shades story — three prompts: fix now · package it · morning plan schedules today's moves.

Demo — office chaos

flowchart LR
  demoIssue["GitHub issue filed"] --> demoExecute["execute"]
  demoExecute --> demoLutron["Lutron accents"]
  demoExecute --> demoBond["Bond shade"]
  demoExecute --> demoSonos["Office Sonos"]
  demoExecute --> demoRestore["restore colors + shade + stop"]

Cold open on camera — automated GitHub path, not a prompt you'd type.

Glare → shades

Kitchen counter glare in the afternoon sun Kitchen with shades down — glare gone

Kitchen West — close the shade when afternoon sun glares off the counter.

Phase 1 · fix it now

The prompt

Kent The sun is glaring off of the kitchen counter into my face. Please close the shades to stop the glare.

search

MCP · search

{
  "query": "bond shade kitchen west close position home connector",
  "limit": 8,
  "memoryContext": {
    "task": "Close shades to stop sun glaring off the kitchen counter",
    "entities": ["kitchen counter", "glare", "shades", "Kitchen West"]
  }
}

Sample: slides/samples/search-query-glare-phase1.json

search → ranked hits

  • home:defaultbond_shade_set_position
  • home:default — list Bond devices · shade names + ids
  • Entity detail — deviceId · deviceName · position

search

MCP · search — one capability

{
  "entity": "home_default_bond_shade_set_position:capability"
}

Sample: slides/samples/search-entity-bond-shade.json

execute

MCP · execute

import { codemode } from 'kody:runtime'

const KITCHEN_WEST = { deviceId: '4a0522ccbb052cb1', deviceName: 'Kitchen West' }

export default async function main() {
  await codemode.home_default_bond_shade_set_position({
    ...KITCHEN_WEST,
    position: 0,
  })

  return { ok: true, position: 0, device: KITCHEN_WEST.deviceName }
}

Sample: slides/samples/execute-close-shades.ts

execute → result

{
  "ok": true,
  "position": 0,
  "device": "Kitchen West"
}

Shades closed — glare stopped.

Sample: slides/samples/execute-result-close-shades.json

Phase 2 · make it reusable

The prompt

Kent Why don't we make a package for controlling the shades to make that easier?

package export

close-shades.ts

import { codemode } from 'kody:runtime'

const KITCHEN_WEST = { deviceId: '4a0522ccbb052cb1', deviceName: 'Kitchen West' }

export default async function closeShades() {
  await codemode.home_default_bond_shade_set_position({
    ...KITCHEN_WEST,
    position: 0,
  })

  return { ok: true, position: 0, device: KITCHEN_WEST.deviceName }
}

Import path: kody:@kentcdodds/kitchen-shade-control/close-shades · sample: close-shades.ts

package source

package.json

{
  "name": "@kentcdodds/kitchen-shade-control",
  "exports": {
    "./close-shades": "./close-shades.ts"
  },
  "kody": {
    "id": "kitchen-shade-control",
    "description": "Close Kitchen West Bond shade to stop afternoon counter glare.",
    "tags": ["home", "shades", "bond", "kitchen"]
  }
}

Sample: package-kitchen-shade-control-package.json

Phase 2 · package source

README · Intent

README.md · what Kody surfaces in search

# @kentcdodds/kitchen-shade-control

Close the Kitchen West Bond shade when afternoon sun glares off the counter.

## Intent

This package exists so Kent can close the Kitchen West shade with one import
instead of repeating device id and codemode calls every time the counter glare
prompt comes up.

Sample: package-kitchen-shade-control-readme.md

package_save

MCP · package_save + repo workflow

  • Create package @kentcdodds/kitchen-shade-control
  • Clone · add package.json · README · exports
  • Commit · push · publish

Samples: package-kitchen-shade-control-package.json · package-kitchen-shade-control-readme.md

invoke export

MCP · execute — import the published export

import closeShades from 'kody:@kentcdodds/kitchen-shade-control/close-shades'

export default async function main() {
  return await closeShades()
}

Sample: slides/samples/invoke-close-shades.ts

Phase 3 · automate it

The prompt

Kent Why don't we do this: automatically close and open the shades to avoid the sun ever reflecting into my kitchen?

package update

scheduleKitchenGlareDay · sun geometry · one-shot workflows

export default async function scheduleKitchenGlareDay() {
  const day = new Date().toISOString().slice(0, 10)
  const closeAt = await findGlareCloseTime()
  const openAt = await findGlareOpenTime()

  await workflows.create({
    exportName: './run-glare-event',
    runAt: closeAt,
    idempotencyKey: `kitchen-glare-close:${day}`,
    params: { action: 'close' },
  })
  // mirror workflows.create for openAt · action: 'open'

  return { closeAt, openAt }
}

Samples: schedule-kitchen-glare-day.ts · run-glare-event.ts

package job

package.json · kody.jobs

"jobs": {
  "daily-plan": {
    "entry": "./jobs/daily-plan.ts",
    "schedule": {
      "type": "cron",
      "expression": "0 10 * * *"
    },
    "timezone": "UTC"
  }
}

Morning daily-plan runs this · Kody syncs on publish · run-glare-event at each scheduled time.

Same pipeline · many times

What I use Kody for

  • Morning briefing — weather, calendar signals, one Discord message
  • Personal history — autobiography via daily prompts and journal threads
  • Cursor Cloud agents — launch, follow up, and inspect agents from Kody
  • Office / home — shades, Lutron accents, Sonos (you saw the chaos)

Same pipeline · many times

What I use Kody for

  • GitHub → office alert — issue filed → lights, shade, music → restore
  • Promo / YouTube — scheduled signals without opening five dashboards
  • Deploy — toddler game to Cloudflare without logging into the UI
  • Saved packages — agents search and import kody:@scope/pkg

Also: Miami weather → Spotify playlist — same composition pattern.

Kent's instance

What one Kody account looks like

Capabilities 198

home:default 126 · built-in 72

Saved packages 55

380 exports · 13 hosted apps

Snapshot 2026-06-17 · slides/samples/kody-instance-stats.json

Kent's instance

Jobs & connected surface

Scheduled jobs 42

25 enabled · 17 disabled

Connected surface 11 · 51 · 83

OAuth · secret refs · user values

Code Mode vs tool catalog

Same workflow as separate MCP tools → context noise + more failure points.

  • Weather tool · shade tool · recording flag tool · restore tool…
  • One execute — agent writes the glue once
  • Package + triggers — glue becomes deterministic and callable

Free upgrades

Piggyback what you already pay for

  • Model upgrades — Cursor · Codex · Claude Code get smarter → same Kody packages and jobs · better composition · no redeploy
  • Subscriptions — Cowork · Claude integrations · GitHub · Spotify — you already have accounts · Kody wires them without a second assistant bill
  • Brain improves in the host · plumbing stays thin · saved work persists in packages

Kevin's Cowork answer on slide 3 — fair if that's enough for you.

Integrations

OAuth · saved accounts · search discovery

  • search surfaces integrations before you build — GitHub · Google · Spotify…
  • Connect at heykody.dev — OAuth in browser · tokens server-side
  • Packages import helpers — kody:@kentcdodds/google-products · multi-account YouTube

Secrets & permissions

  • Agent never sees raw credentials — not in prompts · not in execute results
  • Secret reference — placeholder in fetch · host injects on approved domains only

Secrets & permissions

Secret reference

MCP · execute

export default async function main() {
  const response = await fetch(
    'https://api.github.com/user/repos?per_page=5',
    {
      headers: {
        Authorization: 'Bearer {{secret:githubPat|scope=user}}',
        Accept: 'application/vnd.github+json',
      },
    },
  )

  const repos = await response.json()
  return { count: repos.length }
}

Sample: execute-secret-reference.ts

Secrets & permissions

Host approval blocked

MCP · execute → error · first fetch to a new domain

{
  "error": "Secrets require host approval: [...]",
  "errorDetails": {
    "kind": "host_approval_required_batch",
    "missingApprovals": [
      {
        "secretName": "githubPat",
        "host": "api.github.com"
      }
    ]
  }
}

Sample: execute-host-approval-error.json

Secrets & permissions

OAuth by provider name

  • createAuthenticatedFetch('spotify') — tokens stay server-side · no secret placeholder in code
  • Connect at heykody.dev · agent names the integration · you approved hosts when you connected
  • Permissions don't auto-expand when the model upgrades — same approved surface

Secrets & permissions

OAuth execute

MCP · execute

import { createAuthenticatedFetch } from 'kody:runtime'

export default async function main() {
  const fetchSpotify = await createAuthenticatedFetch('spotify')
  const response = await fetchSpotify(
    'https://api.spotify.com/v1/me/player',
  )
  const player = await response.json()
  return { isPlaying: player?.is_playing ?? false }
}

Sample: execute-oauth-fetch.ts

Better with Kent

Durable skills for people who ship software

  • Brain = your coding agent · Kody = thin wire + durable packages
  • Experimental lab · github.com/kentcdodds/kody
  • What would you package after the third repeat?

Subscribe · comment · get better with me