Claude Cowork can now keep working after you close your laptop.
WIRED reports that Anthropic expanded Cowork beyond the desktop app, so users do not need an active desktop session to keep tasks running. Users can interact with limited versions through the Claude smartphone app or web browser, and Cowork can continue tasks after the user clocks out.
That is a useful feature.
It also creates a runtime engineering problem.
If agents can run unattended, they need stop conditions before provider calls execute.
A chatbot waits. An agent continues.
A chatbot is mostly reactive.
The user sends a message.
The model responds.
The interaction pauses.
An agent is different.
It may:
call a model
call tools
inspect results
add context
retry
call another tool
generate a document
send another provider request
keep going while the user is away
That means the agent runtime needs controls.
Not only logs.
Not only dashboards.
Controls before execution.
The naive loop
A simple agent loop might look like this:
while (!task.done) {
const response = await provider.call({
model: task.model,
messages: task.messages,
});
task = await applyAgentStep(task, response);
}
This is easy to write.
It is also unsafe for unattended workflows.
There is no max-step limit.
No budget check.
No retry-storm detection.
No prompt-loop detection.
No known-pricing check.
No no-progress stop.
If the agent gets stuck, it keeps creating provider calls until something external stops it.
That “something” might be a provider error, user intervention, an account limit, or a bill.
None of those are ideal runtime controls.
Add a pre-call decision
A safer loop puts a guard before every provider call.
const decision = guard.beforeCall({
runId: task.id,
model: task.model,
messages: task.messages,
stepCount: task.steps.length,
retryCount: task.retryCount,
budgetRemaining: task.budgetRemaining,
previousPrompts: task.previousPrompts,
progressState: task.progress,
});
if (!decision.allowed) {
return {
status: "stopped",
reason: decision.reason,
error: decision.error,
};
}
const response = await provider.call({
model: task.model,
messages: task.messages,
});
The exact API does not matter.
The placement matters.
The runtime checks the call before the provider sees it.
What should the runtime check?
- Known model pricing
If the runtime cannot price the model, it cannot enforce a reliable budget.
if (!pricingCatalog.has(model)) {
return {
allowed: false,
reason: "unknown_model_pricing",
};
}
Do not guess inside an unattended loop.
A typo, fallback, gateway rewrite, or model alias can break cost assumptions.
- Task budget
Every unattended run should have a task-level budget.
if (estimatedNextCallCost > budgetRemaining) {
return {
allowed: false,
reason: "budget_exceeded",
};
}
Monthly dashboards are useful, but they are late.
A task budget stops the next call before spend is created.
- Max steps
Agents need explicit step limits.
if (stepCount >= maxSteps) {
return {
allowed: false,
reason: "max_steps_exceeded",
};
}
A run that cannot finish inside a reasonable number of steps should stop cleanly.
- Retry storms
Retries are normal.
Retry storms are not.
if (retryCount >= maxRetries && recentErrorsAreSimilar(errors)) {
return {
allowed: false,
reason: "retry_storm_detected",
};
}
The goal is not to remove retries.
The goal is to prevent repeated failure from becoming the workload.
- Prompt loops
Agents often get stuck by asking almost the same thing again.
if (similarToRecentPrompt(currentPrompt, previousPrompts)) {
return {
allowed: false,
reason: "similar_prompt_loop",
};
}
This catches a common pattern:
the agent looks active, but it is not exploring a new path.
- No progress
A run can consume steps while producing no useful movement.
Track progress signals:
errors decreasing
files changing meaningfully
tests improving
checklist items completing
retrieved information changing
user-defined success criteria improving
If progress does not change after several steps, stop.
if (!madeProgress(recentSteps)) {
return {
allowed: false,
reason: "no_progress",
};
}
Why this matters more for always-running agents
When the user is watching, some failures are caught manually.
When the agent runs overnight, the runtime becomes the first line of control.
That changes the design requirement.
An unattended agent needs:
local budgets
max-step policies
retry budgets
prompt-loop detection
known model pricing
structured stop reasons
These are not advanced features.
They are basic operating rules.
Where AI CostGuard fits
AI CostGuard is the local-first TypeScript/Node.js runtime safety layer I’m building for AI-agent projects.
It focuses on pre-call protection for:
retry storms
prompt loops
max-step explosions
runaway agent execution
unknown model pricing
budget overruns
uncontrolled provider calls
invisible AI-agent cost risk
It is not a billing ledger.
It is not a hard security boundary.
It does not replace provider dashboards.
The goal is to help the runtime decide whether the next provider call should execute.
Takeaway
Always-running agents are useful because they remove friction.
That same lack of friction is the risk.
If an agent can keep working after the laptop closes, it needs runtime rules for when to stop.
https://github.com/salimassili62-afk/ai-costguard
Top comments (0)