@foldrun/core 2.7.0 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/store.ts CHANGED
@@ -981,6 +981,8 @@ export interface AgentInfo {
981
981
  /** Colleagues this agent may DELEGATE to (`subagents:`) — each runs with
982
982
  * its own context and its own tools, no wider than this agent's. */
983
983
  subagents: string[];
984
+ /** `sandbox: own` — delegated to, it runs each job in its own sandbox. */
985
+ sandbox?: "own";
984
986
  /** The `skills:` allowlist, or null when the field is absent — which is
985
987
  * not the same thing: absent inherits every skill in scope, an empty
986
988
  * list withholds all of them. */
@@ -1633,6 +1635,7 @@ export function listAgents(tenant: string, workspace: string): AgentInfo[] {
1633
1635
  retiredTools: retiredToolNames(data),
1634
1636
  consults: refNames(data.agents),
1635
1637
  subagents: refNames(data.subagents),
1638
+ ...(data.sandbox === "own" ? { sandbox: "own" as const } : {}),
1636
1639
  skills: data.skills === undefined ? null : refNames(data.skills),
1637
1640
  secrets: Array.isArray(data.secrets) ? data.secrets.map(String) : [],
1638
1641
  budget: runBudget(data.budget),
@@ -3294,6 +3297,17 @@ export function sumAttempts(tries: StepAttempt[]): Pick<StepAttempt, "costUsd" |
3294
3297
  export { type TokenUsage, type PromptLayerRecord, tokensOf, sumTokens, cacheHitRate, measuredHitRate, runTokens, promptLayersChanged, promptCacheNote } from "./tokens.ts";
3295
3298
  import { sumTokens, type TokenUsage, type PromptLayerRecord } from "./tokens.ts";
3296
3299
 
3300
+ /** One job a step handed to an isolated sub-agent. `id` is the job's own
3301
+ * record's `delegation` — not an index, which moves when records are
3302
+ * spliced in before it. */
3303
+ export interface StepDelegation {
3304
+ id: string;
3305
+ agent: string;
3306
+ status: "running" | "completed" | "failed" | "skipped";
3307
+ costUsd?: number | null;
3308
+ ms?: number | null;
3309
+ }
3310
+
3297
3311
  /** The marker rescueStep puts in a rescue's instruction; how a rescue record
3298
3312
  * written before `rescueOf` existed is still recognised. */
3299
3313
  export const RESCUE_MARKER = "<rescue>\nYou are taking this step over:";
@@ -3303,6 +3317,14 @@ export function isRescueRecord(step: { rescueOf?: string; instruction?: string |
3303
3317
  return !!step.rescueOf || (step.instruction ?? "").includes(RESCUE_MARKER);
3304
3318
  }
3305
3319
 
3320
+ /** Is this record put in by the runner rather than by the flow — an on-fail
3321
+ * rescue, or an isolated sub-agent's run (`subagentOf`)? Neither is launched
3322
+ * with a group, handed on as a group's output, reset by a loop, or copied by
3323
+ * a re-run as work. */
3324
+ export function isSplicedRecord(step: { rescueOf?: string; subagentOf?: string; instruction?: string | null }): boolean {
3325
+ return !!step.subagentOf || isRescueRecord(step);
3326
+ }
3327
+
3306
3328
  export interface StepRecord {
3307
3329
  agent: string;
3308
3330
  instruction: string;
@@ -3463,6 +3485,19 @@ export interface StepRecord {
3463
3485
  * steps). Records written before this field are recognised by their
3464
3486
  * instruction's `<rescue>` block instead — see isRescueRecord. */
3465
3487
  rescueOf?: string;
3488
+ /** Set on an isolated sub-agent's run (`sandbox: own`, delegate-tools.ts):
3489
+ * the agent of the step that delegated to it. It ran in its own sandbox
3490
+ * inside that step; only its reply went back. Like a rescue it is not a
3491
+ * step of the flow — see isSplicedRecord. */
3492
+ subagentOf?: string;
3493
+ /** On an isolated sub-agent's run: its job's id, as the delegating step's
3494
+ * `delegations[].id` names it. */
3495
+ delegation?: string;
3496
+ /** On a step that delegated to isolated sub-agents: each job, in order. */
3497
+ delegations?: StepDelegation[];
3498
+ /** On an isolated sub-agent's run: the workspace paths its sandbox's
3499
+ * changes were applied to — what its delegating step is told it wrote. */
3500
+ wrote?: string[];
3466
3501
  /**
3467
3502
  * Sandbox seconds this step rented, and how many of them were cold start.
3468
3503
  * Set only on the isolated path — an in-process step rents nothing, so
@@ -4254,6 +4289,9 @@ export function runVerdict(run: Pick<RunRecord, "verdict" | "summary">): Verdict
4254
4289
 
4255
4290
  export function runSummary(run: RunRecord): string | null {
4256
4291
  for (let i = run.steps.length - 1; i >= 0; i--) {
4292
+ // A sandbox: own job is appended after every step of the flow; its reply
4293
+ // went to the step that asked, which speaks for the run.
4294
+ if (run.steps[i].subagentOf) continue;
4257
4295
  const line = stepHeadline(run.steps[i]);
4258
4296
  if (line) return line;
4259
4297
  }
package/src/subagents.ts CHANGED
@@ -42,6 +42,25 @@ export interface SubagentSpec {
42
42
  model: string;
43
43
  }
44
44
 
45
+ /** An isolated sub-agent (`sandbox: own` on its own agent.md): what the
46
+ * parent's sandbox needs to offer it as a tool. Everything else — its tools,
47
+ * secrets, network, size — is resolved on the worker when the job arrives,
48
+ * from its own file, and never enters the parent's sandbox. */
49
+ export interface DelegateSpec {
50
+ name: string;
51
+ description: string;
52
+ }
53
+
54
+ /** Does this agent run in its own sandbox when delegated to? */
55
+ export function ownSandbox(front: { sandbox?: unknown }): boolean {
56
+ return front.sandbox === "own";
57
+ }
58
+
59
+ /** The tool the parent calls to hand an isolated sub-agent a job. */
60
+ export function delegateToolName(agent: string): string {
61
+ return `delegate_${agent.replaceAll("-", "_")}`;
62
+ }
63
+
45
64
  /** The SDK's delegation tool, under both names it has had. Never granted to a
46
65
  * sub-agent: that is what makes the depth one. */
47
66
  export const DELEGATE_TOOLS = ["Agent", "Task"] as const;