Let AI agents update work trackers without blind writes.
One TypeScript API for GitHub Issues, GitLab, Linear, Jira, and Azure DevOps—with previewed diffs, atomic idempotency coordination, and stale-update protection.
Get started · Run examples · Compare providers · Read the safety model · Ask a question
npm install work-sdkTry the complete safe-write lifecycle without an account, token, or network request:
import { createWorkClient } from "work-sdk";
import { memoryWorkAdapter, workItemFixture } from "work-sdk/testing";
const work = createWorkClient({
adapter: memoryWorkAdapter({
items: [workItemFixture({
id: "123",
identifier: "DEMO-123",
title: "Ship the retry fix",
})],
}),
});
const change = await work.prepareUpdate("123", {
state: "closed",
});
console.log(change.summary, change.changes, change.warnings);
const result = await work.commit(change, {
idempotencyKey: "demo:close:123",
});When the workflow is ready, replace the memory adapter with one provider adapter. Credentials stay inside that adapter and are never copied into prepared changes.
Provider APIs disagree about statuses, priorities, assignees, rich text, pagination, errors, and concurrency. That makes apparently simple agent actions risky: retries can duplicate comments, stale reads can overwrite newer work, and unsupported fields can disappear silently.
Work SDK uses an explicit safe-write protocol:
- Prepare fetches the current item, resolves a normalized change, and returns warnings.
- Inspect lets your code, agent, or approval UI review the exact field-level diff.
- Commit verifies plan integrity and revision, executes the write, and returns a receipt.
Prepared changes are JSON-serializable and contain no credentials.
| Choose Work SDK when… | Choose an official provider SDK when… |
|---|---|
| An agent or automation writes to more than one tracker | You need the provider's complete API surface |
| You need a diff or approval step before a mutation | You only read data or perform a single trusted write |
| Duplicate-risk must be coordinated and ambiguous outcomes surfaced | Native request/response types are more important than portability |
| Stale plans must fail before overwriting newer work | You want provider-specific features with no normalization |
Work SDK is a focused safety and portability layer, not a replacement for every provider endpoint.
import { github } from "work-sdk/github";
const adapter = github({
token: process.env.GITHUB_TOKEN!,
owner: "acme",
repo: "web",
});import { linear } from "work-sdk/linear";
const adapter = linear({
apiKey: process.env.LINEAR_API_KEY!,
teamId: "team-id",
});import { gitlab } from "work-sdk/gitlab";
const adapter = gitlab({
project: "acme/platform",
token: process.env.GITLAB_TOKEN!,
// apiBaseUrl: "https://gitlab.example.com/api/v4",
});GitLab supports GitLab.com and Self-Managed, private-token and OAuth auth, search, Markdown comments, explicit issue-type maps, and guarded label writes. Missing labels are rejected by default because GitLab would otherwise create them silently.
import { jira } from "work-sdk/jira";
const adapter = jira({
baseUrl: "https://acme.atlassian.net",
email: process.env.JIRA_EMAIL!,
apiToken: process.env.JIRA_API_TOKEN!,
projectKey: "ENG",
});import { azureDevOps } from "work-sdk/azure-devops";
const adapter = azureDevOps({
organization: "acme",
project: "Platform",
auth: { type: "entra", token: process.env.AZURE_DEVOPS_TOKEN! },
});Azure DevOps supports Microsoft Entra and PAT authentication, WIQL search, custom-process state/type maps, priorities, Markdown comments, parent relations, and native JSON Patch revision tests.
Credentials stay inside adapters and are never copied into prepared changes.
- Overview
- Getting started
- Runnable example apps
- Safe-write protocol
- Provider comparison
- GitHub guide
- GitLab guide
- Linear guide
- Jira Cloud guide
- Azure DevOps guide
- Client API reference
- Normalized errors
- Agent integration
- Testing
const page = await work.list({ state: ["unstarted", "started"], limit: 25 });
const issue = await work.get("ENG-123");
const create = await work.prepareCreate({
title: "Retry failed webhook deliveries",
description: "Add exponential backoff and a dead-letter path.",
priority: "high",
});
const update = await work.prepareUpdate(issue.id, {
title: "Retry and reconcile failed webhook deliveries",
labels: ["reliability"],
});
const comment = await work.prepareComment(issue.id, {
body: "Implemented in PR #481 and verified in staging.",
});
const receipt = await work.commit(comment);
receipt.action; // "comment"
receipt.comment.body; // string — action-specific receipt typing in v0.5The default in-memory store atomically coordinates callers inside one process. Each key is bound to the normalized provider, action, target, and input. For serverless or multi-process systems, pass a durable store whose acquire operation uses a transaction, compare-and-swap, or unique conditional write:
import type { IdempotencyStore } from "work-sdk";
const store: IdempotencyStore = {
async acquire(key, intentFingerprint) {
return db.claimWorkSdkIntent({ key, intentFingerprint });
},
async complete(key, leaseId, result) {
await db.completeWorkSdkIntent({ key, leaseId, result });
},
async abandon(key, leaseId, outcome) {
await db.abandonWorkSdkIntent({ key, leaseId, outcome });
},
};
const work = createWorkClient({ adapter, idempotencyStore: store });Use a stable key derived from the business event, not from a random agent run ID.
Semantic no-op updates still validate the current revision but skip the provider mutation entirely.
WorkInFlightError means another worker owns the key. WorkAmbiguousCommitError means a write may have reached the provider and must be reconciled before retrying. See the idempotency-store contract.
Every adapter declares what it can represent:
if (!work.capabilities.priorities) {
// Keep priority out of the normalized update or route it through a
// provider-specific project/custom-field implementation.
}
if (work.capabilities.concurrency.update !== "atomic") {
// GitHub, GitLab, Linear, and Jira use a best-effort preflight check.
// Azure DevOps updates use an atomic /rev test.
}Unsupported fields surface as warnings during preparation. Provider-specific raw payloads remain available on normalized entities for escape hatches.
Errors have stable classes and codes across providers:
import {
WorkAmbiguousCommitError,
WorkConflictError,
WorkRateLimitError,
} from "work-sdk";
try {
await work.commit(change);
} catch (error) {
if (error instanceof WorkConflictError) {
// Fetch and prepare again. Work SDK never silently forces a stale plan.
}
if (error instanceof WorkRateLimitError) {
console.log(error.retryAfterMs);
}
if (error instanceof WorkAmbiguousCommitError) {
// Reconcile using the business key and provider data. Do not blind-retry.
}
}The work-sdk/testing entry point includes deterministic fixtures and a memory adapter. The repository also runs an internal shared contract suite for first-party adapters.
pnpm install
pnpm check
pnpm test
pnpm buildThe repository uses Node.js 20+, strict TypeScript, Vitest, and pnpm.
See SECURITY.md. Never expose provider credentials in browser code, prepared changes, logs, or model context.