Worker boundaries
Newsence runs three production Workers with deliberately different authority.
| Worker | Owns | Calls | Does not own |
|---|---|---|---|
web-tanstack |
Product UI, authentication, DomainRpc, Postgres product transactions, user files and product R2 lifecycle |
CORE_WORKER, THINK_WORKER |
Agent transcripts, model execution, acquisition and enrichment |
workers/think-worker |
Think Agent sessions, AI execution, tool policy, native Workspace scratch files, podcast orchestration | DOMAIN |
Product SQL, product R2 authority, public-corpus indexing |
workers/core-worker |
Acquisition, enrichment, AI Search, entities, queues and Workflows | APP for narrow app-owned lifecycle operations |
Product workspace/document policy, Agent transcripts, subscriptions |
The browser reaches Think through the authenticated app proxy. Think calls the
app-owned DomainRpc for product reads and writes. The app and Core communicate
through a service binding. Core’s public fetch surface remains intentionally
unavailable; engine operations are RPC, scheduled handlers, queues, or Workflows.
Knowledge and scratch
Section titled “Knowledge and scratch”Product knowledge is not a filesystem mount:
- public discovery uses Core-owned AI Search;
- document/resource reads and workspace source listing are authorized by the app Worker on every call;
- Think exposes this as one bounded
knowledgetool; - Native Workspace owns only session-local scratch files in DO SQLite.
Postgres remains canonical for documents, resources, memberships, podcasts, quotas, and session catalog rows. R2 remains canonical for product binary objects. Native Workspace files are working state and can only become product state through an explicit app-domain action.
bash operates on a virtual filesystem rooted at /. Code Mode’s execute
runs in a Worker isolate, with optional Chromium/CDP access through the Browser
Run binding. This Worker has no Container or Sandbox binding.
Contract ownership
Section titled “Contract ownership”Cloudflare service bindings are the runtime boundary. Do not introduce a generic cross-worker contracts package.
- Think protocol, tool, podcast, and runtime DTOs live under
workers/think-worker/src. - Core engine and workflow DTOs live under
workers/core-worker/src. - Generated
Service<>bindings come from multi-configwrangler typeswhen the target entrypoint can be loaded under the caller’s TypeScript project. - Otherwise the caller refines only its consumed methods with
Rpc.Provider, and the receiving Worker validates untrusted payloads at runtime.
Do not import source files across Worker directories. Product policy and exact input validation remain behind the owner Worker’s RPC implementation.
Resource ingestion
Section titled “Resource ingestion”Keep the user’s request, the saved resource, and its execution tasks separate:
| Concept | Authority and durable state |
|---|---|
| Import | Web owns the user’s intent, destination, admission policy, and retry decision. Its user-scoped ResourceProgress DO retains the admission receipt, current attempt, and subscriptions. |
| Resource | Core owns canonical identity, classification, content, and normalized persistence in Postgres. Web owns raw-file ownership, profile saves, placements, and canonical attachment. |
| Task | Core owns execution, provider retries, and Workflow/queue lifecycle. Web’s ResourceTaskProgress DO retains the progress projection and shares it with subscribing imports. |
Processing state stays in DO storage; Resource and UserFile do not carry job
status. A readable original can be ready while translation, references, or search
indexing continue as separate tasks.
For managed imports, the Task DO also guards execution admission: Core must claim
the current task before starting provider work.
New URLs enter Core as immutable drafts. Core processes the draft before creating or merging its canonical row, then calls Web to apply the user’s save target. An already-published URL can be saved immediately. Uploaded PDFs first establish the raw resource, ownership, and placement in one Web transaction. That transaction must also obtain durable DO admission before committing; dispatch waits until ownership is committed. A new PDF admission failure rolls back the upload, while a failed retry of an existing PDF preserves the readable file.
Maintain the flow at these entry points:
- Web admission:
web-tanstack/src/server/resource-ingest.server.tsowns URL and PDF admission, fallback grants, receipt outcomes, operator retries, and completion checks. Web’ssrc/server/resource-ingest.functions.ts,src/routes/api.upload.ts, andsrc/server/domain-rpc.tsadapt authenticated requests and RPC calls. Resolve fallback grants before taking database locks. - Storage and saves:
web-tanstack/src/server/user-file-persistence.server.tsowns byte validation, quota, deduplication, and object-storage lifecycle. It calls admission inside the ownership transaction.web-tanstack/src/server/resource-write.server.tsowns the Postgres writes; keep DO scheduling out of its transaction helpers. Preserve the current-attempt check before applying a Core completion result. - Execution and progress:
workers/core-worker/src/ingest/workflow.tsowns processing order;workers/core-worker/src/ingest/progress.tsowns task reporting and execution policy.web-tanstack/src/server/resource-progress.server.tsimplements both DOs, durable dispatch, subscriptions, and authenticated progress delivery. Keep physical tasks shareable across imports. - Frontend:
web-tanstack/src/components/workspace/resourceImport.tsowns submission, per-item outcomes, retry input, and mutation side effects.ResourceProgressProvider.tsxin that directory owns the progress connection, snapshot store, and progress UI.web-tanstack/src/lib/queries/resources.tsowns cache invalidation. Each dialog retains its own draft and pending state.
Import activity and warnings reflect processing and translation. Indexing and reference enrichment run in the background; their existing subscriptions deliver completion notifications for cache refresh, including after an import settles. They do not report running progress or keep the import active. Their terminal reports seal the leaf task atomically, without a second RPC. Parent enrollment still protects the handoff to user-visible work. Deploy Web before Core so the receiving progress contract is available first.
Every shared translation registers its Task DO before Workflow admission, even when it starts from background monitoring without a subscriber. Later imports subscribe to that same ledger. Progress reports run in required durable steps; Web unavailability delays translation through step retries. Automatic acquisition can run without a progress ledger, but translation always uses one. There is no native-status reconciliation or reader polling. Native Workflow status is consulted only to deduplicate admission and select retry generations, with completed results retained for seven days. Resource reads return persisted content; socket completion notifications refresh affected caches, including when an initial read was already in flight. Closing processing enrollment also refreshes content to cover a shared translation that completed before subscription. Both progress DOs remain in Web.
Background subject discovery uses a separate Workflow identity from user-owned subject processing. Both still converge on the same canonical resource and shared translation identity. Concurrent first acquisitions may duplicate provider work; this prevents a background task from silently inheriting a user’s progress graph.
Add platform acquisition and normalization in
workers/core-worker/src/ingest/platforms/, with detection dispatch in
workers/core-worker/src/ingest/acquisition.ts. The Web admission flow stays
platform-independent and consumes persisted Core facts. A new provider should not
require another frontend or Web processing pipeline.
Data ownership checks
Section titled “Data ownership checks”These searches should remain empty when changing authority-sensitive code:
rg "INSERT INTO workspaces|UPDATE user_documents|INSERT INTO document_versions" workers/think-worker/srcrg "deleteDocument|updateDocumentShare|deleteResource|removeResourceFromSource" workers/core-worker/srcrg "env\\.CORE\\.(createWorkspace|createDocument|saveDocument)" workers/think-worker/srcThink product mutations should call app-owned methods through env.DOMAIN.
Core should not acquire product write authority merely because it can read the
same Postgres cluster.
Deployment
Section titled “Deployment”Each package owns its install, type generation, lint/typecheck, build, and deploy commands. Cloudflare Workers Builds should use these roots:
| Worker | Root | Production gate | Deploy |
|---|---|---|---|
newsence-web-tanstack |
/web-tanstack |
pnpm run typecheck && pnpm run build |
pnpm run deploy |
newsence-think |
/workers/think-worker |
pnpm lint && pnpm run typecheck |
pnpm run deploy |
newsence-core |
/workers/core-worker |
pnpm run typecheck && pnpm lint |
pnpm run deploy |
Service-binding changes should normally be additive for one deploy window. A deliberately breaking release requires an explicit maintenance window and a documented target/consumer order. Never assume independent Workers Builds will deploy in a coordinated order.
Native Think production cutover
Section titled “Native Think production cutover”Breaking Think action changes retire existing chats through the session deletion ledger. The maintenance cron schedules native DO destruction; documents and generated images remain. Do not translate old action keys or resume old turns.
During a coordinated cutover, chat_sessions_action_cutover temporarily blocks
new chat allocation. Apply its removal only after Web and Think are deployed:
- From
web-tanstack, export the direct production database asDIRECT_DATABASE_URL, then runpnpm db:migrate:deploy. - In a controlled CI or operator environment, run
pnpm db:drift-checkwith a separate disposableSHADOW_DATABASE_URL. The audit verifies both the migration ledger and drift, including partial indexes, column-list foreign keys, views, functions, and triggers that Prisma Schema Language cannot represent. - Deploy
web-tanstack, thenworkers/think-worker, then Core when changed. The app is Think’s service-binding callee and must be available first. - Apply the cutover-release migration to reopen chat allocation. Confirm the old session deletion ledger has drained before ending maintenance. Run drift and runtime checks when authorized; deployment success alone does not verify application behavior.
Secrets stay in Cloudflare Worker secrets or bindings. Think owns its model bindings and provider routing; the app owns auth, billing, email, and product-storage credentials; Core owns ingest-platform credentials.
Practical rule
Section titled “Practical rule”If a value answers “what does this RPC payload mean?”, it belongs with the API owner’s contract. If it answers “what is this product operation allowed to do?”, it belongs behind the owner Worker rather than in shared code.