Adapters
Runtime adapters and feature contracts
Overview
Adapters bridge stax's canonical format and runtime-specific configuration. Each adapter understands one runtime family and provides:
- typed authoring-time configuration
- materialization targets and feature metadata
- model and permission configuration for the target runtime or target import surface
Adapters do not directly run agents. They describe how a consumer should materialize, install, or import an artifact for a runtime, workspace, IDE, hosted platform, or cloud control plane.
For runtimes with stable dot-directory contracts such as Claude Code and OpenClaw, adapters SHOULD declare exact file targets and scope (user, project, workspace, local) so consumers can write files in the exact locations the runtime expects.
For hosted or remote agent systems, adapters MAY describe import payloads, API-facing settings, or compatibility targets instead of local filesystem outputs.
Adapter output contract
All adapters MUST compile to the same shape.
interface AdapterConfig {
type: string; // e.g. "claude-code"
runtime: string; // e.g. "claude-code"
adapterVersion: string; // schema version for this adapter payload
runtimeVersionRange?: string; // tested runtime-version range for exact claims
model?: string;
modelParams?: Record<string, unknown>;
importMode?: "filesystem" | "api" | "bundle" | "object-map";
fidelity?: "byte-exact" | "schema-exact" | "best-effort" | "unsupported";
config: Record<string, unknown>; // runtime-specific options
features: AdapterFeatureMap; // translation capabilities
targets?: MaterializationTarget[]; // runtime-native files/settings
}
interface AdapterFeatureMap {
prompt?: "native" | "embedded" | "unsupported";
persona?: "native" | "embedded" | "unsupported";
rules?: "native" | "embedded" | "unsupported";
skills?: "native" | "unsupported";
mcp?: "native" | "translated" | "unsupported";
surfaces?: "native" | "translated" | "unsupported";
secrets?: "native" | "consumer-only";
toolPermissions?: "native" | "translated" | "unsupported";
modelConfig?: "native" | "translated" | "unsupported";
exactMode?: boolean;
}
interface MaterializationTarget {
kind: "file" | "directory" | "setting" | "api" | "bundle" | "object";
path: string;
description?: string;
scope?: "user" | "project" | "workspace" | "local" | "remote" | "account" | "organization";
exact?: boolean;
mediaType?: string;
}path MAY represent a virtual or API-facing target identity for non-filesystem consumers.
Consumers MUST treat unknown config fields as adapter-specific. Consumers SHOULD ignore unknown additive fields within a supported adapterVersion major.
modelParams validation
modelParams is a pass-through bag of model configuration (temperature, top_p, max_tokens, etc.). Adapters and consumers MUST apply the following rules:
- Adapters SHOULD document which
modelParamskeys they support and their valid ranges - Consumers MUST validate
modelParamsvalues against the target model's constraints when known (e.g.,temperatureMUST be a number between 0 and 2 for most models) - Consumers MUST reject
modelParamskeys that contain non-alphanumeric characters other than_and- - Consumers MUST warn on unrecognized
modelParamskeys rather than silently ignoring them - Builders SHOULD validate
modelParamstypes at build time (e.g.,temperatureis a number, not a string)
exactMode semantics
The exactMode field in AdapterFeatureMap indicates whether the adapter supports exact runtime file materialization — writing files byte-for-byte into the runtime's documented directory structure.
exactMode: true— the adapter declares that itstargetslist is complete and covers the runtime's full file contract. Consumers operating inexactmaterialization mode (see 15 — Materialization) SHOULD prefer adapters withexactMode: trueexactMode: falseor omitted — the adapter provides best-effort targets. Consumers operating inexactmode MUST warn or fail ifexactModeis nottrueexactModeis an adapter-level declaration about capability, not a consumer-level mode selector. The consumer's materialization mode (portablevsexact) determines behavior;exactModeon the adapter tells the consumer whether exact materialization is feasible- Adapters claiming
exactMode: trueSHOULD declareruntimeVersionRange. Consumers SHOULD treat missing runtime-version information as a weaker exactness claim and MAY downgrade it tobest-effortfor policy purposes
Adapter versioning
adapterVersion MUST be a valid semver string. Consumers SHOULD interpret it as follows:
- Major version change — breaking schema change in
configorfeatures. Consumers MUST reject unsupported major versions rather than guessing. - Minor version change — new optional fields added. Consumers SHOULD accept and ignore unknown additive fields within the same major.
- Patch version change — bug fixes or documentation clarifications only. No schema changes.
When a consumer encounters an adapterVersion with a higher major version than it supports, it MUST skip that adapter (trying fallbacks if available) or fail with exit code 5.
Adapter config safety
Although config is typed as Record<string, unknown>, consumers MUST apply the following safety rules:
- Consumers MUST NOT execute, eval, or shell-expand values from
config - Consumers MUST validate
configvalues against the expected adapter schema before applying them - Consumers SHOULD reject
configentries that contain shell metacharacters, path traversals, or URLs in fields not expected to contain them - Adapters SHOULD document their
configschema so consumers can validate it - If a consumer cannot validate an adapter's
configschema (e.g., unknown adapter type), it MUST warn and MAY refuse to materialize
Usage
import { defineAgent } from "stax";
import claudeCode from "@stax/claude-code";
export default defineAgent({
name: "backend-engineer",
version: "3.1.0",
adapter: claudeCode({
model: "claude-opus-4-1",
modelParams: { temperature: 0.3 },
permissions: {
allowedTools: ["Read", "Edit", "Bash", "Grep"],
denyRules: ["Bash(rm -rf *)"],
},
}),
});Current 1.0.0 adapter set
@stax/claude-code
claudeCode({
model: "claude-opus-4-1",
modelParams: { temperature: 0.3 },
permissions: {
allowedTools: ["Read", "Edit", "Bash", "Grep", "Write"],
denyRules: ["Bash(rm -rf *)"],
},
});Expected targets:
CLAUDE.mdor.claude/CLAUDE.md.mcp.json.claude/settings.json.claude/skills/
For Claude Code, adapters SHOULD support an exact materialization mode that writes files in the documented Claude scopes described in 17 — Runtime File Contracts and 18 — Adapter: @stax/claude-code.
@stax/claude MAY exist as a compatibility alias, but @stax/claude-code is the canonical package name in spec 1.0.0.
@stax/codex
codex({
model: "gpt-5-codex",
approval: "on-request",
sandbox: "workspace-write",
});Expected targets:
AGENTS.mdat the selected project or user scope.codex/config.toml- repository or user skill locations described in 20 — Adapter:
@stax/codex
@stax/openclaw
For OpenClaw agent workspaces.
Expected targets:
<workspace>/AGENTS.md<workspace>/SOUL.md<workspace>/TOOLS.md<workspace>/IDENTITY.md<workspace>/USER.md<workspace>/HEARTBEAT.md<workspace>/BOOTSTRAP.md<workspace>/skills/<workspace>/memory/
OpenClaw adapters SHOULD preserve exact Markdown bytes for mapped surface files and MUST NOT merge them into a single prompt document. See 19 — Adapter: @stax/openclaw.
@stax/generic
generic({
model: "any-model-id",
runtime: "my-custom-runtime",
importMode: "api",
fidelity: "schema-exact",
config: {},
});@stax/generic is the escape hatch for remote, hosted, and cloud consumers that do not yet have a first-class exact adapter contract.
@stax/generic MUST declare:
importModefidelity- at least one target
Generic adapters SHOULD also declare a schema or API reference in config.
Consumers MUST treat generic adapters as non-certifiable for exactness unless a stricter adapter-specific contract exists.
@stax/cursor
cursor({
model: "claude-sonnet-4",
writeRules: true,
writeMcp: true,
writeSkills: true,
});Expected targets:
AGENTS.mdat project root.cursor/rules/*.mdcwith trigger frontmatter.cursor/mcp.json.cursor/skills/
Cursor adapters SHOULD translate canonical rules to MDC frontmatter format with alwaysApply, globs, and description fields. See 33 — Adapter: @stax/cursor.
@stax/github-copilot
githubCopilot({
model: "claude-sonnet-4",
writeInstructions: true,
writePathInstructions: true,
writeMcp: true,
writeSkills: true,
});Expected targets:
.github/copilot-instructions.mdfor repo-wide instructions.github/instructions/*.instructions.mdfor path-scoped rules.vscode/mcp.jsonwithserversroot key.github/skills/
Copilot adapters SHOULD translate glob-scoped rules to .instructions.md files with applyTo frontmatter. See 34 — Adapter: @stax/github-copilot.
@stax/windsurf
windsurf({
model: "claude-sonnet-4",
writeRules: true,
writeInstructions: true,
});Expected targets:
AGENTS.mdat project root.windsurf/rules/*.mdwith trigger frontmatter.windsurf/workflows/*.mdfor skills (translated)
Windsurf adapters SHOULD translate canonical rules to Windsurf trigger frontmatter (always_on, model_decision, glob, manual). MCP is user-scoped only. See 35 — Adapter: @stax/windsurf.
@stax/opencode
opencode({
model: "anthropic/claude-sonnet-4-20250514",
agent: {
build: { model: "anthropic/claude-sonnet-4-20250514" },
},
});Expected targets:
AGENTS.mdfor instructionsopencode.jsoncfor config, model settings, and MCP.opencode/skill/**/SKILL.mdfor skills (native).opencode/command/*.mdfor commands (translated)
OpenCode adapters SHOULD write opencode.jsonc using JSONC format with mcp key for MCP servers (type: "local" with command as string array). See 36 — Adapter: @stax/opencode.
Illustrative future adapters
Hosted-platform adapters are directionally in scope for stax, but they are not defined as runtime-specific normative 1.0.0 adapters today. Until those contracts are specified, implementations SHOULD treat them as future targets or custom adapters rather than as current exact-conformance claims.
Feature value semantics
The AdapterFeatureMap uses three translation strategies. Their meanings are:
| Value | Meaning | When to use |
|---|---|---|
native | The runtime has a first-class mechanism for this feature. The consumer writes it to a dedicated file or setting. | Claude Code skills → .claude/skills/, OpenClaw surfaces → named workspace files |
embedded | The runtime has no dedicated mechanism. The consumer embeds the content into another surface, typically the prompt or instruction file. | Persona → embedded as text in CLAUDE.md |
translated | The runtime has a mechanism, but the canonical format requires structural transformation (not just file placement). | MCP canonical JSON → .mcp.json with different key names; surfaces → renamed workspace files |
unsupported | The runtime cannot represent this feature at all. The consumer MUST warn and MAY omit it. | Skills on a runtime with no skill support |
consumer-only | The feature is meaningful but resolution happens entirely at the consumer layer, not in the adapter. | Secrets — the adapter declares needs, the consumer resolves values |
Adapter authors MUST use embedded when content is inlined into a prompt-like file, and translated when content is written to a dedicated file but requires structural changes.
Compatibility behavior
Consumers SHOULD:
- check
type,runtime, andadapterVersion - verify that required features can be materialized
- warn on lossy translations
- try
adapterFallbackwhen the primary adapter is unsupported
A consumer MUST fail clearly if no supported adapter can represent the artifact.
Lossy translation policy
If a runtime cannot natively represent a canonical feature, a consumer MAY:
- embed it into prompt text
- surface a warning and omit it
- fail materialization if the feature is required by policy
Consumers SHOULD NOT silently drop material features such as rules, MCP servers, or tool permissions.
Creating custom adapters
import { defineAdapter } from "stax";
export default defineAdapter<MyRuntimeOptions>((options) => ({
type: "my-runtime",
runtime: "my-runtime",
adapterVersion: "1.0.0",
model: options.model,
config: {
sandbox: options.sandbox,
},
features: {
prompt: "embedded",
rules: "embedded",
mcp: "unsupported",
},
targets: [{ kind: "file", path: "MY_RUNTIME.md", description: "Instructions" }],
}));Custom adapters MAY be published under any npm package name.