Redis Agent Playbooks quickstart
Publish an FAQ, retrieve it for an agent, and update the answer with Python, HTTP, or the Iris CLI.
Use Redis Agent Playbooks to publish a FAQ, retrieve its answer to “Where can I see how my refund is progressing?”, and update the answer agents receive.
Before you begin
Create a playbook and wait until it is Ready. Create an authoring key with write and publish permissions. Both include read.
This walkthrough needs no agent framework or model-provider key.
Follow one tab throughout the page. The Python SDK and command-line interface (CLI) require Python 3.11 or later. The cURL and CLI examples use Bash and jq.
Connect to the playbook
-
For Python or CLI examples, install the unified package:
python -m venv .venv source .venv/bin/activate python -m pip install 'redis-iris>=1.0.0' -
Copy the Endpoint and Playbook ID from the console's Connect dialog. Save them with your authoring key in
.env. Replace the placeholders in this file. Use the endpoint's base address, includinghttps://, without the/iris/data/v1/agent-playbooks/...path.IRIS_AGENT_PLAYBOOKS_URL='<data-plane-endpoint>' IRIS_AGENT_PLAYBOOKS_PLAYBOOK_ID='<playbook-id>' IRIS_AGENT_PLAYBOOKS_API_KEY='<playbook-api-key>' -
Load the file in the shell where you run the examples:
set -a; source .env; set +a -
Expand an example to copy the complete program. Save it as
quickstart.py,quickstart.cli.sh, orquickstart.sh(cURL), then runpython quickstart.py,bash quickstart.cli.sh, orbash quickstart.sh.
Keep .env out of source control and keys out of skill files and model prompts. Use iris.toml profiles for several deployments. All three clients use the authoring key in .env for this walkthrough.
AgentPlaybooks.from_env() reads the connection values. Its public .client property exposes authoring, search, and review operations. The CLI reads the same environment variables and uses --json when saving or processing responses.
from redis_iris.agent_playbooks import AgentPlaybooks
playbook = AgentPlaybooks.from_env()
print(playbook.client.get_playbook_health())
from redis_iris.agent_playbooks import AgentPlaybooks
playbook = AgentPlaybooks.from_env()
print(playbook.client.get_playbook_health())
faq_content = {
"intents": ["Check a refund", "Find an existing refund"],
"attributes": {"locale": "en-US", "topic": "refund"},
"payload": {
"faq": {
"question": "How do I check the status of a refund?",
"answer": (
"Open the order in your order history and select Refund status. "
"If no refund is listed, contact support with your order number."
),
}
},
}
draft = playbook.client.create_faq(content=faq_content)
faq_id = draft.entry.entry_id
version = draft.version.version
print(faq_id, version, draft.entry.publication)
stored = playbook.client.get_faq_version(faq_id=faq_id, version=version)
print(stored.content)
playbook.client.publish_faq(faq_id=faq_id, version=version)
results = playbook.client.search_faqs(
query="Where can I see how my refund is progressing?",
filter_={"eq": {"name": "locale", "value": "en-US"}},
limit=5,
)
for match in results.items:
if match.faq is not None:
print(match.entry_id, match.version, match.score, match.faq.answer)
discovery = playbook.discover(
"Help a customer find their refund status",
filter_={"eq": {"name": "topic", "value": "refund"}},
)
print(discovery.as_prompt())
from copy import deepcopy
updated_content = deepcopy(faq_content)
updated_content["payload"]["faq"]["answer"] += (
" Support can check the current stage but cannot guarantee a bank posting date."
)
updated = playbook.client.append_faq_version(faq_id=faq_id, content=updated_content)
print("Review this version:", updated.version.version, updated.version.content)
playbook.client.publish_faq(faq_id=faq_id, version=updated.version.version)
updated_results = playbook.client.search_faqs(
query="Where can I see how my refund is progressing?",
filter_={"eq": {"name": "locale", "value": "en-US"}},
limit=5,
)
for match in updated_results.items:
if match.entry_id == faq_id and match.faq is not None:
print("Published answer:", match.version, match.faq.answer)
set -euo pipefail
iris agent-playbooks health
set -euo pipefail
iris agent-playbooks health
cat > faq.json <<'JSON'
{
"entryKey": "refund-status",
"question": "How do I check the status of a refund?",
"answer": "Open the order in your order history and select Refund status. If no refund is listed, contact support with your order number.",
"intents": ["Check a refund", "Find an existing refund"],
"attributes": {"locale": "en-US", "topic": "refund"}
}
JSON
iris agent-playbooks faqs validate faq.json
iris --json agent-playbooks faqs push faq.json > faq-draft.json
FAQ_ID=$(jq -er '.results[0].entryId' faq-draft.json)
VERSION=$(jq -er '.results[0].version' faq-draft.json)
jq . faq-draft.json
iris --json agent-playbooks faqs show "$FAQ_ID" --version "$VERSION" | jq .content
iris agent-playbooks faqs publish "$FAQ_ID" --version "$VERSION"
cat > search.json <<'JSON'
{
"query": "Where can I see how my refund is progressing?",
"filter": {"eq": {"name": "locale", "value": "en-US"}},
"limit": 5
}
JSON
iris --json agent-playbooks faqs search --input search.json | jq .items
cat > discover.json <<'JSON'
{
"query": "Help a customer find their refund status",
"filter": {"eq": {"name": "topic", "value": "refund"}},
"limit": 5
}
JSON
iris --json agent-playbooks discover --input discover.json | jq .
jq '.answer += " Support can check the current stage but cannot guarantee a bank posting date."' \
faq.json > faq-update.json
iris --json agent-playbooks faqs push faq-update.json > faq-updated.json
UPDATED_VERSION=$(jq -er '.results[0].version' faq-updated.json)
iris --json agent-playbooks faqs show "$FAQ_ID" --version "$UPDATED_VERSION" | jq .content
iris agent-playbooks faqs publish "$FAQ_ID" --version "$UPDATED_VERSION"
iris --json agent-playbooks faqs search --input search.json | jq .items
set -euo pipefail
PLAYBOOK_API="${IRIS_AGENT_PLAYBOOKS_URL%/}/iris/data/v1/agent-playbooks/playbooks/$IRIS_AGENT_PLAYBOOKS_PLAYBOOK_ID"
curl --fail-with-body --silent --show-error \
"$PLAYBOOK_API/health" \
-H "Authorization: Bearer $IRIS_AGENT_PLAYBOOKS_API_KEY" | jq .
set -euo pipefail
set -euo pipefail
PLAYBOOK_API="${IRIS_AGENT_PLAYBOOKS_URL%/}/iris/data/v1/agent-playbooks/playbooks/$IRIS_AGENT_PLAYBOOKS_PLAYBOOK_ID"
curl --fail-with-body --silent --show-error \
"$PLAYBOOK_API/health" \
-H "Authorization: Bearer $IRIS_AGENT_PLAYBOOKS_API_KEY" | jq .
cat > faq.json <<'JSON'
{
"content": {
"intents": ["Check a refund", "Find an existing refund"],
"attributes": {"locale": "en-US", "topic": "refund"},
"payload": {"faq": {
"question": "How do I check the status of a refund?",
"answer": "Open the order in your order history and select Refund status. If no refund is listed, contact support with your order number."
}}
}
}
JSON
curl --fail-with-body --silent --show-error \
-X POST "$PLAYBOOK_API/faqs" \
-H "Authorization: Bearer $IRIS_AGENT_PLAYBOOKS_API_KEY" \
-H 'Content-Type: application/json' --data-binary @faq.json > draft.json
FAQ_ID=$(jq -er '.entry.entryId' draft.json)
VERSION=$(jq -er '.version.version' draft.json)
jq '{entryId: .entry.entryId, version: .version.version, publication: .entry.publication}' draft.json
curl --fail-with-body --silent --show-error \
"$PLAYBOOK_API/faqs/$FAQ_ID/versions/$VERSION" \
-H "Authorization: Bearer $IRIS_AGENT_PLAYBOOKS_API_KEY" | jq .content
curl --fail-with-body --silent --show-error \
-X PUT "$PLAYBOOK_API/faqs/$FAQ_ID/publication" \
-H "Authorization: Bearer $IRIS_AGENT_PLAYBOOKS_API_KEY" \
-H 'Content-Type: application/json' \
--data "$(jq -n --argjson version "$VERSION" '{version: $version}')" | jq .publication
curl --fail-with-body --silent --show-error \
-X POST "$PLAYBOOK_API/faqs/search" \
-H "Authorization: Bearer $IRIS_AGENT_PLAYBOOKS_API_KEY" \
-H 'Content-Type: application/json' \
--data '{"query":"Where can I see how my refund is progressing?","filter":{"eq":{"name":"locale","value":"en-US"}},"limit":5}' \
| jq '.items[] | {entryId, version, score, answer: .faq.answer}'
curl --fail-with-body --silent --show-error \
-X POST "$PLAYBOOK_API/entries/discover" \
-H "Authorization: Bearer $IRIS_AGENT_PLAYBOOKS_API_KEY" \
-H 'Content-Type: application/json' \
--data '{"query":"Help a customer find their refund status","filter":{"eq":{"name":"topic","value":"refund"}},"limit":5}' | jq .
jq '.content.payload.faq.answer += " Support can check the current stage but cannot guarantee a bank posting date."' \
faq.json > faq-update.json
curl --fail-with-body --silent --show-error \
-X POST "$PLAYBOOK_API/faqs/$FAQ_ID/versions" \
-H "Authorization: Bearer $IRIS_AGENT_PLAYBOOKS_API_KEY" \
-H 'Content-Type: application/json' --data-binary @faq-update.json > updated.json
jq .version updated.json
curl --fail-with-body --silent --show-error \
-X PUT "$PLAYBOOK_API/faqs/$FAQ_ID/publication" \
-H "Authorization: Bearer $IRIS_AGENT_PLAYBOOKS_API_KEY" \
-H 'Content-Type: application/json' \
--data "$(jq '{version: .version.version}' updated.json)" | jq .publication
curl --fail-with-body --silent --show-error \
-X POST "$PLAYBOOK_API/faqs/search" \
-H "Authorization: Bearer $IRIS_AGENT_PLAYBOOKS_API_KEY" \
-H 'Content-Type: application/json' \
--data '{"query":"Where can I see how my refund is progressing?","filter":{"eq":{"name":"locale","value":"en-US"}},"limit":5}' \
| jq --arg id "$FAQ_ID" '.items[] | select(.entryId == $id) | {version, answer: .faq.answer}'
Keep the printed identifier for cleanup. SDK and cURL examples create another entry when rerun. The CLI uses refund-status as a stable entry key: faqs push creates missing content, appends changed content, and skips unchanged content. Start with an unused key for this walkthrough. After an uncertain write, inspect entry state before retrying.
Create an FAQ
Create one answer with a locale and topic for retrieval filters. Save the returned entry identifier and version.
faq_content = {
"intents": ["Check a refund", "Find an existing refund"],
"attributes": {"locale": "en-US", "topic": "refund"},
"payload": {
"faq": {
"question": "How do I check the status of a refund?",
"answer": (
"Open the order in your order history and select Refund status. "
"If no refund is listed, contact support with your order number."
),
}
},
}
draft = playbook.client.create_faq(content=faq_content)
faq_id = draft.entry.entry_id
version = draft.version.version
print(faq_id, version, draft.entry.publication)
cat > faq.json <<'JSON'
{
"entryKey": "refund-status",
"question": "How do I check the status of a refund?",
"answer": "Open the order in your order history and select Refund status. If no refund is listed, contact support with your order number.",
"intents": ["Check a refund", "Find an existing refund"],
"attributes": {"locale": "en-US", "topic": "refund"}
}
JSON
iris agent-playbooks faqs validate faq.json
iris --json agent-playbooks faqs push faq.json > faq-draft.json
FAQ_ID=$(jq -er '.results[0].entryId' faq-draft.json)
VERSION=$(jq -er '.results[0].version' faq-draft.json)
jq . faq-draft.json
cat > faq.json <<'JSON'
{
"content": {
"intents": ["Check a refund", "Find an existing refund"],
"attributes": {"locale": "en-US", "topic": "refund"},
"payload": {"faq": {
"question": "How do I check the status of a refund?",
"answer": "Open the order in your order history and select Refund status. If no refund is listed, contact support with your order number."
}}
}
}
JSON
curl --fail-with-body --silent --show-error \
-X POST "$PLAYBOOK_API/faqs" \
-H "Authorization: Bearer $IRIS_AGENT_PLAYBOOKS_API_KEY" \
-H 'Content-Type: application/json' --data-binary @faq.json > draft.json
FAQ_ID=$(jq -er '.entry.entryId' draft.json)
VERSION=$(jq -er '.version.version' draft.json)
jq '{entryId: .entry.entryId, version: .version.version, publication: .entry.publication}' draft.json
The first version is 1, and publication is absent or empty. You can review the entry, but search and discovery cannot return it yet.
The CLI prepares a flat FAQ JSON file and validates it locally before pushing it. Its result is an action report containing the entry identifier and version, rather than the HTTP creation response. push leaves the version unpublished unless you request publication.
Review and publish the answer
Read the exact version you created.
stored = playbook.client.get_faq_version(faq_id=faq_id, version=version)
print(stored.content)
iris --json agent-playbooks faqs show "$FAQ_ID" --version "$VERSION" | jq .content
curl --fail-with-body --silent --show-error \
"$PLAYBOOK_API/faqs/$FAQ_ID/versions/$VERSION" \
-H "Authorization: Bearer $IRIS_AGENT_PLAYBOOKS_API_KEY" | jq .content
Review the question and answer. Then publish the version you read.
playbook.client.publish_faq(faq_id=faq_id, version=version)
iris agent-playbooks faqs publish "$FAQ_ID" --version "$VERSION"
curl --fail-with-body --silent --show-error \
-X PUT "$PLAYBOOK_API/faqs/$FAQ_ID/publication" \
-H "Authorization: Bearer $IRIS_AGENT_PLAYBOOKS_API_KEY" \
-H 'Content-Type: application/json' \
--data "$(jq -n --argjson version "$VERSION" '{version: $version}')" | jq .publication
The publication response identifies the version that agents can retrieve.
Search with a different question
Search with a paraphrase of the stored question.
results = playbook.client.search_faqs(
query="Where can I see how my refund is progressing?",
filter_={"eq": {"name": "locale", "value": "en-US"}},
limit=5,
)
for match in results.items:
if match.faq is not None:
print(match.entry_id, match.version, match.score, match.faq.answer)
cat > search.json <<'JSON'
{
"query": "Where can I see how my refund is progressing?",
"filter": {"eq": {"name": "locale", "value": "en-US"}},
"limit": 5
}
JSON
iris --json agent-playbooks faqs search --input search.json | jq .items
curl --fail-with-body --silent --show-error \
-X POST "$PLAYBOOK_API/faqs/search" \
-H "Authorization: Bearer $IRIS_AGENT_PLAYBOOKS_API_KEY" \
-H 'Content-Type: application/json' \
--data '{"query":"Where can I see how my refund is progressing?","filter":{"eq":{"name":"locale","value":"en-US"}},"limit":5}' \
| jq '.items[] | {entryId, version, score, answer: .faq.answer}'
Check for your entry identifier, version 1, and the answer about order history. Scores and rankings vary with the embedding model and other entries. If no match appears, repeat only the search after a short wait, then check the locale filter and similarity threshold.
Discover FAQs for an agent
Discovery returns FAQ answers and skill previews in one request. This example filters for refund guidance to add to the agent's context.
discovery = playbook.discover(
"Help a customer find their refund status",
filter_={"eq": {"name": "topic", "value": "refund"}},
)
print(discovery.as_prompt())
cat > discover.json <<'JSON'
{
"query": "Help a customer find their refund status",
"filter": {"eq": {"name": "topic", "value": "refund"}},
"limit": 5
}
JSON
iris --json agent-playbooks discover --input discover.json | jq .
curl --fail-with-body --silent --show-error \
-X POST "$PLAYBOOK_API/entries/discover" \
-H "Authorization: Bearer $IRIS_AGENT_PLAYBOOKS_API_KEY" \
-H 'Content-Type: application/json' \
--data '{"query":"Help a customer find their refund status","filter":{"eq":{"name":"topic","value":"refund"}},"limit":5}' | jq .
With no pins, pinnedEntries is empty. Matching published entries appear in discoveredEntries. Python exposes these collections as discovery.pinned and discovery.discovered; as_prompt() formats them for agent context. When you add skills later, discovery returns their previews without loading all their instructions.
Publish an updated answer
Append a complete replacement version to the same entry. Include the attributes and intents you want to retain.
from copy import deepcopy
updated_content = deepcopy(faq_content)
updated_content["payload"]["faq"]["answer"] += (
" Support can check the current stage but cannot guarantee a bank posting date."
)
updated = playbook.client.append_faq_version(faq_id=faq_id, content=updated_content)
print("Review this version:", updated.version.version, updated.version.content)
jq '.answer += " Support can check the current stage but cannot guarantee a bank posting date."' \
faq.json > faq-update.json
iris --json agent-playbooks faqs push faq-update.json > faq-updated.json
UPDATED_VERSION=$(jq -er '.results[0].version' faq-updated.json)
iris --json agent-playbooks faqs show "$FAQ_ID" --version "$UPDATED_VERSION" | jq .content
jq '.content.payload.faq.answer += " Support can check the current stage but cannot guarantee a bank posting date."' \
faq.json > faq-update.json
curl --fail-with-body --silent --show-error \
-X POST "$PLAYBOOK_API/faqs/$FAQ_ID/versions" \
-H "Authorization: Bearer $IRIS_AGENT_PLAYBOOKS_API_KEY" \
-H 'Content-Type: application/json' --data-binary @faq-update.json > updated.json
jq .version updated.json
The identifier stays the same, and search still serves the first publication. Review and publish the new version, then search again.
playbook.client.publish_faq(faq_id=faq_id, version=updated.version.version)
updated_results = playbook.client.search_faqs(
query="Where can I see how my refund is progressing?",
filter_={"eq": {"name": "locale", "value": "en-US"}},
limit=5,
)
for match in updated_results.items:
if match.entry_id == faq_id and match.faq is not None:
print("Published answer:", match.version, match.faq.answer)
iris agent-playbooks faqs publish "$FAQ_ID" --version "$UPDATED_VERSION"
iris --json agent-playbooks faqs search --input search.json | jq .items
curl --fail-with-body --silent --show-error \
-X PUT "$PLAYBOOK_API/faqs/$FAQ_ID/publication" \
-H "Authorization: Bearer $IRIS_AGENT_PLAYBOOKS_API_KEY" \
-H 'Content-Type: application/json' \
--data "$(jq '{version: .version.version}' updated.json)" | jq .publication
curl --fail-with-body --silent --show-error \
-X POST "$PLAYBOOK_API/faqs/search" \
-H "Authorization: Bearer $IRIS_AGENT_PLAYBOOKS_API_KEY" \
-H 'Content-Type: application/json' \
--data '{"query":"Where can I see how my refund is progressing?","filter":{"eq":{"name":"locale","value":"en-US"}},"limit":5}' \
| jq --arg id "$FAQ_ID" '.items[] | select(.entryId == $id) | {version, answer: .faq.answer}'
The result for your FAQ should now identify version 2 and include the added sentence about bank posting dates. If it still shows version 1 or is absent, repeat only the search.
Clean up
Keep the sample published while testing an agent. Complete-program copies retain the FAQ. When you finish, set PLAYBOOK_FAQ_ID to the printed entry identifier and run the deletion example with the same connection values. Withdraw it instead to retain history.
playbook.client.delete_faq(faq_id=faq_id)
import os
from redis_iris.agent_playbooks import AgentPlaybooks
playbook = AgentPlaybooks.from_env()
print(playbook.client.get_playbook_health())
faq_id = os.environ["PLAYBOOK_FAQ_ID"]
playbook.client.delete_faq(faq_id=faq_id)
iris agent-playbooks faqs delete "$PLAYBOOK_FAQ_ID" --yes
set -euo pipefail
iris agent-playbooks faqs delete "$PLAYBOOK_FAQ_ID" --yes
curl --fail-with-body --silent --show-error \
-X DELETE "$PLAYBOOK_API/faqs/$FAQ_ID" \
-H "Authorization: Bearer $IRIS_AGENT_PLAYBOOKS_API_KEY"
set -euo pipefail
set -euo pipefail
PLAYBOOK_API="${IRIS_AGENT_PLAYBOOKS_URL%/}/iris/data/v1/agent-playbooks/playbooks/$IRIS_AGENT_PLAYBOOKS_PLAYBOOK_ID"
curl --fail-with-body --silent --show-error \
"$PLAYBOOK_API/health" \
-H "Authorization: Bearer $IRIS_AGENT_PLAYBOOKS_API_KEY" | jq .
FAQ_ID="${PLAYBOOK_FAQ_ID:?Set PLAYBOOK_FAQ_ID to the printed entry identifier}"
curl --fail-with-body --silent --show-error \
-X DELETE "$PLAYBOOK_API/faqs/$FAQ_ID" \
-H "Authorization: Bearer $IRIS_AGENT_PLAYBOOKS_API_KEY"
Use Python asynchronously
Unified Python helpers use names such as adiscover and askill. Generated operations on .client use an _async suffix. This standalone example uses async with to close connections after the search. Save it as async_search.py and run python async_search.py with the same environment variables and a reader key. Keep an FAQ published to test matching.
import asyncio
from redis_iris.agent_playbooks import AgentPlaybooks
async def main():
async with AgentPlaybooks.from_env() as playbook:
results = await playbook.client.search_faqs_async(
query="Where can I see how my refund is progressing?",
limit=5,
)
for match in results.items:
if match.faq is not None:
print(match.faq.answer)
asyncio.run(main())
import asyncio
from redis_iris.agent_playbooks import AgentPlaybooks
async def main():
async with AgentPlaybooks.from_env() as playbook:
results = await playbook.client.search_faqs_async(
query="Where can I see how my refund is progressing?",
limit=5,
)
for match in results.items:
if match.faq is not None:
print(match.faq.answer)
asyncio.run(main())
AgentPlaybooksSettings.timeout sets the request timeout in seconds. Generated .client operations accept timeout_ms in milliseconds. For custom transport settings, construct a generated client and pass it to AgentPlaybooks(client=...). See the generated client's custom HTTP client documentation for ownership and configuration details.
Next steps
- Manage FAQs to roll back or withdraw an answer.
- Manage skills to publish a procedure and load supporting files.
- Review proposals to accept suggested answers before publishing them.
- Integrate with an agent to retrieve context before a turn and load skills through MCP.