Agents, agent clusters & realms
Learn how JavaScript agents, agent clusters, realms, intrinsics, shared memory, and the ShadowRealm proposal shape threads and globals.
- 01Tell three words apartSay what an agent, an agent cluster, and a realm each are, and which question each one answers.
- 02Check values from another realmPredict when
instanceof Arraysays false for a real array, and useArray.isArrayor plain copied data instead. - 03Know what agents can shareSay when agents may share bytes with a
SharedArrayBuffer, and why a new realm does not mean a new thread.
Three words that sound alike
This lesson is about three words. They sound alike, but each one answers a different question:
- An agent is the thing that runs your JavaScript. It answers: who runs this code?
- An agent cluster is a group of agents. It answers: which agents may share some things?
- A realm is one set of global things: one
globalThisand one copy of the built-ins, likeArray. It answers: whichArraydoes this code use?
An agent has three parts:
- a call stack: the functions running right now,
- a job queue: the work waiting to start,
- one or more realms.
A realm holds a global object, globalThis. It also holds its own built-in values, such as Array, Object, and Promise. The spec calls these built-in values intrinsics.
This lesson comes after Modules through a bundler. The next lesson, How workers work, shows what it costs to start a new agent and to send data to it.
What an agent is
An agent is the thing that runs JavaScript. It runs one job at a time. Each agent has its own:
- call stack: the functions running right now,
- job queue: the work waiting to start,
- realms: its sets of global things.
The page in a browser tab is one agent. Each worker is another agent. A worker is a script that runs beside the page, on its own.
console.log(typeof globalThis, typeof Array);Line 1 uses typeof. It gives a word that says what kind of value a name holds. The line prints object function:
globalThisis an object.Arrayis a function that makes arrays.
This only tells you about the agent that runs the line. A worker has its own globalThis, so this line says nothing about it.
Think of a school. Each teacher has a timetable. A teacher answers one question at a time. A teacher can teach in more than one classroom. Each classroom has its own copy of the textbooks.
- In real life: A teacher answers one question at a time
- In JavaScript: An agent runs one job at a time
- In real life: A teacher has a timetable
- In JavaScript: An agent has a call stack and a job queue
- In real life: Each classroom has its own textbooks
- In JavaScript: Each realm has its own built-ins, like
Array
Two agents can run at the same time when the computer gives each one its own thread. A thread is a separate line of work that the computer runs alongside others. Even so, the main rule is simple: normal code in a worker does not share globalThis with the page.
globalThis, so a name the page sets is not there in the worker.Agent clusters and shared memory
An agent cluster is a group of agents that may share some things. A page and the workers it starts can be in one cluster.
Most values you send to a worker are copied. The browser makes a fresh copy with a method called structured clone. The worker gets the copy, not your original.
A SharedArrayBuffer is different. It is a block of raw bytes that several agents can use together, when the browser allows it. If one agent changes a byte, the others see the change.
const bytes = new SharedArrayBuffer(4);
const pageView = new Int32Array(bytes);
const workerView = new Int32Array(bytes);
pageView[0] = 3;
console.log(workerView[0]);The code reads the bytes through a typed array. A typed array, like Int32Array, is a view that reads bytes as numbers. Line by line:
- Line 1 makes 4 shared bytes.
- Lines 2 and 3 make two views of those same bytes.
- Line 4 writes
3throughpageView. - Line 5 reads through
workerViewand prints3.
This code has no Run button. The lesson editor can't turn on the safety settings that SharedArrayBuffer needs.
pageView, so line 5 reads 3 through workerView.Browsers usually let a page use SharedArrayBuffer only when the page is cross-origin isolated. That means the page sends two safety headers. They keep content from other websites out of the page unless that content agrees.
Sharing bytes does not make teamwork safe on its own. When two agents read and write the same bytes, they use Atomics. Atomics is a set of functions that do each read or write as one whole step, so the agents agree.
Step through one write to shared memory. A real SharedArrayBuffer also needs the browser's safety settings. When agents take turns writing, they also need Atomics.
script
const workerAgent = { cluster: "school", memory: pageAgent.memory }; pageAgent.memory[0] = 3;console.log(workerAgent.memory[0]);Realms and their built-ins
A realm is one set of global things. It has its own globalThis and its own built-ins, the intrinsics.
An iframe is a common example. An iframe is a small page shown inside another page. It has its own:
globalThis,Array,Object.prototype, the object where all plain objects find shared methods,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);Every array is linked to an object called Array.prototype. That is where shared methods, like push, live. Line by line:
- Lines 1 and 2 make an iframe and add it to the page.
- Line 4 makes an array with the iframe's own
Array. - Line 5 prints
false. The array is linked to the iframe'sArray.prototype, not to the page's. - Line 6 prints
true.Array.isArraysees that the value is an array. - Line 7 prints
false. The iframe'sArrayand the page'sArrayare two different functions.
Array and Object. So line 7 prints false.This code has no Run button. Try it in a normal page, with an iframe from the same website. The lesson editor runs code in a locked-down frame, so it can't read the iframe's contentWindow, which is the iframe's own globalThis.
A realm is not a thread. One agent can have several realms. So a new iframe gives you a new Array, but the iframe's code does not run at the same time as the page.
Checking a value from another realm
value instanceof Array asks a yes-or-no question: is this value linked to this realm's Array.prototype? It follows the value's chain of linked prototypes to find out.
Inside one realm, that works. But an array from an iframe is linked to the iframe's Array.prototype, so the page's check says no. That makes instanceof a weak check for values that come from an iframe or a worker.
instanceof Array check says false, but Array.isArray says true, because it is still an array.Step through a check on an array from another realm. instanceof depends on the realm. Array.isArray doesn't.
script
const frameArray = { realm: "frame", values: ["tea"] }; function check(value) { return { instanceofArray: value.realm === "page", isArray: Array.isArray(value.values), };} console.log(check(frameArray).instanceofArray, check(frameArray).isArray);The replay uses a small model, not a real iframe. The model marks one array as made in the frame, so its instanceof Array result is false. Array.isArray still says true. A real iframe array gives the same two answers.
The table lists checks that look alike. Read each row as: "when I write this check, it depends on this, and here is a safer choice."
| When you write | What it depends on | A safer choice |
|---|---|---|
value instanceof Array | This realm's Array.prototype. An array from an iframe gives false. | Array.isArray(value) |
value.constructor === Array | This realm's Array function. An array from an iframe gives false. | Array.isArray(value) |
| A check on the value's prototype | The prototypes of one realm. Each realm has its own. | Check the fields you expect, or use Object.prototype.toString.call(value). |
| Sending your own objects | The sender's objects and prototypes. The other side gets a copy, not your object. | Send plain data. The browser copies it with structured clone. |
One symbol list for the whole cluster
Each realm has its own built-ins. But a few things are shared by the whole cluster. One of them is the global symbol registry.
- A symbol is a unique value. It is often used as a property key.
- The global symbol registry is a list of symbols. Each symbol is saved under a name.
Symbol.for("cart")looks up the namecartin that list. If the name is not there yet, it adds a new symbol.- Plain
Symbol()always makes a brand-new symbol. It never uses the list.
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);- Lines 1 and 2 make an iframe and add it to the page.
- Line 4 asks the list for
cartfrom the page. - Line 5 asks the list for
cartfrom the iframe. - Line 6 prints
true. The page and the iframe are in the same cluster, so they share one list and get the same symbol.
This is one shared list. It does not mean every object is shared.
This code has no Run button. Try it in a normal page, with an iframe from the same website. The lesson editor's locked-down frame can't read the iframe's contentWindow.
Don't use this list to send data between realms. Send data on purpose, for example in a message. Use Symbol.for only when you need the same symbol in each realm.
The ShadowRealm proposal
ShadowRealm is a proposed new feature. TC39, the group that decides what goes into JavaScript, is still working on it. It would let your code make a fresh realm inside the current agent:
- The new realm would have its own
globalThisand its own built-ins. - It would not get a new thread.
if (typeof ShadowRealm === "function") {
const realm = new ShadowRealm();
console.log("available");
} else {
console.log("not available");
}Line 1 checks that ShadowRealm exists before line 2 uses it. This is called feature detection: you test for a feature before you use it. If the check fails, line 5 prints not available.
In September 2026, the proposal's page says Stage 2.7. A stage shows how far a proposal has got. It does not mean browsers have the feature. So this lesson never says it is available, and the example checks first.
ShadowRealm's goal is to keep one set of globals and built-ins apart from another, inside the same agent. It does not replace workers or the browser's rules about other websites. It is not a safe place to run code you don't trust.
Try it: one array, two realms
Change one thing at a time:
- Pick the realm that makes an array.
- Pick the realm that checks it.
- Choose whether the two agents are in one cluster. Only agents in one cluster may share memory.
const sameRealm = madeIn === checkedIn;
console.log(sameRealm); // like instanceof Array
console.log(true); // like Array.isArray
console.log(true); // like comparing two Symbol.for("cart")
console.log(sameCluster); // may the agents share memory?instanceof Array: false
Array.isArray: true
Symbol.for("cart") === Symbol.for("cart"): true
Shared memory allowed: true
One realm makes the array and another realm checks it, so instanceof Array is false. Array.isArray is true, and the Symbol.for check is true. The agents are in one cluster, so they may share memory.
When the two realms are different, instanceof Array is false, but Array.isArray stays true. The Symbol.for result stays true, because the realms share one symbol list. When you untick the cluster box, shared memory is no longer allowed.
Where you meet this in real code
Most apps meet agents and realms in one of these places:
- an iframe on your page,
- a worker,
- a test tool that runs code in a fresh realm (Node calls this a VM context),
- a library that gets values from other code.
The safe habit is the same in each place. Send plain data, like strings, numbers, arrays, and plain objects. Write down the shape each message should have. Structured clone copies plain data well, and the copy doesn't depend on the sender's prototypes.
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"));This code runs in Node, not in the browser. vm.runInNewContext runs a piece of code in a fresh realm and hands back the result. Line by line:
- Line 3 makes an empty array in the new realm.
- Line 4 prints
false. The array is linked to the new realm'sArray.prototype. - Line 5 prints
true.Array.isArraystill sees an array. - Line 6 prints
true. Both realms get the same symbol forcart, because they share one symbol list.
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);
}This Node file runs twice: once as the main program, and once as a worker. isMainThread is true only in the main program.
- Line 4: the main program sets
globalThis.schoolName. - Line 5: it starts a worker from the same file. It prints the first message the worker sends.
- Line 7: the worker checks whether
schoolNameisundefinedin its ownglobalThis, and sends the answer.
The program prints true. The worker is a separate agent with its own globalThis, as in the picture of the page and the worker.
- A worker's own call stack and job queue
- A page running one job at a time
- Letting two agents use one SharedArrayBuffer
- The symbol list that
Symbol.foruses - An iframe's own
Array - One
globalThisand the names on it
Put each card under the word it describes: agent, agent cluster, or realm.
Common mistakes
- “A realm is a thread.” A realm is one set of global things. A worker is an agent, and it has its own realm.
- “Agents in a cluster share every value.” Normal messages are copied. Only special tools, like
SharedArrayBuffer, share bytes. - “
instanceofworks the same for every value.” It looks for one realm's prototype, so it can say no to a real array from another realm. - “Every browser has ShadowRealm.” It is still a proposal, at Stage 2.7. Check that it exists before you use it.
Practice exercises
Type the two words that the first example prints.
console.log(typeof globalThis, typeof Array);It prints object function.
An iframe sends your page an array. Name the check that still works.
Use Array.isArray(value). It works for arrays made in another realm too.
Is a worker a separate agent from the page? Answer yes or no.
Yes. A worker is a separate agent.
What must a page usually be before a browser lets it use SharedArrayBuffer?
Browsers usually need the page to be cross-origin isolated before they allow SharedArrayBuffer.
Which proposal stage is ShadowRealm at?
The proposal is at Stage 2.7, so your code still needs to check that it exists.
Your app sends a cart summary to a worker. Which way of copying plain data should it use?
Use structured clone: send plain data, and the browser copies it for the worker.
Check your understanding
Keep the three words apart. Agents run code. Clusters decide what agents may share. Realms hold the globals and built-ins.
Question 1 of 7Which description fits a JavaScript agent best?
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 Arraysay false for an array from an iframe?Choose an answer to see the explanation.
Question 4 of 7In one agent cluster, what do two
Symbol.for("cart")calls return?Choose an answer to see the explanation.
Question 5 of 7Which sentence about SharedArrayBuffer is true?
Choose an answer to see the explanation.
Question 6 of 7What does this lesson say about ShadowRealm?
Choose an answer to see the explanation.
Question 7 of 7You send normal data to a worker. Which choice doesn't depend on any realm's prototypes?
Choose an answer to see the explanation.
Key takeaways
- An agent has a call stack, a job queue, and realms. The page and each worker are separate agents.
- An agent cluster decides what agents may share, such as a
SharedArrayBuffer. - Each realm has its own
globalThisand its own built-ins, likeArray. - To check for an array from another realm, use
Array.isArray, notinstanceof Array. - ShadowRealm is a Stage 2.7 proposal. Check that it exists before you use it.
Remember the one-liner.
Agents run code, clusters decide what is shared, and realms hold the globals and built-ins.
Coming next: How workers work.
Follow one agent's job queue
- The call stack holds the functions that are running right now.
- The job queue holds work that can start after the current job ends.
An agent never runs two JavaScript jobs at the same moment. It finishes one job. Then it can take the next job from the queue.
const jobs = ["mark homework", "answer question"];
const completed = [];
completed.push(jobs.shift());
console.log(completed.join(", "));
console.log(jobs.join(", "));- Line 1 makes a list of two waiting jobs.
- Line 2 makes an empty list of finished jobs.
- Line 4 takes only the first waiting job and puts it in the finished list.
- Line 5 prints
mark homework. - Line 6 prints
answer question, which is still waiting.
This is a tiny model, not the browser's real job queue. It shows one rule: one agent does one job at a time. A page and a worker can make progress at the same moment only because they are two agents. Each has its own call stack, job queue, and globals.
Sharing happens only when you ask for it
When you send a normal object to a worker, the browser makes a copy with structured clone. The worker gets the same values. But it gets a new object, not your object. So two agents can't change the same cart object by accident.
const message = { kind: "cart", items: ["tea"] };
console.log(message.kind);
console.log(Array.isArray(message.items));- Line 1 makes a plain message with a
kindand a list ofitems. - Line 2 prints
cart. - Line 3 prints
true, becauseitemsis an array.
A message like this is the easy, normal choice for your app. After structured clone copies it, items is still an array.
SharedArrayBuffer is different on purpose. Agents that are allowed can make typed-array views of the very same bytes. In browsers, the page usually has to be cross-origin isolated first. See Shared memory for Atomics, and Browser security model for the safety settings.
Each realm has its own built-ins
An intrinsic is a built-in value that a realm starts with. Each realm has its own Array, Object, Object.prototype, and other built-ins. An array made on the page is linked to 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. The array'sconstructor, the function that made it, is this realm'sArray. - Line 3 prints
true. The array's prototype is this realm'sArray.prototype.
An iframe can run the same three lines and also get true twice. But its Array is not the page's Array. So a new realm gives you new built-ins, without a new thread. To learn more about global things, continue with Realms and globals.
Always check for ShadowRealm first
ShadowRealm is a proposal for a fresh realm inside the current agent. When this lesson was checked, the proposal's page said Stage 2.7. A stage says how far the design has got. It does not promise that a visitor's browser has the feature.
if (typeof ShadowRealm === "function") {
const realm = new ShadowRealm();
console.log("available", typeof realm.evaluate);
} else {
console.log("not available");
}- Line 1 checks for
ShadowRealm. Usingtypeofhere never throws, even when the name is missing. - If it exists, line 2 makes a realm, and line 3 prints
available function. - If it doesn't, line 5 prints
not available.
The example is useful either way, so you can run it in any browser.
A separate realm is not a worker. This proposal is also not a way to run code you don't trust. Workers, website origins, and clear message shapes each solve other problems. Here, the safe habit is simple: check that the feature exists before you use it.
Fix a real iframe array check
Imagine a shop page. A checkout iframe inside it sends the page an array of cart items. The page checks it with items instanceof Array.
- In a test, this works, because the test makes the array in the same realm.
- On the real page, it fails, because the iframe makes the array.
function acceptsCart(items) {
return items instanceof Array;
}
console.log(acceptsCart(["tea"]));Line 2 checks for this realm's Array.prototype. Line 5 prints true only because the array is made in the same realm. An array from an iframe would give 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 sees arrays from any realm. Line 5 still prints true. The same function also accepts an array from the iframe.
After that, check the cart's items too. Knowing it is an array is only the first step. You still need to check that each item has the shape you expect.
Pick the check that fits your question
Use instanceof when you want to know whether a value was made by one exact class, in your own realm. That works well for your own class objects. It is not a good check for data from an iframe, a VM context, or a library.
const value = ["tea"];
console.log(Array.isArray(value));
console.log(Object.prototype.toString.call(value));- Line 1 makes the example array.
- Line 2 prints
true.Array.isArrayis the best check for arrays. - Line 3 prints
[object Array].Object.prototype.toString.callgives a short text label for any built-in kind of value.
For arrays, use Array.isArray, because it asks exactly the question you mean. Use the text label when you are debugging and want to know what kind of value you have. Avoid value.constructor === Array. It compares with one realm's Array, so it fails for the same reason as instanceof.