Skip to content

createSeepient() ​

Create a persistent agent with session memory, provider switching, and abort support. Unlike askSeepient() which is stateless, an agent maintains conversation history across calls.

Signature ​

typescript
function createSeepient(options?: CreateSeepientOptions): Promise<Seepient>

WARNING

createSeepient() is async -- always await it. The agent needs to resolve the provider configuration and optionally load persisted session state before it is ready.

Quick example ​

typescript
import { createSeepient } from "seepient";

const agent = await createSeepient({
  model: "gpt-5.4",
  systemPrompt: "You are a concise coding assistant.",
});

const reply = await agent.chat("What is a closure?");
console.log(reply.text);

// Context is preserved across calls
const followUp = await agent.chat("Show me an example in TypeScript");
console.log(followUp.text);

// Check cumulative usage
console.log(agent.getUsage());

Parameters ​

Stateless Embedding & Multi-Tenancy

For multi-tenant workers and cloud functions requiring full state injection (audit, policy, capability ledger, sessions), use createSeepient or askSeepient. See Stateless Workers and Multi-Tenant Isolation for architecture, tenancy modes, and fail-closed rules.

options (optional) ​

NameTypeDefaultDescription
tenancy"single" | "multi""single" (auto-upgraded to "multi" if tenant signals detected)Tenancy mode. "multi" enforces fail-closed storage and runtime injection
modelstringProvider defaultModel identifier, e.g. "gpt-5.4", "claude-sonnet-4-6-20260320"
providerstring"openai"Feeds the permission pipeline's modelProviderClass audit label
purposePurpose"text"Purpose routing hint (see Purpose reference for all 15 supported values)
tier"efficient" | "standard" | "complex"(none)Model capability tier hint
providerAccountstring(none)Active provider account name (persisted and restored with session state)
providersRecord<string, any>(none)Programmatic provider account definitions for isolated or in-memory runtimes
modelAssignmentsPurposeModelMap(none)Custom purpose-and-tier model routing assignments
credentialsCredentialStoreIn-memory empty defaultInjected credential store (e.g. MemoryCredentialStore for isolated runtimes) — put records with put(id, {kind: "api_key", keyValue}) and reference them via credential: { kind: "seepient", id }
overlayFilestring(none)Config overlay file path, or ":memory:" for zero-disk ephemeral agents
adapterInferenceAdapterAggregateInferenceAdapterCustom inference adapter or test double
override{ providerAccount?, model?, thinkingLevel? }(none)Per-instance model and account override
runtimeProviderRuntimecreateAmbientProviderRuntime() in single-mode; required isolated runtime in multi-modeProvider runtime instance managing credentials, configurations, and inference adapters
principalIdstring"sdk-user"Identity of the calling principal/user, threaded into audit events and capability grants
sessionIdstringAuto-generated UUIDExplicit session ID (^[a-zA-Z0-9_-]+$) for tracking and persistence
auditStoreAuditStoreLocal file audit storeInjected audit store for recording action lifecycle events
policyStorePolicyStoreLocal file policy storeInjected policy store for grant snapshots and mutations
capabilityLedgerCapabilityLedgerLocal file capability ledgerInjected ledger for capability lease consumption and revocations
systemPromptstring"You are a helpful assistant."System prompt prepended to every conversation
tools(string | UserToolDefinition | AnyToolRegistration)[]All built-inTool names, group constants, or custom tool registrations (trustedHostTool, preparedTool, brokerConnector)
consentModeConsentModedeny-by-defaultPermission consent mode ("ask-everything", "edit-enabled", "autonomous"). When omitted, unpredeclared effectful tools are denied unless pre-granted in policy or an approvalBroker is supplied
deploymentCeilingCapabilitySet | Capability[](none)Maximum capability lease permitted for any execution
principalPolicyCapabilitySet | Capability[](none)Pre-granted capabilities for the calling principal
approveToolApproveToolFn(none)Interactive tool approval callback
approvalBrokerApprovalBroker(none)Custom approval broker for permission escalation
commitHelperCommitHelperNative helperCustom or mock exact-commit verifier helper
networkBrokerNetworkAdapterStandard adapterCustom broker network adapter with SSRF / IP pinning rules
cwdstringprocess.cwd()Workspace directory for file operations and skill discovery
skillsstring[] | booleantrueSpecific skill names, true for all, or false to disable skill scanning and catalog injection
sourcesSkillSource[](none)Injected skill sources for multi-tenant skill scoping. Disables ambient skill discovery in multi mode
maxStepsnumber10Maximum agent loop iterations per call
persiststring | PersistenceBackend | PersistenceConfig(none)Directory path, backend instance, or config object (e.g. { type: "memory" }). File persistence writes are atomic (tmp + rename).
hooksHooks(none)Lifecycle callbacks
middlewareMiddleware[](none)Request/response pipeline functions (auth, logging, rate limiting, etc.)
metadataRecord<string, unknown>{}Adapter-specific metadata passed to middleware via PipelineContext
configRecord<string, unknown>{}Extra config passed to tool handlers

Partial State Store Injection Warning

For fully stateless zero-disk execution, all three permission contracts (auditStore, policyStore, and capabilityLedger) must be injected together along with persist. If 1 or 2 permission stores are injected, the SDK logs a warning ([seepient] WARNING: Partial state store injection detected...); the missing stores resolve to the defaults — in-memory when ambient store defaults are unregistered (seepient-core), otherwise the local filesystem (~/.seepient or ./.seepient).

Permission Pipeline Always Active

The permission pipeline is always active across all SDK entry points (createSeepient, askSeepient). Every tool execution is evaluated by policy and recorded in the audit trail.

::: note Tool Registration and Declaration Validation Custom host callbacks bind into the execution boundary at agent creation time. setTools(tools: string[]) accepts tool names only by design and enables/disables already registered tools; custom registrations must be supplied at composition time in createSeepient({ tools: [...] }).

When declaring trustedHostTool, declarations fail closed with descriptive errors if unknown effects are supplied or required descriptor fields are omitted:

  • Allowed effects: "network-egress", "secret-use", "model-egress" (in that order).
  • "network-egress" requires destinations: string[].
  • "secret-use" requires secretRefs: string[].
  • "model-egress" requires dataClasses: string[]. :::

Seepient interface ​

The object returned by createSeepient():

Conversation & Lifecycle Methods ​

MethodSignatureDescription
chat(message: string) => Promise<AgentResponse>Send a message and get the full response. Context is preserved.
chatStream(message: string, options?: Omit<AskSeepientOptions, "stream" | "signal">) => Promise<AskSeepientStreamResult>Send a message with streaming output. Returns async iterables and SSE helpers.
switchProvider(accountOrModel: string, model?: string) => Promise<void>Switch the provider account (and optionally model) used for subsequent calls. One argument switches the model only.
setSystemPrompt(prompt: string) => voidUpdate the system prompt. Replaces the existing system message in history.
setTools(tools: string[]) => voidUpdate active tools by name. Custom tool registrations cannot be added dynamically via setTools.
abort() => voidAbort the currently running chat() or chatStream() call.
clear() => voidClear conversation history. Keeps the system prompt.
getHistory() => Message[]Return a copy of the full conversation history.
getUsage() => CumulativeUsageReturn cumulative token usage across all calls.
flushAudit() => Promise<number>Flush pending terminal audit events (returns flushed count).
close() => Promise<void>Abort any running calls and flush buffered audit events.
dispose() => Promise<void>Closes agent, flushes audit logs, and removes all runtime listeners.

Provider Management Methods (Spec 013 / Spec 021) ​

MethodSignatureDescription
listProviders() => Promise<string[]>List distinct upstream provider names (e.g. ["anthropic", "openai"]).
getCatalog() => Promise<readonly AvailableModel[]>Return all discovered and declared models across all configured accounts.
getAssignments() => PurposeModelMapReturn current purpose-and-tier routing assignments.
addProvider(input: AccountInput) => Promise<SaveResult>Add or update a provider account with credentials.
removeProvider(id: string, opts?: { force?: boolean }) => Promise<DeleteResult>Delete a configured provider account.
setAssignment(purpose, tier, target) => Promise<SaveResult>Assign a model to a purpose and tier.
clearAssignment(purpose, tier) => Promise<SaveResult>Remove an assignment for a purpose and tier.
resolve(opts: { purpose, tier?, override? }) => Promise<ResolutionResult>Preview how a turn will route without invoking the model.
reload() => Promise<{ revision: number }>Force-reload provider configuration from the backing store.

AgentResponse ​

Returned by chat():

typescript
interface AgentResponse {
  text: string;
  toolCalls: ToolCall[];
  usage: Usage;
}

CumulativeUsage ​

Returned by getUsage():

typescript
interface CumulativeUsage {
  totalPromptTokens: number;
  totalCompletionTokens: number;
  totalCost: number;
  requestCount: number;
}

Examples ​

Basic multi-turn conversation ​

typescript
import { createSeepient } from "seepient";

const agent = await createSeepient({
  systemPrompt: "You are a helpful travel advisor.",
});

const r1 = await agent.chat("What are the top 3 things to do in Tokyo?");
console.log(r1.text);

const r2 = await agent.chat("Which of those is best for families?");
console.log(r2.text);

// The agent remembers the full conversation
console.log(`History: ${agent.getHistory().length} messages`);
console.log(`Total requests: ${agent.getUsage().requestCount}`);

Streaming responses ​

Use chatStream() for real-time output:

typescript
import { createSeepient } from "seepient";

const agent = await createSeepient({
  model: "claude-sonnet-4-6-20260320",
  provider: "anthropic",
});

const stream = await agent.chatStream("Explain transformers architecture", {
  onText: (delta) => process.stdout.write(delta),
});

const text = await stream.fullText;
console.log(`\nTokens: ${(await stream.usage).totalTokens}`);

Provider switching ​

Switch the active provider account or model mid-conversation:

typescript
import { createSeepient } from "seepient";

const agent = await createSeepient({ provider: "openai", model: "gpt-5.4" });

// Start with the default resolution for the model
const r1 = await agent.chat("What is the capital of France?");
console.log(r1.text);

// Switch to another configured provider account for the next turn
await agent.switchProvider("main", "claude-sonnet-4-6-20260320");

const r2 = await agent.chat("Tell me more about its history");
console.log(r2.text);

TIP

switchProvider(account, model) targets a provider account from your configuration; with a single argument it switches the model only. The conversation history is preserved, so context carries over seamlessly.

Session persistence ​

Persist conversation history so the agent can resume across process restarts:

typescript
import { createSeepient } from "seepient";

// Option 1: File-based persistence (just pass a path)
const agent = await createSeepient({
  tenancy: "single",
  persist: "./sessions/my-agent",
});

// Option 2: In-memory persistence (great for testing)
const agent2 = await createSeepient({
  tenancy: "single",
  persist: { type: "memory" },
});

// Option 3: Explicit file config
const agent3 = await createSeepient({
  tenancy: "single",
  persist: { type: "file", path: "/var/data/sessions" },
});

await agent.chat("My name is Alice");
await agent.chat("I'm working on a React project");

// In a new process, recreate the agent with the same persist path and sessionId:
// const agent2 = await createSeepient({
//   tenancy: "single",
//   sessionId: "my-session-id",
//   persist: "./sessions",
// });
// To resume a previous conversation, the caller must supply the same sessionId.

Custom persistence backends ​

Register custom backends (Redis, SQLite, encrypted storage, etc.) with registerBackend:

typescript
import { registerBackend, createSeepient, type PersistenceBackend, type SessionData } from "seepient";

class RedisBackend implements PersistenceBackend {
  readonly __persistenceBackend = true as const;

  constructor(private url: string) { /* connect */ }

  async save(sessionId: string, data: SessionData): Promise<void> {
    await redis.set(`session:${sessionId}`, JSON.stringify(data));
  }
  async load(sessionId: string): Promise<SessionData | null> {
    const raw = await redis.get(`session:${sessionId}`);
    return raw ? JSON.parse(raw) : null;
  }
  async delete(sessionId: string): Promise<void> {
    await redis.del(`session:${sessionId}`);
  }
  async list(): Promise<string[]> {
    const keys = await redis.keys("session:*");
    return keys.map((k) => k.replace("session:", ""));
  }
}

// Register once at startup
registerBackend("redis", (config) => new RedisBackend(config.url as string));

// Then use by type name
const agent = await createSeepient({
  tenancy: "single",
  persist: { type: "redis", url: "redis://localhost:6379" },
});

Pass a backend instance directly ​

typescript
const myBackend: PersistenceBackend = {
  readonly __persistenceBackend: true as const,
  async save(id, data) { /* custom logic */ },
  async load(id) { return null; },
  async delete(id) {},
  async list() { return []; },
};

const agent = await createSeepient({ tenancy: "single", persist: myBackend });

Dynamic tools ​

Change the available tools at runtime:

typescript
import { createSeepient } from "seepient";

const agent = await createSeepient({
  tools: ["core"], // Only shell, read_file, write_file, datetime
});

await agent.chat("Read ./package.json and tell me the version");

// Add web search for the next query
agent.setTools(["core", "web_search"]);

await agent.chat("Now search for the latest version of this package on npm");

Abort a running call ​

typescript
import { createSeepient } from "seepient";

const agent = await createSeepient();

// Start a long-running request
const promise = agent.chat("Analyze all files in this repository");

// Abort after 5 seconds
setTimeout(() => agent.abort(), 5000);

try {
  const result = await promise;
} catch (err) {
  console.log("Agent was aborted");
}

INFO

abort() cancels the in-flight HTTP request to the LLM provider, not just the agent loop between steps. The AbortSignal propagates through to the underlying provider SDK (OpenAI, Anthropic, etc.), so network resources are released immediately.

Concurrency: Mutex acquisition ensures caller-owned lock release: chat() and chatStream() are serialized — a second call blocks until the first completes. This prevents concurrent mutations of the shared message history.

Inspect history ​

typescript
import { createSeepient } from "seepient";

const agent = await createSeepient();
await agent.chat("Hello");
await agent.chat("What can you do?");

const history = agent.getHistory();
for (const msg of history) {
  console.log(`[${msg.role}] ${msg.content.slice(0, 80)}`);
}

// Clear to start fresh
agent.clear();
console.log(agent.getHistory().length); // 1 (just the system prompt)

Persistence types ​

PersistenceBackend interface ​

typescript
interface PersistenceBackend {
  /** Brand discriminator */
  readonly __persistenceBackend: true;
  save(sessionId: string, data: SessionData): Promise<void>;
  load(sessionId: string): Promise<SessionData | null>;
  delete(sessionId: string): Promise<void>;
  list(): Promise<string[]>;
}

PersistenceConfig ​

typescript
interface PersistenceConfig {
  type: string;           // "file", "memory", or custom registered type
  [key: string]: unknown; // Backend-specific options (path, url, etc.)
}

Built-in backends and factory functions:

  • createPersistenceBackend(config) -- Creates a backend from a config object
  • registerBackend(type, factory) -- Registers a custom backend type

Middleware ​

Add cross-cutting concerns (logging, auth, rate limiting) to agent execution:

typescript
import {
  createSeepient,
  loggingMiddleware,
  rateLimitMiddleware,
  authMiddleware,
} from "seepient";

const agent = await createSeepient({
  middleware: [
    authMiddleware({
      validate: (ctx) => !!ctx.metadata.apiKey,
      errorMessage: "Missing API key",
    }),
    rateLimitMiddleware({
      maxRequests: 60,
      windowMs: 60_000,
      keyExtractor: (ctx) => String(ctx.metadata.userId ?? "anonymous"),
    }),
    loggingMiddleware({
      logRequest: true,
      logResponse: true,
    }),
  ],
  metadata: { apiKey: process.env.MY_API_KEY, userId: "user-123" },
});

Custom middleware ​

typescript
import type { Middleware } from "seepient";

const auditLog: Middleware = async (ctx, next) => {
  console.log(`[audit] request ${ctx.requestId} started`);
  const start = Date.now();

  await next(); // Continue to the agent loop

  console.log(`[audit] request ${ctx.requestId} finished in ${Date.now() - start}ms`);
};

const agent = await createSeepient({ middleware: [auditLog] });
  • askSeepient() -- Stateless one-shot execution (streaming via stream: true)
  • Tools -- Built-in and custom tool reference

createSeepient() in seepient-core ​

createSeepient() ships in both packages. In seepient-core — the engine-only package for serverless and SDK-only installs — the constructor, tenancy, and consent machinery are identical to this page; what differs is the package around it:

  • No built-in tools: register custom or trusted-host tools explicitly; built-in names reject at registration with an error naming seepient.
  • No sandbox / MCP gateway / media generation: boundary-needing operations and unregistered media deny typed instead of executing.
  • In-memory store defaults in single mode: with no stores injected, a core construction resolves the in-memory store set (zero ambient writes); inject persist/stores for anything durable.
  • The same providers/credential option family (providers, modelAssignments, credentials, adapter) reaches the vendor through the same runtime bootstrap as the full package.

If you want the slimmest multi-turn shape with the session object owning its history, use createChat() instead.

Released under the Business Source License 1.1.