Getting started#
This guide installs Conduit, binds one host adapter, installs portable assets through it, routes a lifecycle decision through it, and produces conformance evidence for your binding. For the ideas behind each step, read Concepts. For every export, read the Reference.
Install#
Conduit is published to npm as @kontourai/conduit. It is an ES module
package ("type": "module", import exports only) and requires Node 22 or
newer.
npm install @kontourai/conduit
The package has no runtime dependencies. It does not depend on any host or framework package, so installing it does not pull in Claude Code, Codex, Pi, Kiro, Strands, or VoltAgent.
Three entrypoints are exported:
| Import | Contents |
|---|---|
@kontourai/conduit |
Core contracts, both adapter shapes, the Claude Code, Codex, OpenCode, Strands, and VoltAgent profiles, lifecycle binding, and conformance evidence |
@kontourai/conduit/pi |
The Pi profile and Pi extension lifecycle handlers |
@kontourai/conduit/kiro |
The Kiro CLI profile and the Kiro hook evaluator |
The package also exports the committed reference evidence
(@kontourai/conduit/conformance/host-conformance.json) and its JSON Schema
(@kontourai/conduit/schemas/host-conformance.schema.json).
Bind a local harness#
A local harness, such as Claude Code, Codex, or OpenCode, is configured through files. Conduit never decides where those files live. You supply two bindings:
resolveTarget(asset)returns where an asset goes, orundefinedif you have no place for it.write(target, content)performs the write. It may be async.
import { createCodexAdapter } from "@kontourai/conduit";
const codex = createCodexAdapter({
resolveTarget: asset => configuredTargets[asset.kind]?.(asset.id),
write: (target, content) => hostConfigWriter(target, content),
});
configuredTargets and hostConfigWriter are yours. Conduit does not discover
home directories, read credentials, or edit host configuration on its own.
Install portable assets#
An asset is an id, a kind (skill, agent, hook, prompt, command,
or context), and its content.
const receipt = await codex.install([
{ id: "governance-hook", kind: "hook", content: hookConfig },
{ id: "review", kind: "agent", content: agentDefinition },
]);
The adapter writes each asset whose kind the host supports and that your
resolveTarget maps. The receipt lists what was installed and what was skipped:
installedentries carry the assetid,kind, and asha256:digest of the content.skippedentries carry theid,kind, and areason:unsupportedwhen the host profile marks that kindunavailable, orunmappedwhenresolveTargetreturned nothing.
In the example above the Codex profile marks agent as unavailable, so
review is skipped as unsupported. The receipt never contains asset content
or the target your binding resolved, so it is safe to log or store.
Project a lifecycle decision#
Your application decides what should happen at each lifecycle phase. The adapter then reduces that decision to what the host can actually do:
const outcome = await codex.project(
{ phase: "before-tool", sessionId },
{ decision: "deny", reason: "policy denied", modelContext: "Review the rule first." },
);
If the host cannot act on the phase, cannot block there, or cannot inject context there, the adapter downgrades the outcome instead of claiming an effect that will not happen. Outcome projection lists the exact rules.
Bind an in-process framework#
For an SDK or framework that runs inside your process, use the framework
adapters. You supply applyOutcome, which turns a projected outcome into the
framework's own hook result, and optionally installAsset.
import { bindAdapterLifecycle, createStrandsAdapter } from "@kontourai/conduit";
const adapter = createStrandsAdapter({
installAsset: asset => registerWithApplication(asset),
applyOutcome: (event, outcome) => strandsHookBridge(event, outcome),
});
const dispose = bindAdapterLifecycle({
adapter,
registrar: {
register(phase, handler) {
return applicationHooks.on(phase, frameworkEvent =>
handler(toConduitEvent(phase, frameworkEvent)),
);
},
},
evaluate: event => applicationPolicy.evaluate(event),
});
bindAdapterLifecycle registers one handler per phase the host supports and
returns a function that removes them. Framework lifecycle
bindings covers this pattern in more detail. Pi and Kiro
have their own helpers, described in the Reference.
Record evidence for your binding#
The committed matrix in Host conformance matrix only
proves Conduit's side of each adapter. Before you rely on a host for runtime
selection, generate host-bound evidence with your real bindings and the real
host version:
import {
createConformanceReport,
renderConformanceMatrix,
serializeConformanceReport,
} from "@kontourai/conduit";
const report = await createConformanceReport([
{
adapter: codex,
evidenceScope: "host-bound",
adapterVersion: conduitVersion,
hostId: "codex",
hostVersion: installedCodexVersion,
},
]);
const json = serializeConformanceReport(report);
const markdown = renderConformanceMatrix(report);
The probes call your adapter for real: install is called with one probe asset
of every kind, so your write or installAsset binding runs, and project is
called for every phase. Point the bindings at a location where probe assets are
harmless.
Every check that fails, and every capability that is not native, appears in
the report's limitations. See Conformance evidence.