Power Lifecycle

Understand City-owned Power instances, dynamic call contexts, and execution leases.

City owns Powers. city.powers.add(new MyPower()) registers one unique instance. A Power ID cannot be registered twice in one City, and Agents do not store Power instances or Registries.

Two lifecycle checkpoints

  • initialize(context) runs once when the Power enters City. Register host Actions and initialize resources owned by the Power here.
  • dispose(context) runs once when the Power is removed or City closes. Release those resources here.
class ProviderPower extends Power {
  readonly name = "provider";

  async initialize({ storage }: PowerLifecycleContext) {
    await this.provider.open(storage.path);
  }

  async dispose() {
    await this.provider.close();
  }
}

Both methods receive the same stable PowerLifecycleContext. Agent, Session, and Workspace entry or removal do not trigger Power lifecycle callbacks. There is no generic connect/disconnect protocol.

city.powers.add() explicitly rejects when initialization fails. The failed instance never enters the execution Registry and cannot contribute Actions, Hooks, or System content. City retains an observable status: "error" snapshot with last_error in city.powers.snapshots(). A host should isolate that Power and continue starting other subjects instead of treating an optional Power as an application-wide startup dependency. A successful retry for the same Power ID replaces the error snapshot with ready.

Call context

City freshly projects a PowerContext for every Action, Hook, System, and Availability call. Its agent, workspace, and optional session/turn handles can be used directly. Workspace is the project resource for the current call; it is not a Power lifecycle boundary or a default resource-isolation key.

Long-lived connections, timers, schedulers, and workers must follow their actual domain ownership. A browser Provider may be created lazily for an Agent, a chat channel may be keyed by account, and a scheduler may follow task definitions. dispose must ultimately close every resource.

Execution leases

A Session captures an immutable Power execution view at each Step checkpoint. New Steps stop seeing a removed Power immediately. An existing Step may finish with its captured instance, and City calls dispose only after the final execution lease is released.

city.close() stops Sessions before releasing Powers and Workspaces, and aggregates cleanup errors. A single Power dispose failure does not prevent other Powers from cleaning up.