Skip to content

[codex] cut over OS streams runtime#1370

Merged
jonastemplestein merged 1 commit into
mainfrom
melted-blender
Jun 5, 2026
Merged

[codex] cut over OS streams runtime#1370
jonastemplestein merged 1 commit into
mainfrom
melted-blender

Conversation

@jonastemplestein

@jonastemplestein jonastemplestein commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Summary

Cuts apps/os over to the stream Durable Object and StreamProcessorRunner from packages/streams, removing the old OS usage of the shared stream DO/processor mixins. The existing OS domain behaviour stays in place, but stream storage, live subscriptions, and processor delivery now go through the new package runtime.

What changed

  • Added OS-local adapters for the package stream runtime and a standalone OS StreamProcessorRunner Durable Object.
  • Rewired project, codemode, repo, Slack, and agent Durable Objects to configure package stream subscriptions and wait on package runner catch-up.
  • Reworked project stream ORPC routes and StreamsCapability.stream() to use withStreamConnectionFromWorkers() and pass an RpcTarget sink into stream.subscribe().
  • Added a focused deployment e2e smoke that creates a project and proves ORPC stream create/list/append/read/live-stream on a nested stream path.
  • Documented the cutover shape and decisions in docs/streams-os-migration/ and ADRs.

Validation

  • pnpm typecheck
  • pnpm test
  • pnpm format:check
  • pnpm exec oxlint . --threads 1 --deny-warnings
  • pnpm --dir apps/os sqlfu:check
  • pnpm --dir apps/os test:project-ingress
  • pnpm --dir apps/os test:codemode-session
  • pnpm --dir apps/os test:project-mcp-server-connection
  • doppler run --project os --config dev_jonas -- pnpm --dir apps/os e2e -t "creates a disposable project and uses project streams through oRPC" --run

The workerd suites still print existing sourcemap/websocket close noise, but exit successfully.

Environment Config Lease

No active environment config lease.

OS

Status: released
Commit: 2f6cd72
Preview: https://os.iterate-preview-2.com
Summary: Preview app released.
Workflow run
Updated: 2026-06-05T14:02:33.817Z


Note

High Risk
This is a hard cutover of core stream storage, live subscriptions, and processor delivery across agents, codemode, projects, repos, and Slack; behavior depends on polling catch-up and is explicitly not a backwards-compatible data migration.

Overview
Cuts apps/os over to @iterate-com/streams for stream storage and processor delivery, replacing the shared StreamDurableObject plus withStreamProcessor / withStreamProcessorRunner mixins.

The STREAM binding now targets the package Stream durable object, and a new STREAM_PROCESSOR_RUNNER binding hosts a standalone StreamProcessorRunner that wires built-in, capnweb-websocket subscriptions (subscriptionKey + processorSlug) instead of callable afterAppend RPC on domain DOs. new-stream-runtime.ts adapts legacy OS event/cursor shapes to the package stream RPC.

Agent, codemode, project, repo, Slack integration, and Slack agent DOs drop in-DO processor runners; they append stream/subscription-configured events, then poll runner runtimeState() until catch-up. Agent-specific stream logic (codemode side effects, child agents) moves into an agent-host processor inside StreamProcessorRunner.

Project stream APIs (StreamsCapability, oRPC streamEvents) use withStreamConnectionFromWorkers and live stream.subscribe sinks; stream listing walks initialized paths from runtime state instead of D1 catalog. CONTEXT.md records the POC cutover (no legacy subscription translation, histories may be discarded).

An admin e2e now exercises create/list/append/read/live-stream on a project stream via oRPC.

Reviewed by Cursor Bugbot for commit 2f6cd72. Bugbot is set up for automated code reviews on this repo. Configure here.

@jonastemplestein jonastemplestein marked this pull request as ready for review June 5, 2026 12:51
Comment thread apps/os/src/entry.workerd.ts
Comment thread apps/os/src/domains/codemode/durable-objects/codemode-session.ts
Comment thread apps/os/src/domains/codemode/durable-objects/codemode-session.ts

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 879a9fd. Configure here.

Comment thread apps/os/src/domains/agents/durable-objects/agent-durable-object.ts
@jonastemplestein jonastemplestein merged commit 4452dad into main Jun 5, 2026
9 checks passed
@jonastemplestein jonastemplestein deleted the melted-blender branch June 5, 2026 14:00
jonastemplestein added a commit that referenced this pull request Jun 5, 2026
Slack-routed agent streams never registered the LLM processors
(agent-chat/agent/provider), so a user message landed as an
agent/input-added event that nothing consumed — the agent never replied.

Regression from #1370 (streams runtime cutover): the routed bootstrap used
to subscribe a callable to AgentDurableObject.afterAppend (which woke the
agent and registered its processors via onInstanceWake); the cutover
replaced it with a built-in agent-host processor that never wakes the agent
for its own stream. What actually starts a processor is the
subscription-configured event, which was never appended for the LLM
processors on routed streams.

Fix:
- agent-host now wakes the AgentDurableObject for its own stream on
  stream/created (ensureAgentRunnerForOwnStream); onInstanceWake registers
  agent-chat/agent/LLM + setup events, whose subscription-configured events
  Stream#reconcileOutboundConnections then dials into runners. Verified the
  runner replays from offset 0, so agent-host reliably sees stream/created
  (always offset 1).
- Align the bootstrap agent-host subscription key with the canonical
  AgentDurableObject key so the two declarations dedupe to a single runner.
- Use the new-runtime event prefix (events.iterate.com/stream/) in the OS
  call-sites that compare against new-runtime core events: the
  child-stream-created check (was the legacy /core/ prefix, which the new
  runtime never emits), the project agents-root jsonata matcher, and the
  stream-composer UI examples.

Scope note: a broader /core/ -> /stream/ rename across the shared package and
the separate events.iterate.com app was reverted — it broke the events
runtime e2e (append 500s) and is unrelated to this bug. Tracked as a
follow-up.

Verified: full-repo typecheck, oxlint, oxfmt, affected OS unit tests pass.
Not yet deployed/round-tripped on a preview.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
jonastemplestein added a commit that referenced this pull request Jun 5, 2026
Slack-routed agent streams never registered the LLM processors
(agent-chat/agent/provider), so a user message landed as an
agent/input-added event that nothing consumed — the agent never replied.

Regression from #1370 (streams runtime cutover): the routed bootstrap used
to subscribe a callable to AgentDurableObject.afterAppend (which woke the
agent and registered its processors via onInstanceWake); the cutover
replaced it with a built-in agent-host processor that never wakes the agent
for its own stream. What actually starts a processor is the
subscription-configured event, which was never appended for the LLM
processors on routed streams.

Fix:
- agent-host now wakes the AgentDurableObject for its own stream on
  stream/created (ensureAgentRunnerForOwnStream); onInstanceWake registers
  agent-chat/agent/LLM + setup events, whose subscription-configured events
  Stream#reconcileOutboundConnections then dials into runners. Verified the
  runner replays from offset 0, so agent-host reliably sees stream/created
  (always offset 1).
- Align the bootstrap agent-host subscription key with the canonical
  AgentDurableObject key so the two declarations dedupe to a single runner.
- Use the new-runtime event prefix (events.iterate.com/stream/) in the OS
  call-sites that compare against new-runtime core events: the
  child-stream-created check (was the legacy /core/ prefix, which the new
  runtime never emits), the project agents-root jsonata matcher, and the
  stream-composer UI examples.

Scope note: a broader /core/ -> /stream/ rename across the shared package and
the separate events.iterate.com app was reverted — it broke the events
runtime e2e (append 500s) and is unrelated to this bug. Tracked as a
follow-up.

Verified: full-repo typecheck, oxlint, oxfmt, affected OS unit tests pass.
Not yet deployed/round-tripped on a preview.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
jonastemplestein added a commit that referenced this pull request Jun 5, 2026
## What was broken

Slack agents stopped responding. A user `@mention` becomes an
`agent/input-added` event on the routed agent stream, but **nothing
consumes it** — the LLM processors (`agent-chat` / `agent` / the
provider processor) were never registered on Slack-routed streams.
Observed live on `templestein2` stream
`/agents/slack/c09trdv61v4/ts-1780670924-517029`: `slack-agent` ran
(produced the input), but no `agent`/LLM processor existed, so no reply.

## Root cause — regression from #1370 (streams runtime cutover)

- **Old runtime:** `routedStreamBootstrapEvents` subscribed a *callable*
to `AGENT` DO `afterAppend`. Invoking it woke `AgentDurableObject` →
`onInstanceWake` registered `agent-chat`/`agent`/LLM/`agent-host` +
seeded setup events.
- **After #1370:** that was replaced with a built-in `agent-host`
processor whose `afterAppend` only runs `ensureChildAgentRunner` (+
codemode handlers). It never wakes the agent for its own stream, so the
LLM processors are never registered.
- What actually *starts* a processor is the `subscription-configured`
event (`Stream#reconcileOutboundConnections` dials a runner per
subscription key). Those were never appended for the LLM processors on
routed streams.
- Compounding: `ensureChildAgentRunner` compared against the legacy
`events.iterate.com/core/child-stream-created`, but the new runtime
emits `events.iterate.com/stream/child-stream-created`.

Dashboard-created agents were unaffected because `new.tsx` explicitly
subscribes the full processor set. **PR #1371 would not have fixed
this** — it only adds `stream-processor-registered` *marker* events
(which don't start processors) and only to the UI flow.

## The fix

- **Wake the agent for routed streams**
(`ensureAgentRunnerForOwnStream`): when `agent-host` runs on a routed
agent stream, on the `stream/created` event it initializes that stream's
`AgentDurableObject` → `onInstanceWake` registers the LLM processors and
setup events; the resulting `subscription-configured` events are what
`reconcileOutboundConnections` dials. Verified the runner replays from
offset 0 (`replayAfterOffset: snapshot?.offset ?? 0`), so `agent-host`
reliably sees `stream/created` (always offset 1).
- **Dedupe the agent-host runner:** align the bootstrap `agent-host`
subscription key with the canonical `AgentDurableObject` key (runner DOs
are keyed by `${namespace}:${path}:${subscriptionKey}`), so the two
declarations resolve to one runner.
- **Use the new-runtime `events.iterate.com/stream/` prefix** at the OS
call-sites that compare against new-runtime core events: the broken
`child-stream-created` check (local constants), the project agents-root
jsonata matcher, and the stream-composer UI examples.

## Verification

- Full-repo `pnpm typecheck` (18 projects), `oxlint`, `oxfmt`
- Affected OS unit tests pass
- Not yet deployed / round-tripped on a preview — recommend a preview
deploy + one test `@mention` before prod (`Preview / e2e` is otherwise
skipped on PRs).

## Scope note

An earlier revision also did a broad `events.iterate.com/core/` →
`/stream/` rename across the shared package and the separate
**events.iterate.com** app. That **broke the events runtime e2e**
(append → 500) and is unrelated to this bug, so it was reverted out of
this PR. If we still want the events platform on the `/stream/` prefix
it needs its own investigation — tracked as a follow-up.

## Follow-ups (found while auditing #1370)

- `ProjectDurableObject.afterAppend` is **orphaned**: the old runtime
forwarded lifecycle events to the project config-worker `afterAppend`
hook; the new built-in `project-lifecycle` processor has no
`afterAppend` and nothing calls it.
- `CodemodeSession.afterAppend` is orphaned (likely benign — resolves
locally via `appendAndConsume`).
- `agents.e2e.test.ts` has vacuous `/core/error-occurred` assertions;
worth real Slack-path e2e coverage so this can't regress silently.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- CLOUDFLARE_PREVIEW -->
## Environment Config Lease
<!-- CLOUDFLARE_PREVIEW_STATE -->
<!--
{
  "apps": {
    "os": {
      "appDisplayName": "OS",
      "appSlug": "os",
      "status": "deployed",
      "updatedAt": "2026-06-05T19:21:55.576Z",
      "headSha": "2ba51d83ff4b86b9ca37a30b463cd3cd9e1f4d54",
      "message": null,
      "publicUrl": "https://os.iterate-preview-2.com",
"runUrl": "https://github.com/iterate/iterate/actions/runs/27035212234",
      "shortSha": "2ba51d8"
    }
  },
  "environmentConfigLease": {
    "dopplerConfig": "preview_2",
    "leasedUntil": 1780690796123,
    "leaseId": "23930c5a-2e1b-49b4-b10a-c83b489c0794",
    "slug": "preview-2",
    "type": "environment-config-lease"
  }
}
-->
<!-- /CLOUDFLARE_PREVIEW_STATE -->
Lease: `preview-2`
Doppler config: `preview_2`
Type: `environment-config-lease`
Leased until: 2026-06-05T20:19:56.123Z

### OS
Status: deployed
Commit: `2ba51d8`
Preview: https://os.iterate-preview-2.com
[Workflow
run](https://github.com/iterate/iterate/actions/runs/27035212234)
Updated: 2026-06-05T19:21:55.576Z
<!-- /CLOUDFLARE_PREVIEW -->

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Touches agent stream bootstrap and durable-object initialization on
every routed agent stream; wrong event matching or wake ordering could
affect non-Slack agents, but changes are narrowly scoped to host
processor and Slack routing.
> 
> **Overview**
> Restores **Slack-routed agent streams** after the streams runtime
cutover (#1370): routed streams only got `slack-agent` + `agent-host`,
so `agent/input-added` events were never consumed because LLM processors
were never registered.
> 
> **`ensureAgentRunnerForOwnStream`** initializes the stream’s
`AgentDurableObject` on `events.iterate.com/stream/created` (via
`agent-host` `afterAppend`, using **`keepAlive`** to avoid deadlocking
catch-up). That runs `onInstanceWake`, which appends the LLM processor
subscriptions and setup events.
> 
> **Slack bootstrap** now uses
**`agentProcessorSubscriptionConfiguredEvent`** so the routed
`agent-host` subscription key matches `AgentDurableObject`, deduping to
a single runner.
> 
> **Event type alignment** for the new runtime: `child-stream-created`
and related core lifecycle types use `events.iterate.com/stream/…`
instead of legacy `…/core/…` in agent host logic, the agents-root
jsonata matcher, and stream composer presets.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
2ba51d8. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant