Power Call Surfaces

Distinguish City management, Workspace execution, PowerContext, and the Power model tools.

Powers expose four call surfaces for different callers:

SurfaceCallerPurpose
city.powersHost applicationAdd or remove unique instances and invoke host/config actions
city.powers.scope({ agent_id: agent.id, workspace_id: workspace.id })Host execution codeCall Actions, Availability, or Hooks in one Agent/Workspace scope
context.city.powersPower codeCall another Power Action, pipeline, or effect in the current scope
skillModel in a SessionCall a Power Action provided by City

City management surface

city.powers.add(skill_power);
console.log(city.powers.snapshots());
await city.powers.remove("skill");

city.powers.invoke() and invoke_config() call host-management Actions registered during start(); they are not Session execution Actions.

Workspace execution surface

const result = await city.powers.scope({ agent_id: agent.id, workspace_id: workspace.id }).run_action({
  power: "skill",
  action: "lookup",
  payload: { name: "frontend-design" },
});

This surface explicitly binds an Agent and Workspace, allowing City to create the correct PowerContext.

PowerContext

const result = await context.city.powers.run_action({
  power: "skill",
  action: "list",
  payload: {},
});
await context.city.powers.effect("audit.observe", result.data ?? null);

context.session, context.agent.sessions, and context.workspace are directly callable handles. IDs are reserved for stable identity and serialization.

Model Tool

Each Power with Actions compiles into its own model-facing tool whose name is the Power name. There is no shared plugin_call entry: the model calls the skill tool here, and ai for the City-owned image and speech capabilities.

skill({
  action: "lookup",
  args: { name: "frontend-design" },
});

The Tool is always bound to the current Session's Agent/Workspace execution lease and cannot observe another Agent's bindings.