跳转至

11 · PTC / Code Mode、隔离Node运行时、workflow和Ralph

本章把三件常被叫成“代码执行”的功能拆开:PTC用一段模型程序调用工具;workflow用受限编排脚本运行子Agent;Ralph用部署固定脚本让fresh worker逐轮推进。它们复用runtime,但拥有不同权限、上下文、输出和成功定义。源码固定main da00f7f,发布版实验固定rc.2。

11.1 为什么程序调用工具是一种不同的模型交互方式

native mode把一个个工具schema交给模型,每次组合往往需多次request;PTC mode把visible tools的typed SDK放prompt,wire上主要使用 run_code。模型可以在一次程序里读多份资料、并发readonly调用、筛选、聚合,仅打印/返回相关结果。both同时支持native和PTC,但不是“多装了一个独立工具包”;这是ToolRuntime的presentation策略。

flowchart LR Request["模型请求: run_code + 类型SDK"] --> Program["async TypeScript body"] Program --> Proc["新Node进程 / 私有JSON通道"] Proc --> Binding["tools.name(args) binding"] Binding --> Scheduler["Host ToolRuntime staged scheduler"] Scheduler --> Guards["原有guards / permission / approval"] Guards --> Tool["业务工具体"] Tool --> JSON["typed value / ToolCallError"] JSON --> Program Program --> Curate["print/return curated结果"] Curate --> Result["外层 tool/result 进入model history"] Scheduler --> Log["nested dispatch start/settle日志"]

run_code参数是description、code,code为async function body,top-level await/return可用;TypeScript只支持erasable syntax,由Node stripTypeScriptTypes移除类型,不运行完整tsc、不执行你的自定义tsconfig,也不保证静态类型检查过。enum等需要生成JS的语法不能当作通用TS编译支持。源码:ptc.ts · const TYPESCRIPT_FLAVOR、index.ts · const stripped = stripTypeScriptTypes。

// 程序结构示范。真实name/args/output来自当次SDK声明,
// 这里的 lookup_note 是课程自定义工具,不是DSH内置工具。
const ids = ['intro', 'design'];
const results = await Promise.all(
  ids.map(id => tools.lookup_note({ id }))
);
return results.filter(Boolean);

不要向模型注入整本工具手册却忘了挂对应runtime。Tools实际assembly会requirePtcRuntime,语言必须与SDK renderer/flavor一致;definition/catalog reader的fallback TypeScript描述不代表模型请求可以无runtime成功。

11.2 typed SDK来自同一份工具schema,未绕过Host校验

Tools有unified参数/输出schema,renderers分别生成TypeScript/Python可读声明。visible set以caller agent scope为准,readonly/exclusive等concurrency信息作为SDK提示和runtime事实。它只把schema表述成模型可读类型,不能把模型程序变成可信代码,也不能代替tool参数JSON验证。

Host为visible tools逐个创建binding,跳过run_code避免直接自递归;namespace是null-prototype对象,name作为own property定义,__proto__/constructor不会碰原型setter。每次subdispatch重新按caller agent view解析注册工具,并按submission-time snapshot与current executionMode安排,registry变化不会凭旧SDK赋予无限新能力。源码:ptc.ts · for (const schema of registry.schemas(exec.agent))、bootstrap.ts · export function makeNamespaces。

program参数/result必须lossless JSON;child→host的frame做运行时校验,undefined、函数、循环引用、非有限数字等不能靠JSON.stringify悄悄丢字段后冒充原值。ToolCallError暴露toolName字段,模型可以显式catch某个工具失败;“Promise fulfilled”仍要检查外层runtime result.error或Tools的isError。

11.3 Node runtime是真进程,vm不是这层隔离

NodePtcRuntime.isolation='process',每次fresh Node process。它先 resolve(request)把cwd、timeout和authority补为明确spec,再 run(spec)执行;run必须已有sandboxPolicy和absolute cwd。direct filesystem access按本次file policy约束,而Node APIs仍可通过dynamic import使用。program-visible process.env开始为空,这并不意味着不能访问网络或任何读取路径。源码:index.ts · export class NodePtcRuntime。

流程连续读 execute():建abort controller、output ledger、wall timer → strip erasable TS →准备namespace boot data → resolve execution-world Node executable → 选bootstrap argv →按policy调用sandbox.confine(danger-full-access不包) →scrub启动env →subprocess.spawn带control/stdout/stderr pipes →等待ready →给boot data →处理call/reply/done →terminate managed range并等exit/drain输出 →resolve result。source/built/packaged三种bootstrap选取不能随意混用;SSH world还需预先verified bootstrap资产。源码:index.ts · private async execute、launch.ts · export function bootstrapArgs。

runtime默认配置 值 真实含义
timeoutMs 120000 包括nested tool/approval等待的elapsed budget
maxTimeoutMs 600000 数字budget部署上限
maxOutputBytes 67108864 logs、completion、diagnostic序列化组合上限
maxOldGenerationSizeMb 512 V8oldgen,不覆盖所有native/外部内存
maxMessageBytes 134217728 control frame、outstanding args、queued traffic
maxPendingCalls 128 同时Host binding calls上限
graceMs 3000 managed termination/输出drain宽限

这些是provider Config默认,不表示每个profile都必须照用。服务spec可明确timeoutMs:null表示无deadline,而model-facing run_code timeoutMs要求正数并封顶,0不能关闭deadline。unload标disposed,abort live runs并awaitfinished;之后resolve/run拒绝。源码:index.ts · static Config。

process stop可以中断CPU死循环,不依赖程序自觉检查signal;Host binding calls不随process消失自动结束,caller负责signal与drain。这正是run_code外层的runController和scheduler cleanup职责。资源结果在清理后结算,不把“child发done”当成整个调用已无资源。

11.4 私有通道与输出账本防止“日志也是协议”

control pipe与stdout/stderr分开。JsonChannel限制帧长、queued writes,Host要求ready先于program frame;call ids、global/name、args、pending count/bytes都验证。恶意或错误child发送越界frame得到protocol outcome,不会直接让它调用任意Host方法。

bootstrap给console有限五种level方法,把util.inspect结果写LogBuffer;stdout/stderr.write也被截获,raw native pipe输出仍由Host预算监管。combined JSON-byte accounting包括数组引号、逗号与escaping,而不是简单text.length,UTF-8与JSON转义不会偷偷超额。completion必须lossless JSON;超上限不是截出一个看似完整value,而是明确output-limit。源码:bootstrap.ts · export class LogBuffer、bootstrap.ts · export function prepareCompletion。

runtime PtcRunResult把programfailure放error字段,kinds区分exception、timeout、abort、worker-exit、invalid-output、output-limit、protocol、sandbox-unavailable;run_code consumer再转换成model-facing toolerror并保留捕获logs/沙箱提示。配置/调用前置条件误用仍可throw,不能把“正常program outcome不reject”解释为任何情况下永不throw。源码:types.ts · export interface PtcRunFailure。

11.5 nested调用复用native staged scheduler,Promise.all不等于所有检查并行

run_code里Host建立single ordered lane。每个nested call提交时分配 <outer>:ptc:<n>,保留rootCallId、parenttoken、calleragent、run signal;argument给dispatch/log分别detached JSON,避免tool修改args让日志漂移。

ordered start依次:append tool/ptc-dispatch-start → scheduler.prepare(guards/pre-execute)→launch body。只有around-dispatch/body可以并发,post-execute/finalization/context deferral/resultcommit也按submission order进single lane。相邻parallel类calls可以重叠到maxParallel;exclusive等pool drain,独占直到commit包括post-execute完成,再放行后续。lazy executionMode检查让queue中工具注册变化不能绕过新exclusive分类。源码:ptc.ts · interface PendingDispatch。

sequenceDiagram participant G as Program participant Q as Host ordered lane participant A as parallel body A participant B as parallel body B participant E as exclusive body C G->>Q: submit A, B, C Q->>A: ordered prepare A / dispatch Q->>B: ordered prepare B / dispatch B-->>Q: B较早settles,等待A的commit A-->>Q: A settles Q->>Q: commit A / post-execute Q-->>G: A value Q->>Q: commit B / post-execute Q-->>G: B value Q->>E: drain pool后prepare C / dispatch E-->>Q: C settles Q->>Q: commit C,释放exclusive barrier Q-->>G: C value

这意味着read-onlytool只有声明并被registry分类parallel才会并行;tool读取代码“看起来没有写”不等于scheduler知道。helper真正concurrency与JS Promise.all的表达各负一半责任。

每个settlement经 tools/ptc-dispatch-log waterfall可用spill/preview改durable copy,但program收到的typed value不被这条日志变形;settle value可先归还,logWork被tracked,外层结束前必须drain,使nested events不掉到已关闭turn之外。backpressure限制未完成log tasks防内存增长。源码:ptc.ts · const settle = (result:。

11.6 PTC权限、历史与副作用边界

program级sandbox escalation只授权这一次program direct effects;nested tools仍使用各自standing policy和approval。不把“允许run_code写workspace”变成“允许program中的外部删除/付款工具自动放行”。需要更宽program权限时先approver,再runtime launch;nested guard依次在Host执行。源码:ptc.ts · const standingPolicy。

model history只接收外层curated logs/value,不把100次nested完整返回逐个塞模型。nested start/settle事件保存审计重建;成功image-bearingresult另defer成typed context,additionalContexts与concludesTurn按结果pipeline转发。只有successful nested result携terminalmarker,policy转成failure不能让catch程序误触turn结论。

任何outer settlement都会abort runController并drain admitted dispatches,未startqueue abandoned且不伪造已执行日志。已经完成的write/HTTP/browser action 不回滚;program不会自动replay。catch ToolCallError并continue也是程序选择,不会撤销先前result。自己tool需要幂等性或显式补偿,尤其一次程序里组合多个有外部效果操作。

11.7 workflow是子Agent编排,而不等于run_code能访问所有Node API

WorkflowEngine定义start→WorkflowRun;PTC实现要求typescriptruntime和subagents、sandboxPolicy。start同步校验meta、script parse、provider存在、requestmaxTotalAgents≤deploymentceiling,无法开始则throw;run一旦返回,正常执行失败是result.stopReason,不以随机Promise rejection暴露。holder拥有run,engine卸载不会使捕获runtime/subagenthandle自动无效。源码:index.ts · start(request:、host.ts · export class PtcWorkflowRun。

workflowguest在外层confinedNodeprocess里再建vm context,仅提供 agent、parallel、pipeline、phase、log、args。编排脚本没有fs/network/timer/Node APIs,由子Agent执行实际任务;vm是语言运行上下文,不是安全隔离替代品,真正进程/fileconfinement在PTC层。syncTimeoutMs只管初始同步slice,whole-run取消/cleanup归Host。源码:runtime.ts · export class WorkflowExecution。

// workflow script body;meta另传,不写export const meta进body。
phase('读取与审查');
return await pipeline(args.files,
  async (_previous, file) => agent(`阅读 ${file} 并给出证据`, {
    label: `研究 ${file}`,
  }),
  async (previous, file) => agent(
    `独立核对 ${file} 的结论:${previous}`, { label: `复核 ${file}` }
  ),
);

并非保证上述审查child与第一child观点真正统计独立:你把previous喂给第二worker,属于交叉核对;真正独立审核应给原始文件与验收条件,先不要给作者结论,收报告后再合并。

11.8 workflow的null语义、fatal错误、caps和背景job

agent(prompt, opts)无schema返回最终text,schema给validated object;child普通failed结果返回null,infrastructure startup/result rejection是fatal;provider/model是LLM route overrides,subagentProvider另由engine/request选。unknown opts、unsupported schema、misusedhook、trippedcaps不应当普通null吞掉。支持options只有label/phase/schema/provider/model,effort/isolation/agentType被明确标deferred,不要从Claude Code接口类推。

parallel(thunks)是await-all barrier、ordinarythrow的item为null;pipeline(items,...stages)每item独立跑没有跨stage barrier,stageordinarythrow使该itemnull并跳余下stages。fatal hook/provider errors传播终止整个脚本。maxConcurrentAgents默认auto min(16,max(1,cores−2));maxTotalAgents默认1000;maxItemsPerCall默认4096;FIFOslot只调度Agent,不限制所有任意JS计算内存。源码:runtime.ts · private async agent、index.ts · const limits: WorkerLimits。

foreground tool等待run.result,总是dispose;非completed变toolerror。background返回ownedjobid,需要ctx.jobs和callercontroller,不能只留一个丢失Promise;job输出ring提供progress,completedvalue在notice到来。tool-workflow还把top-levelrun/childstart/end写log-only parent事件;记录失败被contained并disable记录,不影响正在执行的业务tool,因此trace完整性要检查,不能把“返回成功”泛称每个durable record都已写成功。源码:index.ts · function createWorkflowRecorder。

cancel停program并取消children,result结算等admitted startup/disposal到quiescence;即使script忘await agent Promise,也不能让child独立逃逸。仍然不回滚已交付的外部效果。

11.9 Ralph是fresh context迭代,不是普通goal的别名

tool-ralph以部署固定 RALPH_SCRIPT,用户只提供objective与较小maxRounds,不能改script/provider/schema/validation。每round fresh structuredchild不继承parentconversation或previouschildsession,长期记忆是sharedworkspace,跨round只有bounded report:status、summary、evidence、nextSteps、blocker。provider必须支持outputSchema且inheritsParentContext=false。源码:index.ts · const RALPH_SCRIPT、index.ts · function requireFreshProvider。

report.status continue要求非空nextSteps与空blocker;complete要求非空evidence、无nextSteps、空blocker;blocked要求具体blocker;整个handoff长度封顶。terminal包含complete、blocked、budget-limited,child普通failure给round-failed;budget-limited不是完成。schema验证只保证报告字段,不验证“测试通过”这段文字是真的,Host业务仍需 independently verify证据。tool默认maxRounds256,而preset row可64且disabled,这两个默认层别混为一谈。

flowchart TB Objective["immutable objective / maxRounds"] --> Round["fresh structured child"] Workspace["shared workspace实际状态"] --> Round Report["上一轮bounded report"] --> Round Round --> Validate["字段 + status约束 + 字符上限"] Validate --> Continue{status} Continue -->|continue| Cap{达到轮数上限?} Cap -->|否| Report Cap -->|是| Limited["budget-limited"] Continue -->|complete| Done["complete + evidence报告"] Continue -->|blocked| Block["blocked + concrete blocker"] Round -->|普通child失败| Failed["round-failed"]

源码usageguidance要求直接用户明确请求Ralph/fresh-agent iteration才用;这是这个工具的产品约束,普通same-session长期工作用goal。不要因为读了功能介绍就替用户后台开启无界freshworker。自己的产品应设置更小轮数/总child/token/费用上限,并提供独立验收工具。

11.10 选择哪条设计用于自己的Agent

单模型批量工具调用用PTC;很多独立研究/审查片段用workflow;必须保持长目标同Session上下文用goal;确实要求freshworker重读workspace的迭代才用Ralph。三者都要监测typed outcome和cleanup,而不只awaitresolvedPromise。Main和rc.2的PTCruntime/workflow主体基本相同,删除invariantcompanions不表示更换了隔离模型;实验版本仍以具体锁定exports为准。