Most security automation is good at running a tool and returning a file. The harder problem begins on the next run.
Did anything change? Is a new finding actually important? Was the resource already present yesterday? Did we finish scanning every account? If an analyst asks a follow-up question, can the system answer from the same evidence—or does it have to start all over again?
We ran into this while building recurring cloud inventory, posture management, and incident-analysis workflows. Our scanners could collect plenty of data, but the platform needed a reliable memory between runs. It also needed a safe place for the TypeScript logic that decides what is current, what changed, and what should happen next.
Cloudflare Durable Objects became that layer for us.
This is a generalized account of how we use them. The examples contain no real tenant names, account details, findings, or production measurements.
The first use case: “Tell me what changed”
Consider a routine cloud-security refresh. We run an inventory collector and a posture scanner on a recurring schedule. The inventory tells us what exists; the posture scan tells us which controls those resources pass or fail.
Simply storing the new files is not enough. As soon as the run finishes, we want to know:
- which resources appeared or disappeared;
- which findings are new, resolved, or more severe;
- whether the scan covered the expected environment;
- which changes are routine and which need attention;
- and exactly which run supports each conclusion.
We store the immutable raw results and manifests in S3. After those objects are sealed, the collection runtime sends a signed completion callback to a Cloudflare Worker. The Worker routes that callback to the correct tenant’s Durable Object.
Inside the object, TypeScript verifies the run identity, manifest, artifact digest, schema, and execution receipt. Only then does it update the current SQL projection. The object compares the newly accepted state with the previous accepted state and writes deterministic claims such as “inventory changed” or “urgent posture findings need review.”
That is where Durable Objects first proved their value for us. The comparison logic is next to the current state, and updates for one tenant are serialized. We do not need a separate lock service to stop two late callbacks from racing to become the latest run.
What happens when a scan starts
The Durable Object does not run the scanners themselves. Tools such as cloud inventory collectors and posture scanners need container images, command-line dependencies, parallelism, and longer execution windows. We keep that work in ephemeral Modal compute.
The flow looks like this:

The Durable Object owns the state machine: requested, running, accepted, failed, or retried. Modal owns the heavy collection. S3 owns the evidence.
This separation turned out to be useful beyond scaling. If a callback is retried with the same identity and the same content, the object treats it as an idempotent retry. If the identity is reused with different content, it rejects the conflict. If a scanner fails, the previous accepted projection remains available instead of being replaced by a partial result.
The second use case: an analyst asks a question
The more interesting moment happens after collection.
An analyst opens the dashboard and asks, “What changed in our cloud posture?” The dashboard does not download raw scan files or connect to SQLite. It sends an authenticated request to the Worker, which invokes a second, tenant-scoped Durable Object: the analysis agent.
The agent asks the security-state object for a bounded snapshot containing the relevant inventory, findings, changes, and evidence references. Deterministic TypeScript and SQL select the facts. If model-assisted analysis is enabled, the agent gives only that bounded evidence packet to the model and asks for a structured assessment.
The final answer can include:
- a plain-language description of the change;
- why the change may matter;
- the affected services or control families;
- sensible investigation or remediation steps;
- and references back to the evidence used.
The agent stores the answer together with its evidence references, prompt and model identity, status, and timing. A dashboard can show the latest result, a job can include it in a report, and a delivery adapter can post it to an approved collaboration channel.
This gives follow-up questions continuity. The platform remembers what was analyzed and why without giving the model unrestricted access to a database.
Facts first, narrative second
We deliberately split the analysis into two layers.
The first layer is deterministic. SQL and TypeScript calculate exact changes, coverage, severity shifts, and correlations between inventory and findings. This layer works whether or not a language model is available.
The second layer is interpretive. A model can turn the selected facts into a more useful risk narrative, prioritize questions for an analyst, or explain the likely business impact. Its response must match a schema and cite evidence identifiers before we store it.
We do not execute SQL written by the model. The model never decides which scan is current, and it cannot edit the evidence projection. It explains an evidence packet chosen by deterministic code.
That boundary lets us use an LLM where it is genuinely helpful without making the entire workflow probabilistic. If the model is unavailable, users still receive the exact change analysis. If it is available, they also receive a clearer interpretation.
The third use case: recurring incident review
We use a similar pattern for incident and managed-detection workflows. External telemetry arrives with offenses or alerts and their contributing events. A tenant-scoped incident-state object normalizes that evidence, tracks what has already been analyzed, and records flags and delivery state.
The deterministic layer groups related activity, identifies changes in status, and checks whether the same evidence has already been processed. The analysis layer can assess likely impact, likelihood, and root-cause hypotheses. Once the result passes validation, the platform can deliver a concise summary and retain the full evidence-bound analysis for the dashboard.
The important pattern is not tied to a particular security product. Any feed that produces stable evidence identifiers and signed or otherwise verifiable ingestion can use the same stateful workflow.
How the objects wake up
A Durable Object is not a continuously running agent. Most of the time, it is idle and costs us no active compute.
It wakes when something meaningful happens:
- a user starts a scan or investigation;
- a collection job reports completion;
- a new incident payload arrives;
- an alarm says that local work, delivery, or a retry is due;
- or another authorized object asks it for state.
An alarm calls a TypeScript method on the exact object that owns the schedule and state. That method can decide whether work is really due, avoid starting a duplicate, record the new lifecycle state, dispatch the work, and set the next alarm.
We still keep broader product and job policy in our control plane. Object alarms are excellent for precise wakeups and retries; they should not quietly become an invisible replacement for the job definitions users see and manage.
Why we use more than one object per tenant
We started with a simple rule: separate evidence state from interpretation. That led to specialist objects for cloud-security state, analysis history, and incident state.
They are ordinary classes deployed once. The Worker maps each authenticated tenant to stable, tenant-specific object names, so Cloudflare gives each one an independent execution context and SQLite database. The browser never sees Cloudflare’s internal object IDs.
This division keeps permissions and failure modes easier to understand. The analysis agent can read a bounded security context but cannot advance an evidence head. The incident object can manage delivery retries without owning cloud inventory. The security-state object can accept scanner callbacks without having to know how a report will eventually be written.
SQLite is our fast view, not our evidence vault
The local SQL database makes dashboards and investigations fast. It is not the final source of truth.
We think of the layers this way:
S3 holds what was sealed and must remain auditable.
Durable Object SQL holds what we need to query quickly now.
Modal provides the temporary compute required to collect it.
Every Durable Object projection should be rebuildable from the accepted S3 manifests. That rule prevents a convenient cache from becoming irreplaceable infrastructure. It also means a schema migration or a damaged projection does not erase the historical evidence.
What users actually notice
Users do not need to know which Durable Object handled a request. They notice that the dashboard opens on the latest accepted state, that a scan can run in the background, and that an investigation returns an answer tied to evidence.
They can start with an overview, open the latest run for raw detail, compare it with earlier runs, or ask a specific question. The same accepted state can support a dashboard, a recurring risk-analysis job, a compliance report, or a message to an operations channel.
The Durable Object is what makes those experiences feel like one continuous system rather than a collection of unrelated scanner outputs.
What we learned
The biggest lesson was that agentic behavior needs dependable state more than it needs an always-running agent.
Durable Objects give us a practical place to combine serialized decisions, local SQL, TypeScript business logic, alarms, and tenant isolation. They work because we keep their role focused: they coordinate and remember; ephemeral compute performs the heavy collection; S3 preserves the evidence; models add interpretation only after deterministic analysis has established the facts.
That has given us a useful kind of autonomy. The system can wake when something changes, understand the new state in context, produce an evidence-backed analysis, deliver it, and go idle again.



