How OpenSEO Addresses SiteAuditWorkflow and AuditScratchpad DO Instances

OpenSEO addresses SiteAuditWorkflow and AuditScratchpad DO instances through a dedicated auxiliary Cloudflare worker (open-seo-audit) that binds the workflow cross-script as SITE_AUDIT_WORKFLOW and resolves the scratchpad Durable Object via the getAuditScratchpad(auditId) helper using the AUDIT_SCRATCHPAD namespace.

OpenSEO runs site-audit jobs inside a specialized auxiliary Cloudflare Worker that hosts two tightly-coupled components: a Cloudflare Workflow for orchestration and a Durable Object (DO) for transient crawl state. Understanding how these SiteAuditWorkflow and AuditScratchpad DO instances are created, addressed, and cleaned up is essential for developers working with the every-app/open-seo codebase.

Architecture Overview

The system separates concerns between the main application worker and an auxiliary audit worker named open-seo-audit. This auxiliary worker, defined in src/audit-worker.ts, re-exports the SiteAuditWorkflow and hosts the AuditScratchpad Durable Object class. Cross-script bindings declared in wrangler.audit.jsonc and wrangler.jsonc enable the main worker to trigger workflow instances while the auxiliary worker manages the scratchpad lifecycle.

Two critical environment bindings enable instance addressing:

  • SITE_AUDIT_WORKFLOW: A cross-scriptWorkflow binding that allows the main worker to create and retrieve workflow instances.
  • AUDIT_SCRATCHPAD: A Durable Object namespace binding that provides access to scratchpad instances via string-derived IDs.

Creating and Addressing SiteAuditWorkflow Instances

The SiteAuditWorkflow is instantiated through the SITE_AUDIT_WORKFLOW binding. When a user initiates an audit, the AuditService class creates a new workflow instance using the create() method.

According to the source code in src/server/features/audit/services/AuditService.ts (lines 109-126), the workflow creation follows this pattern:

await env.SITE_AUDIT_WORKFLOW.create({
  workflow: "SiteAuditWorkflow",
  payload: {
    auditId,
    billingCustomer,
    projectId,
    startUrl,
    config,
  },
});

This spawns a durable Cloudflare Workflow instance that owns the entire lifecycle of the audit. To retrieve an existing instance for status checks or termination, the code uses env.SITE_AUDIT_WORKFLOW.get(auditId), which returns a workflow handle for that specific audit ID.

Managing AuditScratchpad DO Instances

While the workflow orchestrates phases, the AuditScratchpad Durable Object stores transient crawl state—including the frontier queue, page mirrors, and link graph—in an embedded SQLite database. Each audit receives its own isolated scratchpad instance.

The addressing mechanism lives in src/server/features/audit/AuditScratchpad.ts (lines 48-56). The helper function getAuditScratchpad(auditId) resolves the DO instance:

import { getAuditScratchpad } from "@/server/features/audit/AuditScratchpad";

const scratchpad = getAuditScratchpad(auditId);

Internally, this function accesses the env.AUDIT_SCRATCHPAD namespace and derives the DO ID from the audit identifier:

const id = namespace.idFromName(auditId);
return namespace.get(id);

The scratchpad exposes RPC methods including seedStart, seedSitemapUrls, claimChunk, recordBatch, and runFinalizeChecks that the workflow calls during execution phases defined in src/server/workflows/siteAuditWorkflowPhases.ts.

Lifecycle Management and Cleanup

Both components implement explicit cleanup strategies to prevent resource leakage.

Workflow termination occurs naturally when the workflow reaches its final step, though the instance remains addressable via get() for historical queries.

Scratchpad destruction happens through two pathways:

  1. Explicit cleanup: The workflow instructs the auxiliary worker to destroy the scratchpad via the destroyScratchpad(auditId) function exposed in src/audit-worker.ts (lines 29-33).

  2. Self-healing alarms: If the workflow crashes, the scratchpad implements a self-cleanup alarm (defined in src/server/features/audit/AuditScratchpad.ts, lines 88-98) that wipes its storage after 7 days.

// Inside the auxiliary worker or workflow finalization
await getAuditScratchpad(auditId).destroy();

Integration Code Examples

The following patterns demonstrate how these instances interact during a typical audit:

Initializing the crawl state:

const scratchpad = getAuditScratchpad(auditId);
await scratchpad.seedStart(normalizedStartUrl);
await scratchpad.seedSitemapUrls(sitemapUrls);

Processing batches during the crawl phase:

const { urls, isRetry } = await scratchpad.claimChunk(chunkNumber, 100);
// ... perform crawling ...
await scratchpad.recordBatch(results);

Final cleanup:

// Within the workflow's finalize phase
await env.SITE_AUDIT_WORKFLOW.get(auditId).destroyScratchpad(auditId);

Summary

  • Cross-script bindings in wrangler.audit.jsonc and wrangler.jsonc link the main worker to the auxiliary open-seo-audit worker via SITE_AUDIT_WORKFLOW.
  • Workflow instances are created via env.SITE_AUDIT_WORKFLOW.create() and retrieved via .get(auditId) in src/server/features/audit/services/AuditService.ts.
  • Scratchpad DO instances are addressed through the getAuditScratchpad(auditId) helper using the AUDIT_SCRATCHPAD namespace with idFromName(auditId).
  • Transient state is stored in the scratchpad's embedded SQLite database, accessed through RPC methods like claimChunk and recordBatch.
  • Resource cleanup combines explicit destroy() calls with automatic 7-day alarms to ensure scratchpads do not persist indefinitely.

Frequently Asked Questions

How does the main OpenSEO worker communicate with the SiteAuditWorkflow?

The main worker communicates through the SITE_AUDIT_WORKFLOW binding declared in wrangler.jsonc. This cross-script binding allows the main worker to call env.SITE_AUDIT_WORKFLOW.create() to start new audits and env.SITE_AUDIT_WORKFLOW.get(auditId) to retrieve existing workflow instances, even though the workflow logic resides in the separate open-seo-audit auxiliary worker.

What is the relationship between the SiteAuditWorkflow and the AuditScratchpad?

The SiteAuditWorkflow is a Cloudflare Workflow that orchestrates the audit phases (discovery, crawling, Lighthouse analysis, finalization), while the AuditScratchpad is a Durable Object that stores transient crawl-specific state. The workflow addresses the scratchpad via the getAuditScratchpad(auditId) helper to maintain the frontier queue and link graph, ensuring that crawl state persists across workflow steps without polluting the main database.

How are AuditScratchpad DO instances isolated between different audits?

Each audit receives its own isolated scratchpad instance because the getAuditScratchpad helper generates a unique Durable Object ID using namespace.idFromName(auditId), where auditId is a unique string identifier. This ensures that the SQLite database and state for one audit never intersects with another, providing complete data isolation at the DO level.

What happens if a SiteAuditWorkflow crashes before completing cleanup?

If a workflow crashes before calling destroyScratchpad(), the AuditScratchpad Durable Object includes a self-cleanup mechanism. As implemented in src/server/features/audit/AuditScratchpad.ts (lines 88-98), the scratchpad sets an alarm that automatically wipes its embedded SQLite storage after 7 days, preventing orphaned data from consuming resources indefinitely.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →