chore(emdash-bot): port to Flue 2.0.3 with Workers traces (bot next-gen 1/5) - #2375
Conversation
|
Scope checkThis PR changes 756 lines across 13 files. Large PRs are harder to review and more likely to be closed without review. If this scope is intentional, no action needed. A maintainer will review it. If not, please consider splitting this into smaller PRs. See CONTRIBUTING.md for contribution guidelines. |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-demo-cache | aecf8b7 | Aug 09 2026, 08:35 AM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-demo-do | 2578317 | Aug 09 2026, 07:19 AM |
@emdash-cms/admin
@emdash-cms/auth
@emdash-cms/auth-atproto
@emdash-cms/blocks
@emdash-cms/cloudflare
@emdash-cms/contentful-to-portable-text
emdash
create-emdash
@emdash-cms/gutenberg-to-portable-text
@emdash-cms/plugin-cli
@emdash-cms/plugin-types
@emdash-cms/registry-client
@emdash-cms/registry-lexicons
@emdash-cms/registry-verification
@emdash-cms/sandbox-workerd
@emdash-cms/x402
@emdash-cms/plugin-ai-moderation
@emdash-cms/plugin-atproto
@emdash-cms/plugin-audit-log
@emdash-cms/plugin-color
@emdash-cms/plugin-embeds
@emdash-cms/plugin-field-kit
@emdash-cms/plugin-forms
@emdash-cms/plugin-webhook-notifier
commit: |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-playground | aecf8b7 | Aug 09 2026, 08:34 AM |
There was a problem hiding this comment.
Pull request overview
Ports infra/emdash-bot from the Flue 0.4 nightly line to Flue 2.0.3, updates the bot’s generated Flue glue code to the Flue 2 APIs (notably { output } tool envelopes and dispatch receipt field changes), and enables Workers trace collection.
Changes:
- Upgrade
@flue/runtime/@flue/viteto 2.0.3 and alignagentsto^0.20.1, including workspace overrides/lockfile updates. - Update Flue 2 API call sites (
createSandbox,{ output }return envelopes,submissionIdreceipt field) and adjust classifier dispatch/read timing behavior. - Enable Cloudflare Workers traces via
wrangler.jsoncand add explicit Flue tracing instrumentation at app entry.
Reviewed changes
Copilot reviewed 12 out of 13 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| pnpm-workspace.yaml | Removes nightly pin exclusions and updates the Flue→Hono override to the 2.0.3 selector. |
| pnpm-lock.yaml | Lockfile refresh reflecting Flue 2.0.3 + agents upgrade and related dependency graph changes. |
| infra/emdash-bot/wrangler.jsonc | Enables Workers observability traces collection. |
| infra/emdash-bot/vitest.workers.config.ts | Increases integration test timeout to accommodate remote-proxy warmup. |
| infra/emdash-bot/tests/unit/sandbox-deadline.test.ts | Updates tests for createSandbox() API rename. |
| infra/emdash-bot/package.json | Updates Flue and agents dependency pins to the Flue 2 stack. |
| infra/emdash-bot/.flue/lib/sandbox-deadline.ts | Ports factory hook from createSessionEnv to createSandbox. |
| infra/emdash-bot/.flue/lib/orchestrator.ts | Updates receipt field use from dispatchId to submissionId. |
| infra/emdash-bot/.flue/lib/observer.ts | Removes deprecated dispatchId correlation fallback. |
| infra/emdash-bot/.flue/lib/classifier-client.ts | Refactors classifier dispatch/read flow for Flue 2 admission semantics + shared timeout budget. |
| infra/emdash-bot/.flue/app.ts | Installs Cloudflare tracing instrumentation at module scope. |
| infra/emdash-bot/.flue/agents/investigate.ts | Updates tool return to Flue 2 { output } envelope. |
| infra/emdash-bot/.flue/agents/classify-command.ts | Updates tool return to Flue 2 { output } envelope. |
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| async createSandbox(context) { | ||
| const env = await withDeadline( | ||
| factory.createSessionEnv(context), | ||
| factory.createSandbox(context), | ||
| options.defaultTimeoutMs, | ||
| "Sandbox session creation", | ||
| ); |
| } catch (err) { | ||
| return { kind: "error", error: errorMessage(err) }; | ||
| } |
| // Module scope: registers before (and overrides) Flue's default tracing install. | ||
| // Omitting `content` captures payloads; `content: false` would drop them. | ||
| instrument(createCloudflareTracing()); |
Overlapping PRsThis PR modifies files that are also changed by other open PRs:
This may cause merge conflicts or duplicated work. A maintainer will coordinate. |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
docs | a360854 | Aug 09 2026, 06:34 AM |
There was a problem hiding this comment.
This is the right scope for a skeleton slice: a pure, mechanical Flue 2.0.3 framework port of the private infra/emdash-bot package with no behavior redesign. The diff stays inside the bot package, pnpm-workspace.yaml, and the lockfile; no published packages, admin UI, SQL, or logged-out routes change. The API renames (dispatchId ⇢ submissionId, createSessionEnv ⇢ createSandbox), the { output } tool-return envelopes, and the new init()/dispatch()/read() classifier flow all match the stated Flue 2 contract. I found no blocking bugs or regressions.
Re-review status: the previous review's classifier dispatch-path coverage gap is now filled in (dispatch stall, budget exhaustion, rejected read), so that suggestion is resolved. The shared-timeout robustness concern is still present. New in this pass are AGENTS.md comment-discipline violations in the changed files and a low-confidence note that Flue 2 may have made useDataWriter async, in which case the agent write calls need await.
Headline: clean mechanical port, but trim the narrative comments and verify the writer calls and classifier deadline before merging.
Findings
-
[suggestion]
infra/emdash-bot/.flue/app.ts:24-25The added module-scope comments explain and justify the tracing design ("registers before and overrides Flue's default tracing install", "Omitting
contentcaptures payloads"). AGENTS.md says comments should not justify decisions or narrate design rationale; that context belongs in the commit message / PR description. The code itself already expresses what happens, so these lines can be removed.import { Investigate } from "./agents/investigate.js"; import { installAgentObserver } from "./lib/observer.js"; import { registerCoreRoutes } from "./routes.js"; instrument(createCloudflareTracing()); installAgentObserver(); -
[suggestion]
infra/emdash-bot/.flue/lib/classifier-client.ts:44-46This comment both justifies the design and references a "GitHub's webhook ack window" motivation that does not match the current architecture: the webhook handler (
routes.ts) returns 202 immediately after enqueueing the DO, and classification runs inside the DO alarm. AGENTS.md disallows comments that justify decisions or narrate rationale. The shared-deadline code that follows is self-explanatory, so the comment can be deleted.const handle = init(ClassifyCommand, { id: `classify-${crypto.randomUUID()}`, uid: null, }); const deadline = Date.now() + CLASSIFY_TIMEOUT_MS; const receipt = await withDeadline( -
[suggestion]
infra/emdash-bot/wrangler.jsonc:90-91The added comment explains why
traces.enabledis set. AGENTS.md disallows comments that justify decisions or narrate design rationale; the flag name and value are self-explanatory and the surrounding PR description already documents the Flue 2 tracing contract."observability": { "enabled": true, "traces": { "enabled": true, -
[suggestion]
infra/emdash-bot/vitest.workers.config.ts:48-49The added comment explains/justifies the
testTimeoutbump. AGENTS.md disallows comments that justify decisions; the timeout value is a configuration knob and the rationale belongs in the PR description.test: { include: ["tests/integration/**/*.test.ts"], testTimeout: 20_000, }, -
[suggestion]
infra/emdash-bot/.flue/lib/classifier-client.ts:47-71classifyCommentsplits the 10 s budget between admission and read usingDate.now(). Wall-clock time can jump backwards or forwards, so the remaining budget can be larger or smaller than actually elapsed. Whendispatch()returns with a tiny positive remainder,handle.read(..., AbortSignal.timeout(remainingMs))will almost certainly abort immediately. Consider using a single monotonic budget around the wholedispatch + readsequence if Flue's API allows it, or at least a monotonic deadline source such asperformance.now(). -
[suggestion]
infra/emdash-bot/.flue/agents/classify-command.ts:66writeResult(classification)is called in an async toolrun()withoutawait. If Flue 2'suseDataWriterreturns a Promise (which is typical for durable writes), the tool step may settle before the data is persisted and rejections become unhandled. The same pattern appears at line 89. Verify the return type; if it is async, await these calls.setResult(classification); await writeResult(classification); return { output: classification }; -
[suggestion]
infra/emdash-bot/.flue/agents/investigate.ts:120writeResult(reportedResult)is called in the durable toolreport_resultwithoutawait. TheuseAgentStartanduseAgentFinishcallbacks do the same at lines 99 and 151. If Flue 2'suseDataWriterreturns a Promise, the step may complete before the data is persisted and rejections become unhandled. Verify the return type; if it is async, await all three write calls.const reportedResult = { result: data, ok: true, pushed }; await writeResult(reportedResult); setReported(true);
a360854 to
2578317
Compare
|
Round-2 items addressed: the classifier budget now uses The ~ 🤖 Claude Fable 5 |
There was a problem hiding this comment.
This remains the right scope for the skeleton slice: a purely mechanical Flue 2.0.3 port of infra/emdash-bot with no behavior redesign. The renames (dispatchId ⇢ submissionId, createSessionEnv ⇢ createSandbox), the { output } tool-return envelopes, and the new dispatch()/read() classifier flow all line up with the Flue 2 contract.
I re-read the changed files, the new classifier-client tests, and the lockfile diff. No logic regressions or security issues jumped out. The classifier-client shared-timeout concern from the prior pass is now addressed: it uses performance.now() instead of wall-clock Date.now() and the dispatch/read split is covered by unit tests for admission stall, budget exhaustion, and rejected reads. The wrangler.jsonc and classifier-client comment-discipline notes from the prior review are also fixed.
What is still open from the prior pass:
- Two AGENTS.md comment-discipline violations remain in the current diff:
infra/emdash-bot/.flue/app.tsadds a comment that justifies the instrumentation ordering.infra/emdash-bot/vitest.workers.config.tsadds a comment that justifies thetestTimeoutvalue.
- The potential
useDataWriterasync issue is still unaddressed and unverified in this pass. I still cannot confirm the Flue 2 return type without runnable typechecking or node_modules, but ifuseDataWriterreturns a Promise then the unawaitedwriteResultcalls inclassify-command.tsandinvestigate.tscould settle the step before the durable write persists and turn rejections into unhandled failures.
All remaining findings are suggestions; none of them block merging the port itself.
One small side note: the lockfile diff is broader than the PR prose suggests — it drops tarball: URLs from many Cloudflare packages across the lockfile, not just the two infra/* importers. I don’t believe that changes the resolved tree, but you may want to confirm the pnpm version so the diff matches the stated intent.
Findings
-
[suggestion]
infra/emdash-bot/.flue/app.ts:25The added comment justifies why
instrument()needs to be at module scope and why it must precede Flue’s default tracing install. AGENTS.md allows comments for non-obvious ordering constraints, but the instrumentation call is already self-explanatory and the PR description already documents the Flue 2 tracing contract. The comment is reviewer-facing rationale rather than a footgun a future reader would otherwise miss.import { Investigate } from "./agents/investigate.js"; import { installAgentObserver } from "./lib/observer.js"; import { registerCoreRoutes } from "./routes.js"; instrument(createCloudflareTracing()); installAgentObserver(); -
[suggestion]
infra/emdash-bot/vitest.workers.config.ts:46The added comment explains the timeout rationale (
Below ~20s the first test flakes...). AGENTS.md says comments should not justify decisions; thetestTimeoutvalue is a configuration knob and its rationale belongs in the PR description, not in the config file.test: { include: ["tests/integration/**/*.test.ts"], testTimeout: 20_000, }, -
[suggestion]
infra/emdash-bot/.flue/agents/classify-command.ts:42The
writeResultcalls at lines 66 and 89 are not awaited. The prior pass flagged this as a low-confidence concern, but it is still unaddressed and unverified in this pass. If Flue 2 madeuseDataWriterreturn aPromisefor durable writes (the common pattern), the tool step may settle and the agent may finish before the data is persisted, and any rejection becomes an unhandled rejection.I cannot confirm the exact return type without runnable typechecking or inspectable
@flue/runtimetypes, so please verify. If it is async, await both calls:// line 66 setResult(classification); await writeResult(classification); return { output: classification };// line 89 const fallback = { event: "none", arg: null, reasoning: "No clear command selected" }; setResult(fallback); await writeResult(fallback); -
[suggestion]
infra/emdash-bot/.flue/agents/investigate.ts:76The
writeResultcalls at lines 99, 120, and 151 are also not awaited. As withclassify-command.ts, if Flue 2’suseDataWriteris async, the agent step may complete before the durable result is persisted and rejections become unhandled failures.I cannot confirm this from the checked-out files alone, so please verify the Flue 2 return type. If it returns a Promise, await all three write calls:
// line 99 const result = failedResult( `I couldn't prepare the investigation sandbox: ${errorMessage(error)}`, ); await applyInvestigationResult(input, result, false, false); await writeResult({ result, ok: false, pushed: false }); setReported(true); log.error("sandbox setup failed", { error: errorMessage(error) });// line 120 const reportedResult = { result: data, ok: true, pushed }; await writeResult(reportedResult); setReported(true); log.info("investigation reported", {// line 151 await applyInvestigationResult(input, result, false, false); await writeResult({ result, ok: false, pushed: false }); setReported(true); log.warn("agent stopped without reporting", { runId: input.runId });
Move the bot off the reviewed Flue 2 nightly onto the 2.0.3 release, and align its bundled agents SDK dep with what @flue/vite ships (0.20.1). The workspace hono override and cooldown-exclude pins are updated to match; the nightly excludes are dropped since 2.0.3 is past the release-age cooldown.
Flue 2.0.3 tightens several surfaces the bot uses:
- defineTool run() with an output schema must return { output } (envelope),
not the bare value.
- init() handles split dispatch (returns a receipt) from read() (awaits the
reply); the classifier now dispatches then reads with its timeout signal.
- DispatchReceipt.dispatchId is renamed submissionId.
- FlueEvent no longer carries dispatchId (observer drops it).
- SandboxFactory's primary method is createSandbox; createSessionEnv is
deprecated, so withSandboxDeadlines must wrap createSandbox or the
deadlines are silently bypassed. The unit test's mock follows suit.
Behavior is unchanged; these are mechanical adaptations to the new types.
Flue 2's generated Worker entry installs content-on Cloudflare tracing by default; this flag activates collection on the account. Content is enabled (issues/PRs are public), matching the design's diagnosable-by-default goal.
The AI binding forces the vitest workers pool onto a remote proxy; the first test to run pays the connection warmup and intermittently overran the 5s default. Raise to 20s so the suite is deterministic.
…pp entry Register instrument(createCloudflareTracing()) at module scope so content capture is explicit rather than relying on Flue's default install. Content is on (public issue/PR data); the API expresses on/off as omit/false, with no true literal.
…ut budget The dispatch/read split left handle.dispatch() (which resolves only on admission) with no deadline, so an admission stall would hang classifyComment past GitHub's webhook ack window instead of erroring. Wrap dispatch in withDeadline and share one budget with the read so admission + settlement stay within the 10s envelope. Also trims narrative from the traces comment in wrangler.jsonc.
…eterministically on an exhausted budget
…m rationale comments
2578317 to
b6095ef
Compare
What does this PR do?
Slice 1 of the bot next-gen stack (design: maintainer-triggered triage/repro bot replacing the Actions-driven gen-1 pipeline). This slice is a pure framework port with no behavior redesign:
infra/emdash-botmoves from@flue0.4-nightly to Flue 2.0.3, with Workers trace collection enabled.@flue/runtime+@flue/vite→ 2.0.3;agents→ ^0.20.1 (aligned with what@flue/vitebundles). Rootpnpm-workspace.yaml: drops the two now-dead nightlyminimumReleaseAgeExcludepins and repoints the@flue/runtime>honooverride to 2.0.3. Lockfile changes are confined to the two privateinfra/*importers plus one catalog drift-correction; no published package's resolved tree changes.run()returns{ output }envelopes;dispatch()now resolves on admission so the classifier doesdispatch()+read(receipt)under a single shared 10s budget (admission stall now rejects instead of hanging past GitHub's webhook ack window);DispatchReceipt.submissionIdrename;FlueEventdropsdispatchId;withSandboxDeadlinesrewritten onto the newcreateSandboxfactory method — under Flue 2 the oldcreateSessionEnvwrapper would have been silently bypassed, so this closes a latent deadline hole.Flue<Pascal>Agentnaming produces class names byte-identical to the existing migration v3. Migrations block untouched.observability.traces.enabledin wrangler + explicitinstrument(createCloudflareTracing())at app-entry module scope (content-on; public data). Verified against the built runtime that the generated entry's default installer sees the existing instrumentation and no-ops — no double-install.Groundwork for the stack: slice 2 (state-machine trigger/fix-loop states), slice 3 (@cloudflare/computer execution layer), slice 4 (pkg.pr.new fix loop), slice 5 (evals + Actions cutover).
Closes #
Type of change
Checklist
pnpm typecheckpasses — n/a-with-note:infra/emdash-botis not in any typecheck gate and has a pre-existing red state (68 errors from unresolvable DO generics; files byte-identical to main). The port introduces zero new errors; every error the upgrade caused was fixed.pnpm lintpasses — oxlint--type-awareclean on all changed filespnpm testpasses — emdash-bot unit 83/83, integration 29/29 (workers pool)pnpm formathas been run — oxfmt no-op (already tab-correct)infra/emdash-botis not a published packageAI-generated code disclosure
Screenshots / test output
Try this PR
Open a fresh playground →
A full working EmDash site, deployed from this branch. Each visit gets its own session-scoped sandbox: no login needed and no shared state. Try the admin, edit content, hit the public site.
Tracks
bot-nextgen/01-flue2-skeleton. Updated automatically when the playground redeploys.