Publish the voice agent as @iterate-com/voice-agent; projects pull it in from package.json
The realtime voice agent (configs/voice-agent/voice-agent.ts and its four
siblings, ~325 KB) was installed by voicelab deploy and by the mobile app's
embedded copy committing the source files into each project's config repo.
Every project then carried its own copy, and the copies aged separately: on
2026-09-07 the iterate and templestein projects' copies differed from
each other in comments only, but both were ~280 lines ahead of this repo's
template (a voice-agent/say event and an idle farewell that live in no
branch here), and the iterate project's copy failed the iterate project's
own lint rules.
The packaged starter apps already show the shape that avoids this: a worker
ref names an entry point inside node_modules, the config repo contributes
only package.json, and the platform builds the guest from the installed
package. This task moves the voice agent to that shape as a second workspace
package next to @iterate-com/docs.
What this change does#
packages/voice-agent(@iterate-com/voice-agent, 0.1.0): an ordinary library build with two entries../workeris the agent (the stateless entrypoint as default export,VoiceAgentFacet), withiteratea peer and zod a dependency — both external, resolved from the config repo's own package.json when the platform buildsvoice-agent.ts..is what a project worker, the CLI and the phone import:VoiceAgentApp(a partialfetchfor thevoiceapp slug, plussetup/remove), the worker refs (which namevoice-agent.tsin the config repo, as they always did, same durable key so facet state survives), the installer, and the guest's RPC surface as plain types the entrypoint classimplements. The root entry imports nothing but zod: the SDK's ref types carry Cloudflare's runtime types, which the mobile app cannot compile against, soref.tsspells the shapes locally andref.test.tspins them to the SDK's;app.tstypes the project handle structurally like the Docs app's bridge. Declarations come fromtsc -p tsconfig.dts.json(rolldown-plugin-dts's printer crashes on function types inside interfaces). Unit tests for the installer, the refs and the app live in the package; the agent's behavioural tests stay inapps/os/scripts/voicelab/and import the sources directly.- The install is two dependency lines and a three-line file. A config
repo declares the package and zod (the SDK's processor entry leaves zod
external; every template already declares it) and holds
voice-agent.ts=export { default, VoiceAgentFacet } from "@iterate-com/voice-agent/worker". The platform builds that file the way it builds worker.ts. The repo holds the agent's name, not a copy; a project subclasses in the same file if it needs to.packages/voice-agent/INSTALL.mdsays this for an agent making the edit. - Publishing:
.github/workflows/pkg-pr-new.ymlbuilds and publishes the package with the other two, sohttps://pkg.pr.new/iterate/iterate/@iterate-com/voice-agent@{main,<sha>,<pr>}exist on every push and PR. No platform change:apps/os/src/pkg-pr-new.tsrecognises specs by URL shape, so a deployment pins this package to its own ref exactly as it pinsiterate. voicelab deploywrites the two files (--specto pin,--prune-legacyto delete the sources an older deploy committed beside voice-agent.ts);talkfills gaps if absent and never overwrites a pin or a voice-agent.ts holding something else.- Mobile:
voice-setup.tsuses the package's installer and refs; the embedded copy (voice-template.generated.tsand its codegen) is gone. Which build a project runs is now the platform's pin rather than the app's own copy — a deliberate change, recorded in the code. configs/voice-agentis the template version:package.json,voice-agent.ts, and aworker.tsthat hands the voice app slug toVoiceAgentApp. The browser client behind that slug istasks/2026-09-08-voice-web-chat-app.md; until it exists the slug answers 501 and says so.
Not done here, deliberately#
- Upstream the deployed copies' delta first. The
sayevent and idle farewell (~280 lines, contract still 19.0.0) exist only in project config repos. Moving those projects onto the package before that delta lands inpackages/voice-agentwould downgrade them. Bring it over as its own reviewed PR, then runvoicelab deploy --prune-legacyagainstiterateandtemplestein. - Preview proof. Done on slot 10 for the first shape (a package-only
project answered
healthand folded a setup certificate through the facet); to be repeated for the re-export shape, since the platform now buildsvoice-agent.tsfrom the repo and resolves the package and zod from its package.json. An actual audio call is only covered by the voicelab e2e. - The SDK's bundling lists.
packages/iteratebundles capnweb, sqlfu, yaml and zod into its starter-app workers on the ground that the host does not install what a tarball dependency declares; the proof above shows it resolving capnweb from theiteratetarball's own dependencies. Whether those lists (and the templates' zod lines) are still needed istasks/2026-09-08-tarball-transitive-dependencies.md. - npm. pkg.pr.new refs are the version that matters to the platform;
a tag-triggered
npm publish --provenancecan come later if a registry version is wanted.
Rollout wrinkle#
A warm stateful facet keeps the bundle it booted with. talk already kills
it after a changed install; a project that upgrades by editing
package.json by hand needs the same restart before the new build serves.