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策略。
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。
这意味着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,这两个默认层别混为一谈。
源码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为准。