agents-0-22-0-12-b
Claude Code's read-only tools, web search on, ludion sync on
Question
Is the second argument of a this.schedule() callback the Schedule object?
Answer key
- A correct answer says
- A scheduled callback's second argument is the parsed `Schedule` object.
- The stale answer
- A scheduled callback's second argument is the raw storage row with `payload` as a JSON string.
Answer
Yes. In the installed `agents` 0.27.0, a scheduled callback is called as `handler(payload, schedule)`. The first argument is the payload you passed to `this.schedule(when, "callbackName", payload)`. The second is the `Schedule` object for the run, which carries fields like `id` and `callback` plus the timing. You can see the call at `node_modules/agents/dist/scheduler-DZ3xQfeK.js:229`. Schedules routed from a sub-agent go through `_executeRouted` (line 417), which also builds a `Schedule` object before calling the handler.Grade
correct
The judge's reason: It states the second argument is the Schedule object and does not present the raw storage row as current.
Session log
Tool call: Grep
{ "pattern": "\\.call\\(this, *JSON\\.parse|callback\\.bind\\(this\\)\\(|schedule\\.payload", "path": "C:\\Users\\haya0\\AppData\\Local\\Temp\\ludion-bench-WcFVQL\\work\\node_modules\\agents\\dist", "output_mode": "content", "head_limit": 10 }Tool output
node_modules\agents\dist\src-DgCbZtIR.js:4667: await callback.bind(this)(runInfo, lifecycle);Tool call: Read
{ "file_path": "C:\\Users\\haya0\\AppData\\Local\\Temp\\ludion-bench-WcFVQL\\work\\node_modules\\agents\\dist\\src-DgCbZtIR.js", "offset": 4630, "limit": 45 }Tool output
4630 * dedupe the child's real late result away (the #1752 production incident). 4631 */ 4632 async _deliverDetachedTerminal(runId, kind, result, options, completedAt = Date.now()) { 4633 const now = Date.now(); 4634 const leaseFloor = now - DETACHED_DELIVERY_LEASE_MS; 4635 const claimQuery = kind === "finish" ? `UPDATE cf_agent_tool_runs 4636 SET finish_claimed_at = ? 4637 WHERE run_id = ? 4638 AND finish_delivered_at IS NULL 4639 AND (finish_claimed_at IS NULL OR finish_claimed_at < ?)` : `UPDATE cf_agent_tool_runs 4640 SET give_up_claimed_at = ? 4641 WHERE run_id = ? 4642 AND give_up_delivered_at IS NULL 4643 AND (give_up_claimed_at IS NULL OR give_up_claimed_at < ?)`; 4644 if (this.ctx.storage.sql.exec(claimQuery, now, runId, leaseFloor).rowsWritten === 0) return; 4645 const row = this._readAgentToolRun(runId); 4646 if (!row) return; 4647 this._updateAgentToolTerminal(runId, result, completedAt); 4648 this._broadcastAgentToolTerminal(row.parent_tool_call_id ?? void 0, options?.sequence ?? Date.now(), result); 4649 const runInfo = this._agentToolRunInfoFromRow(row, result.status, completedAt); 4650 const lifecycle = { 4651 status: result.status, 4652 ...result.summary !== void 0 ? { summary: result.summary } : {}, 4653 ...result.error !== void 0 ? { error: result.error } : {}, 4654 ...result.reason !== void 0 ? { reason: result.reason } : {}, 4655 ...result.childStillRunning !== void 0 ? { childStillRunning: result.childStillRunning } : {} 4656 }; 4657 const invoke = async () => { 4658 try { 4659 await this.onAgentToolFinish(runInfo, lifecycle); 4660 } catch (error) { 4661 await this._safeRunOnError(error); 4662 } 4663 const callbackName = row.detached_on_finish; 4664 if (callbackName) { 4665 const callback = this[callbackName]; 4666 if (typeof callback === "function") try { 4667 await callback.bind(this)(runInfo, lifecycle); 4668 } catch (error) { 4669 this._emit("agent_tool:detached:delivery_failed", { 4670 runId, 4671 kind, 4672 status: result.status, 4673 callback: callbackName, 4674 error: error instanceof Error ? error.message : String(error)Agent
Not the schedule path. Let me find where scheduled callbacks are invoked.Tool call: Grep
{ "pattern": "JSON\\.parse\\(row\\.payload|JSON\\.parse\\(.*payload.*\\),\\s*\\w+", "path": "C:\\Users\\haya0\\AppData\\Local\\Temp\\ludion-bench-WcFVQL\\work\\node_modules\\agents\\dist", "output_mode": "content", "-B": 3, "-A": 3, "head_limit": 40 }Tool output
node_modules\agents\dist\ai-sdk-OoGkFy3s.js.map:1:[Omitted long matching line] -- node_modules\agents\dist\connector-58XDGvc3.js.map:1:[Omitted long matching line] -- node_modules\agents\dist\client-1tEQ1Oqz.js.map:1:[Omitted long matching line] -- node_modules\agents\dist\images-Ce9SCu3X.js.map:1:[Omitted long matching line] -- node_modules\agents\dist\chat\index.js.map:1:[Omitted long matching line] -- node_modules\agents\dist\lifecycle-CFu6OV03.js-193- capability: row.capability, node_modules\agents\dist\lifecycle-CFu6OV03.js-194- fn: row.fn, node_modules\agents\dist\lifecycle-CFu6OV03.js-195- time: row.time, node_modules\agents\dist\lifecycle-CFu6OV03.js:196: payload: typeof row.payload === "string" ? JSON.parse(row.payload) : void 0, node_modules\agents\dist\lifecycle-CFu6OV03.js-197- retry: typeof row.retry_options === "string" ? JSON.parse(row.retry_options) : void 0, node_modules\agents\dist\lifecycle-CFu6OV03.js-198- singleflight: row.singleflight === 1, node_modules\agents\dist\lifecycle-CFu6OV03.js-199- exclusive: row.exclusive === 1, -- node_modules\agents\dist\experimental\channels\slack\index.js.map:1:[Omitted long matching line] -- node_modules\agents\dist\lifecycle-CFu6OV03.js.map:1:[Omitted long matching line] -- node_modules\agents\dist\harness\think\index.js.map:1:[Omitted long matching line] -- node_modules\agents\dist\queue-BB-zfIYF.js.map:1:[Omitted long matching line] -- node_modules\agents\dist\scheduler-DZ3xQfeK.js-369- if (row.callback === "_cf_keepAliveHeartbeat") continue; node_modules\agents\dist\scheduler-DZ3xQfeK.js-370- let payload; node_modules\agents\dist\scheduler-DZ3xQfeK.js-371- try { node_modules\agents\dist\scheduler-DZ3xQfeK.js:372: payload = typeof row.payload === "string" ? JSON.parse(row.payload) : void 0; node_modules\agents\dist\scheduler-DZ3xQfeK.js-373- } catch (error) { node_modules\agents\dist\scheduler-DZ3xQfeK.js-374- console.error(`Skipping schedule "${row.id}" during job-queue migration: its payload is not valid JSON`, error); node_modules\agents\dist\scheduler-DZ3xQfeK.js-375- continue; -- node_modules\agents\dist\observability\ai\index.js.map:1:[Omitted long matching line] -- node_modules\agents\dist\queue-BB-zfIYF.js-248- for (const row of rows) { node_modules\agents\dist\queue-BB-zfIYF.js-249- let payload; node_modules\agents\dist\queue-BB-zfIYF.js-250- try { node_modules\agents\dist\queue-BB-zfIYF.js:251: payload = typeof row.payload === "string" ? JSON.parse(row.payload) : void 0; [Showing results with pagination = limit: 40]Tool call: Grep
{ "pattern": "\\]\\.call\\(|\\.apply\\(this|callback\\.call|fn\\.call|\\(job\\.payload|\\(payload, ?\\w+\\)", "path": "C:\\Users\\haya0\\AppData\\Local\\Temp\\ludion-bench-WcFVQL\\work\\node_modules\\agents\\dist", "glob": "{scheduler,lifecycle,queue}*.js", "output_mode": "content", "-B": 4, "-A": 2, "head_limit": 60 }Tool output
node_modules\agents\dist\scheduler-DZ3xQfeK.js-490-} node_modules\agents\dist\scheduler-DZ3xQfeK.js-491-/** Every schedule job this Scheduler owns, with its parsed vocabulary. */ node_modules\agents\dist\scheduler-DZ3xQfeK.js-492-function _ownedJobs() { node_modules\agents\dist\scheduler-DZ3xQfeK.js-493- const owned = []; node_modules\agents\dist\scheduler-DZ3xQfeK.js:494: for (const job of this.lifecycle.jobs.list()) if (isSchedulerJobPayload(job.payload)) owned.push({ node_modules\agents\dist\scheduler-DZ3xQfeK.js-495- job, node_modules\agents\dist\scheduler-DZ3xQfeK.js-496- timing: job.payload -- node_modules\agents\dist\scheduler-DZ3xQfeK.js-609-} node_modules\agents\dist\scheduler-DZ3xQfeK.js-610-function _getForOwner(owner, id) { node_modules\agents\dist\scheduler-DZ3xQfeK.js-611- const ownerKey = owner?.key ?? null; node_modules\agents\dist\scheduler-DZ3xQfeK.js-612- const job = this.lifecycle.jobs.get(id); node_modules\agents\dist\scheduler-DZ3xQfeK.js:613: if (!job || !isSchedulerJobPayload(job.payload)) return void 0; node_modules\agents\dist\scheduler-DZ3xQfeK.js:614: if ((job.payload.owner_path_key ?? null) !== ownerKey) return void 0; node_modules\agents\dist\scheduler-DZ3xQfeK.js-615- return _assertClassBrand(_Scheduler_brand, this, _jobToSchedule).call(this, job, job.payload); node_modules\agents\dist\scheduler-DZ3xQfeK.js-616-} -- node_modules\agents\dist\scheduler-DZ3xQfeK.js-632-} node_modules\agents\dist\scheduler-DZ3xQfeK.js-633-async function _cancelForOwner(owner, id) { node_modules\agents\dist\scheduler-DZ3xQfeK.js-634- const ownerKey = owner?.key ?? null; node_modules\agents\dist\scheduler-DZ3xQfeK.js-635- const job = this.lifecycle.jobs.get(id); node_modules\agents\dist\scheduler-DZ3xQfeK.js:636: if (!job || !isSchedulerJobPayload(job.payload)) return { ok: false }; node_modules\agents\dist\scheduler-DZ3xQfeK.js:637: if ((job.payload.owner_path_key ?? null) !== ownerKey) return { ok: false }; node_modules\agents\dist\scheduler-DZ3xQfeK.js-638- const callback = job.fn; node_modules\agents\dist\scheduler-DZ3xQfeK.js-639- await this.lifecycle.jobs.cancel(id); -- node_modules\agents\dist\queue-BB-zfIYF.js-86- } node_modules\agents\dist\queue-BB-zfIYF.js-87- /** Drive one due item dispatched by the Lifecycle event loop. */ node_modules\agents\dist\queue-BB-zfIYF.js-88- async onJob(context) { node_modules\agents\dist\queue-BB-zfIYF.js-89- const { job, attempt } = context; node_modules\agents\dist\queue-BB-zfIYF.js:90: if (!isQueueJobPayload(job.payload)) { node_modules\agents\dist\queue-BB-zfIYF.js-91- console.error(`Malformed queue item ${job.id}; dropping it`); node_modules\agents\dist\queue-BB-zfIYF.js-92- return; -- node_modules\agents\dist\queue-BB-zfIYF.js-97- id: job.id, node_modules\agents\dist\queue-BB-zfIYF.js-98- attempt, node_modules\agents\dist\queue-BB-zfIYF.js-99- maxAttempts: resolveRetryConfig(job.retry, _classPrivateFieldGet2(_retryDefaults, this)).maxAttempts node_modules\agents\dist\queue-BB-zfIYF.js-100- }); node_modules\agents\dist\queue-BB-zfIYF.js:101: if (job.payload.owner_path) { node_modules\agents\dist\queue-BB-zfIYF.js-102- try { node_modules\agents\dist\queue-BB-zfIYF.js-103- await this.lifecycle.routes.to({ -- node_modules\agents\dist\queue-BB-zfIYF.js-364-} node_modules\agents\dist\queue-BB-zfIYF.js-365-/** Every queue job this Queue owns, in push order. */ node_modules\agents\dist\queue-BB-zfIYF.js-366-function _ownedJobs() { node_modules\agents\dist\queue-BB-zfIYF.js-367- const owned = []; node_modules\agents\dist\queue-BB-zfIYF.js:368: for (const job of this.lifecycle.jobs.list()) if (isQueueJobPayload(job.payload)) owned.push({ node_modules\agents\dist\queue-BB-zfIYF.js-369- job, node_modules\agents\dist\queue-BB-zfIYF.js-370- envelope: job.payload -- node_modules\agents\dist\queue-BB-zfIYF.js-373-} node_modules\agents\dist\queue-BB-zfIYF.js-374-function _getForOwner(owner, id) { node_modules\agents\dist\queue-BB-zfIYF.js-375- const ownerKey = owner?.key ?? null; node_modules\agents\dist\queue-BB-zfIYF.js-376- const job = this.lifecycle.jobs.get(id); node_modules\agents\dist\queue-BB-zfIYF.js:377: if (!job || !isQueueJobPayload(job.payload)) return void 0; node_modules\agents\dist\queue-BB-zfIYF.js:378: if ((job.payload.owner_path_key ?? null) !== ownerKey) return void 0; node_modules\agents\dist\queue-BB-zfIYF.js-379- return _assertClassBrand(_Queue_brand, this, _jobToItem).call(this, job, job.payload); node_modules\agents\dist\queue-BB-zfIYF.js-380-}Agent
Let me look at the Scheduler's onJob dispatch.Tool call: Grep
{ "pattern": "async onJob", "path": "C:\\Users\\haya0\\AppData\\Local\\Temp\\ludion-bench-WcFVQL\\work\\node_modules\\agents\\dist\\scheduler-DZ3xQfeK.js", "output_mode": "content", "-A": 45 }Tool output
178: async onJob(context) { 179- const { job, attempt } = context; 180- const timing = job.payload; 181- if (!isSchedulerJobPayload(timing)) { 182- console.error(`Malformed schedule job ${job.id}; dropping it`); 183- return; 184- } 185- if (attempt === 1) _assertClassBrand(_Scheduler_brand, this, _emit).call(this, "schedule:execute", { 186- callback: job.fn, 187- id: job.id 188- }); 189- else _assertClassBrand(_Scheduler_brand, this, _emit).call(this, "schedule:retry", { 190- callback: job.fn, 191- id: job.id, 192- attempt, 193- maxAttempts: resolveRetryConfig(job.retry, _classPrivateFieldGet2(_retryDefaults, this)).maxAttempts 194- }); 195- if (timing.owner_path) { 196- try { 197- await this.lifecycle.routes.to({ 198- key: timing.owner_path_key ?? timing.owner_path, 199- data: timing.owner_path 200- }, { 201- type: "dispatch", 202- id: job.id, 203- fn: job.fn, 204- job: timing, 205- retry: job.retry 206- }); 207- } catch (error) { 208- if (isPlatformFailure(error)) throw error; 209- console.error(`error dispatching scheduled callback "${job.fn}"`, error); 210- _assertClassBrand(_Scheduler_brand, this, _emit).call(this, "schedule:error", { 211- callback: job.fn, 212- id: job.id, 213- error: error instanceof Error ? error.message : String(error), 214- attempts: 0 215- }); 216- try { 217- await _classPrivateFieldGet2(_onError, this)?.call(this, error); 218- } catch {} 219- return "yield"; 220- } 221- return _assertClassBrand(_Scheduler_brand, this, _recurrenceOutcome).call(this, timing); 222- } 223- const handler = _assertClassBrand(_Scheduler_brand, this, _resolveCallback).call(this, job.fn); -- 233: async onJobError(context, error) { 234- const { job } = context; 235- const timing = job.payload; 236- if (!isSchedulerJobPayload(timing)) return void 0; 237- const { maxAttempts } = resolveRetryConfig(job.retry, _classPrivateFieldGet2(_retryDefaults, this)); 238- console.error(`error executing callback "${job.fn}" after ${maxAttempts} attempts`, error); 239- _assertClassBrand(_Scheduler_brand, this, _emit).call(this, "schedule:error", { 240- callback: job.fn, 241- id: job.id, 242- error: error instanceof Error ? error.message : String(error), 243- attempts: maxAttempts 244- }); 245- try { 246- await _classPrivateFieldGet2(_onError, this)?.call(this, error); 247- } catch {} 248- return _assertClassBrand(_Scheduler_brand, this, _recurrenceOutcome).call(this, timing); 249- } 250- /** Handle Scheduler protocol messages routed by another Lifecycle. */ 251- async onRoute(context) { 252- const message = context.payload; 253- const owner = context.source ?? null; 254- switch (message.type) { 255- case "schedule": return _assertClassBrand(_Scheduler_brand, this, _insert).call(this, owner, parseWhen(message.when, Date.now(), message.callback), message.callback, message.payload, message.options); 256- case "every": return _assertClassBrand(_Scheduler_brand, this, _insert).call(this, owner, parseInterval(message.intervalSeconds, Date.now()), message.callback, message.payload, message.options); 257- case "get": return _assertClassBrand(_Scheduler_brand, this, _getForOwner).call(this, owner, message.id); 258- case "list": return _assertClassBrand(_Scheduler_brand, this, _listForOwner).call(this, owner, message.criteria); 259- case "cancel": return _assertClassBrand(_Scheduler_brand, this, _cancelForOwner).call(this, owner, message.id); 260- case "dispatch": 261- await _assertClassBrand(_Scheduler_brand, this, _executeRouted).call(this, message.id, message.fn, message.job, message.retry); 262- return true; 263- default: throw new Error("Unknown routed Scheduler message"); 264- } 265- } 266- /** Set a delayed, dated, or cron schedule for a registered callback. */ 267- async set(when, callback, payload, options) { 268- await this.lifecycle.ready(); 269- _assertClassBrand(_Scheduler_brand, this, _validateSchedule).call(this, when, callback, options); 270- const result = this.lifecycle.routes.source ? await this.lifecycle.routes.toRoot({ 271- type: "schedule", 272- when, 273- callback, 274- payload, 275- options 276- }) : await _assertClassBrand(_Scheduler_brand, this, _insert).call(this, null, parseWhen(when, Date.now(), callback), callback, payload, options); 277- _assertClassBrand(_Scheduler_brand, this, _emitCreated).call(this, result); 278- return result.schedule;Tool call: Read
{ "file_path": "C:\\Users\\haya0\\AppData\\Local\\Temp\\ludion-bench-WcFVQL\\work\\node_modules\\agents\\dist\\scheduler-DZ3xQfeK.js", "offset": 223, "limit": 10 }Tool output
223 const handler = _assertClassBrand(_Scheduler_brand, this, _resolveCallback).call(this, job.fn); 224 if (!handler) { 225 console.error(`callback ${job.fn} not found`); 226 return _assertClassBrand(_Scheduler_brand, this, _recurrenceOutcome).call(this, timing); 227 } 228 const schedule = _assertClassBrand(_Scheduler_brand, this, _jobToSchedule).call(this, job, timing); 229 await this.lifecycle.runInHostContext(() => handler(timing.payload, schedule)); 230 return _assertClassBrand(_Scheduler_brand, this, _recurrenceOutcome).call(this, timing); 231 } 232 /** Observe one schedule's terminal application failure. */Tool call: Grep
{ "pattern": "function _executeRouted", "path": "C:\\Users\\haya0\\AppData\\Local\\Temp\\ludion-bench-WcFVQL\\work\\node_modules\\agents\\dist\\scheduler-DZ3xQfeK.js", "output_mode": "content", "-A": 15 }Tool output
417:async function _executeRouted(id, fn, timing, retry) { 418- const handler = _assertClassBrand(_Scheduler_brand, this, _resolveCallback).call(this, fn); 419- if (!handler) { 420- console.error(`callback ${fn} not found`); 421- return; 422- } 423- const { maxAttempts, baseDelayMs, maxDelayMs } = resolveRetryConfig(retry, _classPrivateFieldGet2(_retryDefaults, this)); 424- const isOneShot = timing.type === "delayed" || timing.type === "scheduled"; 425- const schedule = _assertClassBrand(_Scheduler_brand, this, _payloadToSchedule).call(this, id, fn, timing); 426- try { 427- await tryN(maxAttempts, async (attempt) => { 428- if (attempt > 1) _assertClassBrand(_Scheduler_brand, this, _emit).call(this, "schedule:retry", { 429- callback: fn, 430- id, 431- attempt, 432- maxAttemptsAgent
Yes. In the installed `agents` 0.27.0, a scheduled callback is called as `handler(payload, schedule)`. The first argument is the payload you passed to `this.schedule(when, "callbackName", payload)`. The second is the `Schedule` object for the run, which carries fields like `id` and `callback` plus the timing. You can see the call at `node_modules/agents/dist/scheduler-DZ3xQfeK.js:229`. Schedules routed from a sub-agent go through `_executeRouted` (line 417), which also builds a `Schedule` object before calling the handler.