See two worlds share one runtime and config while keeping provider routes, service state, clocks, logs, and lifecycle ownership separate.
An instance is one mutable service world. A runtime applies the same resolved config to every instance it creates.
This checked test creates two instances on one runtime, verifies their service URLs share an origin but not a path, writes Grace only to the first, and reads both through provider-shaped HTTP:
import { createTestRuntime } from "localhost2137/testing";
import { expect, it } from "vitest";
import config from "../localhost.config.js";
it("isolates service state and routes for two instances of one config", async () => {
const runtime = await createTestRuntime({
config,
port: 0,
storage: "temporary",
});
try {
const first = await runtime.createInstance();
try {
const second = await runtime.createInstance();
try {
const firstUrl = new URL(first.slack.connection.apiUrl);
const secondUrl = new URL(second.slack.connection.apiUrl);
expect(firstUrl.origin).toBe(secondUrl.origin);
expect(firstUrl.pathname).not.toBe(secondUrl.pathname);
expect(firstUrl.pathname.endsWith("/slack/api/")).toBe(true);
expect(secondUrl.pathname.endsWith("/slack/api/")).toBe(true);
await first.slack.createUser({ name: "Grace" });
await expect(readUserNames(first.slack.connection)).resolves.toEqual([
"localhost2137-bot",
"Grace",
]);
await expect(readUserNames(second.slack.connection)).resolves.toEqual([
"localhost2137-bot",
]);
} finally {
await second.destroy();
}
} finally {
await first.destroy();
}
} finally {
await runtime.close();
}
});
async function readUserNames(
connection: Readonly<{ apiUrl: string; botToken: string }>,
): Promise<string[]> {
const response = await fetch(new URL("users.list", connection.apiUrl), {
headers: { authorization: `Bearer ${connection.botToken}` },
});
const payload = (await response.json()) as {
members: Array<{ name: string }>;
ok: true;
};
expect(response.status).toBe(200);
expect(payload.ok).toBe(true);
return payload.members.map(({ name }) => name);
}pnpm exec vitest run test/instance-isolation.test.tsThe negative assertion is the important one: the second provider route does not see Grace. A test that only verifies distinct IDs or successful creation has not yet proved state isolation.
| Concern | Owner |
|---|---|
| Host, port, server process, and control token | Runtime |
| Plugin factories, service keys, credentials, callback destinations, and other plugin config | Runtime template |
| Plugin records and service storage | Instance and service |
| Clock, seed status, logs, and tracked plugin work | Instance |
| Provider API URL and app-facing connection values | Derived for the selected instance |
Two worlds may allocate the same provider-shaped record IDs because each plugin database is local to its instance. They still run the same plugin code and resolved configuration.
This is state isolation, not a process or security sandbox. Instances share a runtime endpoint and
privileged control credential. The runtime supplies application processes only instance-scoped
provider connection values. localhost run does not sanitize the process's inherited environment.
The public route contains both instance and service keys:
http://127.0.0.1:2137/dev/slack/api/
http://127.0.0.1:2137/review/slack/api/Those are two worlds for the same slack mount. Passing one connection object to an SDK or HTTP
client selects its world. Every setup and inspection operation must target that same instance.
If a test arranges review but the app still uses the dev URL, both paths can work while observing
different state. Compare the app-facing URL before assuming an operation lost data.
This CLI sequence owns one named world and exercises each transition:
pnpm exec localhost instance create lifecycle-demo --seed
pnpm exec localhost instance reset lifecycle-demo
pnpm exec localhost instance reset lifecycle-demo --seed
pnpm exec localhost instance destroy lifecycle-demo| Transition | Identity and route | Replacement state |
|---|---|---|
| Create | Allocates a new instance ID and route. | Empty unless --seed or { seed: true }. |
| Reset | Keeps the same ID, route, and persistence policy. | Replaces service state and clock; empty unless seed is explicit. |
| Destroy | Retires the ID and route. | Removes stored instance state; typed handles become unusable. |
Reset is useful when one owner deliberately reuses a named development world. Independent tests
usually create independent instances instead. Cleanup belongs in finally, as the checked test
shows; “ephemeral” does not mean fire-and-forget.
Seed test worlds covers successful and failed seeded transitions.
Persistence determines whether the runtime restores an instance after restart. It does not change provider behavior or widen the isolation boundary.
localhost dev creates persistent dev; restarting the daemon preserves it until reset/destroy.createTestRuntime creates ephemeral worlds inside owned temporary storage; closing the runtime
removes that storage after its instances are destroyed.The daemon or suite owns the runtime. A test or worker owns the instances it creates. An instance owner must not close a runtime shared by other workers.
Instances isolate state when every interaction is instance-aware. They do not isolate runtime-scoped plugin config or external application state.
A callback URL defined in plugin config is shared by every instance of that mount. Creating another world does not add an instance header or rewrite that URL. Parallel callback tests need genuine provider-shaped correlation, serialization, or separately configured runtimes and receivers.
Use another instance when mutable plugin state must differ. Use another runtime when configuration, ports, or callback receivers must differ. Configuration defines the shared template, Integration testing defines ownership, and Callbacks and webhooks defines receiver routing.
Operations and emulated APIs
Use privileged controls to arrange or inspect one world without replacing the provider-shaped application interaction under test.
Callbacks and webhooks
Follow a checked operation through signed HTTP delivery, an application handler, an SDK reply, tracked completion, and state inspection.