SDKs
Python
Install the Harnesslayer Python SDK and initialize a client with an organization API key. The SDK is the recommended path for app initialization, channel setup, profile state, and streaming API-channel runs.
pip install harnesslayer from harnesslayer import Harnesslayer client = Harnesslayer(api_key="YOUR_HARNESSLAYER_API_KEY")
For Claude runtimes, start from a valid .claude folder. The reference docs include a sample template for the expected folder shape.
Python SDK
Initialize an app
client.app.init creates a new app when the slug does not exist and otherwise returns the existing app. When creating a new app, it validates version_path, uploads that folder as the first app version, and registers it as the app head version.
app = client.app.init(
"typedef-app",
type="claude",
version_path=".claude",
# optional
name="Typedef App",
limit={
"amount": 100,
"interval": "monthly",
"message": "You've run out of credits!",
},
# optional
credentials=[
{
"type": "static_bearer",
"mcp_server_url": "https://mcp.linear.app/mcp",
"token": "lin_api_XXX",
}
],
)Python SDK
Create a new version
If you want to publish another app version, call app.version.create. Pass version_path to upload a local app folder, or pass version_id when an uploaded bundle only needs to be registered.
# Publish a new version from a local app folder. app.version.create(version_path=".claude") # Or register a version id after uploading the files separately. app.version.create(version_id="ver-...")
Python SDK
Initialize channels
app.channel.init creates a channel when the slug is new and otherwise returns the existing channel. Channel settings persist after initialization. API channels are used with channel.run; iMessage channels listen for incoming messages after setup.
# API channel
channel = app.channel.init("my-api", type="api")
# iMessage channel
channel = app.channel.init(
"my-imessage",
type="imessage",
data={
"sendBlueAPIKey": "XXX",
"sendBlueSecretKey": "XXX",
"fromNumber": "+17862139363",
},
)Python SDK
Run an API channel
Run API channels with a stable session_id, a product user identifier, and the input message. The returned stream yields events until harnesslayer.session_end.
stream = channel.run(
session_id="my-custom-session-id",
user_id="stephen@harnesslayer.co",
input="Hello",
)
for event in stream:
event_type = event.data["type"]
if event_type == "harnesslayer.session_end":
token_usage = event.data["usage"]
break
print(event)Python SDK
iMessage behavior
For iMessage channels, the agent listens for incoming messages automatically after initialization. You do not need to call channel.run() in your script. Incoming image media is forwarded to Claude and OpenClaw apps as image input.
Python SDK
Initialize profiles
Initialize a profile before calling channel.run when you need explicit control over the user identifier and state source. If you do not initialize a profile, one is created automatically when an API channel run starts or when an iMessage user sends a text or image.
profile = app.profile.init(
identifier=self._context.user_identifier,
state_path=folder_path,
metadata={"plan": "team"},
auto_update_state=False, # optional, defaults to True
)Retrieve existing profiles by keyword-only profile_id or by app-specific identifier.
profile = app.profile.retrieve(profile_id="profile-...") profile_by_identifier = app.profile.retrieve(identifier="user@example.com")
Python SDK
Clone with transforms
Use app.version.clone and profile.state.clone to create new snapshots from existing app versions or profile states. Pass version_id or state_id to clone a specific source; omit the id to clone the latest source. Transforms can deep-merge JSON files or replace Markdown files with full string content.
# Clone from a specific app version.
app.version.clone(
version_id="ver_123",
transform={
"agent.json": {
"name": "Hello from cloned version",
}
},
)# Replace Markdown file content while cloning.
app.version.clone(
transform={
"README.md": (
"# Hello from cloned version\n\n"
"This Markdown file was replaced during clone.\n"
),
},
)# Clone the latest state. If content is unchanged, no new state is created.
profile.state.clone(
transform={
"agent.json": {
"mcp_servers": {
"$by": "name",
"$patch": {
"harnesslayer": {
"url": f"https://mcp-server.com/mcp?sessionId={new_id}",
}
},
},
}
}
)# Patch every object in a JSON array.
app.version.clone(
transform={
"cron/jobs.json": {
"jobs": {
"$all": {
"enabled": True,
}
},
}
}
)# Replace a top-level field.
profile.state.clone(
transform={
"agent.json": {
"name": "Hello World",
}
}
)
# Deep-merge nested fields. Unspecified fields are preserved.
profile.state.clone(
transform={
"agent.json": {
"metadata": {
"template": "hello-world",
}
}
}
)Python SDK
Sync versions
After creating a new app version, call app.version.sync() to sync the app head version to profiles and states. Pass strategy="manual", strategy="current", strategy="incoming", or strategy="smart". Pass profile_ids to sync only specific profiles.
# Example: after app.version.clone(...) or another version update. app.version.sync(strategy="smart") app.version.sync(strategy="manual", profile_ids=["prof-...", "prof-..."])
Python SDK
Rollback versions and states
Use app.version.rollback to move the app head version back to an existing version. Use profile.state.rollback to move a single profile head state back to an existing state.
# Roll back the app head to an existing version.
app.version.rollback(
to_version_id="ver-...",
reason="Rollback to the last stable version",
)
# Roll back one profile head to an existing state.
profile = app.profile.retrieve(profile_id="prof-...")
profile.state.rollback(
to_state_id="state-...",
reason="Restore profile to a known good state",
)SDKs
TypeScript
Install the Harnesslayer TypeScript SDK and initialize a client with an organization API key.
npm install @pickaxe/harnesslayer
import { Harnesslayer } from "@pickaxe/harnesslayer"
const client = new Harnesslayer({ apiKey: "YOUR_HARNESSLAYER_API_KEY" })TypeScript SDK
Initialize an app
client.app.init creates a new app when the slug does not exist and otherwise returns the existing app. When creating a new app, it validates versionPath, uploads that folder as the first app version, and registers it as the app head version.
const app = await client.app.init("typedef-app", {
type: "claude",
versionPath: ".claude",
// optional
name: "Typedef App",
limit: {
amount: 100,
interval: "monthly",
message: "You've run out of credits!",
},
credentials: [
{
type: "static_bearer",
mcp_server_url: "https://mcp.linear.app/mcp",
token: "lin_api_XXX",
},
],
})TypeScript SDK
Create a new version
If you want to publish another app version, call app.version.create. Pass versionPath to upload a local app folder, or pass versionId when an uploaded bundle only needs to be registered.
// Publish a new version from a local app folder.
await app.version.create({ versionPath: ".claude" })
// Or register a version id after uploading the files separately.
await app.version.create({ versionId: "ver-..." })TypeScript SDK
Initialize channels
app.channel.init creates a channel when the slug is new and otherwise returns the existing channel. API channels are used with channel.run; iMessage channels listen for incoming messages after setup.
// API channel
const channel = await app.channel.init("my-api", {
type: "api",
})
// iMessage channel
const imessageChannel = await app.channel.init("my-imessage", {
type: "imessage",
data: {
sendBlueAPIKey: "XXX",
sendBlueSecretKey: "XXX",
fromNumber: "+17862139363",
},
})TypeScript SDK
Run an API channel
Run API channels with a stable sessionId, a product user identifier, and the input message. The returned async iterable yields events until harnesslayer.session_end.
const stream = channel.run("my-api", {
sessionId: "my-custom-session-id",
userId: "stephen@harnesslayer.co",
input: "Hello",
})
for await (const event of stream) {
if (event.data.type === "harnesslayer.session_end") {
const tokenUsage = event.data.usage
break
}
console.log(event)
}TypeScript SDK
iMessage behavior
For iMessage channels, the agent listens for incoming messages automatically after initialization. You do not call channel.run() for iMessage channels. Incoming image media is forwarded to Claude and OpenClaw apps as image input.
TypeScript SDK
Initialize profiles
Initialize a profile before calling channel.run when you need explicit control over the user identifier and state source. If you do not initialize a profile, one is created automatically when an API channel run starts or when an iMessage user sends a text or image.
const profile = await app.profile.init("user@example.com", {
statePath: folderPath,
metadata: { plan: "team" },
})Retrieve existing profiles by profileId or by app-specific identifier.
const profile = await app.profile.retrieve({ profileId: "profile-..." })
const profileByIdentifier = await app.profile.retrieve({
identifier: "user@example.com",
})TypeScript SDK
Clone with transforms
Use app.version.clone and profile.state.clone to create new snapshots from existing app versions or profile states. Pass versionId or stateId to clone a specific source; omit the id to clone the latest source.
// Clone from a specific app version.
await app.version.clone({
versionId: "ver-...",
transform: {
"agent.json": {
name: "Hello from cloned version",
},
},
})// Replace Markdown file content while cloning.
await app.version.clone({
transform: {
"README.md": [
"# Hello from cloned version",
"",
"This Markdown file was replaced during clone.",
].join("\n"),
},
})// Clone the latest state. If content is unchanged, no new state is created.
await profile.state.clone({
transform: {
"agent.json": {
mcp_servers: {
"$by": "name",
"$patch": {
harnesslayer: {
url: `https://mcp-server.com/mcp?sessionId=${newId}`,
},
},
},
},
},
})TypeScript SDK
Sync versions
After creating a new app version, call app.version.sync() to sync the app head version to profiles and states. Pass strategy as manual, current, incoming, or smart. Pass profileIds to sync only specific profiles.
// Example: after app.version.clone(...) or another version update.
await app.version.sync({ strategy: "smart" })
await app.version.sync({
strategy: "manual",
profileIds: ["prof-...", "prof-..."],
})TypeScript SDK
Rollback versions and states
Use app.version.rollback to move the app head version back to an existing version. Use profile.state.rollback to move one profile head state back to an existing state.
// Roll back the app head to an existing version.
await app.version.rollback({
toVersionId: "ver-...",
reason: "Rollback to the last stable version",
})
// Roll back one profile head to an existing state.
const profile = await app.profile.retrieve({ profileId: "prof-..." })
await profile.state.rollback({
toStateId: "state-...",
reason: "Restore profile to a known good state",
})