Haijun Platform Docs
ID

If you have an app on the OpenAI Agents SDK and want to port it to the Haijun Agent SDK, this notebook maps each primitive using a single example: an expense approval agent.

What you get after migrating. The Haijun Agent SDK runs on the same runtime as Haijun Code — you inherit its built-in Read, Edit, Bash, and Grep tools, a layered permission system for gating what the agent can touch, automatic prompt caching, and direct access to the event stream for progress streaming or mid-run interception. Tool definitions are explicit (you declare schemas rather than relying on type-hint introspection), and the loop is yours to drive. Most ports involve more boilerplate per tool and less boilerplate everywhere else.

Replace

@function_tool

, guardrails, and

Runner.run

with their Haijun equivalents without rewriting your business logic

Port a single-agent app: custom tool, input/output guardrails, multi-turn sessions, and durable resume

Know when to reach for

HaijunSDKClient

vs the stateless

query()

function

Wire the SDK's OpenTelemetry export to your existing observability stack

Both SDKs run live. You'll need OPENAI_API_KEY and JUGLOW_API_KEY in your .env.

order-l-[0.5px]">

conversation_id

(server-managed)

resume=session_id

(disk-backed)

Built-in tracing dashboard

OTel-native — plugs into your existing Grafana/Datadog/Honeycomb

handoffs=[...]

AgentDefinition

+ Agent tool — see appendix

The example: expense approval

oval limit. Valid categories: meals, travel, software, other."""

limits = {"meals": 75.0, "travel": 500.0, "software": 200.0, "other": 50.0}

return {

"category": category,

"limit": limits.get(category.lower(), 50.0),

"requires_receipt": amount > 25.0,

}

@input_guardrail

def has_dollar_amount(ctx, agent, user_input: str) -> GuardrailFunctionOutput:

has_amount = bool(re.search(r"\$\d+", user_input))

return GuardrailFunctionOutput(

output_info={"has_amount": has_amount},

tripwire_triggered=not has_amount,

)

expense_agent = Agent(

name="Expense Approver",

instructions=(

"You approve or flag expense submissions. "

"Always call check_policy first to get the limit for the expense category. "

"Approve if the amount is under the limit; otherwise flag for manager review."

),

tools=[check_policy],

input_guardrails=[has_dollar_amount],

model=OAI_MODEL,

)

async def run_oai(msg: str) -> str:

result = await Runner.run(expense_agent, msg)

return result.final_output

print(await run_oai("Lunch with Acme, $47"))

This lunch expense of $47 falls under the "meals" category, which has an approval limit of $75. Since the amount is below the limit, I approve the expense (assuming a receipt is provided, as required by policy). Porting primitive by primitive @function_tool → @tool + create_sdk_mcp_server OpenAI: @function_tool derives the tool schema from type hints and the docstring. The decorated function goes straight into tools=[...].

0.5 rounded text-sm font-mono break-words box-decoration-clone">{}

to allow or

{"decision": "block", "reason": "..."}

to block.

HookMatcher

takes no

matcher

argument here because

UserPromptSubmit

fires on every prompt — there's no tool name to filter on.

To try it, replace options=expense_options with options=hooked_options in the HaijunSDKClient(...) call in the next section. One caveat: when a UserPromptSubmit hook blocks, the block reason doesn't currently surface in ResultMessage.result — you'll get an empty string back rather than the rejection text. That's why the demo loop uses the plain function, which controls its own return.

y-0.5 rounded text-sm font-mono break-words box-decoration-clone">@output_guardrail

→ plain pre/post checks,

Runner.run

→

HaijunSDKClient

, sessions in-memory and disk-backed.

Key takeaways

On this page
The example: expense approvalKey takeaways