Client usage
Examples below match the published SDKs:
- Python:
exemplar-harness-sdk· live demos - TypeScript:
@exemplar-dev/exemplar-harness-typescript-sdk· GitHub · examples
Set EXEMPLAR_API_KEY, then:
Python
from exemplar_harness import Harness
harness = Harness.from_env()Memory — add, search, and recall
At least one scope is required on create (user_id / userId, agent_id / agentId, session_id / sessionId, or app_id / appId).
| Param | Values | Notes |
|---|---|---|
search_mode / searchMode | hybrid · semantic · keyword | Omit to use the server default (hybrid when enabled) |
format | json · json_compact · markdown · toon | recall defaults to markdown; search defaults to json |
Python
memory = harness.memory(user_id="user-123", session_id="chat-abc", app_id="my-app")
memory.add("User prefers bullet-point answers.", memory_type="preference")
# Prompt-ready string (server-rendered body when available)
context = memory.recall(
"how should I format answers?",
format="markdown",
search_mode="hybrid",
)
# Structured hits
results = memory.search(
"formatting preferences",
top_k=5,
search_mode="hybrid",
format="json",
)
# Records + rendered body (toon / markdown / json_compact)
envelope = memory.search_envelope("theme settings", format="toon", search_mode="hybrid")
prompt_block = envelope.body # inject into the system prompt
listed = memory.list(limit=20)
record = memory.get(listed[0].memory_id)
memory.update(record.memory_id, content="User prefers numbered lists.")
memory.delete(record.memory_id)Generic hook for any agent loop (no framework dependency):
from exemplar_harness.integrations.memory import MemoryHook
memory = harness.memory(user_id="user-123", session_id="chat-abc", app_id="my-app")
hook = MemoryHook(memory, recall_top_k=5, auto_add=False)
user_input = "What do you know about me?"
recall = hook.before_turn(user_input) # prepend to system prompt
# ... run LLM ...
hook.after_turn(user_input, assistant_output) # no-op unless auto_add=TrueSkills — create, search, install
Python
skills = harness.skills()
record = skills.create(
name="refund-policy",
instructions="# Refund policy\n\nReturns within 30 days.\n\nSee [references/policy.md](references/policy.md).",
description="Refund workflow",
tags=["support"],
files={"references/policy.md": "# Policy\n\n30-day returns.\n"},
)
items = skills.list(limit=20)
fetched = skills.get("refund-policy")
hits = skills.search("refund", top_k=5)
# Materialize SKILL.md + supporting files for agent runtimes
skills.install(".agents/skills", names=["refund-policy"])Prefer skills.install(dest) folders (SKILL.md + files). record.instructions is the markdown body only—useful for quick editor/MCP use, not the full skill package.
HITL — approval gates
Trigger pauses with SDK helpers (hitl.ask, request_approval, framework tools). Operators approve or deny in the dashboard Marshal → HITL Approvals inbox. Blocking wait polls; there are no webhooks. See Human-in-the-loop.
Python
from exemplar_harness import Harness, HITLTimeoutError
harness = Harness.from_env(agent_id="deploy-agent")
hitl = harness.hitl()
try:
result = hitl.ask(
"Deploy build 1234 to production?",
description="All checks green. Blast radius: payments service.",
payload={"build": "1234", "service": "payments"},
ttl_seconds=1800, # server-side expiry
timeout=1800, # local wait budget (seconds)
poll_interval=5,
)
except HITLTimeoutError:
result = None
if result and result.approved:
deploy()
elif result and result.state == "rejected":
print(f"Rejected: {result.response.comment}")Free-text input, single choice, and non-blocking create/cancel:
answer = hitl.ask("Which rollback strategy?", request_type="input")
print(answer.response.text)
choice = hitl.ask(
"Pick a deployment region",
request_type="select",
options=["us-east-1", "eu-west-1"],
)
print(choice.response.option)
request = hitl.request_approval("Rotate production credentials?")
status = hitl.get(request.request_id)
if status.is_pending:
hitl.cancel(request.request_id)As a tool in any agent framework (wrap the sync ask call):
from langchain_core.tools import tool
@tool
def ask_human_approval(question: str) -> str:
"""Ask a human operator to approve or reject an action."""
result = harness.hitl().ask(question, timeout=900)
if result.approved:
return "approved"
return f"denied ({result.state}): {result.response.comment or 'no comment'}"Prompts — create, build, run
Python
prompts = harness.prompts()
record = prompts.create(
name="support-summary",
title="Support summary",
messages=[
{"role": "system", "content": "Be concise."},
{"role": "user", "content": "Summarize topic: {{topic}}."},
],
variables=["topic"],
)
result = prompts.run("support-summary", variables={"topic": "returns"})
print(result["content"])
# Local {{var}} substitution for your own agent framework (no Exemplar model call)
built = prompts.build("support-summary", variables={"topic": "returns"})
# built.messages -> [{"role": "system", ...}, {"role": "user", ...}]Publish a new version, or run inline without a stored prompt:
prompts.publish_version(
"support-summary",
messages=[
{"role": "system", "content": "Be concise."},
{"role": "user", "content": "Bullet summary for: {{topic}}."},
],
change_notes="Use bullets",
)
inline = prompts.run_inline(
messages=[
{"role": "system", "content": "Be concise."},
{"role": "user", "content": "Say hello."},
],
model="openai/gpt-4o-mini",
)Relay — policy decide / observe / evaluate
harness.relay({ surface }) enforces org Relay Control rules on runtime tool calls (allow / ask / deny). Shares the Harness API key, base URL, and agent_id / agentId. Optional EXEMPLAR_RELAY_BASE overrides the Relay API origin.
Runnable samples: python/relay/ · typescript/relay/ · catalog Live SDK examples.
Surfaces (wire header x-relay-surface): claude_sdk | adk | agno | openai_agents | langchain | langgraph | pydantic_ai | crewai | semantic_kernel | mastra (TypeScript).
| Adapter | Python | TypeScript |
|---|---|---|
| Claude Agent SDK hooks | relay.claude_hooks(...) | relay.claudeHooks(...) |
| OpenAI Agents guardrails | relay.openai_tool_input / openai_tool_output | relay.openaiToolInput / openaiToolOutput |
| LangChain / LangGraph middleware | relay.langchain_middleware / langgraph_middleware | relay.langchainMiddleware / langgraphMiddleware |
| Mastra tool hooks | — | relay.mastraHooks(...) |
| Pydantic AI hooks | relay.pydantic_ai_hooks(...) | — (Python-only) |
| CrewAI tool hooks | relay.crewai_hooks / crewai_register | — (Python-only) |
| Semantic Kernel filter | relay.semantic_kernel_filter / semantic_kernel_register | — (Python-only) |
| Agno tool hook | relay.agno_tool_hook(...) | — (Python-only) |
| Google ADK callbacks | relay.adk_before_tool / adk_after_tool | — (Python-only) |
Python
from exemplar_harness import Harness
from exemplar_harness.relay import RelayDecision
harness = Harness.from_env(agent_id="support-bot")
relay = harness.relay(surface="langchain", source_app="my-app")
# LangChain / LangGraph create_agent middleware
agent = create_agent(
model=...,
tools=[...],
middleware=[relay.langchain_middleware(session_id="sess-abc")],
)
# Pydantic AI
# agent = Agent(..., capabilities=[relay.pydantic_ai_hooks(session_id="sess-abc")])
# CrewAI: before, after = relay.crewai_hooks(session_id="sess-abc")
# Semantic Kernel: relay.semantic_kernel_register(kernel, session_id="sess-abc")
# Agno: tool_hooks=[relay.agno_tool_hook(session_id="sess-abc")]
# Hook-free dry-run (CI / preflight)
verdict = relay.evaluate(
tool_name="shell",
arguments={"command": "rm -rf /tmp/x"},
user_id="user_123",
)
if verdict.decision is RelayDecision.DENY:
raise RuntimeError(verdict.reason)IDE Connect (Cursor, Claude Code, Codex, OpenCode) still uses Console Relay → Connect / exemplar-skills. SDK Relay is for in-process agent frameworks that share the same Control + Enforcement stack. See Relay and Connect surfaces.
MCP tools
harness.tools() builds framework-native MCP clients wired to the Exemplar MCP server (override with EXEMPLAR_MCP_URL). Live demos use this for Linear and other platform tools.
# Agno — pattern from examples/live/agno_demo.py
from agno.agent import Agent
from exemplar_harness.integrations.agno import harness_agno_post_hook
tools_client = harness.tools()
mcp = tools_client.for_provider("agno")
await mcp.connect()
try:
agent = Agent(
name="support-bot",
model=..., # e.g. Gemini / OpenAIChat
tools=[mcp],
post_hooks=[
harness_agno_post_hook(
harness, session_id="sess-abc", agent_id="support-bot"
)
],
)
await agent.arun("Find open Linear issues about session ingest.")
finally:
await mcp.close()Other providers expose either a native client (for_provider("langchain"), "claude_agent", …) or a tool schema + execute path (for_provider_tools_schema / execute_provider_tool for OpenAI, Anthropic, LiteLLM, Google GenAI, and similar). Supported names: HarnessTools.supported_providers().
Full recipes for LangChain, Agno, Google ADK, and adapter SDKs—including MCP + A2A mixes—live under Tools & MCP: Frameworks with Marshal SDK.
Session ingest
Direct ingest (no framework)
Python
harness.ingest(
"generic",
session_id="sess-abc",
event="turns",
data={
"turns": [
{
"input": "What is harness eval?",
"output": "Automated judge over agent sessions.",
"model": "gpt-4o",
}
]
},
agent_id="my-agent",
source_app="my-app",
)Session helper with auto judge / session eval
Ingest alone stores the session. Set flags to queue Insights (judge) and/or Session Evals after ingest. Full console + SDK trigger guide: Evals.
Python
session = harness.session(
"sess-abc",
agent_id="support-bot",
source_app="my-app",
auto_judge_run=True, # Insights (LLM judge)
auto_session_eval=True, # per-turn Session Evals
)
session.ingest(
"generic",
event="turns",
data={"turns": [{"input": "Hello", "output": "Hi!", "model": "gpt-4o"}]},
)
# Or trigger judge later on existing sessions
harness.runs.trigger(session_ids=["sess-abc"], sync=False)Framework helpers (Python)
Attach an ingest hook so agent turns land in Marshal (and optionally queue Insights / Session Evals). Guide: Evals → Agent hooks.
pip install "exemplar-harness-sdk[agno]"from agno.agent import Agent
from agno.models.openai import OpenAIChat
from exemplar_harness import Harness
from exemplar_harness.integrations.agno import harness_agno_post_hook
harness = Harness.from_env()
agent = Agent(
name="support-bot",
model=OpenAIChat(id="gpt-4o"),
post_hooks=[
harness_agno_post_hook(
harness,
session_id="sess-abc",
agent_id="support-bot",
source_app="my-app",
auto_judge_run=True,
auto_session_eval=True,
)
],
)
agent.run("Summarize our refund policy.")pip install "exemplar-harness-sdk[langchain]"from langchain_openai import ChatOpenAI
from exemplar_harness import Harness
from exemplar_harness.integrations.langchain import make_langchain_callback_handler
harness = Harness.from_env()
handler = make_langchain_callback_handler(
harness,
session_id="sess-abc",
chain_name="support-bot",
source_app="my-app",
auto_judge_run=True,
auto_session_eval=True,
)
llm = ChatOpenAI(model="gpt-4o-mini", callbacks=[handler])
# Pass the same handler in invoke/ainvoke config={"callbacks": [handler]}pip install "exemplar-harness-sdk[openai]"from openai import OpenAI
from exemplar_harness import Harness
from exemplar_harness.integrations.openai import HarnessOpenAICallback, sdk_chat_completion
harness = Harness.from_env()
callback = HarnessOpenAICallback(
harness,
session_id="sess-abc",
agent_id="support-bot",
source_app="my-app",
auto_judge_run=True,
auto_session_eval=True,
)
client = OpenAI()
sdk_chat_completion(
harness,
callback,
client,
model="gpt-4o",
messages=[{"role": "user", "content": "Summarize our refund policy."}],
)Other first-class extras: [google-adk] (ingest_adk_session(..., auto_judge_run=True)), [claude-agent]. Additional frameworks (LangGraph, CrewAI, LiteLLM, …) are listed in the Python SDK README — Framework extras .
Framework helpers (TypeScript)
npm install @exemplar-dev/exemplar-harness-typescript-sdk aiimport { generateText } from "ai";
import {
Harness,
ExemplarVercelAISession,
} from "@exemplar-dev/exemplar-harness-typescript-sdk";
const harness = Harness.fromEnv({ agentId: "support-bot" });
const session = new ExemplarVercelAISession(harness, {
sessionId: "sess-abc",
autoJudgeRun: true,
autoSessionEval: true,
});
const prompt = "Summarize our refund policy.";
await generateText({
model: yourModel,
prompt,
onFinish: session.onFinish(prompt),
});import { Agent, run } from "@openai/agents";
import {
Harness,
wrapOpenAIAgentsRun,
} from "@exemplar-dev/exemplar-harness-typescript-sdk/integrations/openai-agents";
const harness = Harness.fromEnv({ agentId: "support-bot" });
const tracedRun = wrapOpenAIAgentsRun(harness, run, {
sessionId: "sess-abc",
autoJudgeRun: true,
autoSessionEval: true,
});
await tracedRun(new Agent({ name: "Assistant", instructions: "Be helpful." }), prompt);Also available: Anthropic, Claude Agent SDK, LangChain.js / LangGraph.js, Mastra, Google GenAI, LlamaIndex.TS, Portkey. Full matrix: Frameworks with Marshal SDK and the TypeScript SDK README .
Live demos
Full curated catalog: Live SDK examples · Repo: exemplar-platform-samples
git clone https://github.com/Exemplar-Dev/exemplar-platform-samples.git
cd exemplar-platform-samples/python
pip install -r requirements.txt && cp .env.example .env
EXEMPLAR_API_KEY=... python -m platform.skills
EXEMPLAR_API_KEY=... python -m platform.prompts
EXEMPLAR_API_KEY=... python -m platform.memory
EXEMPLAR_API_KEY=... python -m platform.hitl
EXEMPLAR_API_KEY=... python -m frameworks.langchain_mcp
EXEMPLAR_API_KEY=... python -m relay.evaluate
EXEMPLAR_API_KEY=... python -m relay.langchain
EXEMPLAR_API_KEY=... python -m relay.openai_agents| Feature | Sample |
|---|---|
| Skills CRUD | python/platform/skills.py |
| Prompts CRUD | python/platform/prompts.py |
| Memory CRUD | python/platform/memory.py |
| HITL approvals | python/platform/hitl.py |
| Agno + MCP | python/frameworks/agno_mcp.py |
| LangChain + MCP | python/frameworks/langchain_mcp.py |
| Relay adapters | python/relay/ · typescript/relay/ |
TypeScript Relay + ingest: typescript/.