# Chi module contract

Chi modules attach a listener during their extension factory phase:

~~~ts
pi.events.on("chi:discover", (chi: ChiBase) => {
	chi.register({
		id: "chi-example",
		version: "1.0.0",
		dependencies: [],
		api: exampleApi,
		config: {
			schemaVersion: 1,
			schema: exampleConfigSchema,
		},
		async initialize(chi, config) {
			// Start the module using config and direct registry access.
		},
		onConfigChange(chi, current, changes) {
			// Synchronize retained runtime state after /chi changes a setting.
		},
	});
});
~~~

Use a type-only import:

~~~ts
import type { ChiBase } from "@henkaku-center/chi-base/contract";
~~~

The contract defines:

- id: a unique registry identifier;
- version: the module release version used in status and diagnostics;
- dependencies: module IDs that must be ready first;
- api: the direct API available through chi.get or chi.require;
- config: a versioned Zod object contract;
- initialize(chi, config): the once-per-session startup callback;
- onConfigChange(chi, current, changes): an optional callback after this
  module's effective configuration changes.

`changes` contains `{ scope, key, previous, current }` entries for changed
effective top-level fields. `current` is the complete parsed effective config.
The callback may be asynchronous and is awaited before `setConfigValue` or
`/chi` reports success.

The first configuration version supports either z.object({}) or a non-empty
object containing only required top-level z.enum or z.string fields with
defaults:

~~~ts
const schema = z.object({
	mode: z.enum(["safe", "fast"]).default("safe"),
	profile: z.string().default("default"),
});
~~~

Modules use chi.get<T>(id) for optional dependencies and
chi.require<T>(id) for required dependencies. require throws a diagnostic
when the module is absent, blocked, failed, or not ready.

The registry exposes list(), getConfig(), and setConfigValue(). Updating a
setting does not call initialize again. A module with `onConfigChange` is
responsible for updating its running state; modules without it are silently
updated and can pull the latest value with `getConfig()`. Pi's event bus is not
used for configuration changes.

Module IDs must be unique within a session. If the same ID is registered twice,
Chi marks both conflicting registrations as failed and initializes neither,
but continues discovering unrelated modules. Chi Base reports one startup
error for the duplicated ID and hints that a global and local installation may
be clashing.
