OpenAI Agents SDK
An agent in the OpenAI Agents SDK runs through Runner.run. The model
loops over your function tools inside that call, and hands back a final
output. So the kslate calls sit outside the run. You read the record, call
Runner.run, and write what it returned.
Runner.run is a coroutine and decide is an ordinary function, so
decide runs it with asyncio.run. The model here is Claude through the
SDK’s LiteLLM adapter.
import asyncio
import os
from agents import Agent, Runner, function_tool, set_tracing_disabled
from agents.extensions.models.litellm_model import LitellmModel
from kslate import Agent as KslateAgent, Client, StalePremiseError, Write, run
RECORD = "incidents/inc-31"
client = Client("http://127.0.0.1:8080", api_key="rem-token")
set_tracing_disabled(True) # the SDK traces to OpenAI by default
@function_tool
def restart_deployment(name: str) -> str:
"""Restart a Kubernetes deployment. Returns the kubectl output."""
return f"deployment.apps/{name} restarted"
remediator = Agent(
name="Remediator",
instructions="You carry out one remediation step. Reply with one line saying what you did.",
tools=[restart_deployment],
model=LitellmModel(model="anthropic/claude-sonnet-5", api_key=os.environ["ANTHROPIC_API_KEY"]),
)
def decide(fields):
plan = fields["planned_action"]["value"]
print(f"carrying out: {plan}")
result = asyncio.run(Runner.run(remediator, f"Carry out: {plan}"))
return [Write(op="append", field="actions_taken", entry=result.final_output, evidence="kubectl output")]
try:
run(KslateAgent(decide=decide), client, record=RECORD, max_attempts=1)
print("write accepted")
except StalePremiseError as e:
for m in e.moved:
print(f"rejected: {m.field} moved v{m.your_version} -> v{m.current_version} by {m.current_author}")
print("the tool already ran, so this does not re-decide")Both libraries export a class called Agent, so one of them is renamed
on import. run() sends the versions it read as based_on, and the
gateway rejects the write if planned_action moved while the run was in
progress.
Run it against the gateway from Install, with a plan already on the record:
carrying out: roll back api deployment
write acceptedThe gateway rejects the write when the diagnoser replaces the plan while the run is in progress:
carrying out: restart payments-api pods
rejected: planned_action moved v1 -> v2 by apikey:diagnoser
the tool already ran, so this does not re-decideWhy max_attempts=1
restart_deployment acts inside the run, before any kslate write exists.
If the gateway rejects the write and run() called decide again, the
model would restart the deployment a second time.
max_attempts=1 calls decide once and raises the rejection instead.
Handling
rejections
covers what to do with it. Leave max_attempts at its default only when
running your tools twice is harmless.
Handoffs and the record
A handoff to another SDK agent happens inside Runner.run. The gateway
sees the result of the whole run, written once by the identity this client
holds. To attribute each agent’s writes separately, run each one with its
own client, its own credential and its own role. Scope and
identity has the shape of those
roles.
Written against openai-agents 0.20.0 and kslate 0.9.0.