@redis-iris/agent-playbook
v1.0.0
Published
TypeScript SDK for Redis AgentPlaybooks
Keywords
Readme
@redis-iris/agent-playbook
TypeScript SDK for Redis AgentPlaybooks: manage Playbooks and author, publish, and retrieve FAQs and Skills.
Summary
Redis AgentPlaybooks: Public type-specific FAQ and Skill authoring, publication, and exact-version API.
For more information about the API: Redis AI documentation
Documentation
- SDK examples show FAQ and Skill authoring, search, discovery, and proposal review.
- Common workflows cover versioning, publication, pagination, and exact Skill resource verification.
- Available methods list the client API.
- Redis AI documentation provides broader product context; npm lists published package versions.
The examples and workflows on this page form the Playbook SDK guide. The Redis AI documentation provides broader product context.
Two clients, one package
AgentPlaybooks is the product; a Playbook is one instance managed by a user or operator.
AgentPlaybooksService manages Playbook resources on the control plane. Playbook
performs content operations against one Playbook on the data plane. Each client
has its own endpoint, credential, timeout, retry configuration, and HTTP transport.
import { Playbook, AgentPlaybooksService } from "@redis-iris/agent-playbook";
const service = new AgentPlaybooksService({
serverURL: process.env["AGENT_PLAYBOOKS_CONTROL_PLANE_URL"]!,
token: process.env["AGENT_PLAYBOOKS_SERVICE_TOKEN"]!,
});
const instance = await service.getPlaybook(process.env["AGENT_PLAYBOOKS_PLAYBOOK_ID"]!);
const playbook = new Playbook({
serverURL: process.env["AGENT_PLAYBOOKS_DATA_PLANE_URL"]!,
apiKey: process.env["AGENT_PLAYBOOKS_API_KEY"]!,
playbookId: instance.playbookId,
});
const faqs = await playbook.listFaqs();
console.log(faqs);Set these connection values before running the example:
| Variable | Value |
| --- | --- |
| AGENT_PLAYBOOKS_CONTROL_PLANE_URL | Your deployment's control-plane endpoint |
| AGENT_PLAYBOOKS_DATA_PLANE_URL | Its separate data-plane endpoint |
| AGENT_PLAYBOOKS_SERVICE_TOKEN | An authorized control-plane bearer token |
| AGENT_PLAYBOOKS_API_KEY | An agent key scoped to the selected Playbook |
| AGENT_PLAYBOOKS_PLAYBOOK_ID | The service-issued ID of that Playbook |
| AGENT_PLAYBOOKS_DEBUG | Enable data-plane HTTP debug logging |
| AGENT_PLAYBOOKS_SERVICE_DEBUG | Enable control-plane HTTP debug logging |
The SDKs automatically read the API key, Playbook ID, service token, and debug
settings when the corresponding constructor option is omitted. Explicit options
win, followed by AGENT_PLAYBOOKS_* variables, then legacy aliases:
PLAYBOOK_API_KEY, PLAYBOOK_PLAYBOOK_ID, PLAYBOOK_DEBUG,
PLAYBOOK_SERVICE_TOKEN, and PLAYBOOK_SERVICE_DEBUG. An explicitly set empty
new variable suppresses its legacy alias. These aliases do not modify the process
environment or allow credentials to cross between the two clients.
The URL variables above are application settings used by these examples. Both
clients require an explicit server_url (Python) or serverURL (TypeScript);
the core SDKs do not automatically read URLs from the environment.
Use an authorized control-plane bearer token for token and a resource-scoped
agent key for apiKey. TypeScript accepts an async credential provider for either
argument. Pass environment variables explicitly, as above. Creating a Playbook
does not mint a data-plane key; obtain that key through your deployment's Identity
Service. A freshly created resource may need to reach READY before DP calls.
Control-plane methods take resource IDs per operation, such as
service.getPlaybook(playbookId). Existing data-plane methods and constructor
arguments are unchanged. Both clients support timeoutMs, retryConfig,
debugLogger, and a custom httpClient; neither retries requests by default.
For a CP custom transport, import HTTPClient from
@redis-iris/agent-playbook/controlplane. The root HTTPClient export belongs to
DP. The two generated transport classes have separate TypeScript identities;
they can wrap the same caller-supplied fetch function.
Control-plane types are isolated under
@redis-iris/agent-playbook/controlplane/models, with errors under
@redis-iris/agent-playbook/controlplane/models/errors. The existing /models
and /models/errors exports remain data-plane types. AgentPlaybooksServiceOptions
is exported at the package root. See the control-plane reference
for provisioning, lifecycle, clear, and extraction-management operations.
The control-plane client covers:
| Task | Methods |
| --- | --- |
| Provision and manage instances | createPlaybook, listPlaybooks, getPlaybook, updatePlaybook, deletePlaybook |
| Clear content administratively | clearPlaybook (requires product_admin or suite_admin) |
| Manage extraction | listExtractionStores, listExtractionBindings, createExtractionBinding, getExtractionBinding, updateExtractionBinding, deleteExtractionBinding |
| Discover supported models | listModels |
| Discover cloud resources | listDatabases, getNetworkInfo |
| Manage cloud model credentials | getModelCredential, putModelCredential, deleteModelCredential |
Deployment availability
The shared control-plane operations work with both cloud and on-prem deployments.
listModels lists the deployment’s supported models, including the on-prem
embedding allowlist. listDatabases, getNetworkInfo, getModelCredential,
putModelCredential, and deleteModelCredential are cloud only. Their generated method documentation
is labeled Cloud only.
The optional llm field on create/update and resource responses is also cloud
only. Omit it for on-prem requests; on-prem responses do not contain it. No
constructor deployment flag is required: configure the appropriate endpoint.
Credential reads return status only, never a provider API key. These provider
credentials are separate from the control-plane token and data-plane agent key.
Cloud resource management and database discovery require the cloud session
cookie (JSESSIONID) and CSRF header (X-CSRF-Token) in addition to the operator
token; supply these through the client’s HTTP transport. Cloud
responses expose typed endpoint accessibility, egress addresses, and database
network metadata. These optional fields may be absent on-prem.
Extraction binding access and readiness
Store suggestions are scoped to the tenant and do not grant access. Creating a binding synchronously checks the caller's management consent, same-account RAM store ownership, destination readiness, and execution scope. A foreign or missing source is refused without revealing its metadata.
A new binding returns READY with enabled=false. Enable it with an update that
passes its opaque revision as expectedRevision. Every explicit enable revalidates source
consent and ownership with the current caller and can recover a blocked binding.
Pausing or deleting requires destination management access and makes no source or
grant-administration request. Runtime failures can mark a binding blocked;
clients must tolerate unknown blockedReason values.
Scan cadence is deployment-owned worker configuration. Binding requests and
responses omit intervalSeconds; idleWindowSeconds remains user-configurable.
Release status
This SDK follows semantic versioning from 1.0.0. Pin package versions intentionally:
npm install @redis-iris/[email protected]Node.js 20 or later is required. Both ESM and CommonJS imports are supported.
The generated snippets below use <id>, https://api.example.com, and
<AGENT_PLAYBOOKS_API_KEY> as placeholders. Replace them with your deployment URL,
bearer token, and service-issued IDs before running a snippet. In particular,
<id> does not satisfy the Playbook ID format and is rejected before a request.
Unified API migration (Iris #2186)
These clients target the unified Playbook API, merged to Iris main in Iris #2186, at revision
0d9ccad23977a7f0f778253331d6e54a56e8f244, from matched control/data-plane
artifacts in publisher run 41094034623. This includes structured validation
feedback, Skill proposals with revision-bound resource reads, and public health
operations. Later backend changes require a separate contract refresh.
Configure a base origin for each plane. The SDK appends
/iris/v1/agent-playbooks/... for control-plane calls and
/iris/data/v1/agent-playbooks/... for data-plane calls. List and search responses
now expose items (Python and TypeScript) with optional nextCursor / next_cursor
on paginated lists. Continue until that cursor is absent, even after an empty page.
Use minScore / min_score, entryKind / entry_kind, and state in place of
minimumScore, entryType, and proposal status. Enum values such as READY,
FAQ, ADD, and INBOX are uppercase; sorting fields such as updatedAt and
evidenceCount retain their field spelling. Resource IDs are service-issued;
extraction binding IDs use bind_.
Proposal and extraction-binding concurrency now uses opaque string revision
values returned by the service, sent back as expectedRevision / expected_revision.
Compare them only for equality. Stale guarded writes return HTTP 412 with
PreconditionFailedErrorResponseContent; they are distinct from HTTP 409 conflicts.
Playbook metadata responses no longer include tenantId. Administrative clear
requires a scope of ALL, ENTRIES, or PROPOSALS and returns no body (HTTP 204).
TypeScript control-plane PATCH calls now take (playbookId, projectId, body, options);
pass undefined for the reserved project argument when updating a body.
Request errors identify a body field by JSON pointer or a query/path field by
parameter inside errors.
Preview policy and inventory counts
Discovery has a saved previewTokenBudget / preview_token_budget and a separate
previewTokenBudgetEnabled / preview_token_budget_enabled flag. Budgeting defaults
to disabled. Saving a limit alone does not enable it; disabling preserves the
saved limit. PATCH fields are optional: omission preserves each field and applies
no defaults. Explicit null is rejected upstream. Use partial discovery objects;
do not substitute the create/response configuration for an update. The retired
maxPreviewTokens field is not supported.
The estimate covers one discovery preview payload, not an agent's context window
or later Skill reads. budgetTruncated reports candidates omitted for budget;
truncated separately reports result-count truncation.
import { AgentPlaybooksService, Playbook } from "@redis-iris/agent-playbook";
const service = new AgentPlaybooksService({ serverURL: "https://cp.example.com" });
const playbookId = "pb_0123456789abcdef0123456789abcdef";
await service.updatePlaybook(playbookId, undefined, { discovery: { previewTokenBudget: 4000 } });
await service.updatePlaybook(playbookId, undefined, { discovery: { previewTokenBudgetEnabled: true } });
const playbook = new Playbook({ serverURL: "https://dp.example.com", playbookId });
const stats = await playbook.getPlaybookStats(undefined, undefined, undefined, "INBOX");
if (stats.faqs.total.available) console.log(stats.faqs.total.value);
if (stats.proposals.inbox?.available) console.log(stats.proposals.inbox.value);The data-plane stats operation requires content-read permission. published and
exclusive createdAfter/createdBefore filter only FAQ/Skill inventory; view,
actionType, and entryKind filter only proposals. Omit view to request both
queues; an unrequested queue is absent. No sort, cursor, or limit applies.
Each count has available; an available zero is different from an unavailable
count, whose value is absent. These are independent indexed observations, subject
to indexing delay and concurrent changes, rather than an atomic snapshot or a
receipt of how many entries a clear deleted. Python preserves integer precision;
TypeScript callers must account for JavaScript's Number.MAX_SAFE_INTEGER limit.
Earlier beta compatibility changes
Extraction job responses no longer expose intervalSeconds, and
ExtractionBindingState no longer declares PREPARING. Successful binding
creation returns READY and disabled; remove preparation polling and enable
explicitly after reviewing the binding. Remove reads of the retired scan-cadence
field and configure cadence on the deployment instead.
Cloud list/detail responses can report state=UNAVAILABLE while preserving saved
configuration and omitting live database enrichment. This read state does not
confirm deletion or probe content Redis health. It cannot be submitted in a
mutation; writable states remain READY and SUSPENDED.
Upgrading to beta.1
The control-plane client is now AgentPlaybooksService, replacing
PlaybookService without a compatibility alias. Update imports from both
@redis-iris/agent-playbook and @redis-iris/agent-playbook/controlplane.
Rename PlaybookServiceOptions to AgentPlaybooksServiceOptions, and import
AgentPlaybooksServiceError from the control-plane errors subpath in place of
PlaybookServiceError. The data-plane client remains Playbook. Environment variables now use the
product prefix AGENT_PLAYBOOKS; the previous runtime names remain fallback
aliases as described above. See the unified API migration below for the current endpoint paths and contract provenance.
Upgrading from alpha.2
Alpha.3 requires a server with the updated proposal contract. Publish both SDKs
only after the corresponding data-plane rollout is ready. Acceptance returns a
historical result.versionCreated receipt (Python: result.version_created)
instead of top-level entry/version objects. It does not establish current target
existence or publication; publishing remains a separate operation.
Remove evidence schemaVersion (Python: schema_version) and producer version.
Producer name is required; optional promptVersion/prompt_version identifies
the actual prompt set, and commitSHA/commit_sha accepts a full lowercase Git
SHA-1 or SHA-256 revision. Omit unavailable metadata and retain the original
observation timestamp for retries. Creation source is immutable and does not
increment evidenceCount/evidence_count. Proposal list/search support action and
entry type selectors. listProposals now takes optional actionType and entryKind
before RequestOptions; move positional options to the eighth argument. The data-plane clear operation and its models have been removed;
use explicit FAQ/Skill deletion operations when needed. Administrative clear is
available separately through AgentPlaybooksService.clearPlaybook.
Proposal input content is distinct from normalized read content; only FAQ-add
proposals are supported. Future response union members are retained in raw
(Python: UnknownProposalMember; TypeScript: isUnknown: true). Inspect or upgrade
before acting on an unknown response. Unknown members do not enable new writes.
Proposal contentDigest/content_digest is optional in the general response
contract for future actions without content.
Table of Contents
- @redis-iris/agent-playbook
- Documentation
- Two clients, one package
- Release status
- Unified API migration (Iris #2186)
- Earlier beta compatibility changes
- Upgrading to beta.1
- Upgrading from alpha.2
- SDK Installation
- Requirements
- SDK Example Usage
- Common workflows
- Authentication
- Available Resources and Operations
- Standalone functions
- Global Parameters
- Retries
- Error Handling
- Custom HTTP Client
- Debugging
- Development
SDK Installation
The SDK can be installed with either npm, pnpm, bun or yarn package managers.
NPM
npm add @redis-iris/[email protected]PNPM
pnpm add @redis-iris/[email protected]Bun
bun add @redis-iris/[email protected]Yarn
yarn add @redis-iris/[email protected][!NOTE] This package is published with CommonJS and ES Modules (ESM) support.
Requirements
Node.js 20 or later is required. CI validates Node 20, 22, and 24, including ESM and CommonJS imports. The client uses Fetch and Web Streams APIs. Browser, Bun, and Deno compatibility has not been validated for this beta release.
SDK Example Usage
Create an FAQ
Create the first immutable version of a customer-support FAQ. The returned entry is an unpublished draft.
import { Playbook } from "@redis-iris/agent-playbook";
const playbook = new Playbook({
serverURL: "https://api.example.com",
playbookId: "<id>",
apiKey: "<AGENT_PLAYBOOKS_API_KEY>",
});
async function run() {
const result = await playbook.createFaq({
content: {
intents: [
"Where is my refund?",
"Refund still processing",
],
attributes: {
"audience": "support",
"locale": "en-US",
"topic": "refund",
},
metadata: {
"sourceId": "support-refund-policy-v4",
"status": "approved",
},
payload: {
faq: {
question: "How can support check the status of an in-flight refund?",
answer:
"Look up the refund by order ID, share its current stage and date, and do not promise a bank posting date.",
},
},
},
entryKey: "refund-status-check",
});
console.log(result);
}
run();
Add an FAQ version
Append an immutable version to an existing FAQ. Replace the example ID with the entry ID returned by create_faq.
import { Playbook } from "@redis-iris/agent-playbook";
const playbook = new Playbook({
serverURL: "https://api.example.com",
playbookId: "<id>",
apiKey: "<AGENT_PLAYBOOKS_API_KEY>",
});
async function run() {
const result = await playbook.appendFaqVersion(
"ent_0123456789abcdef0123456789abcdef",
{
content: {
intents: [
"Where is my refund?",
"Check refund progress",
],
attributes: {
"audience": "support",
"locale": "en-US",
"topic": "refund",
},
metadata: {
"sourceId": "support-refund-policy-v5",
"status": "approved",
},
payload: {
faq: {
question:
"How can support check the status of an in-flight refund?",
answer:
"Look up the refund by order ID and share its current stage and date. Card refunds normally post 5 to 10 business days after warehouse receipt.",
},
},
},
},
);
console.log(result);
}
run();
Publish an FAQ version
Publish the selected immutable FAQ version so it can be returned by search and discovery.
import { Playbook } from "@redis-iris/agent-playbook";
const playbook = new Playbook({
serverURL: "https://api.example.com",
playbookId: "<id>",
apiKey: "<AGENT_PLAYBOOKS_API_KEY>",
});
async function run() {
const result = await playbook.publishFaq(
"ent_0123456789abcdef0123456789abcdef",
{
version: 2,
},
);
console.log(result);
}
run();
Create a Skill
Create an unpublished Skill from a base64-encoded Agent Skills ZIP. Replace the package placeholder with an encoded archive rooted at late-delivery-resolution/ and containing SKILL.md.
import { Playbook } from "@redis-iris/agent-playbook";
const playbook = new Playbook({
serverURL: "https://api.example.com",
playbookId: "<id>",
apiKey: "<AGENT_PLAYBOOKS_API_KEY>",
});
async function run() {
const result = await playbook.createSkill({
packageBase64: "BASE64_ENCODED_AGENT_SKILL_ZIP",
content: {
intents: [
"Resolve a late delivery",
"Package has not arrived",
],
attributes: {
"audience": "support",
"locale": "en-US",
"topic": "delivery",
},
metadata: {
"sourceId": "runbook-late-delivery-v1",
"status": "approved",
},
},
entryKey: "late-delivery-resolution",
});
console.log(result);
}
run();
Search FAQs
Search only published FAQs, restrict results to exact-match attributes, and override the similarity threshold for this request.
import { Playbook } from "@redis-iris/agent-playbook";
const playbook = new Playbook({
serverURL: "https://api.example.com",
playbookId: "<id>",
apiKey: "<AGENT_PLAYBOOKS_API_KEY>",
});
async function run() {
const result = await playbook.searchFaqs({
query: "Where is my refund?",
filter: {
and: [
{
eq: {
name: "audience",
value: "support",
},
},
{
eq: {
name: "locale",
value: "en-US",
},
},
],
},
minScore: 0.72,
limit: 5,
});
console.log(result);
}
run();
Search Skills
Search only published Skills. Skill matches contain compact previews with canonical exact-version SKILL.md URIs.
import { Playbook } from "@redis-iris/agent-playbook";
const playbook = new Playbook({
serverURL: "https://api.example.com",
playbookId: "<id>",
apiKey: "<AGENT_PLAYBOOKS_API_KEY>",
});
async function run() {
const result = await playbook.searchSkills({
query: "How should I resolve a delayed delivery?",
filter: {
eq: {
name: "audience",
value: "support",
},
},
minScore: 0.72,
limit: 5,
});
console.log(result);
}
run();
Discover Playbook entries
Search published FAQs and Skills together while pinning a known Skill by its immutable entry key. Pins are returned separately from semantic matches.
import { Playbook } from "@redis-iris/agent-playbook";
const playbook = new Playbook({
serverURL: "https://api.example.com",
playbookId: "<id>",
apiKey: "<AGENT_PLAYBOOKS_API_KEY>",
});
async function run() {
const result = await playbook.discoverEntries({
query: "A customer's order is late and they want a refund",
filter: {
eq: {
name: "audience",
value: "support",
},
},
limit: 10,
pinnedEntries: [
{
entryKey: "late-delivery-resolution",
},
],
});
console.log(result);
}
run();
Create a proposal
Propose a new FAQ for review. Launch proposal authoring supports new FAQ entries.
import { Playbook } from "@redis-iris/agent-playbook";
const playbook = new Playbook({
serverURL: "https://api.example.com",
playbookId: "<id>",
apiKey: "<AGENT_PLAYBOOKS_API_KEY>",
});
async function run() {
const result = await playbook.createProposal({
action: {
add: {
target: {
newEntry: {
entryKey: "refund-bank-delay",
},
},
candidate: {
content: {
intents: [
"My refund has not appeared",
],
attributes: {
"audience": "support",
"locale": "en-US",
"topic": "refund",
},
metadata: {
"sourceId": "support-refund-policy-v5",
"status": "proposed",
},
payload: {
faq: {
question:
"What should support say when a settled refund has not appeared?",
answer:
"Confirm the settlement date and explain that the issuing bank controls final posting time.",
},
},
},
},
},
},
});
console.log(result);
}
run();
Submit proposal evidence
Attach immutable source context to an open proposal. Reuse the same evidence ID and payload when retrying; evidence is retained on the proposal and is not copied to the accepted FAQ.
import { Playbook } from "@redis-iris/agent-playbook";
const playbook = new Playbook({
serverURL: "https://api.example.com",
playbookId: "<id>",
apiKey: "<AGENT_PLAYBOOKS_API_KEY>",
});
async function run() {
const result = await playbook.submitProposalEvidence(
"prp_0123456789abcdef0123456789abcdef",
"evd_0123456789abcdef0123456789abcdef",
{
observedAt: new Date("2026-09-29T16:00:00Z"),
producer: {
name: "support-review",
promptVersion: "support-review-v1",
},
source: {
kind: "document",
reference: {
"documentId": "support-refund-policy-v5",
},
content: {
"excerpt": "The issuing bank controls final refund posting time.",
},
},
},
);
console.log(result);
}
run();
Accept a proposal
Promote the reviewed revision to one canonical unpublished FAQ. Concurrent calls and retries resume or return the recorded target.
import { Playbook } from "@redis-iris/agent-playbook";
const playbook = new Playbook({
serverURL: "https://api.example.com",
playbookId: "<id>",
apiKey: "<AGENT_PLAYBOOKS_API_KEY>",
});
async function run() {
const result = await playbook.acceptProposal(
"prp_0123456789abcdef0123456789abcdef",
{
expectedRevision: "reviewed-revision",
},
);
console.log(result);
}
run();
Common workflows
One Playbook client is bound to one playbookId. Supply your deployment's
serverURL and a bearer apiKey; there is no default deployment URL. The examples
use fictional customer-support content. Replace the example IDs with service IDs.
Creating or appending content produces an immutable version. Publishing chooses
which version search and discovery can return; management reads can also retrieve
drafts and exact historical versions.
Create, version, publish, and read an FAQ
import { Playbook } from "@redis-iris/agent-playbook";
const playbook = new Playbook({
serverURL: "https://api.example.com",
playbookId: "pb_0123456789abcdef0123456789abcdef",
apiKey: "<AGENT_PLAYBOOKS_API_KEY>",
});
const content = {
intents: ["Where is my refund?"],
attributes: { audience: "support", locale: "en-US" },
payload: { faq: { question: "When will my refund arrive?", answer: "Check its settlement date." } },
};
const created = await playbook.createFaq({ entryKey: "refund-status-check", content });
const faqId = created.entry.entryId;
const appended = await playbook.appendFaqVersion(faqId, {
content: { ...content, payload: { faq: { ...content.payload.faq, answer: "Card refunds normally post 5 to 10 business days after warehouse receipt." } } },
});
await playbook.publishFaq(faqId, { version: appended.version.version });
const entry = await playbook.getFaq(faqId);
const exact = await playbook.getFaqVersion(faqId, appended.version.version);
const entries = await playbook.listFaqs();
const versions = await playbook.listFaqVersions(faqId);
if (!("faq" in exact.content.payload)) throw new Error("Expected an FAQ payload");
console.log(entry, exact.content.payload.faq.answer, entries.items, versions.items);Continue list requests using nextCursor as the next request's cursor until it
is absent. Lists are not snapshots; concurrent writes can move entries between pages.
Create and version a Skill
An Agent Skills package is a ZIP rooted at its Skill name and containing
SKILL.md, for example late-delivery-resolution/SKILL.md. Base64-encode the
complete ZIP, not each file separately.
import { readFile } from "node:fs/promises";
import { Playbook } from "@redis-iris/agent-playbook";
const playbook = new Playbook({ serverURL: "https://api.example.com",
playbookId: "pb_0123456789abcdef0123456789abcdef", apiKey: "<AGENT_PLAYBOOKS_API_KEY>" });
const content = { intents: ["Resolve a late delivery"], attributes: { audience: "support" } };
const created = await playbook.createSkill({ entryKey: "late-delivery-resolution", content,
packageBase64: (await readFile("late-delivery-resolution-v1.zip")).toString("base64") });
const skillId = created.entry.entryId;
const appended = await playbook.appendSkillVersion(skillId, { content,
packageBase64: (await readFile("late-delivery-resolution-v2.zip")).toString("base64") });
await playbook.publishSkill(skillId, { version: appended.version.version });
console.log(await playbook.getSkill(skillId));
console.log(await playbook.getSkillVersion(skillId, appended.version.version));
console.log(await playbook.listSkills(), await playbook.listSkillVersions(skillId));Search and discovery
import { Playbook } from "@redis-iris/agent-playbook";
const playbook = new Playbook({ serverURL: "https://api.example.com",
playbookId: "pb_0123456789abcdef0123456789abcdef", apiKey: "<AGENT_PLAYBOOKS_API_KEY>" });
const filter = { eq: { name: "audience", value: "support" } };
console.log(await playbook.searchFaqs({ query: "Where is my refund?", filter, minScore: 0.72, limit: 5 }));
console.log(await playbook.searchSkills({ query: "Resolve a late delivery", filter, minScore: 0.72, limit: 5 }));
const discovered = await playbook.discoverEntries({ query: "My delivery is late", filter, limit: 10,
pinnedEntries: [{ entryKey: "late-delivery-resolution" }] });
console.log(discovered);Null-valued equality filters return a validation error from the service.
Search returns published content only. Discovery returns pins separately from
semantic matches, and Skill previews contain exact-version SKILL.md URIs. Check
truncated and budgetTruncated before assuming all matching content was returned.
Review proposals and attach evidence
New proposals have zero attached evidence, including when creation source is supplied.
Creation source is immutable and separate from evidence. Submit further evidence separately, and persist the
evidenceId together with its complete payload before sending it. An exact replay
returns ALREADY_PRESENT; reusing the ID with changed content returns
/errors/proposal-evidence-conflict. Evidence is caller-supplied review context,
not verified attribution, and is not copied to the accepted FAQ. Details expose
evidence and evidenceCount; list/search previews expose evidenceCount.
import { randomUUID } from "node:crypto";
import { Playbook } from "@redis-iris/agent-playbook";
const playbook = new Playbook({ serverURL: "https://api.example.com",
playbookId: "pb_0123456789abcdef0123456789abcdef", apiKey: "<AGENT_PLAYBOOKS_API_KEY>" });
const action = { add: { target: { newEntry: { entryKey: "refund-bank-delay" } },
candidate: { content: { payload: { faq: { question: "Why hasn't my refund appeared?",
answer: "The issuing bank controls final posting time." } } } } } };
const created = await playbook.createProposal({ action,
source: { kind: "document", reference: { documentId: "refund-policy-v5" } } });
const proposalId = created.proposalId;
const evidenceId = "evd_" + randomUUID().replaceAll("-", "");
const evidence = { observedAt: new Date(), producer: { name: "support-review", promptVersion: "support-review-v1" },
source: { kind: "document", reference: { documentId: "refund-policy-v5" },
content: { excerpt: "The issuing bank controls final refund posting time." } } };
// Persist evidenceId and evidence; reuse both unchanged for every retry.
await playbook.submitProposalEvidence(proposalId, evidenceId, evidence);
console.log(await playbook.listProposals("INBOX", "evidenceCount"));
console.log(await playbook.searchProposals({ query: "refund", actionType: "ADD", entryKind: "FAQ" }));
const reviewed = await playbook.getProposal(proposalId);
const replaced = await playbook.replaceProposal(proposalId, { action, expectedRevision: reviewed.revision });
await playbook.dismissProposal(proposalId, { expectedRevision: replaced.revision });
const restored = await playbook.restoreProposal(proposalId);
const accepted = await playbook.acceptProposal(proposalId, { expectedRevision: restored.revision });
if (!("versionCreated" in accepted.result)) throw new Error("Review the unrecognized acceptance result before publishing");
const receipt = accepted.result.versionCreated;
await playbook.publishFaq(receipt.entryId, { version: receipt.version });
// Permanent deletion of the proposal record; the accepted FAQ is retained.
await playbook.purgeProposal(proposalId);Replacement and acceptance use optimistic concurrency. On a revision conflict,
fetch and review the proposal again. Acceptance records one canonical target;
concurrent calls and retries with bounded backoff resume or return that unpublished
FAQ. The result.versionCreated receipt records historical completion, independent
of current target existence or publication. Publish its returned version separately
when it is ready to serve.
Evidence IDs use evd_ plus 32 lowercase hexadecimal characters. Each proposal
retains at most 100 records, subject to byte limits; capacity failures use
/errors/proposal-evidence-capacity. source.reference and source.content, when
supplied, must be JSON objects; their fields are opaque to Playbook. JavaScript
Date has millisecond precision; preserve the submitted timestamp and avoid
round-tripping another client's microsecond timestamp through Date for replay.
Integers above Number.MAX_SAFE_INTEGER cannot be represented exactly as JavaScript
numbers; encode exact large identifiers as strings in opaque evidence documents.
Read and verify exact Skill resources
getSkillResource returns { headers, result }. The result is a
ReadableStream<Uint8Array>; consume it only when ready. Header values are lists,
and names must be read case-insensitively. Every successful read includes
Content-Digest in RFC 9530 sha-256=:<base64>: form. The Skill descriptor uses
sha256:<lowercase-hex>; compare each to the digest of the bytes, not to each other.
import { createHash } from "node:crypto";
import { Playbook } from "@redis-iris/agent-playbook";
const playbook = new Playbook({ serverURL: "https://api.example.com",
playbookId: "pb_0123456789abcdef0123456789abcdef", apiKey: "<AGENT_PLAYBOOKS_API_KEY>" });
const skillId = "ent_0123456789abcdef0123456789abcdef";
const version = await playbook.getSkillVersion(skillId, 1);
if (!("skill" in version.content.payload)) throw new Error("Expected a Skill payload");
const descriptor = version.content.payload.skill.resources[0];
if (!descriptor) throw new Error("No resources in this Skill version");
const response = await playbook.getSkillResource(skillId, 1, descriptor.resourceId);
const bytes = new Uint8Array(await new Response(response.result).arrayBuffer());
const digest = createHash("sha256").update(bytes).digest();
const header = Object.entries(response.headers).find(([key]) => key.toLowerCase() === "content-digest")?.[1][0];
if (descriptor.digest !== "sha256:" + digest.toString("hex") || header !== "sha-256=:" + digest.toString("base64") + ":") {
throw new Error("Resource digest mismatch or missing Content-Digest");
}Authentication
Per-Client Security Schemes
This SDK supports the following security scheme globally:
| Name | Type | Scheme | Environment Variable |
| -------- | ---- | ----------- | ------------------------- |
| apiKey | http | HTTP Bearer | AGENT_PLAYBOOKS_API_KEY |
To authenticate with the API the apiKey parameter must be set when initializing the SDK client instance. For example:
import { Playbook } from "@redis-iris/agent-playbook";
const playbook = new Playbook({
serverURL: "https://api.example.com",
apiKey: "<AGENT_PLAYBOOKS_API_KEY>",
});
async function run() {
const result = await playbook.getServiceLiveness();
console.log(result);
}
run();
Available Resources and Operations
Playbook SDK
- getServiceLiveness - Reports that this API process can answer requests, independently of downstream dependencies.
- getServiceReadiness - Evaluates the same serving dependencies as the operational readiness probe. Returns 503 without dependency details when the service cannot serve requests.
- discoverEntries
- lookupEntries - Resolves bounded entry references to compact management state. Results preserve input order and report misses explicitly. Each item is authoritative when read, but the batch is not a global snapshot.
- listFaqs
- createFaq
- deleteFaqs - Deletes the requested FAQ families sequentially. Each family deletion is atomic, but the batch is not: a family that cannot be deleted is reported in errors and the others are still processed, and deletions completed before a request-level error remain applied. Retrying the same request is safe because already absent entries are ignored.
- searchFaqs
- deleteFaq
- getFaq
- withdrawFaq - Withdraws the FAQ publication without changing version content. An already-unpublished entry is a no-op. Concurrent publication changes are last-write-wins in Playbook v1.
- publishFaq - Publishes the selected immutable FAQ version. Repeating the currently published version is a no-op. Concurrent publication changes are last-write-wins in Playbook v1.
- listFaqVersions
- appendFaqVersion
- getFaqVersion
- getPlaybookHealth
- listProposals
- createProposal - Submit an FAQ or complete Skill package for human review. Extraction and agent submissions share proposalPolicy.maxOpenProposals. When the Inbox is full, HTTP 409 content-limit-exceeded identifies the configured limit and observed open count; the proposal was not saved. Review/dismiss existing proposals or ask an administrator to increase the configured limit before retrying. Capacity is advisory because Search is eventually consistent.
- searchProposals
- purgeProposal
- getProposal
- replaceProposal
- acceptProposal - Promotes the reviewed revision to one canonical unpublished FAQ. Concurrent calls and retries resume or return the proposal's recorded target. A complete target may be visible to management before the proposal reaches promoted, but remains absent from serving until separately published.
- restoreProposal
- dismissProposal
- submitProposalEvidence
- getProposalResource - Returns exact candidate file bytes for the requested proposal revision. A stale revision returns HTTP 412 /errors/revision-conflict; responses must not be cached.
- listSkills
- createSkill
- deleteSkills - Deletes the requested Skill families sequentially. Each family deletion is atomic, but the batch is not: a family that cannot be deleted is reported in errors and the others are still processed, and deletions completed before a request-level error remain applied. Retrying the same request is safe because already absent entries are ignored.
- searchSkills
- deleteSkill
- getSkill
- withdrawSkill - Withdraws the Skill publication without changing version content. An already-unpublished entry is a no-op. Concurrent publication changes are last-write-wins in Playbook v1.
- publishSkill - Publishes the selected immutable Skill version. Repeating the currently published version is a no-op. Concurrent publication changes are last-write-wins in Playbook v1.
- listSkillVersions
- appendSkillVersion
- getSkillVersion
- getSkillResource - Returns the exact immutable Skill resource bytes as a streamed response.
Every successful resource read includes a required Content-Digest response
header. Header names are case-insensitive. The header uses the RFC 9530
sha-256=:<base64>: representation, while the resource descriptor in the
Skill version uses sha256:<lowercase-hex>. Both encode the same SHA-256
digest bytes and must not be compared as strings.
- getPlaybookStats - Returns independent best-effort indexed observations using content-read permissions. Indexing delay, expiry and concurrent writes can make counts differ from list results or prevent publication buckets from summing to the total. This is not an atomic snapshot or a receipt of entries deleted by clear or bulk delete.
Standalone functions
All the methods listed above are available as standalone functions. These functions are ideal for use in applications running in the browser, serverless runtimes or other environments where application bundle size is a primary concern. When using a bundler to build your application, all unused functionality will be either excluded from the final bundle or tree-shaken away.
To read more about standalone functions, check FUNCTIONS.md.
acceptProposal- Promotes the reviewed revision to one canonical unpublished FAQ. Concurrent calls and retries resume or return the proposal's recorded target. A complete target may be visible to management before the proposal reaches promoted, but remains absent from serving until separately published.appendFaqVersionappendSkillVersioncreateFaqcreateProposal- Submit an FAQ or complete Skill package for human review. Extraction and agent submissions share proposalPolicy.maxOpenProposals. When the Inbox is full, HTTP 409 content-limit-exceeded identifies the configured limit and observed open count; the proposal was not saved. Review/dismiss existing proposals or ask an administrator to increase the configured limit before retrying. Capacity is advisory because Search is eventually consistent.createSkilldeleteFaqdeleteFaqs- Deletes the requested FAQ families sequentially. Each family deletion is atomic, but the batch is not: a family that cannot be deleted is reported in errors and the others are still processed, and deletions completed before a request-level error remain applied. Retrying the same request is safe because already absent entries are ignored.deleteSkilldeleteSkills- Deletes the requested Skill families sequentially. Each family deletion is atomic, but the batch is not: a family that cannot be deleted is reported in errors and the others are still processed, and deletions completed before a request-level error remain applied. Retrying the same request is safe because already absent entries are ignored.discoverEntriesdismissProposalgetFaqgetFaqVersiongetPlaybookHealthgetPlaybookStats- Returns independent best-effort indexed observations using content-read permissions. Indexing delay, expiry and concurrent writes can make counts differ from list results or prevent publication buckets from summing to the total. This is not an atomic snapshot or a receipt of entries deleted by clear or bulk delete.getProposalgetProposalResource- Returns exact candidate file bytes for the requested proposal revision. A stale revision returns HTTP 412 /errors/revision-conflict; responses must not be cached.getServiceLiveness- Reports that this API process can answer requests, independently of downstream dependencies.getServiceReadiness- Evaluates the same serving dependencies as the operational readiness probe. Returns 503 without dependency details when the service cannot serve requests.getSkillgetSkillResource- Returns the exact immutable Skill resource bytes as a streamed response.
Every successful resource read includes a required Content-Digest response
header. Header names are case-insensitive. The header uses the RFC 9530
sha-256=:<base64>: representation, while the resource descriptor in the
Skill version uses sha256:<lowercase-hex>. Both encode the same SHA-256
digest bytes and must not be compared as strings.
getSkillVersionlistFaqslistFaqVersionslistProposalslistSkillslistSkillVersionslookupEntries- Resolves bounded entry references to compact management state. Results preserve input order and report misses explicitly. Each item is authoritative when read, but the batch is not a global snapshot.publishFaq- Publishes the selected immutable FAQ version. Repeating the currently published version is a no-op. Concurrent publication changes are last-write-wins in Playbook v1.publishSkill- Publishes the selected immutable Skill version. Repeating the currently published version is a no-op. Concurrent publication changes are last-write-wins in Playbook v1.purgeProposalreplaceProposalrestoreProposalsearchFaqssearchProposalssearchSkillssubmitProposalEvidencewithdrawFaq- Withdraws the FAQ publication without changing version content. An already-unpublished entry is a no-op. Concurrent publication changes are last-write-wins in Playbook v1.withdrawSkill- Withdraws the Skill publication without changing version content. An already-unpublished entry is a no-op. Concurrent publication changes are last-write-wins in Playbook v1.
Global Parameters
A client is bound to one Playbook. Set playbookId in the constructor or set the
AGENT_PLAYBOOKS_PLAYBOOK_ID environment variable. The constructor option takes precedence.
Methods do not expose a per-operation playbookId override; create another client
to use a different Playbook. Supply serverURL explicitly for each client.
import { Playbook } from "@redis-iris/agent-playbook";
const playbook = new Playbook({
serverURL: "https://api.example.com",
playbookId: "pb_0123456789abcdef0123456789abcdef",
apiKey: "<AGENT_PLAYBOOKS_API_KEY>",
});
const result = await playbook.getPlaybookHealth();
console.log(result);Retries
The client makes one attempt by default (strategy: "none"). Configure retries
with retryConfig on the client or retries in an operation's RequestOptions.
An operation's setting takes precedence. Backoff intervals are in milliseconds;
see RetryConfig for the full options.
Use retries only when repeating the operation is safe for your workflow. For
proposal evidence, persist the same evidence ID and payload across attempts;
the service treats an exact replay as ALREADY_PRESENT.
Error Handling
PlaybookError is the base class for all HTTP error responses. It has the following properties:
| Property | Type | Description |
| ------------------- | ---------- | --------------------------------------------------------------------------------------- |
| error.message | string | Error message |
| error.statusCode | number | HTTP response status code eg 404 |
| error.headers | Headers | HTTP response headers |
| error.body | string | HTTP body. Can be empty string if no body is returned. |
| error.rawResponse | Response | Raw HTTP response |
| error.data$ | | Optional. Some errors may contain structured data. See Error Classes. |
Example
import { Playbook } from "@redis-iris/agent-playbook";
import * as errors from "@redis-iris/agent-playbook/models/errors";
const playbook = new Playbook({
serverURL: "https://api.example.com",
});
async function run() {
try {
const result = await playbook.getServiceLiveness();
console.log(result);
} catch (error) {
// The base class for HTTP error responses
if (error instanceof errors.PlaybookError) {
console.log(error.message);
console.log(error.statusCode);
console.log(error.body);
console.log(error.headers);
// Depending on the method different errors may be thrown
if (error instanceof errors.BadRequestErrorResponseContent) {
console.log(error.data$.title); // string
console.log(error.data$.status); // number
console.log(error.data$.detail); // string
console.log(error.data$.instance); // string
console.log(error.data$.type); // models.BadRequestErrorType
}
}
}
}
run();
Error Classes
Primary errors:
PlaybookError: The base class for HTTP error responses.BadRequestErrorResponseContent: Request validation or input decoding failed. Status code400.AuthenticationErrorResponseContent: Authentication credentials are missing, malformed, or invalid. Status code401.ForbiddenErrorResponseContent: The caller is authenticated but not allowed to access the requested resource. Status code403.NotFoundErrorResponseContent: The requested resource does not exist. Status code404.TimeoutErrorResponseContent: The request timed out before the service could complete it. Status code408.ConflictErrorResponseContent: The request conflicts with the current state of the resource. Status code409.PreconditionFailedErrorResponseContent: The write named an expected revision (expectedRevision or If-Match) that is no longer current. Read the resource again and retry. Status code412.PayloadTooLargeErrorResponseContent: The request payload exceeds the maximum supported size. Status code413.ResourceSuspendedErrorResponseContent: The requested resource exists but is suspended by an administrator. Status code423.FailedDependencyErrorResponseContent: A dependent resource required to process the request is unavailable or unhealthy. Status code424.TooManyRequestsErrorResponseContent: The service rejected the request because rate limits were exceeded. Status code429.UnexpectedErrorResponseContent: The service failed with an unexpected internal error. Status code500.ServiceUnavailableErrorResponseContent: ServiceUnavailableError 503 response. Status code503.
Network errors:
ConnectionError: HTTP client was unable to make a request to a server.RequestTimeoutError: HTTP request timed out due to an AbortSignal signal.RequestAbortedError: HTTP request was aborted by the client.InvalidRequestError: Any input used to create a request is invalid.UnexpectedClientError: Unrecognised or unexpected error.
Inherit from PlaybookError:
ResponseValidationError: Type mismatch between the data returned from the server and the structure expected by the SDK. Seeerror.rawValuefor the raw value anderror.pretty()for a nicely formatted multi-line string.
Custom HTTP Client
The TypeScript SDK makes API calls using an HTTPClient that wraps the native
Fetch API. This
client is a thin wrapper around fetch and provides the ability to attach hooks
around the request lifecycle that can be used to modify the request or handle
errors and response.
The HTTPClient constructor takes an optional fetcher argument that can be
used to integrate a third-party HTTP client or when writing tests to mock out
the HTTP client and feed in fixtures.
The following example shows how to:
- route requests through a proxy server using undici's ProxyAgent
- use the
"beforeRequest"hook to add a custom header and a timeout to requests - use the
"requestError"hook to log errors
import { Playbook } from "@redis-iris/agent-playbook";
import { ProxyAgent } from "undici";
import { HTTPClient } from "@redis-iris/agent-playbook/lib/http";
const dispatcher = new ProxyAgent("http://proxy.example.com:8080");
const httpClient = new HTTPClient({
// 'fetcher' takes a function that has the same signature as native 'fetch'.
fetcher: (input, init) =>
// 'dispatcher' is specific to undici and not part of the standard Fetch API.
fetch(input, { ...init, dispatcher } as RequestInit),
});
httpClient.addHook("beforeRequest", (request) => {
const nextRequest = new Request(request, {
signal: request.signal || AbortSignal.timeout(5000)
});
nextRequest.headers.set("x-custom-header", "custom value");
return nextRequest;
});
httpClient.addHook("requestError", (error, request) => {
console.group("Request Error");
console.log("Reason:", `${error}`);
console.log("Endpoint:", `${request.method} ${request.url}`);
console.groupEnd();
});
const playbook = new Playbook({ serverURL: "https://api.example.com",
playbookId: "pb_0123456789abcdef0123456789abcdef", httpClient });Debugging
You can setup your SDK to emit debug logs for SDK requests and responses.
You can pass a logger that matches console's interface as an SDK option.
[!WARNING] Beware that debug logging will reveal secrets, like API tokens in headers, in log messages printed to a console or files. It's recommended to use this feature only during local development and not in production.
import { Playbook } from "@redis-iris/agent-playbook";
const playbook = new Playbook({ serverURL: "https://api.example.com",
playbookId: "pb_0123456789abcdef0123456789abcdef", debugLogger: console });You can also enable a default debug logger by setting an environment variable AGENT_PLAYBOOKS_DEBUG to true.
Development
Maturity
This SDK follows semantic versioning from 1.0.0. Pin an exact published version and upgrade intentionally.
Contributions
While we value open-source contributions to this SDK, this library is generated programmatically. Any manual changes added to internal files will be overwritten on the next generation. We look forward to hearing your feedback. Feel free to open a PR or an issue with a proof of concept and we'll do our best to include it in a future release.
SDK Created by Speakeasy
Playbook validation feedback (MOD-20505)
Authoring accepts at most five unique intents. HTTP 400 failures expose the
shared errors array with a JSON pointer and safe detail; proposal candidates
retain their full nested pointer. A Skill ZIP needs one enclosing directory,
for example demo/SKILL.md, and matching manifest name; flat archives are invalid.
Extraction and agent proposals share proposalPolicy.maxOpenProposals, configured
per Playbook by its administrator on create/update (default 5,000, range 1–10,000).
At capacity, HTTP 409 content-limit-exceeded explains the limit and observed
count. The proposal was not saved. Review/dismiss the backlog or deliberately
change the policy before retrying. This remains an advisory Search-based check.
Programmatic clients raise typed errors; model-facing recovery belongs in the
calling integration, such as redis-iris's opt-in proposal tool adapter.
