Send
Send a message to a session to create the next turn, and read that turn's result
One message is one turn. Messages in the same session run one after another in the same worker, the same working tree, and the same Claude Code conversation. The scope is sessions:write. The SDK examples use the werft client made in Install the SDK, and the HTTP API examples use the shell variables API (the server address) and KEY (the key). The GATE-SPEC {…} messages in the examples are scripts for the development server's fake model (Start).
Prepare a session
const created = await werft.sessions.create({
profile_id: "claude-coding-local",
repository_id: "sample-app",
message: 'GATE-SPEC {"id":"m1","steps":[],"final":"ready"}',
});
const sessionId = created.session_id;Send
const sent = await werft.sessions.messages.send(sessionId, {
message:
'GATE-SPEC {"id":"m2","steps":[{"tool":"Bash","input":{"command":"ls","description":"list files"}}],"final":"listed"}',
});
console.log(sent);{
turn_id: '2',
receipt_id: '…',
receipt_status: 'accepted'
}The response is 202. If the previous turn is still running, the new turn waits as queued and runs when its turn comes.
messageis up to 32,768 UTF-8 bytes. Over that is 413.- A session can hold 20 queued inputs by default. Over that is 429.
Idempotency-Keyis required. If you did not receive a response, send it again with the same key and the same body. The turn is created only once. The SDK makes the key and uses the same key on retries.
Read the result
import { waitForTurn } from "@werft-ai/sdk";
const turn = await waitForTurn(werft, sessionId, sent.turn_id, {
signal: AbortSignal.timeout(5 * 60_000),
});
console.log(`turn ${turn.turn_id}: ${turn.status}`);turn 2: completedJudge whether a turn has finished by this status alone. If it is one of the five values, it has finished. completed means the engine ended the turn normally, not that the requested work was done. It is completed even if the model calls only one tool and ends its answer, or if a tool receives an error. Decide whether the work was done by reading the assistant and tool_result events in Streaming, and if more work is needed, send a follow-up message to the same session.
When you cannot send
In the SDK, the responses below arrive as APIError, and you tell them apart like isApiError(error, "SESSION_PAUSED"). The SDK does not resend a 409.
| Response | Meaning |
|---|---|
409 SESSION_PAUSED | The session is paused. Resume it first (Waiting for approval and resuming) |
409 RECOVERY_REQUIRED | A recovery decision comes first (Close) |
409 SESSION_CLOSING, SESSION_CLOSED | The session is closing or closed |
409 CHECKPOINT_UNAVAILABLE | A checkpoint problem prevents accepting a new turn. The cause is in the response's message. If the transcript is over its limit, open a new session |
409 PROFILE_BLOCKED | An owner blocked the operator profile this session runs on. For a workspace profile, its base is blocked (details.reason_code is base_blocked). Nothing is queued. Once it is unblocked, the same session takes messages again |
| 413 | The message is too long |
| 429 | Queued inputs reached the limit |
Next steps
- Receiving a stream: watch progress while a turn runs
- Attachments