Dayward AI
Week 1 · D4About 4 hours

The OpenAI Agents SDK: Agents, Handoffs, Guardrails, Sessions, Tracing

Fold yesterday's hand-written loop into a few lines of API with the official Agents SDK: how agents hand off to each other, how to put guardrails at the entry and exit, how to remember multiple turns, and how to see every step clearly.

Today's goals 0/3

Sign in to tick these off and save your progress.

Today's Goals

  1. Write a minimal agent with a function tool using Agent and run or Runner, and explain what maxTurns does
  2. Use handoffs to let a triage agent pass the conversation to a specialist agent, and explain the trade-off against "one big agent with many tools"
  3. Write an input-side guardrail and catch its tripwire exception, and explain the split between input-side and output-side guardrails

Yesterday you hand-wrote one round trip of request-a-tool, execute, feed back. Today's questions are: what if that round trip has to run many times over? What if the work is too varied for one contractor? And how do you stop them from taking on work they should not, or saying things they should not? Once you have read this and finished the work, scroll back to the top and tick off the three goals.

Plain-Language Walkthrough

Agent and Runner: folding model, loop, and tools into one object and one function

In yesterday's script, the loop of the model requesting a tool, you executing it, feeding the result back, and asking again ran exactly once. Real tasks usually need several rounds: look up the list, then the details, then decide what to change. Hand-writing that while loop is not hard, but writing it again in every project gets tiresome, and there are edges to handle — a loop that will not stop, tools that throw, arguments that will not parse. The first thing OpenAI's Agents SDK does is put that loop away: the Agent object holds the instructions and the tools, and the run function turns the loop for you.

Back to the contractor analogy: an agent is a contractor with a clear job description (instructions) and a toolbox (tools); run is you handing over the work and waiting for them to come back with it. How many times they reached for a tool in between and how many rounds they thought is not your concern, only the result — unless the round count runs over.

agent.ts
import { Agent, run, tool } from '@openai/agents'
import { z } from 'zod'
 
const listRoutes = tool({
  name: 'list_routes',
  description: 'List every route of the TODO API',
  parameters: z.object({}),
  execute: async () => ['POST /todos', 'GET /todos', 'DELETE /todos/:id'],
})
 
const helper = new Agent({
  name: 'TODO API helper',
  instructions:
    'Answer only questions about this TODO API project, and call a tool before stating any fact.',
  model: process.env.OPENAI_MODEL,
  tools: [listRoutes],
})
 
const result = await run(helper, 'Which endpoints still have no tests?', { maxTurns: 5 })
console.log(result.finalOutput)

Compared with yesterday: the tool helper (the function_tool decorator in Python) turns a zod schema or type annotations into the JSON Schema you hand-wrote; execute is yesterday's runTool; and inside run sits yesterday's filter-for-function_call, feed-back-function_call_output, call-again. What it hides is exactly what you wrote by hand yesterday, which is why you know which layer to look in when something breaks.

maxTurns is one parameter you must understand. A turn is one model call, counted once per round of the loop, and exceeding the limit makes the SDK throw MaxTurnsExceededError (the TypeScript default is 10). It is not a performance parameter but a safety valve: an agent that misreads a tool result and keeps calling the same tool will, without that valve, burn money until you notice. Set it by task type in production: three to five rounds is plenty for a question-answering assistant, while a task editing several files may need twenty or more. TypeScript also has a Runner class — new Runner({ ... }) then runner.run() — for sharing one configuration (model, tracing, a default maxTurns) across a batch of runs; a single run is fine with run.

Function tools: declare the parameters and the SDK generates the schema

Tools are the agent's hands. The list_routes of the last section takes no parameters, while real tools almost always do. Declaring them works the same way on both sides — write the parameters in the type system you already know and the SDK converts them to JSON Schema: zod in TypeScript (note the SDK uses zod v4), and type annotations on the function signature plus a docstring in Python.

tool-with-params.ts
import { tool } from '@openai/agents'
import { z } from 'zod'
 
const todos = [{ id: 1, title: 'Add validation to POST /todos', done: false }]
 
export const markDone = tool({
  name: 'mark_done',
  description: 'Mark one TODO as done',
  parameters: z.object({
    id: z.number().int().describe('the id of the TODO'),
  }),
  execute: async ({ id }) => {
    const todo = todos.find((t) => t.id === id)
    if (!todo) return `There is no TODO with id ${id}`
    todo.done = true
    return `Done: ${todo.title}`
  },
})

Two engineering habits are worth building. First, the description and the parameter descriptions are interface documentation written for the model, and the more they read like plain speech, the less often the model picks the wrong tool or fills in the wrong argument — "Mark one TODO as done" is far better than "update todo." Second, a tool's return value is also written for the model, so returning "There is no TODO with id 7" beats throwing: a thrown exception hands the error text back to the model, which may or may not understand it, whereas a clear sentence is always understood and can be acted on.

When a tool has side effects (the mark_done above mutates data), think through the point from D1: if the model judges it should call the tool, your code really executes it. At the SDK level you can add a human confirmation with a tool-level guardrail (below) or a mechanism such as needsApproval, and the principle is the same as Codex's approvals — an irreversible action needs a human nod first.

Handoffs: the triage agent passes the whole conversation on

What happens when the work gets varied? Expecting one contractor to know everything is unrealistic. What companies do is triage at the front desk: you ask, the desk decides whose job it is, and then walks you over to that colleague, after which you talk to them directly and the desk stays out of it. In the Agents SDK that is a handoff — and note the difference from a tool: calling a tool means the contractor asked something on your behalf and came back to tell you; a handoff means the contractor gives the conversation to someone else, who answers from then on.

In implementation a handoff is a special tool: give the triage agent a set of handoffs and the SDK generates a tool named transfer_to_<agent_name> per target agent; the moment the triage agent calls one, the runtime hands the conversation (optionally filtered) to the target agent to continue.

handoff.ts
import { Agent, run } from '@openai/agents'
import { RECOMMENDED_PROMPT_PREFIX } from '@openai/agents-core/extensions'
 
const validationExpert = new Agent({
  name: 'validation_expert',
  instructions:
    'You handle input validation questions for the TODO API: schema design, error response shapes.',
  model: process.env.OPENAI_MODEL,
})
 
const testingExpert = new Agent({
  name: 'testing_expert',
  instructions: 'You handle unit testing questions for the TODO API: case design, test isolation.',
  model: process.env.OPENAI_MODEL,
})
 
const triage = Agent.create({
  name: 'triage',
  instructions: `${RECOMMENDED_PROMPT_PREFIX}
You are the triage desk. Send validation matters to validation_expert and testing matters to testing_expert; answer anything else yourself, briefly.`,
  model: process.env.OPENAI_MODEL,
  handoffs: [validationExpert, testingExpert],
})
 
const result = await run(triage, 'What should POST /todos return when title is empty?')
console.log(result.lastAgent?.name) // validation_expert
console.log(result.finalOutput)

RECOMMENDED_PROMPT_PREFIX is an official prefix telling the model it is part of a multi-agent system where handing off is a normal operation, and it measurably improves handoff accuracy. result.lastAgent (last_agent in Python) tells you who answered last, which is the most direct way to verify the routing — you will assert on it in the lab. For finer control, wrap it with the handoff() helper: toolNameOverride renames the tool, onHandoff fires a callback at handoff time (logging, notifications), inputFilter filters the history passed to the next agent (dropping earlier tool calls, for example, with ready-made filters such as removeAllTools in the official extensions), and inputType lets the model attach structured metadata at handoff time (a reason, a priority).

So when should you split into several agents with handoffs, and when is one big agent with a pile of tools better? The criterion is not how many tools there are but whether the instructions fight each other. The validation expert's instructions are all about zod and error shapes, the testing expert's all about case isolation and mocks — write both into the same instructions and the model switches between two contexts every time, error rates rise, and the prompt gets longer and more expensive. Split when the instructions are independent and the contexts clearly separable; do not split when the tools are many but share one body of background knowledge. There is also a hard constraint: a handoff happens inside the same run, and input-side guardrails apply only to the first agent in the chain — which leads to the next section.

Guardrails: check the user at the entrance, check the model at the exit

However capable the contractor, two things should not happen: taking on work they should not (a user asking the TODO API helper to write a love poem), and saying something they should not (an answer that leaks a database connection string). The first has to be blocked at the entrance, the second at the exit. The Agents SDK calls those two doors the input guardrail and the output guardrail, collectively guardrails.

A guardrail is a function: it receives the input (or output) and the context and returns a judgment, in which tripwireTriggered (tripwire_triggered in Python) being true means raise the alarm. When the alarm goes off, the run immediately throws InputGuardrailTripwireTriggered or OutputGuardrailTripwireTriggered, which you catch outside and use to decide what to tell the user.

guardrail.ts
import { Agent, run, InputGuardrailTripwireTriggered } from '@openai/agents'
import type { InputGuardrail } from '@openai/agents'
 
// A guardrail that costs nothing: pure rule, no model call
const onTopicOnly: InputGuardrail = {
  name: 'on_topic_only',
  execute: async ({ input }) => {
    const text = typeof input === 'string' ? input : JSON.stringify(input)
    const offTopic = !/todo|endpoint|validation|test|route|api/i.test(text)
    return { outputInfo: { offTopic }, tripwireTriggered: offTopic }
  },
}
 
const triage = new Agent({
  name: 'triage',
  instructions: 'You handle only questions about the TODO API project.',
  model: process.env.OPENAI_MODEL,
  inputGuardrails: [onTopicOnly],
})
 
try {
  const result = await run(triage, 'Write me a love poem')
  console.log(result.finalOutput)
} catch (err) {
  if (err instanceof InputGuardrailTripwireTriggered) {
    console.log('That is unrelated to the project; I only answer TODO API questions.')
  } else {
    throw err
  }
}

The guardrail above is pure rule, at zero cost and zero latency; more commonly you use a cheap small model as the guardrail — running a small classification-only agent inside execute to decide whether this is a project-related question. Input-side guardrails run in parallel with the main agent, so they do not slow a normal request down; and once the alarm goes off, the expensive run on the main agent is cancelled, which is precisely how guardrails save money: a cheap model blocks the requests that would otherwise send an expensive model on a pointless trip.

Input side or output side? The split is clear. The input side governs whether something should be done: off-topic requests, obvious injection attempts, requests outside the service's scope — the earlier they are blocked the more you save. The output side governs whether something may be said: leaked sensitive information, non-compliant formats, advice that breaks a business rule, none of which can be checked until the model has finished speaking. What each misses differs too: the input side cannot catch a normal question that gets an off-the-rails answer, and the output side cannot catch a model that has already called a tool with side effects — so tools with side effects need a third kind, a tool-level guardrail running around each function call. All three together make a complete line of defense.

Remember the hard constraint: input-side guardrails run only on the first agent in the chain, and output-side ones only on the agent that produces the final answer. So guardrails belong on the triage agent, and an input guardrail attached to a specialist agent will never run.

Sessions: hand multi-turn memory to the SDK to store

Yesterday covered the two routes for multi-turn history: server-side storage (previous_response_id) or carrying it yourself. The Agents SDK packages that choice as a session: pass a session object to run and the SDK pulls the history out of it before each run and writes new items back afterwards, and your code never touches the input array again.

session.ts
import { Agent, run, MemorySession } from '@openai/agents'
 
const helper = new Agent({
  name: 'helper',
  instructions: 'You are an assistant for the TODO API project.',
  model: process.env.OPENAI_MODEL,
})
 
const session = new MemorySession() // process memory; gone on restart
 
await run(helper, 'Our validation library is zod.', { session })
const result = await run(helper, 'Which validation library did I just mention?', { session })
console.log(result.finalOutput) // should mention zod

The available stores differ between the two sides: TypeScript has the in-process MemorySession and OpenAIConversationsSession hosted through the OpenAI Conversations interface; Python is richer, with SQLiteSession, RedisSession, SQLAlchemySession, and MongoDBSession, plus an EncryptedSession wrapper that adds transparent encryption to any session. A session's interface has only four actions: read the history, append items, pop the last item, and clear. That odd-looking pop_item has a real use: when a user says to take back what they just said, drop the last turn from the history and rerun.

Session or previous_response_id? A session's history is in your hands (memory, SQLite, Redis), so it can be audited, trimmed, and replayed against another model; a previous_response_id history sits at OpenAI, so requests are minimal but you cannot see the whole picture. You can use both — the SDK also supports the two server-side schemes previousResponseId and conversationId. The principle is yesterday's: a user-facing production system keeps at least one copy of the history itself.

Tracing: on by default

One last thing: once all this is running, how do you know whether triage handed off correctly, whether a guardrail blocked something wrongly, and which step burned the most tokens? The Agents SDK's answer is tracing, and it is on by default — every run produces a trace recording, hierarchically, each of the agent's turns, each model call, each tool call, each handoff, and each guardrail judgment, shipped to the Traces panel on the OpenAI platform for visualization.

You only need three actions. Look: after a real run, find it on the platform's Traces page and expand it to see which turn the triage agent called transfer_to_validation_expert on. Turn it off: the environment variable OPENAI_AGENTS_DISABLE_TRACING=1 disables it globally, and TypeScript also accepts tracingDisabled in the run configuration. Turn it off when handling sensitive data or where compliance requires data not to leave a jurisdiction, or swap in your own exporter with setTraceProcessors — pointing it at OpenTelemetry, say. Group: by default each run is one trace, and to gather a multi-turn conversation into one, wrap several runs with withTrace (with trace(...) in Python) or set groupId to correlate them by conversation id.

Tracing is the most-skipped and most valuable section of this course. Without it, you can only guess when a multi-agent system misbehaves; with it, "why did this question go to the testing expert" is a fact you can see in thirty seconds. The evaluation and observability of D21 of the 30-day course already has an out-of-the-box starting point here.

Source Reading

Hands-On Lab

🧪 D4 lab: a two-agent handoff plus one guardrail

Code location: labs/codex-mastery/day-04-handoff-guardrail

Acceptance criteria:

  1. MOCK=1 pnpm start "what should an empty title return" prints lastAgent as validation_expert, and a testing-related question prints testing_expert.
  2. MOCK=1 pnpm start "write me a love poem" prints the guardrail's friendly refusal rather than a stack trace, and the process exits 0.
  3. In --session mode, asking two questions in a row has the second answer cite a fact given in the first.
  4. Dropping MOCK for one real run, the trace is findable in the Traces panel and the transfer_to_validation_expert step is visible in it.

The code is in labs/codex-mastery/day-04-handoff-guardrail, with four exercise points cut out of starter/ and complete answers in solution/. Under MOCK no model is called, but the guardrails, routing, and sessions all execute for real — which is the point of MOCK: verifying your orchestration logic rather than the model.

  1. Run MOCK=1 pnpm start "which endpoints are there" and watch a minimal agent with a function tool answer while printing lastAgent.
  2. Do exercise 1: split helper into triage plus two specialists wired with handoffs; run one validation question and one testing question and confirm lastAgent names the right specialist each time.
  3. Do exercises 2 and 3: write the onTopicOnly input guardrail onto triage, catch InputGuardrailTripwireTriggered in main and print the friendly message, then run "write me a love poem" and confirm it is blocked.
  4. Do exercise 4: under --session, run twice against the same MemorySession and confirm the second run remembers the first.
  5. Configure the key and model, drop MOCK for one run, then find that run in the Traces panel and see which turn the handoff happened on.

Interview Questions

Today's 3 questions are in the question bank below, weighted toward the criteria for splitting into multiple agents, where a guardrail belongs, and the engineering trade-offs of conversation memory and observability. Expand each one and read the analysis before the key points — practicing the derivation beats memorizing bullets.

Checklist and Tomorrow

  • Write a minimal agent with a function tool using Agent and run or Runner, and explain what maxTurns does
  • Use handoffs to let a triage agent pass the conversation to a specialist agent, and explain the trade-off against "one big agent with many tools"
  • Write an input-side guardrail and catch its tripwire exception, and explain the split between input-side and output-side guardrails
  • Say how the constraint that input guardrails run only on the first agent affects where you place a guardrail
  • All 4 acceptance criteria of the lab pass
  • Answer at least 2 of the 3 interview questions without looking at the key points

Tomorrow (D5) closes the course: take the same task — add input validation and unit tests to the TODO API — through both Codex and Claude Code, compare them across four dimensions of briefing, approval, verification, and cost, and then combine the two tools into a daily write-one-review-one workflow. Putting the comparison last is deliberate: after four days you know how every layer of Codex works, so when you see the differences tomorrow you can judge whether each one is a difference in way of working or a difference in capability — because those two call for entirely different responses.

Interview questions

  • When should you split one agent into several connected by handoffs, and when is a single agent with many tools the better design?什么时候该把一个 Agent 拆成多个、用 handoff 交接?什么时候「一个大 Agent 加很多工具」反而更好?
    Common in ChinaCommon overseasIntermediate#agents-sdk#handoffs#architecture

    How to reason about it · think before answering

    1. This tests your splitting criterion, not API fluency; 'split when there are many tools' is the common wrong answer.
    2. First separate handoffs from tools: a tool call fetches an answer and returns; a handoff transfers the whole conversation so the receiving agent owns it, even though it is implemented as a transfer_to_xxx tool.
    3. The criterion is whether instructions conflict: when two task groups need independent, clashing background, constraints and tone, one instruction block forces constant context switching, longer prompts and more errors, so split; many tools sharing one background do not justify a split.
    4. Name the costs: an extra model call for triage, possible misrouting, input guardrails only on the first agent, and history trimming across agents via inputFilter.
    5. Expect the follow-up: what if triage misroutes? Use RECOMMENDED_PROMPT_PREFIX, assert on lastAgent in regression tests, inspect the handoff turn in tracing, and allow experts to hand back.

    分析过程 · 先想清楚再作答

    1. 这题考的是拆分判据,不是会不会用 API。答「工具多了就拆」是最常见的错误,工具数量不是判据。
    2. 先说清 handoff 与工具的区别:调工具是替你去问一句再回来,handoff 是把对话整个交给另一个 Agent,之后由它负责;实现上 handoff 也是一个名为 transfer_to_xxx 的工具,但语义是转移控制权。
    3. 判据是「指令会不会互相打架」:两组任务需要的背景知识、约束、语气彼此独立且冲突时,塞进一份 instructions 会让模型反复切换上下文、提示越长越贵、出错率上升,这时拆;工具虽多但共享同一套背景的,不拆。
    4. 补拆分的代价:多一次模型调用(分诊那一跳)、路由可能错、输入护栏只在第一个 Agent 上跑、跨 Agent 的历史要靠 inputFilter 裁剪。
    5. 可预期的追问:分诊错了怎么办?用 RECOMMENDED_PROMPT_PREFIX 提高交接准确率,用 lastAgent 做回归断言,用 tracing 看交接发生在哪一轮,必要时让专家 Agent 也能交接回分诊台。

    Key points

    • A handoff transfers conversational control; a tool call only fetches a result
    • Split on conflicting instructions, not on tool count
    • Costs: an extra hop, possible misrouting, input guardrails only on the first agent
    • Control routing quality with the recommended prefix, lastAgent assertions and tracing

    答题要点

    • handoff 转移的是对话控制权,工具调用只是取一次结果
    • 拆分判据是指令是否互相打架,不是工具数量
    • 拆的代价:多一跳、可能路由错、输入护栏只在第一个 Agent 生效
    • 用前缀提示、lastAgent 断言与 tracing 控制路由质量
  • Should guardrails sit on the input side or the output side? What does each cost, what does it catch, and what slips through?guardrail 应该放在输入侧还是输出侧?各自的成本、能拦住什么、拦不住什么?
    Common in ChinaCommon overseasDeep dive#agents-sdk#guardrails#safety

    How to reason about it · think before answering

    1. The crux is 'what slips through'; saying 'use both' without naming each side's blind spot signals no production incidents survived.
    2. Division of labor: input guardrails decide whether to act at all (off-topic, obvious injection, out of scope) and are cheapest early; output guardrails decide whether the answer may be said (leaks, format, policy) and can only run after generation.
    3. Cost: input guardrails run in parallel with the main agent and cancel its expensive run on a tripwire, so a cheap classifier there saves money; output guardrails wait for the full run and only prevent incidents.
    4. Blind spots: input cannot catch a normal question with a drifting answer; output cannot undo a side-effecting tool already called, hence a third layer of tool-level guardrails around each function call.
    5. Add the SDK constraint: input guardrails run only on the first agent, output guardrails only on the agent producing the final answer; misplaced guardrails never execute.
    6. Expect the follow-up: the common failure mode? Too strict, not too loose; regex blocklists over-block real users, so keep a regression set of legitimate requests and watch the false-block rate.

    分析过程 · 先想清楚再作答

    1. 题眼在「拦不住什么」。只说两边都要放而不说各自的漏网情况,就是没在生产里被漏网案例打过脸。
    2. 先给分工:输入侧管「该不该做」——话题越界、明显注入、超出服务范围,越早拦越省;输出侧管「能不能说」——泄露敏感信息、格式不合规、违反业务规则,只有模型说完才能查。
    3. 再说成本:输入护栏与主 Agent 并行跑,警报一响就取消主 Agent 的昂贵运行,所以用便宜小模型做输入护栏是省钱手段;输出护栏必须等主 Agent 跑完,省不了钱,只能防事故。
    4. 漏网情况:输入侧拦不住「问题正常但回答跑偏」;输出侧拦不住「模型已经调了有副作用的工具」——所以有副作用的工具需要第三层,围着每次函数调用跑的工具级护栏。
    5. 补一条 SDK 约束:输入护栏只在链条第一个 Agent 上跑,输出护栏只在产出最终回答的 Agent 上跑,挂错位置等于没挂。
    6. 可预期的追问:护栏最常见的失败模式是什么?太严而不是太松——正则黑名单误拦正常用户;上线前要有正常请求的回归集,误拦率是必看指标。

    Key points

    • Input side decides whether to act and is cheapest early; output side decides what may be said and only runs afterwards
    • Input guardrails run in parallel and cancel the main run, so cheap models save money there; output guardrails only prevent incidents
    • Input misses drifting answers, output misses side effects already taken; tool-level guardrails add the third layer
    • Input guardrails run only on the first agent; the common failure is over-blocking, so keep a regression set

    答题要点

    • 输入侧管该不该做,越早拦越省;输出侧管能不能说,只能事后查
    • 输入护栏与主 Agent 并行、触发即取消,便宜模型在此省钱;输出护栏省不了钱只防事故
    • 输入侧漏「回答跑偏」,输出侧漏「已调有副作用的工具」,需工具级护栏补第三层
    • 输入护栏只在第一个 Agent 生效;常见失败是太严,需正常请求回归集
  • Both Agents SDK sessions and the Responses API's previous_response_id remember multi-turn state. How do you choose, and what role does tracing play?Agents SDK 的 session 和 Responses API 的 previous_response_id 都能记住多轮,怎么选?tracing 在这里起什么作用?
    Common in ChinaCommon overseasIntermediate#agents-sdk#sessions#tracing

    How to reason about it · think before answering

    1. This probes your sensitivity to who holds the state, the SDK-level echo of 'you carry the history yourself'.
    2. Ask three questions: can the history be audited, trimmed or replayed, and kept within data-residency rules? previous_response_id keeps history server-side with minimal requests but answers all three poorly; sessions keep it in your store and answer all three, at the cost of managing storage.
    3. Conclude: prototypes and internal tools take previous_response_id; user-facing production keeps its own copy, for which sessions are the ready-made path; both can coexist.
    4. Of the four session operations, pop_item deserves mention: removing the last turn to honor a user's undo is only possible when you own the history.
    5. Tracing makes multi-agent behavior explainable: on by default, one trace per run recording turns, tool calls, handoffs and guardrail results; group a conversation with withTrace or group_id; disable via env var or swap in your own exporter for sensitive data.
    6. Expect the follow-up: does tracing ship user data out? By default it goes to the platform dashboard, so regulated settings must disable it or replace the processors.

    分析过程 · 先想清楚再作答

    1. 这题考的是对「状态放在谁手里」的敏感度,是 30 天课 D1「历史靠你自己搬」在 SDK 层的翻版。
    2. 拆法是问三件事:历史能不能审计、能不能裁剪或重放、能不能满足数据驻留要求。previous_response_id 的历史在服务端,请求最小、代码最简,但三个问题都答不好;session 的历史在你手里(内存、SQLite、Redis),三个都能做,代价是自己管存储。
    3. 结论:原型与内部工具用 previous_response_id 省事;面向用户的生产系统至少自己落一份历史,session 是现成的落法;两者可以同时用。
    4. session 的四个接口(取、追加、弹出最后一条、清空)里 pop_item 值得点出:用户撤回上一句时把最后一轮拿掉再重跑,这是自己持有历史才能做的事。
    5. tracing 的作用是让多 Agent 系统的行为可解释:默认开启,每次 run 一条,记录每轮、每次工具调用、交接与护栏判断;用 withTrace 或 group_id 把一段对话归到一起;敏感数据场景用环境变量关掉或换成自己的导出器。
    6. 可预期的追问:tracing 会不会把用户数据传出去?默认会传到平台面板,所以合规场景要么关、要么 setTraceProcessors 换成自己的后端。

    Key points

    • previous_response_id keeps history server-side, small and simple, but weak on audit, trimming and residency
    • Sessions keep history in your store, auditable and replayable, with pop_item for undo; production keeps its own copy
    • Tracing is on by default, one trace per run, capturing turns, tools, handoffs and guardrails, grouped via group_id
    • For sensitive data disable it with OPENAI_AGENTS_DISABLE_TRACING or swap in your own exporter

    答题要点

    • previous_response_id 历史在服务端,请求小代码简,但难审计、难裁剪、难满足数据驻留
    • session 历史在自己手里,可审计可重放,pop_item 支持撤回;生产至少自己落一份
    • tracing 默认开、每次 run 一条,记录每轮工具、交接与护栏,用 group_id 归组
    • 敏感数据场景用 OPENAI_AGENTS_DISABLE_TRACING 关掉或换成自己的导出器

Comments