Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions backend/cli/src/provider/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1435,9 +1435,10 @@ export namespace Provider {
...model,
providerID: "openai-codex",
cost: { input: 0, output: 0, cache: { read: 0, write: 0 } },
// Codex OAuth advertises a separate 272k window even when the
// copied public-API entry has a million-token context.
limit: { ...model.limit, context: 272_000 },
// Keep each model's catalog window. Flattening the whole Codex
// family to a legacy input allowance made flagship and mini
// models advertise the same, incorrect context in every picker.
limit: { ...model.limit },
// Codex advertises its own fast tier independently of the public
// API catalog, so synthesize only the modes in the OAuth contract.
modes: codexOAuthModes(model.id),
Expand Down
154 changes: 111 additions & 43 deletions backend/cli/src/science/kernel/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ export type KernelIdentity = {
language: KernelLanguage
}

type KernelCell = {
title: string | null
source: string | null
code: string
status: "running" | "succeeded" | "failed"
executionCount: number | null
messageID: string | null
callID: string | null
}

export class KernelStartupCancelled extends Error {
constructor() {
super("Kernel startup was cancelled before execution.")
Expand Down Expand Up @@ -44,6 +54,7 @@ type Entry = {
startedAt: number | null
lastActivityAt: number | null
authority: ExecutionAuthority.Decision | null
lastCell: KernelCell | null
}

type Pending = {
Expand Down Expand Up @@ -90,6 +101,17 @@ export const KernelStatus = z.object({
started_at: z.number().nullable(),
last_activity_at: z.number().nullable(),
authority: ExecutionAuthority.Decision.nullable(),
last_cell: z
.object({
title: z.string().nullable(),
source: z.string().nullable(),
code: z.string(),
status: z.enum(["running", "succeeded", "failed"]),
execution_count: z.number().int().positive().nullable(),
message_id: z.string().nullable(),
call_id: z.string().nullable(),
})
.nullable(),
// Live usage sampled at request time for running processes. Absent fields
// mean the platform could not report them — render as unavailable, not 0.
resources: z
Expand Down Expand Up @@ -124,6 +146,7 @@ const records = Instance.state(
entry.startedAt = null
entry.lastActivityAt = Date.now()
entry.authority = null
entry.lastCell = null
await persist(entry)
}),
)
Expand Down Expand Up @@ -177,6 +200,7 @@ function restore(value: z.infer<typeof Persisted>) {
startedAt: null,
lastActivityAt: value.last_activity_at,
authority: null,
lastCell: null,
}
records().entries.set(id, entry)
return entry
Expand Down Expand Up @@ -219,6 +243,7 @@ const record = (identity: KernelIdentity) => {
startedAt: null,
lastActivityAt: null,
authority: null,
lastCell: null,
}
records().entries.set(id, value)
return value
Expand Down Expand Up @@ -362,6 +387,7 @@ const entry = async (identity: KernelIdentity, _options?: KernelStartOptions) =>
value.startedAt = null
value.lastActivityAt = Date.now()
value.authority = authority
value.lastCell = null
const drop = () => {
if (records().starts.get(value.key)?.ticket === ticket) records().starts.delete(value.key)
}
Expand Down Expand Up @@ -468,49 +494,78 @@ export namespace KernelRuntime {
const codeState = ProvenanceEnvelope.code(value.environment?.cwd ?? Instance.directory)
const startedAt = Date.now()
value.lastActivityAt = startedAt
return kernel.execute(code, options).then(
async (result) => {
// The count belongs to this cell, so capture it before the awaits below.
// `value.executionCount` is the kernel's running total and every cell
// queued behind this one advances it — reading it back after the persist
// reported the count of whichever cell had most recently finished.
const count = result.executionCount ?? value.executionCount + 1
value.executionCount = count
const completedAt = Date.now()
value.lastActivityAt = completedAt
await persist(value)
const complete = { ...result, executionCount: count }
const node = await provenance(
identity,
value,
code,
startedAt,
completedAt,
codeState,
options?.origin,
complete,
)
return { ...complete, provenanceID: node.id }
},
async (error) => {
const completedAt = Date.now()
value.lastActivityAt = completedAt
if (kernel.crashed) value.state = "crashed"
await persist(value)
const node = await provenance(
identity,
value,
code,
startedAt,
completedAt,
codeState,
options?.origin,
undefined,
error,
)
throw new KernelExecutionError(error, node.id)
},
)
const source = options?.origin?.source ?? (identity.name.startsWith("notebook:") ? identity.name.slice(9) : null)
const cell = (): KernelCell => ({
title: options?.origin?.title?.trim().slice(0, 100) || null,
source,
code: code.length > 12_000 ? `${code.slice(0, 12_000)}\n\n... (truncated)` : code,
status: "running",
executionCount: Math.max(value.executionCount, value.lastCell?.executionCount ?? 0) + 1,
messageID: options?.origin?.messageID ?? null,
callID: options?.origin?.callID ?? null,
})
const running: { cell?: KernelCell } = {}
return kernel
.execute(code, {
...options,
onStart: () => {
running.cell = cell()
value.lastCell = running.cell
value.lastActivityAt = Date.now()
options?.onStart?.()
},
})
.then(
async (result) => {
// The count belongs to this cell, so capture it before the awaits below.
// `value.executionCount` is the kernel's running total and every cell
// queued behind this one advances it — reading it back after the persist
// reported the count of whichever cell had most recently finished.
const count = result.executionCount ?? value.executionCount + 1
value.executionCount = count
const completedAt = Date.now()
value.lastActivityAt = completedAt
const completeCell: KernelCell = {
...(running.cell ?? cell()),
status: result.ok ? "succeeded" : "failed",
executionCount: count,
}
if (!value.lastCell || value.lastCell === running.cell) value.lastCell = completeCell
await persist(value)
const complete = { ...result, executionCount: count }
const node = await provenance(
identity,
value,
code,
startedAt,
completedAt,
codeState,
options?.origin,
complete,
)
return { ...complete, provenanceID: node.id }
},
async (error) => {
const completedAt = Date.now()
value.lastActivityAt = completedAt
const failedCell: KernelCell = { ...(running.cell ?? cell()), status: "failed" }
if (!value.lastCell || value.lastCell === running.cell) value.lastCell = failedCell
if (kernel.crashed) value.state = "crashed"
await persist(value)
const node = await provenance(
identity,
value,
code,
startedAt,
completedAt,
codeState,
options?.origin,
undefined,
error,
)
throw new KernelExecutionError(error, node.id)
},
)
}

export function active(identity: KernelIdentity) {
Expand Down Expand Up @@ -544,6 +599,18 @@ export namespace KernelRuntime {
started_at: active ? value.startedAt : null,
last_activity_at: value.lastActivityAt,
authority: value.authority,
last_cell:
active && value.lastCell
? {
title: value.lastCell.title,
source: value.lastCell.source,
code: value.lastCell.code,
status: value.lastCell.status,
execution_count: value.lastCell.executionCount,
message_id: value.lastCell.messageID,
call_id: value.lastCell.callID,
}
: null,
}
}

Expand Down Expand Up @@ -575,6 +642,7 @@ export namespace KernelRuntime {
value.startedAt = null
value.lastActivityAt = Date.now()
value.authority = null
value.lastCell = null
await persist(value)
}

Expand Down
6 changes: 4 additions & 2 deletions backend/cli/src/science/kernel/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,10 @@ export interface ExecuteOptions {
signal?: AbortSignal
/** Whether to capture rich (MIME) display outputs. Default true. */
rich?: boolean
/** Message and tool call that requested this execution, for lineage. */
origin?: { messageID?: string; callID?: string }
/** Message, tool call, and human-facing cell identity used for lineage/UI. */
origin?: { messageID?: string; callID?: string; title?: string; source?: string }
/** Internal lifecycle hook fired when a queued cell actually starts. */
onStart?: () => void
}

export interface KernelStartOptions {
Expand Down
2 changes: 1 addition & 1 deletion backend/cli/src/server/routes/notebook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ export const NotebookRoutes = lazy(() =>
const result = await KernelRuntime.execute(
identity(body),
body.code,
{ timeout: body.timeout },
{ timeout: body.timeout, origin: { source: body.id } },
{ cwd: await SessionFilesystem.workspace(body.sessionID) },
).catch((error) => {
if (error instanceof KernelStartupCancelled) return error
Expand Down
41 changes: 36 additions & 5 deletions backend/cli/src/tool/notebook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,7 @@ class PythonKernel implements Kernel {

private async run(code: string, opts?: ExecuteOptions): Promise<ExecuteResult> {
if (!this.ready) throw new Error("Python kernel is not running")
opts?.onStart?.()
const proc = this.proc!
this.lastUsed = Date.now()
const timeout = Math.min(Math.max(opts?.timeout ?? 120_000, 5_000), 600_000)
Expand Down Expand Up @@ -578,6 +579,8 @@ export const NotebookTool = Tool.define("notebook", {
description: [
"Execute Python code in a persistent, managed kernel. Variables, imports, and state persist across calls that use the same kernel name.",
"For multiple independent analyses, issue multiple notebook calls in the same response with distinct `kernel` names. Those kernels execute concurrently and appear separately in Compute.",
"Always set `title` to a concise description of the scientific action, not a code fragment or import.",
"Set `source` when the cell belongs to a script or .ipynb file so Compute can identify that source.",
"Never use shell subprocesses to imitate multiple kernels; use this tool's `kernel` parameter instead.",
"After a named analysis is fully saved and verified, call this tool with `action: stop` and the same kernel name so completed workers do not idle.",
"Use instead of `bash python` for analysis — no need to re-import or re-load data between cells.",
Expand All @@ -588,6 +591,20 @@ export const NotebookTool = Tool.define("notebook", {
.object({
action: z.enum(["execute", "stop"]).optional().describe("Execute a cell (default) or stop this named kernel"),
code: z.string().optional().describe("Python code to execute; required when action is execute"),
title: z
.string()
.trim()
.min(1)
.max(100)
.optional()
.describe("Short action label for this cell, for example 'Benchmarking survival classifiers'"),
source: z
.string()
.trim()
.min(1)
.max(1024)
.optional()
.describe("Script or notebook path this cell belongs to, when applicable"),
kernel: z
.string()
.trim()
Expand Down Expand Up @@ -626,7 +643,11 @@ export const NotebookTool = Tool.define("notebook", {
},
}
}
ctx.metadata({ title: `Python · ${name}`, metadata: { kernel: name, language: "python" } })
const title = params.title ?? "Python cell"
ctx.metadata({
title,
metadata: { kernel: name, language: "python", task: title, ...(params.source ? { source: params.source } : {}) },
})

// Executes arbitrary code — same permission gate as bash.
await ctx.ask({
Expand All @@ -639,7 +660,7 @@ export const NotebookTool = Tool.define("notebook", {
const result = await KernelRuntime.execute(identity, params.code!, {
timeout: params.timeout,
signal: ctx.abort,
origin: { messageID: ctx.messageID, callID: ctx.callID },
origin: { messageID: ctx.messageID, callID: ctx.callID, title, source: params.source },
})

const images = result.outputs.filter((o) => o.type === "display" && o.data?.["image/png"])
Expand All @@ -660,19 +681,29 @@ export const NotebookTool = Tool.define("notebook", {
const output = clip(parts.join("\n"))

ctx.metadata({
title: `Python · ${name}`,
metadata: { output, ok: result.ok, provenanceID: result.provenanceID, kernel: name, language: "python" },
title,
metadata: {
output,
ok: result.ok,
provenanceID: result.provenanceID,
kernel: name,
language: "python",
task: title,
...(params.source ? { source: params.source } : {}),
},
})

return {
title: result.ok ? `Python · ${name}` : `Python · ${name} (error)`,
title: result.ok ? title : `${title} (error)`,
output,
metadata: {
stopped: false,
ok: result.ok,
output,
kernel: name,
language: "python",
task: title,
...(params.source ? { source: params.source } : {}),
provenanceID: result.provenanceID,
executionCount: result.executionCount,
hasImages: images.length,
Expand Down
Loading