Nightjar

Nightjar SDK

Submit connected Codex or Claude work from a Node.js backend and collect durable results.

Node.js 22+ · TypeScript · ESM + CommonJS · No runtime dependencies

Read the pilot limits and reliability contract before delegating sensitive or irreplaceable work.

Install and configure

npm install @silicon-intern/nightjar

In the Nightjar dashboard, connect your own Codex or Claude account and create a workspace key under Developers → API keys. Account sign-in and tool authorization happen in the dashboard. Your backend only needs the key:

NIGHTJAR_API_KEY=your_workspace_key
import Nightjar from '@silicon-intern/nightjar';

const nightjar = new Nightjar();
const result = await nightjar.run('Return a short checklist for testing a signup flow.');
console.log(result.output);

No saved agent or workspace ID is required. The key identifies its workspace; Nightjar selects a ready model account belonging to the key creator. For self-hosting, also set NIGHTJAR_BASE_URL.

Keys stay on the server. Other workspace members' subscriptions are not inherited. Dashboard keys have runs:read and runs:write; they cannot administer workspaces or connect accounts.

Named imports and CommonJS also work: import { Nightjar } from '@silicon-intern/nightjar' or const { Nightjar } = require('@silicon-intern/nightjar').

Give work access to your tools

With only NIGHTJAR_API_KEY configured, the first example grants no Nightjar tool connections. To investigate real repository activity, connect GitHub in the dashboard, then select it in Developers → Quickstart and copy the example with its actual connection ID:

const result = await nightjar.run({
  prompt:
    'Summarize the last seven days of changes in the connected repository. Do not modify anything.',
  connectionIds: ['YOUR_GITHUB_CONNECTION_ID'],
});
console.log(result.output);

Replace the placeholder with the selected connection ID. Add other authorized tools through connectionIds as needed. Without a saved agent, omitting this list grants no Nightjar service connections; adding a connection to the workspace does not automatically grant it to your runs. The server enforces each connection's saved resource and access limits.

Submit without blocking your application

const run = await nightjar.runs.create(
  {
    input: 'Return a short checklist for testing a signup flow.',
    metadata: { jobId: 'signup-checklist-001' },
  },
  { idempotencyKey: 'signup-checklist-001' },
);
// Persist run.id beside your job ID, then return HTTP 202.
// A worker can wait later; a webhook can notify your application instead.
const result = await nightjar.runs.result(run.id);
console.log(result.output);

runs.create returns after durable acceptance, usually queued. It accepts an object; the string shorthand belongs to run. Use the same body and idempotency key to recover an uncertain submission. A different body with the same key returns 409 idempotency_conflict; a new key creates new work. Keys contain 1–200 printable ASCII characters without spaces.

run combines submission and waiting for success. Both run and runs.result throw NightjarRunError for unsuccessful stopped work, with the snapshot in error.run. This includes failed, cancelled, needs_input, and a completed model whose deliverables failed. runs.wait returns stopped work for you to handle, while continuing to wait for a completed model's pending publication.

StatusHandling
queued, runningKeep waiting or return to the run later.
completedRead the result when lifecycle.resultReady is true; publication may still be pending or require action.
failedInspect error; partial output is not success.
cancelledWork was cancelled; keep observing if execution is still unconfirmed.
needs_inputInspect attention and lifecycle; enable an answer only when canAnswer is true.

Waits default to one-second polling and 30 minutes. timeoutMs limits local waiting; requestTimeoutMs limits each HTTP operation. Run input timeoutSeconds controls remote execution (30–86,400 seconds; default 1,800 unless inherited from an agent). Requests and waits accept an AbortSignal. A local timeout or abort does not cancel remote work; use runs.cancel(id) explicitly.

Questions, answers, and recovery

Use SDK 1.3.0 or later with the updated server for runs.answer, runs.recover, and the additional webhook event types below. SDK 1.2.0 does not contain these methods. Existing integrations can upgrade the SDK or call the HTTP endpoints directly.

Every updated server snapshot includes lifecycle: use resultReady to recognize a usable completed result, observing to keep observing recovery or publication, and canAnswer to enable an answer form. status remains compatible with existing clients. needs_input can mean a question, a connection problem, or unresolved execution; inspect attention.kind and lifecycle instead of guessing from output. A client connecting to an older server can fall back to status when lifecycle is absent.

const run = await nightjar.runs.get(savedRunId);
if (run.lifecycle?.canAnswer && run.attention?.kind === 'question') {
  // Send attention.id, message and optional choices to your application's UI.
  // Obtain the user's answer there; keep the Nightjar key in this backend.
  const continuation = await nightjar.runs.answer(run.id, {
    requestId: run.attention.id,
    answer: answerFromYourUser,
  });
  // Persist and observe continuation.id; conversationId stays stable.
}

Both providers receive the internal request_input tool, including runs with no service connections. Nightjar records the question before the agent stops; canAnswer becomes true after execution is confirmed stopped. An answer is atomic and idempotent by question ID: the same answer returns the same continuation, and a conflicting answer returns 409 answer_conflict. A cancelled/resolved question rejects later answers. Answering requires write permission and access to the original Environment. HTTP: POST /api/tasks/:id/answer with {requestId, answer}.

Original goals, accepted answers, and working files persist across continuations. Immediate parent results retain up to 100,000 characters; three earlier turns retain bounded excerpts (4,000 characters of instruction and 16,000 of result each). Accepted answers are limited to 100,000 serialized characters per conversation. Run IDs identify attempts; conversationId identifies their shared conversation. Ordinary follow-ups and explicit retries retain lineage.

lifecycle.state: "recovering" means Nightjar is observing the existing process. recovery reports attempt count, next attempt time, and the latest diagnostic. After five automatic attempts, offer runs.recover(id) / POST /api/tasks/:id/recover to request another bounded recovery cycle. Recovery never repeats the goal or extends a lease. Keep the reservation until executionUnconfirmed clears. A confirmed, stopped run can then be continued with an explicit follow-up.

runs.wait returns stopped failures or questions for handling; a completed model with pending publication continues waiting. runs.result rejects a completed run whose deliverables failed. The activity iterator remains open while lifecycle.observing is true, including post-execution publication/recovery updates.

Opt in to server-delivered Slack notifications with notification: {connectionId: selectedSlackId} when creating the run. The selected connection must be a ready Slack account with write permission and a saved self destination. This does not grant Slack tools to the agent. Scheduled runs retain the same opt-in; set notification: null when editing a schedule to turn it off. Answer continuations remain part of the scheduled occurrence and prevent overlapping work. Notifications survive model failure; notification.status reports pending, sent, or failed delivery. An ambiguous send is not blindly repeated: the dashboard asks the user to check Slack before retrying.

Subscribe to task.attention_requested, task.answer_received, task.recovering, task.recovered, and task.updated to observe questions, answers, recovery and publication/notification changes. All contain immutable full task snapshots with the same signed envelope and sequence rules. task.attention_requested can arrive before the pause is complete; the later task.needs_input contains canAnswer: true when it is safe. Existing subscriptions retain their four default stopped events until explicitly changed.

The runnable repository example, scripts/examples/conversation-backend.ts, demonstrates a signed receiver with durable duplicate/out-of-order handling and an answer endpoint. Run it with Node.js 24 and pnpm exec tsx scripts/examples/conversation-backend.ts, providing NIGHTJAR_API_KEY, NIGHTJAR_WEBHOOK_SECRET, and a separate EXAMPLE_APP_TOKEN. Its authenticated demo endpoints listen on loopback port 8787; send Authorization: Bearer EXAMPLE_APP_TOKEN to create, read, or answer a job. The receiver stores full snapshots and acknowledges compact progress without applying it. Reads reconcile with Nightjar. Expose the webhook route through your own public HTTPS endpoint for real delivery, and replace the demo authentication with your application's session and ownership rules before exposing job endpoints to end users.

Structured results

Supply a provider-compatible JSON schema and a runtime parser for output your code depends on:

const result = await nightjar.run(
  {
    prompt: 'Summarize the most important checks for testing a signup flow.',
    outputSchema: {
      type: 'object',
      properties: { summary: { type: 'string' } },
      required: ['summary'],
      additionalProperties: false,
    },
  },
  {
    parse(value: unknown): { summary: string } {
      if (
        typeof value !== 'object' ||
        value === null ||
        !('summary' in value) ||
        typeof value.summary !== 'string'
      )
        throw new Error('Expected a string summary.');
      return { summary: value.summary };
    },
  },
);
console.log(result.structuredOutput.summary);

An existing validator works through parse: (value) => schema.parse(value); async parsers also work. Parsing applies to structuredOutput and is available on runs.result. Without a parser, structured output remains unknown. Parser failure throws NightjarOutputError, with run and cause, without rerunning the model.

Nightjar sends the schema unchanged to the provider and validates the result. Schemas must be under 16 KB without references, patterns, or formats. For portability, use a Draft-07 object schema with required properties and additionalProperties: false on every object. Provider compatibility can still differ; unsupported schemas fail instead of being silently rewritten.

Live applications

In an enabled pilot workspace, describe the application and ask for a public URL. Static and React/Vite publishing tools are supplied automatically to the native agent; no Vercel connection is needed. The agent builds in its Environment, reads structured errors, and can repair and retry within the same run. Updates reuse the app's stable Vercel URL.

const result = await nightjar.run({
  prompt: 'Build a React habit tracker and give me a public URL.',
  connectionIds: [],
});
for (const app of result.apps ?? []) {
  if (app.state === 'live' && app.liveUrl) console.log(app.liveUrl);
  else console.log(app.state, app.error);
}

apps is server-generated metadata, separate from output and any requested structuredOutput schema. SDK 1.2.0 includes these types; install @silicon-intern/nightjar@^1.2.0 from npm. A completed run alone does not prove successful deployment. If an app is pending, reread the run with runs.get(id) until its deployment becomes terminal. App changes after the model stops emit task.updated with the full task snapshot; subscribe to it to observe publication through to readiness.

Published files are public. Servers, SSR, databases, and app secrets are unsupported. The current pilot allows ten apps per workspace, subject to the ten-app global pilot allowance, and 25 MiB per deployment, with a finite hosting lease. See the pilot limits and current lease before depending on a public URL. Publication metadata records the last verified deployment; a later operator pause or lease expiry can make that URL unavailable.

Project downloads and GitHub work

The run's Outputs panel offers Download ZIP for captured project files, plus View branch and View PR when work was delivered to GitHub. Download summary saves the Markdown response; structured results keep their Download JSON action.

Run snapshots add optional artifacts, github, and artifactError fields. The existing output summary and structuredOutput values keep the same shape. Older runs and runs without files may have no artifacts. GitHub links identify verified remote branches and pull requests; they are not inferred from links in the model's summary. To request a push or pull request, select a connection with write access and include that instruction in the goal.

After a cloud run stops and its process cleanup is confirmed, Nightjar captures the projects it changed, including each project's unchanged files. Failed or cancelled runs can therefore have partial project files. Unrelated projects, Git history, dependencies, caches, common credential files, non-template .env files, and symlinks are excluded. Capture is limited to 10,000 eligible entries, 100 MiB before compression, and a 25 MiB ZIP. The pilot has a 250 MiB total artifact storage limit. No archive is created when there are no eligible project changes; a capture or storage failure appears as artifactError without replacing the run result.

runs.downloadArtifact was added in SDK 1.1.0 and is included in the current npm release:

npm install '@silicon-intern/nightjar@^1.2.0'
import { writeFile } from 'node:fs/promises';

const result = await nightjar.runs.result(run.id);
const project = result.artifacts?.[0];
if (project) {
  const bytes = await nightjar.runs.downloadArtifact(result.id, project.id, {
    requestTimeoutMs: 30_000,
    maxRetries: 1,
  });
  await writeFile('./project.zip', bytes);
}
for (const delivery of result.github ?? []) {
  console.log(delivery.repository, delivery.branchUrl);
  if (delivery.pullRequest) console.log(delivery.pullRequest.url);
}
if (result.artifactError) console.error(result.artifactError);

Each artifact includes id, name, mediaType: 'application/zip', sizeBytes, sha256, and an authenticated downloadUrl. The SDK builds the request from the run and artifact IDs against its configured Nightjar server; it does not follow the metadata URL or redirects. It returns a Uint8Array, limits the download to 25 MiB, and supports the same authentication, request deadline, AbortSignal, and opt-in GET retries as other reads. Stopping a download does not cancel the run.

For HTTP integrations, use GET /api/tasks/{runId}/artifacts/{artifactId} with your workspace key. The route requires the same run visibility and runs:read scope as reading the task. A successful response is application/zip; errors use the usual JSON error envelope. The downloadUrl does not grant public access. Saved ZIPs are served by Nightjar and remain downloadable while the Environment sleeps. Check artifactError when capture is unavailable.

GitHub delivery metadata includes repository, branch, branchUrl, commitSha, and an optional pullRequest with number, title, and url. Signed task webhooks carry the same optional metadata, so a backend can download files after processing a completion event. Existing SDK 1.0 methods and result parsing are unchanged.

Receive signed webhooks

Use signed webhooks to update your application as runs start, make progress, ask a question, recover, and deliver results. A successful submission returns the run ID; your backend receives subsequent updates without polling. Relay verified updates from your backend to your own app clients using your application's realtime transport. Nightjar does not expose a resumable SSE stream.

Register a public HTTPS receiver in Developers → Webhooks, choose its events, and save its one-time signing secret on your backend. Events cover the workspace; use runId and data.task.metadata to correlate your jobs. Existing endpoints and new endpoints that omit event selection receive only the four stopped event types. Select the additional events in the contract below to receive questions, answers, recovery, and result-readiness changes. Progress is optional when your application only needs state and results.

Upgrade the receiver before expanding subscriptions. SDK 1.2.0 supports queued, started, progress, and stopped events; SDK 1.1.0 and earlier typed parsers accept stopped events only:

npm install '@silicon-intern/nightjar@^1.2.0'

The five conversation/update event types below require SDK 1.3.0 or later. Verify the installed package supports them before selecting them; the 1.2.0 parser rejects those event types.

Verify the exact raw bytes before JSON parsing. This example uses an application-provided durable inbox:

import express from 'express';
import { parseWebhook, NightjarWebhookError } from '@silicon-intern/nightjar/webhooks';

const app = express();
app.post('/webhooks/nightjar', express.raw({ type: 'application/json' }), async (req, res) => {
  try {
    const event = parseWebhook({
      payload: req.body,
      signature: req.get('nightjar-signature') ?? '',
      secret: process.env.NIGHTJAR_WEBHOOK_SECRET!,
    });
    // Implement this in your application: atomically save the payload with a
    // UNIQUE constraint on event.id. Already-saved IDs are a successful no-op.
    await saveToYourDurableInbox(event.id, event);
    res.sendStatus(204);
  } catch (error) {
    if (error instanceof NightjarWebhookError) {
      res.sendStatus(error.code === 'invalid_signature' ? 401 : 400);
    } else {
      res.sendStatus(500); // A failed durable save must trigger redelivery.
    }
  }
});
app.use(express.json());

saveToYourDurableInbox is your persistence function, not an SDK method. Acknowledge with HTTP 2xx only after durable acceptance, then process the saved event asynchronously. An event can arrive before your submission handler finishes saving the returned run ID; sending your job ID in run metadata helps correlate that race. Keep signing secrets, workspace keys, and payload verification on the server.

For receivers without the SDK, the nightjar-signature header has the form t=UNIX_SECONDS,v1=LOWERCASE_HEX_DIGEST. Compute HMAC-SHA256 with the signing secret over the UTF-8 timestamp, a literal period, then the exact raw request body bytes. Compare the digest in constant time and reject malformed headers and timestamps outside your allowed clock skew. The SDK's default tolerance is five minutes; keep clocks synchronized. Verify before parsing, and never reconstruct the bytes from parsed JSON. verifyWebhook performs this signature check and returns a boolean without validating the event shape; parseWebhook verifies and validates the event envelope.

Webhook event contract

New task events have { id, type, createdAt, runId, workspaceId, sequence, data }. IDs are stable strings beginning with evt_; runId equals data.task.id, and workspaceId equals data.task.workspaceId. sequence is a positive, durable activity ID assigned in increasing order by the single-host database. Compare it within each run to order events; gaps are normal because other activity and filtered events use the same ID space.

EventWhen it is emittedPayload
task.queuedThe run is durably accepted. An idempotent submission replay does not create another queued event.data.task: full queued run snapshot.
task.startedThe worker claims the run and begins dispatching it to the runner.data.task: full running run snapshot.
task.progressA meaningful activity update is persisted while the run is queued or running.Compact data.task identity/status/metadata and data.progress.
task.completedThe provider completes successfully; publication may still be pending.Full data.task; check lifecycle.resultReady before treating the result as ready.
task.failedThe run stops with an error.data.task: full failed run snapshot.
task.cancelledCancellation is recorded.Full data.task; executionUnconfirmed can still require recovery.
task.needs_inputThe provider stops for a question or a problem.Full data.task; inspect attention and lifecycle.canAnswer.
task.attention_requestedA question is saved, before execution necessarily finishes pausing.Full data.task with the question ID, message, and optional choices.
task.answer_receivedAn accepted answer is linked to its continuation.Full parent data.task; observe attention.continuationId next.
task.recoveringRecovery is requested or its attempt/diagnostic changes.Full data.task with recovery and executionUnconfirmed.
task.recoveredThe prior process is confirmed stopped and its Environment reservation is released.Full data.task; a pending question may now be answerable.
task.updatedPublication, notification delivery, or the queue explanation changes.Full data.task; re-evaluate lifecycle, including result readiness.

Progress is a human-readable update, not a token stream or a percentage-complete estimate. It includes agent messages, tool activity, execution notices, and relevant errors; it excludes reasoning, raw token deltas, and result payloads. data.progress.type preserves the source activity type: assistant, tool, system, error, agent.message, tool.called, tool.failed, git.transfer, runner.accepted, runner.unreachable, or task.cancel_requested. data.progress.message is bounded to 16,000 characters. An idle or silent provider may produce no progress messages. The compact task contains only id, workspaceId, status (queued or running), and metadata; completion events retain the full result.

Illustrative progress payload, not evidence of an executed run:

{
  "id": "evt_example",
  "type": "task.progress",
  "createdAt": "2026-09-08T12:00:03.000Z",
  "runId": "task_example",
  "workspaceId": "ws_example",
  "sequence": 103,
  "data": {
    "task": {
      "id": "task_example",
      "workspaceId": "ws_example",
      "status": "running",
      "metadata": { "jobId": "signup-checklist-001" }
    },
    "progress": {
      "type": "agent.message",
      "message": "I am checking the signup flow and its validation rules."
    }
  }
}

The dashboard's Send test event emits webhook.test, which has { id, type, createdAt, data: { workspaceId, message } } and no task or sequence. It bypasses event filtering, requires an enabled endpoint, and does not run an agent. parseWebhook returns a discriminated union covering test, progress, lifecycle, and terminal events. Previously queued terminal payloads may lack the new envelope fields; SDK 1.2.0 continues accepting them. For those payloads, use data.task.id and data.task.workspaceId; do not invent a missing sequence.

Webhook delivery and ordering

Events and their subscribed deliveries are saved durably with their originating activity. Delivery runs independently of agent execution, so a long run does not hold its own queued or progress notifications until completion. The pilot dispatches due deliveries in batches of up to eight on an independent timer; this is not a latency SLA.

Delivery is at least once, with up to eight automatic attempts in total. Failed requests retry with exponential delays starting at five seconds (5, 10, 20, 40, 80, 160, and 320 seconds between attempts), subject to dispatch timing. Retries preserve the event ID and exact JSON body; each attempt receives a fresh timestamp and signature. Headers also include nightjar-event-id, nightjar-delivery-id, and nightjar-delivery-attempt. The attempt header is diagnostic; authenticate and deduplicate using the signed body. Deduplicate using the verified body's event.id, not its signature or delivery ID.

Arrival order is not guaranteed: independent requests, receiver failures, and retries can cause a newer event to arrive first. In your inbox worker, store the highest applied sequence per run and atomically apply newer snapshots only. Keep older events in an ordered activity history if useful, but do not let an older queued/progress event overwrite a completed status. A compact progress payload should update the latest message and status; it must not erase full run fields such as output or artifacts. Track each run separately, and do not wait for contiguous sequence numbers. Handle terminal payloads without sequence separately during an upgrade, without allowing them to regress a newer terminal state.

Inspect attempts in Developers → Webhooks → Recent deliveries. After automatic attempts are exhausted, Retry delivery sends the same event again; it does not rerun the agent. A process interruption can consume a reserved attempt. Manual retry is rejected while an attempt is in flight; wait for it to finish before retrying. An unavailable receiver or exhausted retries can prevent live updates from arriving. Preserve run IDs so you can reconcile with runs.get when needed. This recovery read is optional error handling; receiving queued, started, progress, and terminal updates does not require continuous polling.

Webhook subscriptions

Use Developers → Webhooks → Add endpoint to choose events during creation, or Edit events on an existing endpoint. A workspace administrator manages these subscriptions in a signed-in browser session. Scoped workspace run keys cannot create, update, test, or retry webhooks.

The underlying administration API accepts the same nonempty list of supported task event names. The following JSON is an illustrative request body for POST /api/webhooks, authenticated with an administrator browser session:

{
  "workspaceId": "YOUR_WORKSPACE_ID",
  "url": "https://your-app.example/webhooks/nightjar",
  "eventTypes": [
    "task.queued",
    "task.started",
    "task.progress",
    "task.attention_requested",
    "task.answer_received",
    "task.recovering",
    "task.recovered",
    "task.updated",
    "task.completed",
    "task.failed",
    "task.cancelled",
    "task.needs_input"
  ]
}

Creation returns { webhook, secret }; save the one-time secret on your receiving server. Omitting eventTypes preserves the default selection: task.completed, task.failed, task.cancelled, and task.needs_input. Existing endpoints without this field use those same defaults.

To replace the selection, send PATCH /api/webhooks/{webhookId} in an administrator browser session:

{
  "eventTypes": ["task.started", "task.progress", "task.completed", "task.failed"]
}

Omitting eventTypes in a patch leaves the selection unchanged; an empty list or unknown event name is rejected. Set enabled: false or use Pause to stop an endpoint. Subscriptions affect future events only: opting in does not backfill earlier activity, and changing the selection does not remove already queued deliveries. Test events bypass this selection.

Progress and control

for await (const event of nightjar.runs.events(run.id, { after: 0 })) {
  console.log(event.id, event.type, event.message);
  // Save event.id after processing; pass it as "after" to resume.
}

This SDK iterator polls persisted activity and stops when the run no longer needs observation; it is not SSE. The numeric cursor is filtered locally and does not reduce server response size. These numeric activity IDs supply sequence on new webhooks, but differ from the stable string webhook id. Use signed lifecycle webhooks above when your app needs updates without polling.

HTTP retries are off by default. Set maxRetries to retry GET requests or create/follow-up/answer/retry requests with an explicit idempotency key. Network errors and HTTP 408, 429, 500, 502, 503, and 504 are retryable, within the request deadline. Cancellation, recovery, and unkeyed writes are never automatically retried. Repeating an answer manually with the same question ID and answer is safe even without an idempotency key. An HTTP retry recovers a request; runs.retry starts another model run.

Optional defaults and reuse

Set provider, model, or reasoning when your application needs a specific model configuration. Usually omit runnerId so Nightjar selects your account's ready Environment.

const result = await nightjar.run({
  prompt: 'Return a short checklist for testing a signup flow.',
  provider: 'claude',
  model: 'default',
  timeoutSeconds: 300,
});

Choose concrete model IDs and reasoning levels from the dashboard. default lets the native provider choose. Discovery reads installed CLI metadata without submitting a prompt; reported models still depend on the account's entitlement and quota. Some model choices may consume provider credits. There is no SDK models.list method.

Saved agents are optional reusable instructions and defaults. Existing agentId integrations continue to work. Discover them with nightjar.agents.list(), then use one for repeated work:

const reviewer = nightjar.with({ agentId: 'YOUR_SAVED_AGENT_ID' });
const result = await reviewer.run('Review the most recent changes.');

Replace the placeholder with an agent ID. The run inherits its instructions, provider, model, reasoning, connections, output schema, and deadline, and records the agent revision. Per-run settings override these defaults. connectionIds replaces the inherited list; [] grants no Nightjar service connections. outputSchema: null selects plain text; omission inherits the agent schema. Use nightjar.with({ agentId: null }) to clear a saved-agent default for subsequent submissions.

Client optionDefault
apiKeyNIGHTJAR_API_KEY; required.
workspaceId, agentIdOptional corresponding NIGHTJAR_WORKSPACE_ID and NIGHTJAR_AGENT_ID variables.
baseUrlNIGHTJAR_BASE_URL, then NIGHTJAR_URL, then the hosted origin.
requestTimeoutMs30_000, including HTTP retries and backoff.
maxRetries0; at most 5 additional attempts.
fetchglobalThis.fetch; injected transports must honor AbortSignal.

Explicit options override environment variables. nightjar.with({ workspaceId, agentId }) creates an independent client; omitted values retain defaults, and null clears them. Per-run values override client defaults. An explicit workspaceId must still match the key's workspace access. Neither optional ID is needed for the first-run examples above.

API reference

MethodResult
run(stringOrInput, options?)Submit and wait for successful completion.
with({ workspaceId?, agentId? })Independent client defaults.
runs.create(input, options?)Accepted task.
runs.get(id, options?)Current snapshot.
runs.downloadArtifact(id, artifactId, options?)Project ZIP as Uint8Array; SDK 1.1.0+.
runs.list(workspace?, options?)At most 500 recent runs; no pagination.
runs.result(id, options?)Wait for success; optional output parser.
runs.wait(id, options?)Return stopped work; wait through a completed model's pending publication.
runs.events(id, options?)Async iterator over persisted events.
runs.cancel(id, options?)Cancellation request and current snapshot.
runs.followUp(id, { prompt, runnerId? }, options?)New run with previous context.
runs.answer(id, { requestId, answer }, options?)One linked continuation; SDK 1.3.0+.
runs.recover(id, options?)Bounded environment recovery; SDK 1.3.0+.
runs.retry(id, options?)New run; optional runnerId.
agents.list(workspace?, options?)Saved agents.
parseWebhook(options)Verified event; conversation/update types require the SDK 1.3.0+.
verifyWebhook(options)Boolean signature check.

List methods accept a workspace string or { workspaceId }, or use the client default. HTTP options are signal, requestTimeoutMs, and maxRetries. Submissions additionally accept idempotencyKey. Wait/result/event options add intervalMs and timeoutMs; results accept parse, and events accept after. run supports both result and submission options.

TaskInput supports agentId, workspaceId, prompt, input, provider, model, reasoning, connectionIds, metadata, outputSchema, timeoutSeconds, runnerId, parentTaskId, and notification. Prefer runs.followUp for prior context. Import public types from the package root; Run aliases Task, and CompletedRun<T> narrows successful structured results.

Errors

ErrorHandling
NightjarErrorInspect HTTP status, optional code, requestId, and retryAfterMs.
NightjarConnectionErrorCheck network/base URL; inspect cause.
NightjarProtocolErrorUnexpected response shape; check SDK/server compatibility.
NightjarRunErrorInspect run.lifecycle, run.attention, run.error, and deliverable errors in run.apps.
NightjarTimeoutErrorResume waiting with taskId or cancel explicitly.
Request TimeoutErrorRecover uncertain submissions using the original body/key.
NightjarWebhookErrorCheck raw bytes, signature, secret, clock, and event shape.
NightjarOutputErrorThe runtime parser rejected the completed result; inspect run and cause.

For model_account_required, connect the key creator's own account. For connection_not_ready, recheck or reconnect the selected tool. idempotency_conflict requires the original request body or a new key for a new job. queue_full means wait for current work to finish. Administrative operations require a signed-in browser session.

For question_not_current, answer_conflict, or continuation_exists, reread the run and use its current question or linked continuation. execution_unconfirmed means wait for cleanup or recovery before answering or starting another attempt. recovery_not_required means the state changed before your request; reread it instead of retrying recovery.

Environments and execution limits

An Environment is the execution setup holding native model sign-ins, installed tools, and working files. The SDK can use the ready Environment for your account without a separate setup call.

New runs have native shell and file tools in a persistent directory dedicated to their conversation. Continuations reuse it. Legacy Prized runs retain ~/workspace and remain exclusive. Workers provide Node.js 24, pnpm, Python 3, Git, build tools, curl, ripgrep, and unzip. Private HTTPS Git clones use the selected connection; pushes require write access. SSH, Git LFS, workflow-file permission, and managed browser/computer use are unsupported.

The VM is the trust boundary. Full-permission code can access worker files and model authentication; selected connections cannot constrain credentials independently installed there. Do not share one worker between mutually untrusted users or secrets. Control-plane credentials stay on the server. Cancellation cannot undo writes or guarantee cleanup of deliberately hostile code.

Managed pilot Environments and explicitly enrolled legacy Environments disable idle pause within their original finite lease. The provider idle heuristic is not reliable evidence that an agent has stopped. Recovery can wake the exact existing Environment and restore its earliest hard deadline; it cannot extend it. Files and native sign-ins persist; disk storage remains billable.

Cloud concurrency defaults to one. The operator can set NIGHTJAR_CLOUD_CONCURRENCY=2 for two independent conversations after measuring the workload. Same-conversation work, legacy shared directories, and local execution remain sequential; uncertain execution reserves the entire Environment. Each isolated cloud run has a 1,280 MiB process RSS guard and conservative build settings. These controls are scheduling safeguards, not security isolation or a hard OS memory reservation.

Upgrade from 0.2.0

Change imports from nightjar-sdk to @silicon-intern/nightjar, or preserve them with an alias:

npm install 'nightjar-sdk@npm:@silicon-intern/nightjar@^1.0.0'

Existing named imports, explicit client options, runs methods, types, and verifyWebhook remain. runs.wait still returns unsuccessful stopped runs. Legacy createTask, getTask, cancelTask, and listTasks remain; getTask retains { task, events }. Environment-variable defaults are new: keep explicit options when ambient variables should not choose your workspace or agent.

The original hosted nightjar-sdk@0.2.0 archive remains at /downloads/nightjar-sdk-0.2.0.tgz, and the scoped 1.0.0 archive remains at /downloads/silicon-intern-nightjar-1.0.0.tgz. Both are frozen for existing lockfiles.

Validate an integration

Submit a harmless goal with a stable job ID, repeat the same body/key to confirm one run, and check its real result. Then submit a connected read with explicit connectionIds and confirm the result uses the intended resource. Verify signed task and test webhooks, including duplicate delivery. Handle failures and resume a local timeout by run ID. Test with each provider your application supports. Scheduling is optional.

Instructions for coding agents

Read this guide and the installed TypeScript declarations before integrating. Use runs.create with a stable idempotency key for HTTP handlers, save run IDs, and collect results through workers or verified webhooks. Keep keys out of browser code, prompts, and metadata. Handle unsuccessful runs explicitly and validate structured output at runtime.

Use injected fetch transports for local tests and report real cloud validation separately. This guide ships as node_modules/@silicon-intern/nightjar/GUIDE.md; its copy matches the installed package, while hosted docs are deployed separately.

Package maintenance

Maintainers: pnpm build:sdk builds ESM/CommonJS and declarations, copies this guide, packs the package, and generates hosted docs. Run pnpm check and SDK tests before release. Preserve the legacy archive. An npm publication and a hosted deployment are separate operations.