Axernel SDK
Official TypeScript and Python clients for the Axernel API. Configure agents, open sessions, create runs, stream events, and read results and artifacts.
Install
Not published yet. The packages are not on npm or PyPI today. They ship with the technical preview. Join the waitlist for access.
npm install @axernel/sdkpip install axernelRequires Node.js 20 or later. Ships as ES modules with TypeScript types.Requires Python 3.10 or later. Includes a synchronous and an asynchronous client, with Pydantic models generated from the OpenAPI contract.
Configure the client
Set the API endpoint and a platform API token. The client reads both from the environment.
export AXERNEL_BASE_URL=http://localhost:8080
export AXERNEL_API_KEY=axk_...import { Axernel } from "@axernel/sdk"
const axernel = new Axernel()from axernel import Axernel, models
axernel = Axernel()Constructor options override the environment.Constructor arguments override the environment.
const axernel = new Axernel({
baseUrl: "https://axernel.example.com",
apiKey: process.env.MY_AXERNEL_TOKEN,
timeoutMs: 60_000,
maxRetries: 2,
})axernel = Axernel(
base_url="https://axernel.example.com",
api_key="axk_...",
timeout=60,
max_retries=2,
)The client is a context manager, and AsyncAxernel exposes the same resource names.
from axernel import AsyncAxernel
async with AsyncAxernel() as axernel:
run = await axernel.runs.get("run_id")
async for event in axernel.runs.events(run.id):
print(event.type.value)Quickstart
Give an agent a task that needs a real tool, and get back a typed answer. The agent below runs a shell command in its sandbox to hash a string. The whole flow is six calls.
1. Create a project and pick the platform defaults
A project owns everything else. The model provider and the sandbox template come from the platform catalog, so there is nothing to configure.
import { Axernel } from "@axernel/sdk"
const axernel = new Axernel()
const project = await axernel.projects.create({ name: "SDK quickstart" })
const provider = await axernel.modelProviders.getPlatform("openrouter-deepseek")
const template = await axernel.templates.getPlatform("general-purpose")from axernel import Axernel, models
axernel = Axernel()
project = axernel.projects.create(models.CreateProjectRequest(name="SDK quickstart"))
provider = axernel.model_providers.get_platform("openrouter-deepseek")
template = axernel.templates.get_platform("general-purpose")2. Define the agent
An agent is instructions plus two JSON Schema contracts: what a task must look like going in, and what the answer must look like coming out.
const agent = await axernel.agents.create(project.id, {
name: "Hasher",
templateId: template.id,
configuration: {
harness: "opencode",
modelProviderId: provider.id,
instructions: "Use the shell to do what the task asks. Submit only the requested result.",
contracts: {
input: {
schema: {
type: "object",
properties: { task: { type: "string" } },
required: ["task"],
additionalProperties: false,
},
},
output: {
schema: {
type: "object",
properties: { digest: { type: "string" } },
required: ["digest"],
additionalProperties: false,
},
},
},
limits: { timeoutSeconds: 900, maxSteps: 100 },
artifacts: [],
},
})agent = axernel.agents.create(
project.id,
models.CreateAgentRequest(
name="Hasher",
template_id=template.id,
configuration=models.AgentConfiguration(
harness=models.Harness.opencode,
model_provider_id=provider.id,
instructions="Use the shell to do what the task asks. Submit only the requested result.",
contracts=models.Contracts(
input=models.Contract(
schema_={
"type": "object",
"properties": {"task": {"type": "string"}},
"required": ["task"],
"additionalProperties": False,
}
),
output=models.Contract(
schema_={
"type": "object",
"properties": {"digest": {"type": "string"}},
"required": ["digest"],
"additionalProperties": False,
}
),
),
limits=models.AgentLimits(timeout_seconds=900, max_steps=100),
artifacts=[],
),
),
)3. Open a session and send the task
A session pins this version of the agent. A run is one task inside it.
const session = await axernel.sessions.create(project.id, {
agentId: agent.id,
checkpointingEnabled: true,
})
const run = await axernel.runs.create(session.id, {
input: {
data: { task: "Compute the SHA-256 of the text 'hello axernel' with a shell command." },
},
})session = axernel.sessions.create(
project.id,
models.CreateSessionRequest(agent_id=agent.id, checkpointing_enabled=True),
)
run = axernel.runs.create(
session.id,
models.CreateRunRequest(
input=models.RunInput(
data=models.JSONValue(
{"task": "Compute the SHA-256 of the text 'hello axernel' with a shell command."}
)
)
),
)4. Read the result
runs.result() waits for the run to finish and returns the value the agent submitted, already validated against the output schema.runs.result() waits for the run to finish and returns the value the agent submitted, already validated against the output schema.
const result = await axernel.runs.result<{ digest: string }>(run.id)
console.log(result.digest)result = axernel.runs.result(run.id)
print(result["digest"])306d5032cf03e9480b0a17523bdd7f69c305e7102471c251e7d265e8d9d26947Axernel prepared a sandbox, started the harness with your instructions, let the model call the shell, checked the answer against the contract, and returned it. To watch that happen, stream run events while the run is in flight. To keep going, send another run to the same session: it reuses the sandbox and the conversation.
Resource model
An application normally creates resources in this order.
Project
├── Secrets and uploaded files
└── Agent
├── Template
├── Model provider
├── Instructions
├── Input and output schemas
├── MCP and environment credential bindings
└── Execution limits and artifact requirements
Agent → Session → Run → Events, result, usage and artifacts- A project owns agents, secrets, files, and sessions.
- A template defines the sandbox image, available environment tools, preparation budget, maximum sandbox lifetime, and idle retention.
- A model provider defines the API protocol, endpoint, model, pricing, and private token.
- An agent is a reusable, revisioned configuration.
- A session pins one agent revision. Later agent edits do not alter existing sessions.
- A run supplies task-specific input to that pinned contract.
Projects
const project = await axernel.projects.create({
name: "Pull request automation",
description: "Agents that inspect and update repositories",
})project = axernel.projects.create(
models.CreateProjectRequest(
name="Pull request automation",
description="Agents that update repositories",
)
)Projects can also be listed, iterated, read, and updated.
const page = await axernel.projects.list({ page: 1, pageSize: 50 })
for await (const project of axernel.projects.iterate()) {
console.log(project.id, project.name)
}page = axernel.projects.list(page=1, page_size=50)
for project in axernel.projects.iterate():
print(project.id, project.name)Model providers and templates
Resolve platform-managed resources by their stable installation keys. These endpoints return active platform-owned resources only. Every returned resource has managedBy: "platform" and editable: false.Resolve platform-managed resources by their stable installation keys. These endpoints return active platform-owned resources only. Every returned resource has managed_by=platform and editable=False.
const provider = await axernel.modelProviders.getPlatform("openrouter-deepseek")
const template = await axernel.templates.getPlatform("general-purpose")provider = axernel.model_providers.get_platform("openrouter-deepseek")
template = axernel.templates.get_platform("general-purpose")Use listPlatform() or iteratePlatform() to discover the keys configured by an installation. list() and iterate() return the combined catalog of platform resources and resources owned by the authenticated organization.Use list_platform() or iterate_platform() to discover the keys configured by an installation. list() and iterate() return the combined catalog of platform resources and resources owned by the authenticated organization.
Bring your own provider
An organization can register its own OpenAI-compatible provider. The token is accepted on creation or update and is never returned.
const provider = await axernel.modelProviders.create({
name: "Company inference gateway",
protocol: "openai_compatible",
baseUrl: "https://models.example.com/v1",
modelName: "company/agent-model",
token: process.env.MODEL_PROVIDER_TOKEN!,
pricing: {
inputUsdPerMillion: 0.25,
outputUsdPerMillion: 1.0,
reasoningUsdPerMillion: 1.0,
cacheReadUsdPerMillion: 0.025,
cacheWriteUsdPerMillion: 0.25,
},
})provider = axernel.model_providers.create(
models.CreateModelProviderRequest(
name="Company inference gateway",
protocol=models.ModelProviderProtocol.openai_compatible,
base_url="https://models.example.com/v1",
model_name="company/agent-model",
token=os.environ["MODEL_PROVIDER_TOKEN"],
pricing=models.ModelPricing(
input_usd_per_million=0.25,
output_usd_per_million=1.0,
reasoning_usd_per_million=1.0,
cache_read_usd_per_million=0.025,
cache_write_usd_per_million=0.25,
),
)
)Custom sandbox image
Create a template when the agent needs its own image.
const template = await axernel.templates.create({
name: "Coding environment",
image_ref: "registry.example.com/agents/coding@sha256:<digest>",
environmentTools: [
{ name: "git", description: "Git command-line client" },
{ name: "gh", description: "GitHub command-line client" },
{ name: "node", description: "Node.js runtime" },
],
preparationTimeoutSeconds: 600,
sandboxMaxLifetimeSeconds: 86_400,
sandboxIdleTimeoutSeconds: 1_800,
})template = axernel.templates.create(
models.CreateTemplateRequest(
name="Coding environment",
image_ref="registry.example.com/agents/coding@sha256:<digest>",
environment_tools=[
models.EnvironmentTool(name="git", description="Git command-line client"),
models.EnvironmentTool(name="gh", description="GitHub command-line client"),
models.EnvironmentTool(name="node", description="Node.js runtime"),
],
preparation_timeout_seconds=600,
sandbox_max_lifetime_seconds=86_400,
sandbox_idle_timeout_seconds=1_800,
)
)Secrets
Secrets belong to a project. Values are accepted by create and update requests but are never returned by get or list operations. Responses expose only metadata and key names.
const githubSecret = await axernel.secrets.create(project.id, {
name: "GitHub service account",
description: "Repository access for coding agents",
data: {
token: process.env.GITHUB_TOKEN!,
},
})
console.log(githubSecret.keys) // ["token"]import os
github_secret = axernel.secrets.create(
project.id,
models.CreateSecretRequest(
name="GitHub service account",
description="Repository access for coding agents",
data=models.SecretData({"token": os.environ["GITHUB_TOKEN"]}),
),
)Rotate a value with an update. Delete removes a secret that is no longer referenced.
await axernel.secrets.update(project.id, githubSecret.id, {
data: { token: process.env.ROTATED_GITHUB_TOKEN! },
})
await axernel.secrets.delete(project.id, githubSecret.id)axernel.secrets.update(
project.id,
github_secret.id,
models.UpdateSecretRequest(
data=models.SecretData({"token": os.environ["ROTATED_GITHUB_TOKEN"]}),
),
)
axernel.secrets.delete(project.id, github_secret.id)Do not place secret values in agent instructions, input data, metadata, or MCP URLs. Agent configuration stores a secret ID and key. The runtime resolves the value when it admits a run.
Contracts
Contracts are JSON Schema objects. input.data must match the input schema before the run is queued. The agent's submitted response.value must match the output schema before the run can complete.Contracts are JSON Schema objects. run.input.data must match the input schema before the run is queued. The agent's submitted value must match the output schema before the run can complete.
const inputSchema = {
type: "object",
properties: {
repository: { type: "string" },
issueNumber: { type: "integer", minimum: 1 },
task: { type: "string" },
},
required: ["repository", "issueNumber", "task"],
additionalProperties: false,
}
const outputSchema = {
type: "object",
properties: {
pullRequestUrl: { type: "string" },
summary: { type: "string" },
},
required: ["pullRequestUrl", "summary"],
additionalProperties: false,
}input_contract = models.Contract(
schema_={
"type": "object",
"properties": {
"repository": {"type": "string"},
"issueNumber": {"type": "integer", "minimum": 1},
"task": {"type": "string"},
},
"required": ["repository", "issueNumber", "task"],
"additionalProperties": False,
}
)
output_contract = models.Contract(
schema_={
"type": "object",
"properties": {
"pullRequestUrl": {"type": "string"},
"summary": {"type": "string"},
},
"required": ["pullRequestUrl", "summary"],
"additionalProperties": False,
}
)The schema profile and request limits supported by a deployment are available from the capabilities endpoint.
const capabilities = await axernel.system.capabilities()
console.log(capabilities.schemaProfile)capabilities = axernel.system.capabilities()
print(capabilities.schema_profile)Agents
This agent receives GitHub access as GH_TOKEN from the project secret, and uses the same secret to authenticate a remote MCP server.
const agent = await axernel.agents.create(project.id, {
name: "Issue fixer",
templateId: template.id,
configuration: {
harness: "opencode",
modelProviderId: provider.id,
instructions: [
"Read the requested issue and repository carefully.",
"Make the smallest correct change and run relevant validation.",
"Open a pull request and submit its URL with a concise summary.",
].join("\n"),
contracts: {
input: { schema: inputSchema },
output: { schema: outputSchema },
},
limits: {
timeoutSeconds: 1_800,
maxSteps: 200,
},
environmentCredentialBindings: {
GH_TOKEN: {
secret: { secretId: githubSecret.id, key: "token" },
},
},
mcpServers: {
github: {
remote: {
url: "https://mcp.example.com/github",
authentication: {
type: "bearer",
credential: {
secret: { secretId: githubSecret.id, key: "token" },
},
},
},
},
},
artifacts: [],
},
})github_credential = models.CredentialSource(
secret=models.SecretCredentialSource(
secret_id=github_secret.id,
key="token",
)
)
agent = axernel.agents.create(
project.id,
models.CreateAgentRequest(
name="Issue fixer",
template_id=template.id,
configuration=models.AgentConfiguration(
harness=models.Harness.opencode,
model_provider_id=provider.id,
instructions=(
"Read the requested issue, make the smallest correct change, "
"run relevant validation, open a pull request, and submit its URL."
),
contracts=models.Contracts(
input=input_contract,
output=output_contract,
),
limits=models.AgentLimits(
timeout_seconds=1800,
max_steps=200,
),
environment_credential_bindings={
"GH_TOKEN": github_credential,
},
mcp_servers={
"github": models.MCPServer(
remote=models.MCPRemote(
url="https://mcp.example.com/github",
authentication=models.MCPAuthentication(
type=models.MCPAuthenticationType.bearer,
credential=github_credential,
),
)
)
},
artifacts=[],
),
),
)Configuration fields
| Field | Purpose |
|---|---|
harness | Harness implementation used to execute turns. V1 supports opencode. |
modelProviderId | Organization or platform model provider used by the harness. |
instructions | Reusable behavior and operating instructions for every run. |
contracts.input.schema | Validates run.input.data before admission. |
contracts.output.schema | Validates the value submitted by the agent. |
limits | Default execution timeout and maximum model steps. |
mcpServers | Remote and stdio MCP servers configured before the prompt begins. |
environmentCredentialBindings | Secrets or per-run values injected into harness and tool environment variables. |
artifacts | Files collected after the agent submits an accepted result. |
| Field | Purpose |
|---|---|
harness | Harness implementation used to execute turns. V1 supports opencode. |
model_provider_id | Organization or platform model provider used by the harness. |
instructions | Reusable behavior and operating instructions for every run. |
contracts.input | Validates run.input.data before admission. |
contracts.output | Validates the value submitted by the agent. |
limits | Default execution timeout and maximum model steps. |
mcp_servers | Remote and stdio MCP servers configured before the prompt begins. |
environment_credential_bindings | Secrets or per-run values injected into harness and tool environment variables. |
artifacts | Files collected after the agent submits an accepted result. |
Revisions
Agent updates create a new revision. The expected revision prevents overwriting a concurrent update.
const updated = await axernel.agents.update(
project.id,
agent.id,
{
configuration: {
...agent.configuration,
instructions: `${agent.configuration.instructions}\nPrefer focused tests.`,
},
},
{ expectedRevision: agent.revision },
)updated = axernel.agents.update(
project.id,
agent.id,
models.UpdateAgentRequest(configuration=agent.configuration),
expected_revision=agent.revision,
)MCP servers
MCP servers are part of the agent revision. A remote server declares standard authentication and additional request headers separately. Stdio environment bindings receive the credential value unchanged.
Credential from a project secret
Use a project secret for a reusable credential.
const mcpServers = {
github: {
remote: {
url: "https://mcp.example.com/github",
authentication: {
type: "bearer",
credential: {
secret: { secretId: githubSecret.id, key: "token" },
},
},
},
},
}github_mcp = models.MCPServer(
remote=models.MCPRemote(
url="https://mcp.example.com/github",
authentication=models.MCPAuthentication(
type=models.MCPAuthenticationType.bearer,
credential=models.CredentialSource(
secret=models.SecretCredentialSource(
secret_id=github_secret.id,
key="token",
),
),
),
)
)Credential supplied with every run
Require a fresh credential with every run when the calling application owns the credential lifecycle.
const mcpServers = {
github: {
remote: {
url: "https://mcp.example.com/github",
authentication: {
type: "bearer",
credential: { runRequestCredential: "githubToken" },
},
},
},
}github_mcp = models.MCPServer(
remote=models.MCPRemote(
url="https://mcp.example.com/github",
authentication=models.MCPAuthentication(
type=models.MCPAuthenticationType.bearer,
credential=models.CredentialSource(
run_request_credential="githubToken",
),
),
)
)Exactly one of secret or runRequestCredential is allowed per credential source. Bearer authentication always sends Authorization: Bearer <value>. API-key authentication additionally requires headerName. Use requestHeaders for dynamic custom headers.Exactly one of secret or run_request_credential is allowed per credential source. Bearer authentication always sends Authorization: Bearer <value>. API-key authentication additionally requires header_name. Use request_headers for dynamic custom headers.
const remote = {
url: "https://mcp.example.com/tools",
authentication: {
type: "api_key" as const,
headerName: "X-API-Key",
credential: { runRequestCredential: "toolsApiKey" },
},
requestHeaders: [
{
name: "X-Workspace-ID",
value: { secret: { secretId: workspaceSecret.id, key: "id" } },
},
],
}remote = models.MCPRemote(
url="https://mcp.example.com/tools",
authentication=models.MCPAuthentication(
type=models.MCPAuthenticationType.api_key,
header_name="X-API-Key",
credential=models.CredentialSource(run_request_credential="toolsApiKey"),
),
request_headers=[
models.MCPRequestHeader(
name="X-Workspace-ID",
value=models.CredentialSource(
secret=models.SecretCredentialSource(
secret_id=workspace_secret.id,
key="id",
),
),
)
],
)Stdio servers
A stdio MCP server must already be installed in the template image.
const mcpServers = {
tools: {
stdio: {
command: [
"env",
"TOOLS_DEPLOYMENT_URL=https://tools.example.com",
"node",
"/opt/tools-mcp/index.js",
],
environmentBindings: {
TOOLS_TOKEN: { runRequestCredential: "toolsToken" },
},
},
},
}tools_mcp = models.MCPServer(
stdio=models.MCPStdio(
command=[
"env",
"TOOLS_DEPLOYMENT_URL=https://tools.example.com",
"node",
"/opt/tools-mcp/index.js",
],
environment_bindings={
"TOOLS_TOKEN": models.CredentialSource(
run_request_credential="toolsToken"
)
},
)
)Lifecycle for each run
Run admission
→ validate every required binding
→ resolve project-secret values and request-supplied values
→ send values separately from the persisted assignment
→ supervisor configures remote and stdio MCP servers in the harness
→ require every configured MCP server to connect before prompting
→ execute the turnCredential values are not written into agent configuration, instructions, run input, events, or checkpoints.
Rules
- Each server selects exactly one transport:
remoteorstdio. - Remote URLs cannot contain embedded credentials. Native MCP OAuth is disabled because runs are asynchronous. Supply an explicit credential binding instead.
- Stdio commands must already exist in the template image. The platform does not install an MCP package during a run.
- Environment bindings are for secret values. Put fixed, non-secret configuration in the image, a wrapper executable, or an
envcommand argument as shown above. - Server names may contain letters, digits, underscores, and hyphens.
resultis reserved for the platform result-submission MCP. - Any configured MCP server that cannot connect fails preparation before the model receives the run prompt.
Files
Uploaded files are immutable, belong to a project, and can be attached to a session or to one run.
import { readFile } from "node:fs/promises"
const specification = await axernel.files.upload(project.id, {
data: await readFile("./openapi.yaml"),
name: "openapi.yaml",
mediaType: "application/yaml",
})
const reproduction = await axernel.files.upload(project.id, {
data: await readFile("./issue-285-reproduction.txt"),
name: "issue-285-reproduction.txt",
mediaType: "text/plain",
})specification = axernel.files.upload(
project.id,
"openapi.yaml",
media_type="application/yaml",
)
reproduction = axernel.files.upload(
project.id,
"issue-285-reproduction.txt",
media_type="text/plain",
)upload() accepts a path, bytes, or a binary stream.
Sessions
Session creation pins the current agent revision, template settings, model-provider settings, contracts, MCP configuration, limits, and artifact requirements. It does not create a run or a sandbox.
const session = await axernel.sessions.create(
project.id,
{
agentId: agent.id,
checkpointingEnabled: true,
attachments: [
{
fileId: specification.id,
description: "API specification to use throughout the conversation",
},
],
metadata: { externalConversationId: "conversation_42" },
},
{ idempotencyKey: "create-conversation-42" },
)session = axernel.sessions.create(
project.id,
models.CreateSessionRequest(
agent_id=agent.id,
checkpointing_enabled=True,
attachments=[
models.Attachment(
file_id=specification.id,
description="API specification for this conversation",
)
],
),
idempotency_key="create-conversation-42",
)Use the same session for sequential runs that should share the retained sandbox and the harness conversation. Close the session when it will receive no more runs.
await axernel.sessions.close(project.id, session.id)axernel.sessions.close(project.id, session.id)Runs
Task-specific content belongs in input.data. Run attachments are available only to that run.Task-specific content belongs in input.data. Run attachments are available only to that run.
const run = await axernel.runs.create(
session.id,
{
input: {
data: {
repository: "https://github.com/acme/backend",
issueNumber: 285,
task: "Fix the issue and open a pull request.",
},
attachments: [
{
fileId: reproduction.id,
description: "Issue reproduction details needed only for this run",
},
],
},
timeoutSeconds: 1_500,
maxSteps: 150,
metadata: { jobId: "job_285" },
},
{ idempotencyKey: "job-285-attempt-1" },
)request = models.CreateRunRequest(
input=models.RunInput(
data=models.JSONValue(
{
"repository": "https://github.com/acme/backend",
"issueNumber": 285,
"task": "Fix the issue and open a pull request.",
}
),
attachments=[
models.Attachment(
file_id=reproduction.id,
description="Issue reproduction details needed only for this run",
)
],
),
timeout_seconds=1500,
max_steps=150,
)
run = axernel.runs.create(
session.id,
request,
idempotency_key="job-285-attempt-1",
)A run moves through queued, preparing, running and finalizing, and ends as completed, failed or timed_out.
Run credentials
When the agent requires runRequestCredential bindings, provide their values through the credentials option.When the agent requires run_request_credential bindings, provide their values through the credentials argument.
const run = await axernel.runs.create(session.id, request, {
idempotencyKey: "job-285-attempt-1",
credentials: {
githubToken: process.env.GITHUB_TOKEN!,
toolsToken: process.env.TOOLS_TOKEN!,
},
})run = axernel.runs.create(
session.id,
request,
idempotency_key="job-285-attempt-1",
credentials={
"githubToken": os.environ["GITHUB_TOKEN"],
"toolsToken": os.environ["TOOLS_TOKEN"],
},
)The SDK serializes these values into the X-Run-Credentials header. They are separate from the run body and are not exposed as run input or metadata. Missing credentials required by the pinned agent are rejected before the run is queued.
Events and results
Stream progress over server-sent events.
for await (const event of axernel.runs.events(run.id)) {
switch (event.type) {
case "run.status":
case "message.delta":
case "tool.activity":
case "harness.status":
console.log(event.type, event.data)
break
case "stream.error":
console.error(event.data)
break
}
}for event in axernel.runs.events(run.id):
if event.type.value == "stream.error":
print("stream error", event.data)
else:
# run.status, message.delta, tool.activity, harness.status
print(event.type.value, event.data)Resume after the last persisted event sequence.
for await (const event of axernel.runs.events(run.id, { lastEventId: 42 })) {
// Persist event.sequence after processing each harness event.
}for event in axernel.runs.events(run.id, last_event_id=42):
# Persist event.sequence after processing each harness event.
passstream.end closes the event subscription. It does not establish run success. Read the authoritative run afterward.
const terminalRun = await axernel.runs.wait(run.id, {
timeoutMs: 30 * 60 * 1_000,
pollIntervalMs: 1_000,
})
if (terminalRun.status === "completed") {
console.log(terminalRun.response?.value)
} else {
console.error(terminalRun.error)
}terminal_run = axernel.runs.wait(run.id, timeout=30 * 60, poll_interval=1)
if terminal_run.status == models.RunStatus.completed:
print(terminal_run.response.value)
else:
print(terminal_run.error)When only the accepted structured value matters, ask for the result.
const result = await axernel.runs.result<{
pullRequestUrl: string
summary: string
}>(run.id)
console.log(result.pullRequestUrl)result = axernel.runs.result(run.id)
print(result["pullRequestUrl"])runs.result<T>() throws RunFailedError for failed or timed-out runs. The complete terminal run remains available as error.run.runs.result() raises RunFailedError for failed or timed-out runs. The complete terminal run remains available as error.run.
Replies
An agent can complete a turn by asking for required caller input. runs.result<T>() raises RunNeedsInputError. Its run.response.questions contains the stable question IDs and prompts. Answer every question in one linked reply run.An agent can complete a turn by asking for required caller input. runs.result() raises RunNeedsInputError. Its run.response.questions contains the stable question IDs and prompts. Answer every question in one linked reply run.
import { RunNeedsInputError } from "@axernel/sdk"
try {
const result = await axernel.runs.result(run.id)
console.log(result)
} catch (error) {
if (!(error instanceof RunNeedsInputError)) throw error
const reply = await axernel.runs.reply(
error.run.sessionId,
error.run.id,
{ repository_url: "https://github.com/acme/backend" },
{ timeoutSeconds: 600, maxSteps: 100 },
)
console.log(await axernel.runs.result(reply.id))
}from axernel import RunNeedsInputError
try:
result = axernel.runs.result(run.id)
except RunNeedsInputError as error:
reply = axernel.runs.reply(
error.run.session_id,
error.run.id,
{"repository_url": "https://github.com/acme/backend"},
timeout_seconds=600,
max_steps=100,
)
result = axernel.runs.result(reply.id)The reply continues the same harness conversation. A failed or timed-out reply leaves the original questions pending, so the caller can submit another linked reply.
Before sending, runs.reply() reads the source run, rejects missing or unknown answer keys, and orders answers to match the agent's questions. The API repeats this validation under the session lock.
Artifacts
Artifact requirements belong to the agent configuration.
const artifacts = [
{
name: "report",
fileName: "report.pdf",
mediaType: "application/pdf",
required: true,
},
]artifacts = [
models.ArtifactRequirement(
name="report",
file_name="report.pdf",
media_type="application/pdf",
required=True,
)
]Assign this array to configuration.artifacts. After a completed run, inspect run.artifactOutputs.Assign this list to configuration.artifacts. After a completed run, inspect run.artifact_outputs.
const completed = await axernel.runs.wait(run.id)
for (const output of completed.artifactOutputs) {
if (output.status === "available" && output.artifactId) {
const metadata = await axernel.artifacts.get(output.artifactId)
const fileName = metadata.path.split("/").at(-1) ?? metadata.name
await axernel.artifacts.downloadTo(output.artifactId, `./${fileName}`)
}
}completed = axernel.runs.wait(run.id)
for output in completed.artifact_outputs:
if output.status == models.ArtifactOutputStatus.available and output.artifact_id:
metadata = axernel.artifacts.get(output.artifact_id)
file_name = metadata.path.split("/")[-1] or metadata.name
axernel.artifacts.download_to(output.artifact_id, f"./{file_name}")The platform collects configured artifacts only after successful result submission. Artifact publication status is separate from the structured result and from checkpoint status.
Authentication
Server applications use a platform API token, as shown in the client configuration. Two other flows exist for applications that sign users in.
Browser applications start GitHub OAuth by navigating to the URL the SDK builds. The Axernel server handles the callback, sets browser cookies, and redirects to the configured UI.
window.location.assign(axernel.auth.githubOAuthUrl())Password-based applications can obtain tokens through the API and create an authenticated client.
const unauthenticated = new Axernel({ baseUrl: "https://axernel.example.com" })
const auth = await unauthenticated.auth.login({
email: "[email protected]",
password: process.env.AXERNEL_PASSWORD!,
})
const authenticated = new Axernel({
baseUrl: "https://axernel.example.com",
apiKey: auth.token,
})unauthenticated = Axernel(base_url="https://axernel.example.com")
auth = unauthenticated.auth.login(
models.LoginRequest(
email="[email protected]",
password=os.environ["AXERNEL_PASSWORD"],
)
)
authenticated = Axernel(
base_url="https://axernel.example.com",
api_key=auth.token,
)The SDK also exposes signup, refresh, logout, and currentUser.The SDK also exposes signup, refresh, logout, current_user, and github_oauth_url.
Pagination
List methods return the API's page envelope. iterate methods fetch subsequent pages lazily.List methods return the API's page envelope. iterate methods fetch subsequent pages lazily.
const firstPage = await axernel.agents.list(project.id, {
page: 1,
pageSize: 50,
})
for await (const agent of axernel.agents.iterate(project.id)) {
console.log(agent.id)
}page = axernel.agents.list(project.id, page=1, page_size=50)
for agent in axernel.agents.iterate(project.id):
print(agent.id)Pagination is available for model providers, projects, secrets, files, templates, agents, sessions, and runs.
Idempotency and retries
Reads, and requests protected by an idempotency key, use bounded retries for network failures, 408, 429, and common 5xx responses.
Session and run creation generate an idempotency key automatically. Supply your own key when it must survive an application restart. A repeated key with the same request returns the original admission response. Changing the request or the run credentials while reusing the key returns a conflict.
The SDK never replays an agent execution on its own. Retries apply to the idempotent HTTP admission request.
Errors
API failures raise typed errors. All of them extend AxernelError.
| Error | Raised when |
|---|---|
AuthenticationError | 401. The token is missing, invalid or expired. |
PermissionDeniedError | 403. The token cannot access the resource. |
NotFoundError | 404. The resource does not exist in this organization. |
ConflictError | 409, 412 or 428. A revision or idempotency precondition failed. |
ValidationError | 400, 413 or 422. The request was rejected. |
RateLimitError | 429. |
ServerError | Any 5xx response. |
APIConnectionError | The request never reached the API. |
APITimeoutError | The request exceeded the client timeout. |
ConfigurationError | The client is missing a base URL or is otherwise misconfigured. |
RunFailedError | runs.result() on a failed or timed-out run. Carries run. |
RunNeedsInputError | runs.result() on a run waiting for answers. Carries run. |
RunWaitTimeoutError | runs.wait() gave up before the run reached a terminal status. |
Every APIError exposes status, code, requestId, details, and response headers.Every APIError exposes status_code, code, request_id, details, and response headers.
import { APIError } from "@axernel/sdk"
try {
await axernel.agents.get(project.id, "missing")
} catch (error) {
if (error instanceof APIError) {
console.error(error.status, error.code, error.requestId, error.details)
}
}from axernel import APIError
try:
axernel.agents.get(project.id, "missing")
except APIError as error:
print(error.status_code, error.code, error.request_id, error.details)Resources
Both clients use the same resource names and cover the same operations.
| Resource | Operations |
|---|---|
system | health, readiness, publicConfig, capabilities, openAPI |
auth | signup, login, refresh, logout, currentUser, githubOAuthUrl |
modelProviders | create, list, iterate, get, update, delete, listPlatform, iteratePlatform, getPlatform |
projects | create, list, iterate, get, update |
secrets | create, list, iterate, get, update, delete |
files | upload, list, iterate, get, download, downloadTo |
templates | create, list, iterate, get, listPlatform, iteratePlatform, getPlatform |
agents | create, list, iterate, get, update |
sessions | create, list, iterate, get, close |
runs | create, reply, list, iterate, get, events, wait, result |
artifacts | get, download, downloadTo |
| Resource | Operations |
|---|---|
system | health, readiness, public_config, capabilities, openapi |
auth | signup, login, refresh, logout, current_user, github_oauth_url |
model_providers | create, list, iterate, get, update, delete, list_platform, iterate_platform, get_platform |
projects | create, list, iterate, get, update |
secrets | create, list, iterate, get, update, delete |
files | upload, list, iterate, get, download, download_to |
templates | create, list, iterate, get, list_platform, iterate_platform, get_platform |
agents | create, list, iterate, get, update |
sessions | create, list, iterate, get, close |
runs | create, reply, list, iterate, get, events, wait, result |
artifacts | get, download, download_to |
Current limits
The SDK does not invent client methods for backend functionality that is not in the public OpenAPI contract.
- Skills are not implemented. There is no
skillsfield or resource. Environment-tool declarations, attachments, instructions and MCP servers are supported, and none of them is a substitute for a versioned skill. - Editable workspace sources such as a repository checkout are not accepted by session creation yet. Use attachments for immutable inputs. The agent can still clone a repository with configured tools and credentials.
- Run cancellation and mid-turn steering are not implemented.
- Sessions are closed, not deleted.
- Run event history is available only while the sandbox-local supervisor database remains accessible. Persist any events your application must retain.
- Environment-tool declarations describe what the template image contains. V1 does not execute their optional checks.
- Artifact media type is declared or inferred from the filename. V1 does not inspect the bytes to verify it.