Embedded SDK
Run the whole nikcli server in your own process — same routes, same handlers, no listener and no socket.
Overview
@nikcli-ai/sdk-next is an embedded host. It builds the same typed client as
the network SDK, but hands it a fetch that calls
the server's request handler directly in memory. No port is bound, no socket is opened, and
nothing leaves the process.
Everything else is unchanged: the same routing, the same middleware, the same handlers, codecs and errors. Code written against the network client works against the embedded host without edits — which is the point.
Availability
@nikcli-ai/sdk-next is a workspace package and is not published. It is usable
from inside the monorepo — and it is the migration target for the generated
@nikcli-ai/sdk — but an external project cannot install it yet. If you are
building outside this repository, take the client path.
It also depends on nikcli-ai itself rather than on a contract package, because
embedding means running the real server: the whole agent is in your process, not a stub of it.
Creating a host
NikCli.create returns the client surface, so the call sites look like the network
client with an Effect wrapper around construction.
import { NikCli } from "@nikcli-ai/sdk-next"
import { Effect } from "effect"
const program = Effect.gen(function* () {
const nikcli = yield* NikCli.create({ directory: process.cwd() })
const session = yield* Effect.promise(() =>
nikcli.session.create({ title: "embedded" }),
)
return yield* Effect.promise(() =>
nikcli.session.prompt({
sessionID: session.data!.id,
parts: [{ type: "text", text: "What changed in the last commit?" }],
}),
)
})
The result envelope is the same one the network client uses — calls resolve with
{ data, error } rather than rejecting. See
The result envelope.
As an Effect layer
The same constructor is available as a service, for code that would rather take the host as a dependency than build one.
import { NikCli } from "@nikcli-ai/sdk-next"
import { Effect } from "effect"
const program = Effect.gen(function* () {
const nikcli = yield* NikCli.Service
return yield* Effect.promise(() => nikcli.session.list())
})
await Effect.runPromise(program.pipe(Effect.provide(NikCli.layer))) NikCli.layer only adapts NikCli.create() for dependency injection.
There is one host, reachable two ways.
Directory binding
The host binds requests to its directory — defaulting to
process.cwd() — by setting x-nikcli-directory on any call that does
not already carry its own directory query parameter or header. From there the
request flows through the same per-request instance middleware as network traffic.
const nikcli = yield* NikCli.create({ directory: "/Users/me/project" })
// bound to the host directory
yield* Effect.promise(() => nikcli.session.list())
// explicit selection still wins
yield* Effect.promise(() => nikcli.session.list({ directory: "/Users/me/other" })) Registering tools
The embedded host can do one thing the network client cannot: register tools directly into the
instance's ToolRegistry — the same registry sessions consult and
GET /experimental/tool/ids reports.
import { NikCli, Tool } from "@nikcli-ai/sdk-next"
const program = Effect.gen(function* () {
const nikcli = yield* NikCli.create({ directory: process.cwd() })
yield* nikcli.tools.register(MyTool)
}) A tool is executable code. It cannot travel over HTTP, so there is no endpoint that could offer this and no way to do it from the network client. Registration is the reason to embed rather than connect.
Embedded or client?
| Embedded | Client | |
|---|---|---|
| Package | @nikcli-ai/sdk-next (in-repo) | @nikcli-ai/sdk (npm) |
| Transport | In-memory handler call | HTTP |
| Needs a running server | No | Yes |
| Register tools in code | Yes | No |
| Cross-process / cross-machine | No | Yes |
| Isolation between hosts | None — process-global | Per server |
| Auth | Not applicable | Bearer, basic, Tailscale |
If your program is a script, a bot or a separate interface, connect. If your program is the product and nikcli is a component of it, embed.