Agents, agent clusters & realms
Learn how JavaScript agents, agent clusters, realms, intrinsics, shared memory, and the ShadowRealm proposal shape threads and globals.
- 01Separate three layersExplain how an agent, its cluster, and its realms answer different questions about JavaScript execution.
- 02Choose safe boundary checksPredict cross-realm
instanceofbehavior and useArray.isArrayor structured cloning when needed. - 03Evaluate sharing claimsTell when agents can share SharedArrayBuffer memory and why a fresh realm is not a new thread.
A map for threads and globals
JavaScript has a few words that sound alike but answer different questions. An agent explains who executes JavaScript. An agent cluster explains which agents may share selected state. A realm explains which global object and built-in constructors a piece of code uses.
contentWindow. An agent owns a stack, a job queue, and one or more realms. A realm owns a global environment and intrinsics such as Array, Object, and Promise.
This lesson follows Modules through a bundler. It prepares you for How workers work, where the cost of creating agents and sending data becomes practical.
What an agent is
In plain words, an agent is one teacher working through one thing at a time. It has its own call stack, its own job queue, and a set of classrooms. A browser window is an agent, and each worker is another agent.
console.log(typeof globalThis, typeof Array);Line 1 asks what two names are. It prints object function: globalThis is an object, and Array is a constructor function. This says nothing about another worker, because that worker has its own global environment.
Think of a school. Each teacher has a schedule and talks through one question at a time. The teacher can use more than one classroom, and each classroom has its own copy of the textbooks.
- In real life: One teacher handles one question
- In JavaScript: One agent runs one job at a time
- In real life: A teacher has a schedule
- In JavaScript: An agent has a stack and queue
- In real life: Classrooms have their own textbooks
- In JavaScript: Realms have their own intrinsics
Where the analogy stops: A real agent can run many jobs over time; the analogy does not model scheduling details.
Agents can run in parallel when the host provides separate threads. The important boundary is still simple: ordinary code in one worker does not share its globalThis with the page.
Agent clusters and shared memory
An agent cluster groups agents that are allowed to share selected things. Most values passed to a worker are copied with structured clone. A SharedArrayBuffer is different: multiple agents can view the same bytes when the browser allows it.
const bytes = new SharedArrayBuffer(4);
const pageView = new Int32Array(bytes);
const workerView = new Int32Array(bytes);
pageView[0] = 3;
console.log(workerView[0]);Line 1 allocates four shared bytes. Lines 2 and 3 make two views of those bytes. Line 4 writes 3, so line 5 prints 3. This is deliberately marked Node or worker-style teaching code because ordinary page sandbox examples cannot promise the needed isolation headers.
Browsers generally require cross-origin isolation before exposing SharedArrayBuffer. Sharing bytes also does not make coordination safe; agents use Atomics when reads and writes must agree.
Replay a shared-memory teaching model. Real SharedArrayBuffer use needs the relevant browser security requirements and synchronization with Atomics when agents coordinate writes.
script
const workerAgent = { cluster: "school", memory: pageAgent.memory }; pageAgent.memory[0] = 3;console.log(workerAgent.memory[0]);Realms and intrinsics
A realm is a global environment with its own intrinsic objects. An iframe is a familiar browser example: it has another globalThis, another Array, another Object.prototype, and another Promise.
const frame = document.createElement("iframe");
document.body.append(frame);
const frameArray = new frame.contentWindow.Array("tea");
console.log(frameArray instanceof Array);
console.log(Array.isArray(frameArray));
console.log(frame.contentWindow.Array === Array);Line 1 creates an iframe. Line 4 makes an array using the iframe realm's constructor. Line 5 prints false because that array does not inherit from the page's Array.prototype. Line 6 prints true, and line 7 prints false.
Run this in a normal page with a same-origin iframe. The lesson editor's opaque-origin sandbox cannot read properties from the frame's contentWindow.
A realm is not a thread. One agent can host several realms. The distinction matters because a new iframe can change constructor identity without promising work runs on another CPU.
Cross-realm checks
instanceof follows a prototype chain and asks whether a specific prototype occurs in it. That works inside one realm, but it is a fragile type check at an iframe or worker-facing boundary.
Replay a cross-realm check. It shows why prototype-based checks depend on the realm that supplied the constructor.
script
const frameArray = ["tea"]; // made with the frame's Array function check(value) { return { instanceofArray: value.realm === "page", isArray: true, };} console.log(check({ realm: "frame", value: frameArray }));The replay walks an array made in a second realm. The page's Array is not in that array's prototype chain, so the model returns false for instanceof. Array.isArray instead checks the array nature of the value, so it returns true.
| Check | What it depends on | Safer boundary choice |
|---|---|---|
value instanceof Array | It compares against this realm's Array.prototype chain. | A value made in an iframe can return false. |
value.constructor === Array | It compares constructors from a particular realm. | Use Array.isArray(value) for array recognition. |
| Prototype checks | They depend on realm-owned prototypes. | Use a documented data contract or Object.prototype.toString.call(value). |
| Passing mutable objects | Identity and prototype assumptions travel poorly across a boundary. | Use structured cloning or explicit serialization at a worker boundary. |
A shared symbol registry
Realms have separate intrinsics, but not every identity is realm-local. The global symbol registry is associated with an agent cluster. That makes Symbol.for useful for a shared key, while plain Symbol() always creates a fresh symbol.
const frame = document.createElement("iframe");
document.body.append(frame);
const pageKey = Symbol.for("cart");
const frameKey = frame.contentWindow.Symbol.for("cart");
console.log(pageKey === frameKey);Line 4 asks the page registry for cart. Line 5 asks the iframe's realm in the same cluster for the same key. Line 6 prints true. This does not mean all objects are shared; it is one registry rule.
Run this in a normal page with a same-origin iframe; the editor's opaque-origin sandbox blocks access to the frame's contentWindow.
Do not use this fact as a data channel. Send data explicitly, and use symbols only when symbol identity is the intent.
The ShadowRealm proposal
ShadowRealm is a TC39 proposal for creating a fresh realm inside the current agent. It gives that realm a separate global environment and separate intrinsics without claiming a new thread.
if (typeof ShadowRealm === "function") {
const realm = new ShadowRealm();
console.log("available");
} else {
console.log("not available");
}Line 1 checks before use. In September 2026 the proposal repository reports Stage 2.7. That is a proposal stage, not a guarantee that browsers ship the API, so this lesson never claims availability and the example feature-detects it.
Its goal is isolation of globals and intrinsics within the same agent. It is not a replacement for workers, cross-origin policy, or a security sandbox for untrusted code.
Two teachers, two classrooms
Use this model to change one boundary at a time. Pick the classroom that makes an array, then the classroom that checks it. Finally decide whether the two teachers are in the same building, which represents a cluster that may share memory.
const sameRealm = madeIn === checkedIn;
console.log(sameRealm); // instanceof Array model
console.log(true); // Array.isArray model
console.log(true); // Symbol.for cart key model
console.log(sameCluster); // shared-memory permission modelinstanceof Array: false
Array.isArray: true
Symbol.for("cart") === Symbol.for("cart"): true
Shared memory allowed: true
An array from classroom-b checked in classroom-a makes instanceof Array false, Array.isArray true, Symbol.for equality true, and SharedArrayBuffer sharing true.
When classrooms differ, the model shows instanceof Array as false while Array.isArray remains true. Its symbol result stays true because the global symbol registry belongs to the modeled cluster. Turning off the shared building removes shared-memory permission.
Practical boundaries
Most app code meets this lesson at an iframe, a worker, a testing VM context, or a library that receives values from elsewhere. Start with plain data and a documented message shape. Structured cloning makes the boundary visible and avoids inherited prototype surprises.
import vm from "node:vm";
const list = vm.runInNewContext("[]");
console.log(list instanceof Array);
console.log(Array.isArray(list));
console.log(vm.runInNewContext("Symbol.for('cart')") === Symbol.for("cart"));Node's vm.createContext creates a fresh realm. The three logs are false, true, and true. The first two show the same iframe lesson without a browser; the third verifies the cluster-level symbol registry behavior.
import { Worker, isMainThread, parentPort } from "node:worker_threads";
if (isMainThread) {
globalThis.schoolName = "Page school";
new Worker(new URL(import.meta.url)).once("message", console.log);
} else {
parentPort.postMessage(globalThis.schoolName === undefined);
}The page agent sets a global, but the worker reports true for “is it undefined?”. That is stable Node evidence that a worker has a separate global environment and agent.
- A worker's own call stack and job queue
- A window handling one task at a time
- Permission for two agents to use one SharedArrayBuffer
- The global symbol registry shared by related realms
- An iframe's separate
Arrayconstructor - One global object and its bindings
Put each card beside the layer it describes.
Common mistakes
- “A realm is a thread.” A realm is a global environment. A worker is an agent with its own realm.
- “Every value is shared in a cluster.” Normal messaging copies data; only explicit shared-memory mechanisms share bytes.
- “
instanceofnames a universal type.” It depends on a prototype from one realm. - “ShadowRealm is a shipped browser primitive.” It is a Stage 2.7 proposal and code must feature-detect it.
Practice exercises
Type the two words printed by the first example.
console.log(typeof globalThis, typeof Array);It prints object function.
An iframe sends your page an array. Name the safe check.
Use Array.isArray(value) because it works for arrays made in another realm.
Is a browser worker a separate agent from the window?
Yes. A worker is a separate agent.
Name the browser condition commonly required before SharedArrayBuffer is available.
SharedArrayBuffer generally needs cross-origin isolation in browsers.
What current proposal stage should you report for ShadowRealm?
The proposal reports Stage 2.7, so feature detection remains necessary.
Your app sends a cart summary to a worker. What boundary technique should it use for ordinary data?
Use structured cloning for ordinary message data at the worker boundary.
Check your understanding
Keep the layers separate: agents execute, clusters decide sharing, and realms provide globals and intrinsics.
Question 1 of 7Which description best fits a JavaScript agent?
Choose an answer to see the explanation.
Question 2 of 7What does this print?
Read the code, then predictconst frameArray = []; console.log(Array.isArray(frameArray));Choose an answer to see the explanation.
Question 3 of 7Why can
instanceof Arrayfail for an iframe array?Choose an answer to see the explanation.
Question 4 of 7What does two
Symbol.for("cart")calls in the same agent cluster return?Choose an answer to see the explanation.
Question 5 of 7Which statement about SharedArrayBuffer is accurate?
Choose an answer to see the explanation.
Question 6 of 7What is the status of ShadowRealm in this lesson?
Choose an answer to see the explanation.
Question 7 of 7Which ordinary worker-boundary choice avoids cross-realm prototype assumptions?
Choose an answer to see the explanation.
Key takeaways
- An agent has a stack, a job queue, and realms; a window and worker are separate agents.
- An agent cluster controls selected shared state, including SharedArrayBuffer eligibility.
- Each realm has its own global object and intrinsics.
- Use Array.isArray instead of realm-sensitive instanceof at array boundaries.
- ShadowRealm is a Stage 2.7 proposal: feature-detect it and do not assume browser support.
Remember the one-liner.
Agents execute, clusters govern sharing, and realms own globals and intrinsics.
Coming next: How workers work.
Follow one agent's queue
A stack is where the current nested calls live. A job queue holds work that can start after the current job finishes. The agent does not run two JavaScript jobs at the same instant. It finishes one turn, then the host can choose another queued job.
const jobs = ["mark homework", "answer question"];
const completed = [];
completed.push(jobs.shift());
console.log(completed.join(", "));
console.log(jobs.join(", "));Line 1 creates two waiting jobs. Line 2 creates an empty completed list. Line 4 removes only the first waiting job and saves it. Line 5 prints mark homework. Line 6 prints answer question, which is still waiting.
This is a tiny teaching model, not a browser event-loop implementation. Its important promise is modest: one agent progresses one job at a time. A window and a worker can make progress in parallel because the host gives them separate agents, stacks, queues, and globals.
Sharing is selected, not automatic
Sending a normal object to a worker normally creates a copy through structured clone. The receiver gets data with the same useful values, but it does not receive the sender's ordinary object identity. That rule prevents two agents from silently changing the same cart object.
const message = { kind: "cart", items: ["tea"] };
console.log(message.kind);
console.log(Array.isArray(message.items));Line 1 makes a plain message with a kind and item list. Line 2 prints cart. Line 3 prints true because the copied items value is still an array. This is the easy default for application messages.
SharedArrayBuffer is deliberately different: permitted agents can make typed-array views over the exact same bytes. In browsers, cross-origin isolation is the security condition that usually makes this available. See Shared memory for Atomics, and Browser security model for the isolation boundary.
Intrinsics belong to a realm
An intrinsic is a built-in value a realm starts with. Each realm has its own Array, Object, Object.prototype, and other built-ins. A page array naturally points at the page realm's versions.
const pageArray = ["tea"];
console.log(pageArray.constructor === Array);
console.log(Object.getPrototypeOf(pageArray) === Array.prototype);Line 1 makes an array in this realm. Line 2 prints true because its constructor is this realm's Array. Line 3 also prints true because its direct prototype is this realm's Array.prototype.
An iframe can repeat those three lines and receive its own true results, yet its Array is not the page's Array. That is why realm identity can change without a new CPU thread. Continue with Realms and globals for a wider map of global environments.
Feature detection is the ShadowRealm contract
ShadowRealm is a proposal for a fresh realm in the current agent. The proposal repository reported TC39 Stage 2.7 when this lesson was verified. A proposal stage says where a design is in the standardization process; it does not promise that a visitor's browser implements it.
if (typeof ShadowRealm === "function") {
const realm = new ShadowRealm();
console.log("available", typeof realm.evaluate);
} else {
console.log("not available");
}Line 1 tests the global without throwing. If it is present, line 2 creates a realm and line 3 prints available function. Otherwise line 5 prints not available. The runnable example is intentionally useful in both cases.
A separate realm is not a worker, and this proposal is not an instruction to run untrusted strings. Workers, browser origins, content security policy, and explicit message contracts solve different problems. Treat feature detection as the safe public boundary here.
Repair a real iframe type check
Imagine a product page accepts a cart array from an embedded checkout iframe. The page author writes items instanceof Array. It works in a unit test because the test creates the array in the same realm. It breaks when the iframe creates the real array.
function acceptsCart(items) {
return items instanceof Array;
}
console.log(acceptsCart(["tea"]));Line 2 compares against this realm's Array.prototype. Line 5 prints true only because the literal array is made in the same realm. An iframe array can make that exact check false even though it is still an array.
function acceptsCart(items) {
return Array.isArray(items);
}
console.log(acceptsCart(["tea"]));Line 2 uses Array.isArray, which recognizes array values across realms. Line 5 still prints true, and the same function also accepts an iframe array. Validate the cart's fields after that; being an array is only the first part of a data contract.
Choose the check for the question
Use instanceof when you intentionally need a value built from one particular constructor in one controlled realm. It is useful for local class instances, but it is not a universal label for data arriving from an iframe, a VM context, or a library boundary.
const value = ["tea"];
console.log(Array.isArray(value));
console.log(Object.prototype.toString.call(value));Line 1 creates the example array. Line 2 prints true with the best dedicated array check. Line 3 prints [object Array], a more general built-in tag from Object.prototype.toString.call.
Prefer Array.isArray for arrays because it states the exact question. Use the tag form when you need a broader diagnostic. Avoid value.constructor === Array: it compares another realm-owned identity and fails for the same reason as instanceof.