v0.1.0GitHubnpm
Concepts

Agent

The Agent is the only thing most apps construct directly. Everything else hangs off it — providers, tools, permissions, sessions, skills, subagents. Construction is synchronous and cheap; the resources that cost something (the SQLite session store, MCP child processes, the on-disk skill set) are wired up lazily on the first session() call.

agent.ts
import { Agent } from "@skawld/agent-sdk";
import { OpenAIResponsesProvider } from "@skawld/agent-sdk/providers";
import { defaultTools } from "@skawld/agent-sdk/tools";

const agent = new Agent({
  provider: new OpenAIResponsesProvider({ apiKey: process.env.OPENAI_API_KEY }),
  model: "gpt-5",
  tools: defaultTools(),
  permissions: { mode: "default" },
  systemPrompt: "You are a careful release engineer.",
});

#Constructor

new Agent(options: AgentOptions)
constructor

Build an agent. Throws ConfigError synchronously when provider or model is missing, or when maxRetries is a negative or non-integer value. Everything else is just option capture — no provider call, no disk write, no MCP connect, no skill loader running.

What the constructor actually does, in order:

  1. Validates provider, model, and maxRetries.

  2. Resolves cwd (defaults to process.cwd()) and configDir (defaults to .skawld resolved against cwd).

  3. Materialises the tool registry (opts.tools or defaultTools()) and constructs a PermissionEngine with projectRoot: cwd.

  4. Builds the static system blocks — OS info, shell, Node version, skawld version, permission mode, sorted tool-name list, and any systemPrompt you supplied. These blocks are rebuilt later when MCP, skills, or subagents register additional tools.

  5. Resolves toolConcurrency once via getDefaultToolConcurrency() so a mid-process env-var change cannot race the scheduler.

  6. Stashes lazy closures for the session store, MCP client, skills, and subagents. None of them fire until you call session().

No constructor field is readonly except opts. Mutating opts after construction does not retroactively change the agent — values are copied into internal state at construction time.

#Options

Every field of AgentOptions exported from @skawld/agent-sdk. Only provider and model are required.

providerBaseProviderRequired

An instance of AnthropicProvider, OpenAIResponsesProvider, OpenAIChatCompletionsProvider, or your own subclass of BaseProvider. The agent never instantiates a provider for you — bring your own keys and transport. See Providers.

modelModelIdRequired

Provider-specific model id, e.g. "claude-opus-4-5" or "gpt-5". ModelId is just string — skawld does no validation here and ships no built-in default. An unknown id surfaces as a provider error on the first turn.

toolsToolRegistryOptional

Tools the agent can call. Defaults to defaultTools() — Read, Write, Edit, Bash, Glob, Grep, and the task tools. MCP tools, the Skill tool, and the Subagent tool are appended onto this same registry on first session(). See Tools.

mcpServersRecord<string, McpServerConfig>Optional

External MCP servers keyed by name. Tools are exposed to the model as mcp__<server>__<tool>. Servers connect lazily on the first session() call and disconnect on close(); a failed connect throws and the memo clears so the next session() retries. See MCP.

permissionsPermissionConfigOptional

Policy that gates every tool call. Shape:

{ mode?: "default" | "acceptEdits" | "yolo", rules?: PermissionRule[], canUseTool?: CanUseTool }

. Defaults to { mode: "default", rules: [] }. Rules are first-match-wins. See Permissions.

sessionStoreSessionStoreOptional

Where sessions persist. When omitted, a SqliteSessionStore at .skawld/sessions.db is constructed on the first session() — supplying your own store means the file is never touched. Use InMemorySessionStore in tests. See Sessions.

cwdstringOptional

Working directory for tools and the default resolution root for configDir. Defaults to process.cwd(). Path rules in the permission engine resolve relative paths against this.

configDirstringOptional

Per-project config directory. Skills are loaded from <configDir>/skills/<name>/SKILL.md; subagents from <configDir>/agents/. Defaults to ".skawld" resolved against cwd. Pass an absolute path to override the location entirely.

systemPromptstringOptional

Additional system-prompt text appended after the default skawld blocks. Set this once at construction rather than threading it through every session.run() call — it’s baked into the static system blocks and benefits from prompt caching.

compactionCompactionStrategyOptional

Override compaction. Defaults to the built-in strategy that fires at 80% of the context window. Pass a function (or strategy object) to customise summarisation; pass undefined only if you have wrapped the agent in a controller that handles ContextLengthError itself. See Compaction.

maxRetriesnumberOptional

Max retries for retryable provider errors (5xx, 429, network drops). Defaults to 5, so up to 6 attempts per provider call. Must be a non-negative integer or the constructor throws.

maxOutputTokensnumberOptional

Max output tokens per turn. Omitted by default. When unset, OpenAI providers drop max_tokens from the wire and the API default applies; the Anthropic provider falls back to 32768 because its Messages API requires the field. Pass an explicit number to override both behaviours. Can also be set per-run via RunOptions.maxOutputTokens.

maxTurnsnumberOptional

Hard cap on assistant turns per session.run(). Unbounded by default (Infinity) — a run continues until the model stops calling tools, errors, or is aborted. Hitting the cap yields a TurnLimitError result event. Set a small number for adversarial or untrusted prompts.

cacheTtl"5m" | "1h"Optional

Prompt-cache TTL hint. "5m" by default — Anthropic’s standard ephemeral cache. Pass "1h" for long-idle sessions whose gaps between turns may exceed 5 minutes; the first write costs 2x base but the prefix stays warm for an hour. Only Anthropic honours this; OpenAI providers silently ignore it.

includePartialMessagesbooleanOptional

Emit partial_assistant events carrying token deltas as they stream. Defaults to false. Turn on only when you render the stream to a UI — every consumer pays the iterator cost.

maxOutputTokens is easy to miss. Leaving it unset means a long-thinking Anthropic turn is capped at 32768 tokens, while an equivalent OpenAI turn is bounded only by the API default. If you have a deterministic output budget across providers, set it explicitly.

There is no additionalDirectories, hooks, or abortSignal field on AgentOptions. Run cancellation lives on the session — pass signal to session.run() or call session.abort().

#configDir resolution and override

The directory used for on-disk configuration — skills and subagents — is computed once at construction:

  • When configDir is omitted, the agent uses path.resolve(cwd, ".skawld").
  • When configDir is a relative path, it is resolved against cwd.
  • When configDir is an absolute path, it is used as-is — handy for multi-tenant servers that keep one config tree per user.

Setting it programmatically is the recommended path; there is no environment variable that overrides it. See Configuration for the on-disk layout.

agent.ts
import path from "node:path";
import { Agent } from "@skawld/agent-sdk";
import { AnthropicProvider } from "@skawld/agent-sdk/providers";

const agent = new Agent({
  provider: new AnthropicProvider(),
  model: "claude-opus-4-5",
  // Use a per-tenant config tree instead of the default ./.skawld.
  configDir: path.join(process.cwd(), ".my-agent"),
});

#Methods

agent.session(input?: { id?: string; meta?: Record<string, unknown> }) → Promise<Session>
async

Open a new session or resume an existing one. Pass id to resume — the store creates a fresh record if no session with that id exists, then loads persisted messages into the providerView. The very first call to session() also kicks off lazy setup: MCP servers connect, the SQLite store is allocated (if one wasn't injected), skills are loaded from disk and the Skill tool is registered, and the Subagent tool is registered.

input.idstringOptional

Existing session id to resume. When omitted, the store assigns a new id and returns an empty session. When provided but unknown, the store still creates a record with that id.

input.metaRecord<string, unknown>Optional

Arbitrary metadata persisted alongside the session record. Shallow- merged on subsequent session.updateMeta(patch) calls.

Lazy setup happens exactly once per agent. Concurrent session() calls share one in-flight MCP connect, one skill load, and one subagent load — they each await the same memoised promise. A failed connect clears its memo, so the next session() call retries from scratch.

agent.close() → Promise<void>
async

Release resources held by the agent. Closes MCP child processes if any connected, and closes the session store only if it was ever allocated. Always await before your process exits.

close() is intentionally narrow:

  • It does not abort in-flight runs. Cancel sessions via session.abort() or the run-scoped AbortSignal first.
  • It is a no-op for resources that were never allocated. An agent that never called session() can safely close() without creating a SQLite file or starting an MCP child process — important for short-lived CLI invocations and tests.
  • It does not tear down the provider client. Providers are caller-owned; if you injected a custom one with its own connection pool, close it yourself.

#Agent lifetime

Construct an Agent once per process and reuse it for every session. Construction is synchronous and cheap (object allocation plus a small system-prompt assembly), but the agent owns durable resources — the SQLite store, MCP child processes, the in-memory skill set, and the per-session registry that SkillTool walks to find the active session. Rebuilding the agent per request would re-open the SQLite file, re-spawn MCP children, and re-scan the skills directory on every request.

ResourceCreated inReleased by
System blocks, permission engine, tool registryConstructor (sync)GC
SQLite session storeFirst session() call (lazy)close()
MCP child processesFirst session() call (lazy, memoised)close()
Loaded skills + Skill tool registrationFirst session() call (lazy, memoised)GC
Subagent registry + Subagent tool registrationFirst session() call (lazy, memoised)GC
Per-session entry in the SkillTool lookup mapEvery session() callAgent GC (bounded by your session-creation rate — call close() for the agent in long-running servers if you spin up many sessions)

The agent registers each opened session into an internal map so the SkillTool can find the active session by id. Entries stay for the agent’s lifetime — memory is bounded by your session-creation rate. For very long-lived processes that create large numbers of sessions, prefer one agent per logical tenant and close() it when the tenant goes idle.