Local and Global State (upcoming)
Available in version 1.1.72 (not yet live).
Store values for the current run (local state) or across runs on the same Desktop Agent (global state). Stays on this device; data is never sent to ZeroWork servers.
API zw.state.*
, zw.globalState.*
// @zw-run-locally
// Local per-run state — cleared automatically when the run ends
const runState = zw.state.access();
runState.runStartedAt = Date.now();
// Shared Desktop Agent global state — persists across runs on this device
const globalState = zw.globalState.access();
globalState.totalRuns = (globalState.totalRuns ?? 0) + 1;
// In the browser — work with a JSON-safe snapshot of local or global state
const runStateCopy = await zw.state.browser.getCopy();
runStateCopy.lastSeenAt = Date.now();
await zw.state.browser.commit({ state: runStateCopy });
1. Local Per-Run State — zw.state.*
zw.state.*
A per-run scratchpad automatically initiated on run start. It is deleted when the run ends.
At a Glance
zw.state.access()
→ returns a live mutable object you can read and update anywhere in the run. It can hold anything (e.g., helpers, counters, functions, Maps, Playwright objects like pages or contexts, etc.). It's deleted automatically when the run ends. Not available in the browser (see Use state in the browser further below).async/sync*
await zw.state.clear()
→ clears the whole run state.
*async
in browser, sync
locally (see Local vs. Browser Execution).
Examples
Add a type-safe validator and stringifier around zw.setRef()
// @zw-run-locally
zw.state.access().setRefSafely = ({ ref_id, name, value }, expectedType) => {
if (typeof value !== expectedType) {
throw new Error(`Value for ${name} is not ${expectedType}`);
}
const valueStr = typeof value === "string"
? value
: (value === null || value === undefined)
? ""
: JSON.stringify(value);
zw.setRef({ ref_id, name, value: valueStr });
};
// Use the helper in later blocks within the same run
zw.state.access().setRefSafely({ ref_id, name: "age", value: 42 }, "number");
Counters or flags across multiple Write JS blocks in the same run
// @zw-run-locally
const runState = zw.state.access();
runState.processed = (runState.processed ?? 0) + 1;
await zw.log("processed", runState.processed);
Clear mid-run if needed
await zw.state.clear();
2. Global State — zw.globalState.*
zw.globalState.*
A device-level scratchpad shared across runs on the same Desktop Agent. Persists between runs until the Desktop Agent quits or restarts. Use it when you need to reuse something across runs on one machine (e.g., cached data, a counter of all runs, etc.).
At a Glance
zw.globalState.access()
→ a live mutable object you can read and update across runs. It can hold anything. Not available in the browser (see Use state in the browser further below).async/sync*
await zw.globalState.clear()
→ clears the whole global state.
*async
in browser, sync
locally (see Local vs. Browser Execution).
Example
Track which TaskBots ran today (per device)
// @zw-run-locally
const gs = zw.globalState.access();
const today = new Date().toISOString().slice(0, 10); // YYYY-MM-DD
if (!gs.botsRanToday || gs.botsRanToday.date !== today) {
gs.botsRanToday = { date: today, bots: [] };
}
const taskbotInfo = await zw.getTaskbotInfo();
if (!gs.botsRanToday.bots.includes(taskbotInfo.id)) {
gs.botsRanToday.bots.push(taskbotInfo.id);
}
Notes
It's not durable storage. It's lost when the Desktop Agent quits or restarts. For durable data use:
Variables/tables via
zw.getRef()
/zw.setRef()
Device storage via
zw.deviceStorage.*
As long as the Desktop Agent isn't closed or restarted, global state is not auto-cleaned. Clear what you no longer need.
3. Use State in the Browser — zw.state.browser.*
and zw.globalState.browser.*
zw.state.browser.*
and zw.globalState.browser.*
In the browser you cannot use *.access()
. Use JSON-safe snapshots instead.
At a Glance
async
await zw.state.browser.getCopy({ key?: string })
await zw.globalState.browser.getCopy({ key?: string })
→ returns the current state (or the section underkey
).async
await zw.state.browser.commit({ state: any, key?: string })
await zw.globalState.browser.commit({ state: any, key?: string })
→ accepts any serializable (JSON-safe) value. Ifkey
is provided, only that section is updated.
Examples
Whole snapshot
const stateCopy = await zw.state.browser.getCopy();
stateCopy.step = (stateCopy.step ?? 0) + 1;
await zw.state.browser.commit({ state: stateCopy });
Section-only snapshot with key
const prefs = (await zw.state.browser.getCopy({ key: "prefs" })) ?? {};
prefs.theme = "dark";
await zw.state.browser.commit({ key: "prefs", state: prefs });
Notes
JSON-safe only when using
*.browser.getCopy()
or*.browser.commit()
. Snapshots must be JSON-safe. Functions, Maps, circular references, and similar types cannot be serialized for browser use. If your state contains such values, usekey
to fetch only a JSON-safe section that you need in the browser.In-browser snapshot size limits. Passing snapshots larger than ~3,000,000 characters fails. If your state is large, use
key
to work with just a section. Serialization of smaller objects is also faster.In-browser calls require
await
. In the browser,*.browser.getCopy()
,*.browser.commit()
, and*.clear()
are async.// 🚫 Won't work const stateCopy = zw.state.browser.getCopy(); // missing await stateCopy.runData = {}; // throws because stateCopy is a Promise await zw.state.browser.commit({ state: stateCopy }); // this line never runs // ✅ Works const stateCopy = await zw.state.browser.getCopy(); stateCopy.runData = {}; await zw.state.browser.commit({ state: stateCopy });
*.browser.*
scope is reserved for the browser only.zw.state.browser.*
is meant for the browser context. In local runs, just usezw.state.access()
and make a copy yourself if needed:// @zw-run-locally const s = zw.state.access(); const shallowCopy = { ...s };
Last updated
Was this helpful?