Repository: endojs/endo
Pull request #2325 (2024-06-20 08:15:01)

Further endo daemon CLI support for dot-delimited paths
Adds support for dot-delimited petname paths in the `mkdir`, `remove`, and `spawn` commands, plus everywhere there's a `--name` option (the `install`, `make`, `request`, `adopt`, `store`, `eval`, and `bundle` commands).

Ref: #2023 


FUDCo: Something for the TODO list that came up in the doing of this PR: The switchover to allowing paths where only simple names had previously been supported forced some of the functions that accept a path in the form of a variadic argument list to be converted to take the path in the form of an array of path elements, while other functions remain using the variadic form.  The resulting mixture is quite confusing.  For the sake of cleanliness, consistency, and legibility (not to mention making parameter types less mysterious), all the variadic functions should be converted to the array form.  The fact that this change is not completely trivial to execute highlights the need for it.
kriskowal: I agree all methods should take the array form. Except `lookup` because I don’t make the rules for hub protocol (@dckc).
dckc: The rules for the `lookup` protocol pre-date me. @erights , @michaelfig ? thoughts?
michaelfig: > I’m fine with this as-is, but there’s a chance we’ll have to loosen up `lookup` to accept variadic pet name paths again because that’s the protocol of name hubs on the Agoric chain.

I don't see how that is a requirement: why would the current version of "name hubs on the Agoric chain" be relevant to the daemon's long-term API?  The daemon's pet name system should work with any other name system, using adapters as necessary to be agnostic.


Patch:
diff --git a/packages/cli/src/commands/adopt.js b/packages/cli/src/commands/adopt.js
index 37184fbfe..ddde44a58 100644
--- a/packages/cli/src/commands/adopt.js
+++ b/packages/cli/src/commands/adopt.js
@@ -2,6 +2,7 @@
 import os from 'os';
 import { E } from '@endo/far';
 import { withEndoAgent } from '../context.js';
+import { parsePetNamePath } from '../pet-name.js';
 
 export const adoptCommand = async ({
   messageNumberText,
@@ -12,5 +13,5 @@ export const adoptCommand = async ({
   withEndoAgent(agentNames, { os, process }, async ({ agent }) => {
     // TODO less bad number parsing.
     const messageNumber = Number(messageNumberText);
-    await E(agent).adopt(messageNumber, edgeName, name);
+    await E(agent).adopt(messageNumber, edgeName, parsePetNamePath(name));
   });
diff --git a/packages/cli/src/commands/eval.js b/packages/cli/src/commands/eval.js
index 583c2bb91..e54e6199c 100644
--- a/packages/cli/src/commands/eval.js
+++ b/packages/cli/src/commands/eval.js
@@ -35,7 +35,7 @@ export const evalCommand = async ({
       source,
       codeNames,
       petNames,
-      resultName,
+      parsePetNamePath(resultName),
     );
     console.log(result);
   });
diff --git a/packages/cli/src/commands/install.js b/packages/cli/src/commands/install.js
index 3d5a0c727..564abf5a5 100644
--- a/packages/cli/src/commands/install.js
+++ b/packages/cli/src/commands/install.js
@@ -8,6 +8,7 @@ import { makeReaderRef } from '@endo/daemon';
 import bundleSource from '@endo/bundle-source';
 
 import { withEndoAgent } from '../context.js';
+import { parsePetNamePath } from '../pet-name.js';
 import { randomHex16 } from '../random.js';
 
 const textEncoder = new TextEncoder();
@@ -25,6 +26,7 @@ export const install = async ({
   let bundleReaderRef;
   /** @type {string | undefined} */
   let temporaryBundleName;
+  await null;
   if (programPath !== undefined) {
     if (bundleName === undefined) {
       // TODO alternately, make a temporary session-scoped GC pet store
@@ -41,6 +43,7 @@ export const install = async ({
 
   await withEndoAgent(agentNames, { os, process }, async ({ agent }) => {
     // Prepare a bundle, with the given name.
+    await null;
     if (bundleReaderRef !== undefined) {
       await E(agent).storeBlob(bundleReaderRef, bundleName);
     }
@@ -53,7 +56,7 @@ export const install = async ({
         )}, $id, $cancelled)`,
         ['apps', 'bundle', 'powers'],
         ['APPS', bundleName, powersName],
-        webletName,
+        parsePetNamePath(webletName),
       );
       const webletLocation = await E(weblet).getLocation();
       process.stdout.write(`${webletLocation}\n`);
diff --git a/packages/cli/src/commands/mkdir.js b/packages/cli/src/commands/mkdir.js
index de357ae6e..a9e36b9c7 100644
--- a/packages/cli/src/commands/mkdir.js
+++ b/packages/cli/src/commands/mkdir.js
@@ -6,5 +6,5 @@ import { parsePetNamePath } from '../pet-name.js';
 
 export const mkdir = async ({ agentNames, directoryPath }) =>
   withEndoAgent(agentNames, { os, process }, async ({ agent }) => {
-    await E(agent).makeDirectory(...parsePetNamePath(directoryPath));
+    await E(agent).makeDirectory(parsePetNamePath(directoryPath));
   });
diff --git a/packages/cli/src/commands/remove.js b/packages/cli/src/commands/remove.js
index ba0c7c545..3e8321b3a 100644
--- a/packages/cli/src/commands/remove.js
+++ b/packages/cli/src/commands/remove.js
@@ -2,8 +2,13 @@
 import os from 'os';
 import { E } from '@endo/far';
 import { withEndoAgent } from '../context.js';
+import { parsePetNamePath } from '../pet-name.js';
 
-export const remove = async ({ petNames, agentNames }) =>
+export const remove = async ({ petNamePaths, agentNames }) =>
   withEndoAgent(agentNames, { os, process }, async ({ agent }) =>
-    Promise.all(petNames.map(petName => E(agent).remove(petName))),
+    Promise.all(
+      petNamePaths.map(petNamePath =>
+        E(agent).remove(...parsePetNamePath(petNamePath)),
+      ),
+    ),
   );
diff --git a/packages/cli/src/commands/request.js b/packages/cli/src/commands/request.js
index 153942a33..24a79a4a4 100644
--- a/packages/cli/src/commands/request.js
+++ b/packages/cli/src/commands/request.js
@@ -2,6 +2,7 @@
 import os from 'os';
 import { E } from '@endo/far';
 import { withEndoAgent } from '../context.js';
+import { parsePetNamePath } from '../pet-name.js';
 
 export const request = async ({
   description,
@@ -10,7 +11,11 @@ export const request = async ({
   agentNames,
 }) => {
   await withEndoAgent(agentNames, { os, process }, async ({ agent }) => {
-    const result = await E(agent).request(toName, description, resultName);
+    const result = await E(agent).request(
+      toName,
+      description,
+      parsePetNamePath(resultName),
+    );
     console.log(result);
   });
 };
diff --git a/packages/cli/src/commands/spawn.js b/packages/cli/src/commands/spawn.js
index e4462f020..8697f3ddd 100644
--- a/packages/cli/src/commands/spawn.js
+++ b/packages/cli/src/commands/spawn.js
@@ -3,8 +3,13 @@
 import os from 'os';
 import { E } from '@endo/far';
 import { withEndoAgent } from '../context.js';
+import { parsePetNamePath } from '../pet-name.js';
 
-export const spawn = async ({ petNames, agentNames }) =>
+export const spawn = async ({ petNamePaths, agentNames }) =>
   withEndoAgent(agentNames, { os, process }, async ({ agent }) =>
-    Promise.all(petNames.map(petName => E(agent).provideWorker(petName))),
+    Promise.all(
+      petNamePaths.map(petNamePath =>
+        E(agent).provideWorker(parsePetNamePath(petNamePath)),
+      ),
+    ),
   );
diff --git a/packages/cli/src/endo.js b/packages/cli/src/endo.js
index fcfbb5675..64e13b8ed 100644
--- a/packages/cli/src/endo.js
+++ b/packages/cli/src/endo.js
@@ -162,7 +162,7 @@ export const main = async rawArgs => {
         UNCONFINED: importPath,
         name: resultName,
         bundle: bundleName,
-        worker: workerName = 'NEW',
+        worker: workerName = undefined,
         as: agentNames,
         powers: powersName = 'NONE',
       } = cmd.opts();
@@ -291,10 +291,10 @@ export const main = async rawArgs => {
     .command('remove [names...]')
     .description('forget a named value')
     .option(...commonOptions.as)
-    .action(async (petNames, cmd) => {
+    .action(async (petNamePaths, cmd) => {
       const { as: agentNames } = cmd.opts();
       const { remove } = await import('./commands/remove.js');
-      return remove({ petNames, agentNames });
+      return remove({ petNamePaths, agentNames });
     });
 
   program
@@ -405,10 +405,10 @@ export const main = async rawArgs => {
     .command('spawn [names...]')
     .description('creates a worker')
     .option(...commonOptions.as)
-    .action(async (petNames, cmd) => {
+    .action(async (petNamePaths, cmd) => {
       const { as: agentNames } = cmd.opts();
       const { spawn } = await import('./commands/spawn.js');
-      return spawn({ petNames, agentNames });
+      return spawn({ petNamePaths, agentNames });
     });
 
   program
@@ -591,6 +591,7 @@ export const main = async rawArgs => {
     .description('erases persistent state and stops if running')
     .action(async cmd => {
       const { force } = cmd.opts();
+      await null;
       const doPurge =
         force ||
         /^y(es)?$/u.test(
diff --git a/packages/daemon/src/daemon.js b/packages/daemon/src/daemon.js
index 87ec483fb..97330db49 100644
--- a/packages/daemon/src/daemon.js
+++ b/packages/daemon/src/daemon.js
@@ -439,7 +439,7 @@ const makeDaemonCore = async (
     context.thisDiesIfThatDies(hubId);
 
     const hub = provide(hubId, 'hub');
-    return E(hub).lookup(...path);
+    return E(hub).lookup(path);
   };
 
   /**
@@ -708,6 +708,7 @@ const makeDaemonCore = async (
    * @param {Context} context
    */
   const evaluateFormula = async (id, formulaNumber, formula, context) => {
+    await null;
     if (Object.hasOwn(makers, formula.type)) {
       const make = makers[formula.type];
       const value = await /** @type {unknown} */ (
@@ -839,6 +840,7 @@ const makeDaemonCore = async (
   const formulateReadableBlob = async (readerRef, deferredTasks) => {
     const { formulaNumber, contentSha512 } = await formulaGraphJobs.enqueue(
       async () => {
+        await null;
         const values = {
           formulaNumber: await randomHex512(),
           contentSha512: await contentStore.store(makeRefReader(readerRef)),
@@ -988,6 +990,7 @@ const makeDaemonCore = async (
    * @type {DaemonCore['formulateWorker']}
    */
   const formulateWorker = async deferredTasks => {
+    await null;
     return formulateNumberedWorker(
       await formulaGraphJobs.enqueue(async () => {
         const formulaNumber = await randomHex512();
@@ -1011,6 +1014,7 @@ const makeDaemonCore = async (
     const { specifiedWorkerId, ...remainingSpecifiedIdentifiers } =
       specifiedIdentifiers;
 
+    await null;
     const storeId = (await formulateNumberedPetStore(await randomHex512())).id;
 
     const hostFormulaNumber = await randomHex512();
@@ -1064,6 +1068,7 @@ const makeDaemonCore = async (
     deferredTasks,
     specifiedWorkerId,
   ) => {
+    await null;
     return formulateNumberedHost(
       await formulaGraphJobs.enqueue(async () => {
         const identifiers = await formulateHostDependencies({
@@ -1123,6 +1128,7 @@ const makeDaemonCore = async (
 
   /** @type {DaemonCore['formulateGuest']} */
   const formulateGuest = async (hostAgentId, hostHandleId, deferredTasks) => {
+    await null;
     return formulateNumberedGuest(
       await formulaGraphJobs.enqueue(async () => {
         const identifiers = await formulateGuestDependencies(
@@ -1212,6 +1218,7 @@ const makeDaemonCore = async (
               if (typeof formulaIdOrPath === 'string') {
                 return formulaIdOrPath;
               }
+              await null;
               return (
                 /* eslint-disable no-use-before-define */
                 (
@@ -1657,11 +1664,19 @@ const makeDaemonCore = async (
     const petStore = await provide(petStoreId, 'pet-store');
 
     /**
-     * @param {string} petName - The pet name to inspect.
+     * @param {string|string[]} petName - The pet name to inspect.
      * @returns {Promise<KnownEndoInspectors[string]>} An
      * inspector for the value of the given pet name.
      */
     const lookup = async petName => {
+      if (Array.isArray(petName)) {
+        if (petName.length !== 1) {
+          throw Error(
+            'PetStoreInspector.lookup(path) requires path length of 1',
+          );
+        }
+        petName = petName[0];
+      }
       const id = petStore.identifyLocal(petName);
       if (id === undefined) {
         throw new Error(`Unknown pet name ${petName}`);
diff --git a/packages/daemon/src/directory.js b/packages/daemon/src/directory.js
index 773b45f68..3852f6dd5 100644
--- a/packages/daemon/src/directory.js
+++ b/packages/daemon/src/directory.js
@@ -26,8 +26,12 @@ export const makeDirectoryMaker = ({
   /** @type {MakeDirectoryNode} */
   const makeDirectoryNode = petStore => {
     /** @type {EndoDirectory['lookup']} */
-    const lookup = (...petNamePath) => {
+    const lookup = petNamePath => {
+      if (typeof petNamePath === 'string') {
+        petNamePath = [petNamePath];
+      }
       const [headName, ...tailNames] = petNamePath;
+
       const id = petStore.identifyLocal(headName);
       if (id === undefined) {
         throw new TypeError(`Unknown pet name: ${q(headName)}`);
@@ -41,6 +45,7 @@ export const makeDirectoryMaker = ({
 
     /** @type {EndoDirectory['reverseLookup']} */
     const reverseLookup = async presence => {
+      await null;
       const id = getIdForRef(await presence);
       if (id === undefined) {
         return harden([]);
@@ -62,7 +67,7 @@ export const makeDirectoryMaker = ({
         // eslint-disable-next-line no-use-before-define
         return { hub: directory, name: tailName };
       }
-      const nameHub = /** @type {NameHub} */ (await lookup(...headPath));
+      const nameHub = /** @type {NameHub} */ (await lookup(headPath));
       return { hub: nameHub, name: tailName };
     };
 
@@ -126,12 +131,13 @@ export const makeDirectoryMaker = ({
       if (petNamePath.length === 0) {
         return petStore.list();
       }
-      const hub = /** @type {NameHub} */ (await lookup(...petNamePath));
+      const hub = /** @type {NameHub} */ (await lookup(petNamePath));
       return hub.list();
     };
 
     /** @type {EndoDirectory['listIdentifiers']} */
     const listIdentifiers = async (...petNamePath) => {
+      petNamePath = petNamePath || [];
       const names = await list(...petNamePath);
       const identities = new Set();
       await Promise.all(
@@ -153,12 +159,13 @@ export const makeDirectoryMaker = ({
         yield* petStore.followNameChanges();
         return;
       }
-      const hub = /** @type {NameHub} */ (await lookup(...petNamePath));
+      const hub = /** @type {NameHub} */ (await lookup(petNamePath));
       yield* hub.followNameChanges();
     };
 
     /** @type {EndoDirectory['remove']} */
     const remove = async (...petNamePath) => {
+      await null;
       if (petNamePath.length === 1) {
         const petName = petNamePath[0];
         await petStore.remove(petName);
@@ -211,6 +218,7 @@ export const makeDirectoryMaker = ({
       if (typeof petNamePath === 'string') {
         petNamePath = [petNamePath];
       }
+      await null;
       if (petNamePath.length === 1) {
         const petName = petNamePath[0];
         await petStore.write(petName, id);
@@ -221,7 +229,7 @@ export const makeDirectoryMaker = ({
     };
 
     /** @type {EndoDirectory['makeDirectory']} */
-    const makeDirectory = async (...directoryPetNamePath) => {
+    const makeDirectory = async directoryPetNamePath => {
       const { value: directory, id } = await formulateDirectory();
       await write(directoryPetNamePath, id);
       return directory;
diff --git a/packages/daemon/src/guest.js b/packages/daemon/src/guest.js
index d7d174063..1f3c00299 100644
--- a/packages/daemon/src/guest.js
+++ b/packages/daemon/src/guest.js
@@ -44,13 +44,14 @@ export const makeGuestMaker = ({ provide, makeMailbox, makeDirectoryNode }) => {
       HOST: hostHandleId,
     });
 
+    const directory = makeDirectoryNode(specialStore);
     const mailbox = makeMailbox({
       petStore: specialStore,
+      directory,
       selfId: handleId,
       context,
     });
-    const { petStore, handle } = mailbox;
-    const directory = makeDirectoryNode(petStore);
+    const { handle } = mailbox;
 
     const { reverseIdentify } = specialStore;
     const {
diff --git a/packages/daemon/src/host.js b/packages/daemon/src/host.js
index b17ec66ea..2a39892c6 100644
--- a/packages/daemon/src/host.js
+++ b/packages/daemon/src/host.js
@@ -8,7 +8,11 @@ import { E } from '@endo/far';
 import { makeExo } from '@endo/exo';
 import { makeError, q } from '@endo/errors';
 import { makeIteratorRef } from './reader-ref.js';
-import { assertPetName, petNamePathFrom } from './pet-name.js';
+import {
+  assertPetName,
+  assertPetNamePath,
+  petNamePathFrom,
+} from './pet-name.js';
 import { parseId, formatId } from './formula-identifier.js';
 import { makePetSitter } from './pet-sitter.js';
 import { makeDeferredTasks } from './deferred-tasks.js';
@@ -89,19 +93,21 @@ export const makeHostMaker = ({
       ...platformNames,
       AGENT: hostId,
       SELF: handleId,
+      MAIN: mainWorkerId,
       ENDO: endoId,
       NETS: networksDirectoryId,
       INFO: inspectorId,
       NONE: leastAuthorityId,
     });
 
+    const directory = makeDirectoryNode(specialStore);
     const mailbox = makeMailbox({
       petStore: specialStore,
+      directory,
       selfId: handleId,
       context,
     });
     const { petStore, handle } = mailbox;
-    const directory = makeDirectoryNode(petStore);
 
     const getEndoBootstrap = async () => provide(endoId, 'endo');
 
@@ -138,33 +144,30 @@ export const makeHostMaker = ({
     };
 
     /**
-     * @param {string} workerName
+     * @param {string[]} workerNamePath
      */
-    const provideWorker = async workerName => {
-      /** @type {DeferredTasks<WorkerDeferredTaskParams>} */
-      const tasks = makeDeferredTasks();
-      // eslint-disable-next-line no-use-before-define
-      const workerId = prepareWorkerFormulation(workerName, tasks.push);
+    const provideWorker = async workerNamePath => {
+      const workerId = await E(directory).identify(...workerNamePath);
 
       if (workerId !== undefined) {
         return provide(workerId, 'worker');
       }
 
-      const { value } = await formulateWorker(tasks);
+      /** @type {DeferredTasks<WorkerDeferredTaskParams>} */
+      const tasks = makeDeferredTasks();
+      const { value, id } = await formulateWorker(tasks);
+      await E(directory).write(workerNamePath, id);
       return value;
     };
 
     /**
-     * @param {string} workerName
+     * @param {string | undefined} workerName
      * @param {DeferredTasks<{ workerId: string }>['push']} deferTask
      */
     const prepareWorkerFormulation = (workerName, deferTask) => {
-      if (workerName === 'MAIN') {
-        return mainWorkerId;
-      } else if (workerName === 'NEW') {
+      if (workerName === undefined) {
         return undefined;
       }
-
       const workerId = petStore.identifyLocal(workerName);
       if (workerId === undefined) {
         deferTask(identifiers =>
@@ -175,11 +178,11 @@ export const makeHostMaker = ({
     };
 
     /**
-     * @param {string | 'MAIN' | 'NEW'} workerName
+     * @param {string | 'MAIN' | undefined} workerName
      * @param {string} source
      * @param {string[]} codeNames
      * @param {(string | string[])[]} petNamePaths
-     * @param {string} resultName
+     * @param {string[]} resultName
      */
     const evaluate = async (
       workerName,
@@ -189,7 +192,7 @@ export const makeHostMaker = ({
       resultName,
     ) => {
       if (resultName !== undefined) {
-        assertPetName(resultName);
+        assertPetNamePath(resultName);
       }
       if (petNamePaths.length !== codeNames.length) {
         throw new Error('Evaluator requires one pet name for each code name');
@@ -222,7 +225,7 @@ export const makeHostMaker = ({
 
       if (resultName !== undefined) {
         tasks.push(identifiers =>
-          petStore.write(resultName, identifiers.evalId),
+          E(directory).write(resultName, identifiers.evalId),
         );
       }
 
@@ -240,7 +243,7 @@ export const makeHostMaker = ({
     /**
      * Helper function for makeUnconfined and makeBundle.
      * @param {string} powersName
-     * @param {string} workerName
+     * @param {string | undefined} workerName
      * @param {string | string[]} [resultName]
      */
     const prepareMakeCaplet = (powersName, workerName, resultName) => {
@@ -294,7 +297,7 @@ export const makeHostMaker = ({
     };
 
     /**
-     * @param {string | 'MAIN' | 'NEW'} workerName
+     * @param {string | 'MAIN' | undefined} workerName
      * @param {string} bundleName
      * @param {string | 'NONE' | 'SELF' | 'ENDO'} powersName
      * @param {string} resultName
@@ -398,6 +401,7 @@ export const makeHostMaker = ({
       { introducedNames = {}, agentName = undefined } = {},
     ) => {
       let host = getNamedAgent(petName, 'host');
+      await null;
       if (host === undefined) {
         const { value, id } =
           // Behold, recursion:
@@ -431,6 +435,7 @@ export const makeHostMaker = ({
       { introducedNames = {}, agentName = undefined } = {},
     ) => {
       let guest = getNamedAgent(handleName, 'guest');
+      await null;
       if (guest === undefined) {
         const { value, id } =
           // Behold, recursion:
diff --git a/packages/daemon/src/mail.js b/packages/daemon/src/mail.js
index 93134de85..b205583d5 100644
--- a/packages/daemon/src/mail.js
+++ b/packages/daemon/src/mail.js
@@ -5,7 +5,7 @@ import { makeExo } from '@endo/exo';
 import { makePromiseKit } from '@endo/promise-kit';
 import { q } from '@endo/errors';
 import { makeChangeTopic } from './pubsub.js';
-import { assertPetName } from './pet-name.js';
+import { assertPetName, assertPetNamePath } from './pet-name.js';
 
 import {
   ResponderInterface,
@@ -54,7 +54,7 @@ const makeEnvelope = () => makeExo('Envelope', EnvelopeInterface, {});
 export const makeMailboxMaker = ({ provide }) => {
   /**
     @type {MakeMailbox} */
-  const makeMailbox = ({ selfId, petStore, context }) => {
+  const makeMailbox = ({ selfId, petStore, directory, context }) => {
     /** @type {Map<number, StampedMessage>} */
     const messages = new Map();
 
@@ -218,9 +218,9 @@ export const makeMailboxMaker = ({ provide }) => {
     };
 
     /** @type {Mail['adopt']} */
-    const adopt = async (messageNumber, edgeName, petName) => {
+    const adopt = async (messageNumber, edgeName, petNamePath) => {
       assertPetName(edgeName);
-      assertPetName(petName);
+      assertPetNamePath(petNamePath);
       if (
         typeof messageNumber !== 'number' ||
         messageNumber >= Number.MAX_SAFE_INTEGER
@@ -249,13 +249,14 @@ export const makeMailboxMaker = ({ provide }) => {
         );
       }
       context.thisDiesIfThatDies(id);
-      await petStore.write(petName, id);
+      await E(directory).write(petNamePath, id);
     };
 
     /** @type {Mail['request']} */
     const request = async (toName, description, responseName) => {
+      await null;
       if (responseName !== undefined) {
-        const responseId = petStore.identifyLocal(responseName);
+        const responseId = await E(directory).identify(responseName);
         if (responseId !== undefined) {
           return provide(responseId);
         }
@@ -280,7 +281,7 @@ export const makeMailboxMaker = ({ provide }) => {
       const responseP = provide(responseId);
 
       if (responseName !== undefined) {
-        await petStore.write(responseName, responseId);
+        await E(directory).write(responseName, responseId);
       }
 
       return responseP;
diff --git a/packages/daemon/src/pet-name.js b/packages/daemon/src/pet-name.js
index c5ec49eda..abaf402e9 100644
--- a/packages/daemon/src/pet-name.js
+++ b/packages/daemon/src/pet-name.js
@@ -18,6 +18,18 @@ export const assertPetName = petName => {
   }
 };
 
+/**
+ * @param {string[]} petNamePath
+ */
+export const assertPetNamePath = petNamePath => {
+  if (!Array.isArray(petNamePath) || petNamePath.length < 1) {
+    throw new Error(`Invalid pet name path`);
+  }
+  for (const petName of petNamePath) {
+    assertPetName(petName);
+  }
+};
+
 /**
  * @param {string | string[]} petNameOrPetNamePath
  */
diff --git a/packages/daemon/src/types.d.ts b/packages/daemon/src/types.d.ts
index 3e112d4de..794ca6b09 100644
--- a/packages/daemon/src/types.d.ts
+++ b/packages/daemon/src/types.d.ts
@@ -444,7 +444,7 @@ export interface NameHub {
   followNameChanges(
     ...petNamePath: string[]
   ): AsyncGenerator<PetStoreNameChange, undefined, undefined>;
-  lookup(...petNamePath: string[]): Promise<unknown>;
+  lookup(petNamePath: string | string[]): Promise<unknown>;
   reverseLookup(value: unknown): Array<string>;
   write(petNamePath: string | string[], id: string): Promise<void>;
   remove(...petNamePath: string[]): Promise<void>;
@@ -453,7 +453,7 @@ export interface NameHub {
 }
 
 export interface EndoDirectory extends NameHub {
-  makeDirectory(...petNamePath: string[]): Promise<EndoDirectory>;
+  makeDirectory(petNamePath: string[]): Promise<EndoDirectory>;
 }
 
 export type MakeDirectoryNode = (petStore: PetStore) => EndoDirectory;
@@ -470,7 +470,7 @@ export interface Mail {
   adopt(
     messageNumber: number,
     edgeName: string,
-    petName: string,
+    petName: string[],
   ): Promise<void>;
   dismiss(messageNumber: number): Promise<void>;
   request(
@@ -490,6 +490,7 @@ export interface Mail {
 export type MakeMailbox = (args: {
   selfId: string;
   petStore: PetStore;
+  directory: EndoDirectory;
   context: Context;
 }) => Mail;
 
@@ -580,17 +581,17 @@ export interface EndoHost extends EndoAgent {
     petName?: string,
     opts?: MakeHostOrGuestOptions,
   ): Promise<EndoHost>;
-  makeDirectory(petName: string): Promise<EndoDirectory>;
-  provideWorker(petName: string): Promise<EndoWorker>;
+  makeDirectory(petNamePath: string[]): Promise<EndoDirectory>;
+  provideWorker(petNamePath: string[]): Promise<EndoWorker>;
   evaluate(
     workerPetName: string | undefined,
     source: string,
     codeNames: Array<string>,
     petNames: Array<string>,
-    resultName?: string,
+    resultName?: string[],
   ): Promise<unknown>;
   makeUnconfined(
-    workerName: string | 'NEW' | 'MAIN',
+    workerName: string | undefined | 'MAIN',
     specifier: string,
     powersName: string | 'NONE' | 'SELF' | 'ENDO',
     resultName?: string,


Test patch:
diff --git a/packages/daemon/test/endo.test.js b/packages/daemon/test/endo.test.js
index 96e1841c1..4e6cd36ec 100644
--- a/packages/daemon/test/endo.test.js
+++ b/packages/daemon/test/endo.test.js
@@ -41,6 +41,7 @@ const dirname = url.fileURLToPath(new URL('..', import.meta.url)).toString();
 const takeCount = async (asyncIterator, count) => {
   const values = [];
 
+  await null;
   // eslint-disable-next-line no-plusplus
   for (let i = 0; i < count; i++) {
     // eslint-disable-next-line no-await-in-loop
@@ -71,6 +72,7 @@ const prepareFollowNameChangesIterator = async host => {
  * @param {string} locator
  */
 const prepareFollowLocatorNameChangesIterator = async (host, locator) => {
+  await null;
   const changesIterator = makeRefIterator(
     await E(host).followLocatorNameChanges(locator),
   );
@@ -247,7 +249,7 @@ test('lifecycle', async t => {
   );
   const bootstrap = getBootstrap();
   const host = E(bootstrap).host();
-  await E(host).provideWorker('worker');
+  await E(host).provideWorker(['worker']);
   await E(host).cancel('worker');
   cancel(new Error('Cancelled'));
   await closed.catch(() => {});
@@ -276,7 +278,7 @@ test('store pass-copy values', async t => {
 
   {
     const { host } = await makeHost(config, cancelled);
-    const restoredValue = await E(host).lookup('value');
+    const restoredValue = await E(host).lookup(['value']);
     t.deepEqual(restoredValue, storedValue);
   }
 });
@@ -286,7 +288,7 @@ test('store formula values', async t => {
 
   {
     const { host } = await makeHost(config, cancelled);
-    await E(host).provideWorker('w1');
+    await E(host).provideWorker(['w1']);
     const counter = await E(host).evaluate(
       'w1',
       `
@@ -304,7 +306,7 @@ test('store formula values', async t => {
       `,
       [],
       [],
-      'temporary-retainer',
+      ['temporary-retainer'],
     );
     await E(host).storeValue(counter, 'counter');
     await E(host).remove('temporary-retainer');
@@ -314,7 +316,7 @@ test('store formula values', async t => {
 
   {
     const { host } = await makeHost(config, cancelled);
-    const counter = await E(host).lookup('counter');
+    const counter = await E(host).lookup(['counter']);
     t.is(1, await E(counter).incr());
     t.is(2, await E(counter).incr());
   }
@@ -323,7 +325,7 @@ test('store formula values', async t => {
 
   {
     const { host } = await makeHost(config, cancelled);
-    const counter = await E(host).lookup('counter');
+    const counter = await E(host).lookup(['counter']);
     t.is(1, await E(counter).incr());
     t.is(2, await E(counter).incr());
   }
@@ -334,14 +336,14 @@ test('fail to store non-formula exos', async t => {
   const { cancelled, config } = await prepareConfig(t);
   const { host } = await makeHost(config, cancelled);
   await t.throwsAsync(() => E(host).storeValue(noFormulaExo, 'exo'), {
-    message: 'No corresponding formula for (an object)',
+    message: /^No corresponding formula for/,
   });
 });
 
 test('spawn and evaluate', async t => {
   const { host } = await prepareHost(t);
 
-  await E(host).provideWorker('w1');
+  await E(host).provideWorker(['w1']);
   const ten = await E(host).evaluate('w1', '10', [], []);
   t.is(ten, 10);
 });
@@ -353,13 +355,6 @@ test('anonymous spawn and evaluate', async t => {
   t.is(ten, 10);
 });
 
-test('anonymous spawn and evaluate with new worker', async t => {
-  const { host } = await prepareHost(t);
-
-  const ten = await E(host).evaluate('NEW', '10', [], []);
-  t.is(ten, 10);
-});
-
 // Regression test for https://github.com/endojs/endo/issues/2147
 test('spawning a worker does not overwrite existing non-worker name', async t => {
   const { host } = await prepareHost(t);
@@ -368,8 +363,8 @@ test('spawning a worker does not overwrite existing non-worker name', async t =>
 
   // This resolves with the existing value of 'foo' rather than overwriting it
   // with a new worker.
-  await E(host).provideWorker('foo');
-  await t.throwsAsync(() => E(host).evaluate('foo', '20', [], [], 'bar'), {
+  await E(host).provideWorker(['foo']);
+  await t.throwsAsync(() => E(host).evaluate('foo', '20', [], [], ['bar']), {
     message: 'Cannot evaluate using non-worker',
   });
 });
@@ -380,16 +375,16 @@ test('persist spawn and evaluation', async t => {
   {
     const { host } = await makeHost(config, cancelled);
 
-    await E(host).provideWorker('w1');
+    await E(host).provideWorker(['w1']);
 
-    const ten = await E(host).evaluate('w1', '10', [], [], 'ten');
+    const ten = await E(host).evaluate('w1', '10', [], [], ['ten']);
     t.is(ten, 10);
     const twenty = await E(host).evaluate(
       'w1',
       'number * 2',
       ['number'],
       ['ten'],
-      'twenty',
+      ['twenty'],
     );
 
     // Forget the pet name of the intermediate formula, demonstrating that pet
@@ -405,7 +400,7 @@ test('persist spawn and evaluation', async t => {
   {
     const { host } = await makeHost(config, cancelled);
 
-    const retwenty = await E(host).lookup('twenty');
+    const retwenty = await E(host).lookup(['twenty']);
     t.is(20, retwenty);
   }
 });
@@ -432,7 +427,7 @@ test('store with name', async t => {
 
   {
     const { host } = await makeHost(config, cancelled);
-    const readable = await E(host).lookup('hello-text');
+    const readable = await E(host).lookup(['hello-text']);
     const actualText = await E(readable).text();
     t.is(actualText, 'hello\n');
   }
@@ -466,13 +461,13 @@ test('move renames value, overwriting the "to" name', async t => {
   t.false(await E(host).has('ten'));
   t.true(await E(host).has('decimus'));
 
-  const decimusValue = await E(host).lookup('decimus');
+  const decimusValue = await E(host).lookup(['decimus']);
   t.is(decimusValue, 10);
 });
 
 test('move moves value, from the host to a different name hub', async t => {
   const { host } = await prepareHost(t);
-  const directory = await E(host).makeDirectory('directory');
+  const directory = await E(host).makeDirectory(['directory']);
 
   await E(host).storeValue(10, 'ten');
 
@@ -590,7 +585,7 @@ test('move preserves original name if writing to new name hub fails', async t =>
     message: 'I had one job.',
   });
 
-  const tenValue = await E(host).lookup('ten');
+  const tenValue = await E(host).lookup(['ten']);
   t.is(tenValue, 10);
 });
 
@@ -599,7 +594,7 @@ test('closure state lost by restart', async t => {
 
   {
     const { host } = await makeHost(config, cancelled);
-    await E(host).provideWorker('w1');
+    await E(host).provideWorker(['w1']);
 
     await E(host).evaluate(
       'w1',
@@ -621,14 +616,14 @@ test('closure state lost by restart', async t => {
     `,
       [],
       [],
-      'counter-maker',
+      ['counter-maker'],
     );
     await E(host).evaluate(
       'w1',
       `E(cm).makeCounter() `,
       ['cm'],
       ['counter-maker'],
-      'counter',
+      ['counter'],
     );
     const one = await E(host).evaluate(
       'w1',
@@ -657,7 +652,7 @@ test('closure state lost by restart', async t => {
 
   {
     const { host } = await makeHost(config, cancelled);
-    await E(host).lookup('w1');
+    await E(host).lookup(['w1']);
     const one = await E(host).evaluate(
       'w1',
       `E(counter).incr()`,
@@ -690,7 +685,7 @@ test('persist unconfined services and their requests', async t => {
       makePromiseKit();
     cancelled.catch(cancelFollower);
     const { host } = await makeHost(config, followerCancelled);
-    await E(host).provideWorker('user-worker');
+    await E(host).provideWorker(['user-worker']);
 
     await E(host).evaluate(
       'user-worker',
@@ -701,7 +696,7 @@ test('persist unconfined services and their requests', async t => {
     `,
       [],
       [],
-      'grant',
+      ['grant'],
     );
     const iteratorRef = E(host).followMessages();
     const { value: message } = await E(iteratorRef).next();
@@ -713,7 +708,7 @@ test('persist unconfined services and their requests', async t => {
 
   const requesterFinished = (async () => {
     const { host } = await makeHost(config, cancelled);
-    await E(host).provideWorker('w1');
+    await E(host).provideWorker(['w1']);
     await E(host).provideGuest('h1', {
       agentName: 'a1',
     });
@@ -722,13 +717,13 @@ test('persist unconfined services and their requests', async t => {
     const serviceLocation = url.pathToFileURL(servicePath).href;
     await E(host).makeUnconfined('w1', serviceLocation, 'a1', 's1');
 
-    await E(host).provideWorker('w2');
+    await E(host).provideWorker(['w2']);
     const answer = await E(host).evaluate(
       'w2',
       'E(service).ask()',
       ['service'],
       ['s1'],
-      'answer',
+      ['answer'],
     );
     const number = await E(answer).value();
     t.is(number, 42);
@@ -740,7 +735,7 @@ test('persist unconfined services and their requests', async t => {
 
   {
     const { host } = await makeHost(config, cancelled);
-    const answer = await E(host).lookup('answer');
+    const answer = await E(host).lookup(['answer']);
     const number = await E(answer).value();
     t.is(number, 42);
   }
@@ -754,7 +749,7 @@ test('persist confined services and their requests', async t => {
       makePromiseKit();
     cancelled.catch(cancelFollower);
     const { host } = await makeHost(config, followerCancelled);
-    await E(host).provideWorker('user-worker');
+    await E(host).provideWorker(['user-worker']);
 
     await E(host).evaluate(
       'user-worker',
@@ -765,7 +760,7 @@ test('persist confined services and their requests', async t => {
     `,
       [],
       [],
-      'grant',
+      ['grant'],
     );
     const iteratorRef = E(host).followMessages();
     const { value: message } = await E(iteratorRef).next();
@@ -777,7 +772,7 @@ test('persist confined services and their requests', async t => {
 
   const requesterFinished = (async () => {
     const { host } = await makeHost(config, cancelled);
-    await E(host).provideWorker('w1');
+    await E(host).provideWorker(['w1']);
     await E(host).provideGuest('h1', { agentName: 'a1' });
 
     const servicePath = path.join(dirname, 'test', 'service.js');
@@ -785,13 +780,13 @@ test('persist confined services and their requests', async t => {
       E(host).makeBundle('w1', bundleName, 'a1', 's1'),
     );
 
-    await E(host).provideWorker('w2');
+    await E(host).provideWorker(['w2']);
     const answer = await E(host).evaluate(
       'w2',
       'E(service).ask()',
       ['service'],
       ['s1'],
-      'answer',
+      ['answer'],
     );
     const number = await E(answer).value();
     t.is(number, 42);
@@ -803,7 +798,7 @@ test('persist confined services and their requests', async t => {
 
   {
     const { host } = await makeHost(config, cancelled);
-    const answer = await E(host).lookup('answer');
+    const answer = await E(host).lookup(['answer']);
     const number = await E(answer).value();
     t.is(number, 42);
   }
@@ -813,8 +808,8 @@ test('guest facet receives a message for host', async t => {
   const { host } = await prepareHost(t);
 
   const guest = E(host).provideGuest('guest');
-  await E(host).provideWorker('worker');
-  await E(host).evaluate('worker', '10', [], [], 'ten1');
+  await E(host).provideWorker(['worker']);
+  await E(host).evaluate('worker', '10', [], [], ['ten1']);
 
   const iteratorRef = E(host).followMessages();
   E.sendOnly(guest).request('HOST', 'a number', 'number');
@@ -826,8 +821,8 @@ test('guest facet receives a message for host', async t => {
 
   const { value: message1 } = await E(iteratorRef).next();
   t.is(message1.number, 1);
-  await E(host).adopt(message1.number, 'gift', 'ten2');
-  const ten = await E(host).lookup('ten2');
+  await E(host).adopt(message1.number, 'gift', ['ten2']);
+  const ten = await E(host).lookup(['ten2']);
   t.is(ten, 10);
 
   const guestId = await E(host).identify('guest');
@@ -1093,7 +1088,7 @@ test('followLocatorNameChanges does not notify of redundant pet store writes', a
 test('direct cancellation', async t => {
   const { host } = await prepareHost(t);
 
-  await E(host).provideWorker('worker');
+  await E(host).provideWorker(['worker']);
 
   const counterPath = path.join(dirname, 'test', 'counter.js');
   const counterLocation = url.pathToFileURL(counterPath).href;
@@ -1160,7 +1155,7 @@ test('direct cancellation', async t => {
 test('indirect cancellation via worker', async t => {
   const { host } = await prepareHost(t);
 
-  await E(host).provideWorker('worker');
+  await E(host).provideWorker(['worker']);
 
   const counterPath = path.join(dirname, 'test', 'counter.js');
   const counterLocation = url.pathToFileURL(counterPath).href;
@@ -1228,12 +1223,12 @@ test('indirect cancellation via worker', async t => {
 test.failing('indirect cancellation via caplet', async t => {
   const { host } = await prepareHost(t);
 
-  await E(host).provideWorker('w1');
+  await E(host).provideWorker(['w1']);
   const counterPath = path.join(dirname, 'test', 'counter.js');
   const counterLocation = url.pathToFileURL(counterPath).href;
   await E(host).makeUnconfined('w1', counterLocation, 'AGENT', 'counter');
 
-  await E(host).provideWorker('w2');
+  await E(host).provideWorker(['w2']);
   await E(host).provideGuest('guest', { agentName: 'guest-agent' });
   const doublerPath = path.join(dirname, 'test', 'doubler.js');
   const doublerLocation = url.pathToFileURL(doublerPath).href;
@@ -1268,7 +1263,7 @@ test.failing('indirect cancellation via caplet', async t => {
 test('cancel because of requested capability', async t => {
   const { host } = await prepareHost(t);
 
-  await E(host).provideWorker('worker');
+  await E(host).provideWorker(['worker']);
   await E(host).provideGuest('guest', { agentName: 'guest-agent' });
 
   const messages = E(host).followMessages();
@@ -1277,7 +1272,7 @@ test('cancel because of requested capability', async t => {
   const counterLocation = url.pathToFileURL(counterPath).href;
   E(host).makeUnconfined('worker', counterLocation, 'guest-agent', 'counter');
 
-  await E(host).evaluate('worker', '0', [], [], 'zero');
+  await E(host).evaluate('worker', '0', [], [], ['zero']);
   await E(messages).next();
   E(host).resolve(0, 'zero');
 
@@ -1343,7 +1338,7 @@ test('cancel because of requested capability', async t => {
 test('unconfined service can respond to cancellation', async t => {
   const { host } = await prepareHost(t);
 
-  await E(host).provideWorker('worker');
+  await E(host).provideWorker(['worker']);
 
   const capletPath = path.join(dirname, 'test', 'context-consumer.js');
   const capletLocation = url.pathToFileURL(capletPath).href;
@@ -1367,7 +1362,7 @@ test('unconfined service can respond to cancellation', async t => {
 test('confined service can respond to cancellation', async t => {
   const { host } = await prepareHost(t);
 
-  await E(host).provideWorker('worker');
+  await E(host).provideWorker(['worker']);
 
   const capletPath = path.join(dirname, 'test', 'context-consumer.js');
   await doMakeBundle(host, capletPath, bundleName =>
@@ -1388,7 +1383,7 @@ test('make a host', async t => {
   const { host } = await prepareHost(t);
 
   const host2 = E(host).provideHost('fellow-host');
-  await E(host2).provideWorker('w1');
+  await E(host2).provideWorker(['w1']);
   const ten = await E(host2).evaluate('w1', '10', [], []);
   t.is(ten, 10);
 });
@@ -1396,23 +1391,23 @@ test('make a host', async t => {
 test('name and reuse inspector', async t => {
   const { host } = await prepareHost(t);
 
-  await E(host).provideWorker('worker');
+  await E(host).provideWorker(['worker']);
 
   const counterPath = path.join(dirname, 'test', 'counter.js');
   await E(host).makeUnconfined('worker', counterPath, 'NONE', 'counter');
 
   const inspector = await E(host).evaluate(
     'worker',
-    'E(INFO).lookup("counter")',
+    'E(INFO).lookup(["counter"])',
     ['INFO'],
     ['INFO'],
-    'inspector',
+    ['inspector'],
   );
   t.regex(String(inspector), /Alleged: Inspector.+make-unconfined/u);
 
   const worker = await E(host).evaluate(
     'worker',
-    'E(inspector).lookup("worker")',
+    'E(inspector).lookup(["worker"])',
     ['inspector'],
     ['inspector'],
   );
@@ -1423,7 +1418,7 @@ test('name and reuse inspector', async t => {
 test('eval-mediated worker name', async t => {
   const { host } = await prepareHost(t);
 
-  await E(host).provideWorker('worker');
+  await E(host).provideWorker(['worker']);
 
   const counterPath = path.join(dirname, 'test', 'counter.js');
   await E(host).makeUnconfined('worker', counterPath, 'NONE', 'counter');
@@ -1442,10 +1437,10 @@ test('eval-mediated worker name', async t => {
   // Note that while `worker === counter-worker`, it doesn't matter here.
   const counterWorker = await E(host).evaluate(
     'worker',
-    'E(E(INFO).lookup("counter")).lookup("worker")',
+    'E(E(INFO).lookup(["counter"])).lookup(["worker"])',
     ['INFO'],
     ['INFO'],
-    'counter-worker',
+    ['counter-worker'],
   );
   t.regex(String(counterWorker), /Alleged: EndoWorker/u);
 
@@ -1469,7 +1464,7 @@ test('lookup with single petname', async t => {
 
   const resolvedValue = await E(host).evaluate(
     'MAIN',
-    'E(AGENT).lookup("ten")',
+    'E(AGENT).lookup(["ten"])',
     ['AGENT'],
     ['AGENT'],
   );
@@ -1479,11 +1474,11 @@ test('lookup with single petname', async t => {
 test('lookup with petname path (inspector)', async t => {
   const { host } = await prepareHost(t);
 
-  await E(host).evaluate('MAIN', '10', [], [], 'ten');
+  await E(host).evaluate('MAIN', '10', [], [], ['ten']);
 
   const resolvedValue = await E(host).evaluate(
     'MAIN',
-    'E(AGENT).lookup("INFO", "ten", "source")',
+    'E(AGENT).lookup(["INFO", "ten", "source"])',
     ['AGENT'],
     ['AGENT'],
   );
@@ -1498,7 +1493,7 @@ test('lookup with petname path (caplet with lookup method)', async t => {
 
   const resolvedValue = await E(host).evaluate(
     'MAIN',
-    'E(AGENT).lookup("lookup", "name")',
+    'E(AGENT).lookup(["lookup", "name"])',
     ['AGENT'],
     ['AGENT'],
   );
@@ -1512,7 +1507,7 @@ test('lookup with petname path (value has no lookup method)', async t => {
   await t.throwsAsync(
     E(host).evaluate(
       'MAIN',
-      'E(AGENT).lookup("ten", "someName")',
+      'E(AGENT).lookup(["ten", "someName"])',
       ['AGENT'],
       ['AGENT'],
     ),
@@ -1523,7 +1518,7 @@ test('lookup with petname path (value has no lookup method)', async t => {
 test('evaluate name resolved by lookup path', async t => {
   const { host } = await prepareHost(t);
 
-  await E(host).evaluate('MAIN', '10', [], [], 'ten');
+  await E(host).evaluate('MAIN', '10', [], [], ['ten']);
 
   const resolvedValue = await E(host).evaluate(
     'MAIN',
@@ -1557,8 +1552,8 @@ test('guest cannot access host methods', async t => {
   const { host } = await prepareHost(t);
 
   const guest = E(host).provideGuest('guest');
-  const guestsHost = E(guest).lookup('HOST');
-  await t.throwsAsync(() => E(guestsHost).lookup(), {
+  const guestsHost = E(guest).lookup(['HOST']);
+  await t.throwsAsync(() => E(guestsHost).lookup([]), {
     message: /target has no method "lookup"/u,
   });
   const revealedTarget = await E.get(guestsHost).targetId;
@@ -1578,7 +1573,7 @@ test('read unknown node id', async t => {
   await E(host).write(['abc'], id);
 
   // observe reification failure
-  await t.throwsAsync(() => E(host).lookup('abc'), {
+  await t.throwsAsync(() => E(host).lookup(['abc']), {
     message: /No peer found for node identifier /u,
   });
 });
@@ -1591,13 +1586,13 @@ test('read remote value', async t => {
   await E(hostA).addPeerInfo(await E(hostB).getPeerInfo());
 
   // create value to share
-  await E(hostB).evaluate('MAIN', '"hello, world!"', [], [], 'salutations');
+  await E(hostB).evaluate('MAIN', '"hello, world!"', [], [], ['salutations']);
   const hostBValueIdentifier = await E(hostB).identify('salutations');
 
   // insert in hostA out of band
   await E(hostA).write(['greetings'], hostBValueIdentifier);
 
-  const hostAValue = await E(hostA).lookup('greetings');
+  const hostAValue = await E(hostA).lookup(['greetings']);
   t.is(hostAValue, 'hello, world!');
 });
 
@@ -1614,7 +1609,7 @@ test('round-trip remotable identity', async t => {
     'Far("Echoer", { echo: value => value })',
     [],
     [],
-    'echoer',
+    ['echoer'],
   );
   const echoerId = await E(hostB).identify('echoer');
   await E(hostA).write(['echoer'], echoerId);
@@ -1641,10 +1636,10 @@ test('hello from afar', async t => {
   await E(hostB).addPeerInfo(await E(hostA).getPeerInfo());
 
   // Induce B to connect to A
-  await E(hostA).evaluate('MAIN', '42', [], [], 'ft');
+  await E(hostA).evaluate('MAIN', '42', [], [], ['ft']);
   const ftId = await E(hostA).identify('ft');
   await E(hostB).write(['ft'], ftId);
-  const ft = await E(hostB).lookup('ft');
+  const ft = await E(hostB).lookup(['ft']);
   t.is(ft, 42);
 
   await E(hostB).evaluate(
@@ -1652,7 +1647,7 @@ test('hello from afar', async t => {
     'Far("Echoer", { echo: value => value })',
     [],
     [],
-    'echoer',
+    ['echoer'],
   );
   const echoerId = await E(hostB).identify('echoer');
   await E(hostA).write(['echoer'], echoerId);
@@ -1707,7 +1702,7 @@ test('locate remote value', async t => {
   await E(hostB).addPeerInfo(await E(hostA).getPeerInfo());
 
   // create value to share
-  await E(hostB).evaluate('MAIN', '"hello, world!"', [], [], 'salutations');
+  await E(hostB).evaluate('MAIN', '"hello, world!"', [], [], ['salutations']);
   const hostBValueIdentifier = await E(hostB).identify('salutations');
 
   // insert in hostA out of band
@@ -1727,7 +1722,7 @@ test('invite, accept, and send mail', async t => {
   await E(hostB).accept(invitationLocator, 'alice');
 
   // create value to share
-  await E(hostA).evaluate('MAIN', '"hello, world!"', [], [], 'salutations');
+  await E(hostA).evaluate('MAIN', '"hello, world!"', [], [], ['salutations']);
   const expectedSalutationsId = await E(hostA).identify('salutations');
   await E(hostA).send('bob', ['Hello'], ['salutations'], ['salutations']);
 
@@ -1779,7 +1774,7 @@ test('reverse locate remote value', async t => {
   await E(hostB).addPeerInfo(await E(hostA).getPeerInfo());
 
   // create value to share
-  await E(hostB).evaluate('MAIN', '"hello, world!"', [], [], 'salutations');
+  await E(hostB).evaluate('MAIN', '"hello, world!"', [], [], ['salutations']);
   const hostBValueIdentifier = await E(hostB).identify('salutations');
 
   // insert in hostA out of band