diff --git a/docs/api/agent.md b/docs/api/agent.md index dd9dfe0..745c55c 100644 --- a/docs/api/agent.md +++ b/docs/api/agent.md @@ -1167,9 +1167,159 @@ readonly `string`[] *** +### ProfileMaterializationContract + +Defined in: [agent/profile-materialization.ts:40](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/profile-materialization.ts#L40) + +Declares which AgentProfile axes a concrete run path really carries. + +#### Properties + +##### name + +> **name**: `string` + +Defined in: [agent/profile-materialization.ts:42](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/profile-materialization.ts#L42) + +Human-readable run path, e.g. `createSandboxAct` or `prompt-only-message`. + +##### axes + +> **axes**: readonly [`AgentProfileMaterializationAxis`](#agentprofilematerializationaxis)[] + +Defined in: [agent/profile-materialization.ts:44](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/profile-materialization.ts#L44) + +Profile axes this run path actually carries into execution. + +*** + +### ProfileMaterializationIssue + +Defined in: [agent/profile-materialization.ts:48](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/profile-materialization.ts#L48) + +One changed AgentProfile axis that would be dropped by a run path. + +#### Properties + +##### contract + +> **contract**: `string` + +Defined in: [agent/profile-materialization.ts:49](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/profile-materialization.ts#L49) + +##### axis + +> **axis**: [`AgentProfileMaterializationAxis`](#agentprofilematerializationaxis) + +Defined in: [agent/profile-materialization.ts:50](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/profile-materialization.ts#L50) + +##### reason + +> **reason**: `"unsupported-axis"` + +Defined in: [agent/profile-materialization.ts:51](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/profile-materialization.ts#L51) + +##### supportedAxes + +> **supportedAxes**: readonly [`AgentProfileMaterializationAxis`](#agentprofilematerializationaxis)[] + +Defined in: [agent/profile-materialization.ts:52](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/profile-materialization.ts#L52) + +*** + +### DefineProfileMaterializationContractOptions + +Defined in: [agent/profile-materialization.ts:56](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/profile-materialization.ts#L56) + +Input for declaring a run path's profile-axis support. + +#### Properties + +##### name + +> **name**: `string` + +Defined in: [agent/profile-materialization.ts:57](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/profile-materialization.ts#L57) + +##### axes + +> **axes**: readonly [`AgentProfileMaterializationAxis`](#agentprofilematerializationaxis)[] + +Defined in: [agent/profile-materialization.ts:58](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/profile-materialization.ts#L58) + +*** + +### ValidateProfileMaterializationOptions + +Defined in: [agent/profile-materialization.ts:62](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/profile-materialization.ts#L62) + +Input for checking a candidate diff against a run path. + +#### Extended by + +- [`AssertProfileMaterializationOptions`](#assertprofilematerializationoptions) + +#### Properties + +##### contract + +> **contract**: [`ProfileMaterializationContract`](#profilematerializationcontract) + +Defined in: [agent/profile-materialization.ts:63](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/profile-materialization.ts#L63) + +##### changedAxes + +> **changedAxes**: readonly [`AgentProfileMaterializationAxis`](#agentprofilematerializationaxis)[] + +Defined in: [agent/profile-materialization.ts:64](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/profile-materialization.ts#L64) + +*** + +### AssertProfileMaterializationOptions + +Defined in: [agent/profile-materialization.ts:68](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/profile-materialization.ts#L68) + +Input for throwing on dropped profile axes. + +#### Extends + +- [`ValidateProfileMaterializationOptions`](#validateprofilematerializationoptions) + +#### Properties + +##### contract + +> **contract**: [`ProfileMaterializationContract`](#profilematerializationcontract) + +Defined in: [agent/profile-materialization.ts:63](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/profile-materialization.ts#L63) + +###### Inherited from + +[`ValidateProfileMaterializationOptions`](#validateprofilematerializationoptions).[`contract`](#contract-1) + +##### changedAxes + +> **changedAxes**: readonly [`AgentProfileMaterializationAxis`](#agentprofilematerializationaxis)[] + +Defined in: [agent/profile-materialization.ts:64](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/profile-materialization.ts#L64) + +###### Inherited from + +[`ValidateProfileMaterializationOptions`](#validateprofilematerializationoptions).[`changedAxes`](#changedaxes) + +##### context? + +> `optional` **context?**: `string` + +Defined in: [agent/profile-materialization.ts:70](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/profile-materialization.ts#L70) + +Extra label included in the thrown error, usually the caller or run id. + +*** + ### CreateSandboxActOptions -Defined in: [agent/sandbox-act.ts:47](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/sandbox-act.ts#L47) +Defined in: [agent/sandbox-act.ts:52](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/sandbox-act.ts#L52) #### Type Parameters @@ -1187,7 +1337,7 @@ Defined in: [agent/sandbox-act.ts:47](https://github.com/tangle-network/agent-ru > **baseProfile**: `AgentProfile` -Defined in: [agent/sandbox-act.ts:49](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/sandbox-act.ts#L49) +Defined in: [agent/sandbox-act.ts:54](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/sandbox-act.ts#L54) Canonical agent profile — the same one the prod chat turn uses. @@ -1195,7 +1345,7 @@ Canonical agent profile — the same one the prod chat turn uses. > **sandboxClient**: [`SandboxClient`](runtime.md#sandboxclient-3) -Defined in: [agent/sandbox-act.ts:51](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/sandbox-act.ts#L51) +Defined in: [agent/sandbox-act.ts:56](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/sandbox-act.ts#L56) Sandbox client used to boot the per-run sandbox. @@ -1203,7 +1353,7 @@ Sandbox client used to boot the per-run sandbox. > **buildPrompt**: (`persona`) => `string` -Defined in: [agent/sandbox-act.ts:53](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/sandbox-act.ts#L53) +Defined in: [agent/sandbox-act.ts:58](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/sandbox-act.ts#L58) Persona → prompt. Pure; the eval cell's input. @@ -1221,7 +1371,7 @@ Persona → prompt. Pure; the eval cell's input. > **output**: [`OutputAdapter`](runtime.md#outputadapter)\<`TRunOutput`\> -Defined in: [agent/sandbox-act.ts:55](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/sandbox-act.ts#L55) +Defined in: [agent/sandbox-act.ts:60](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/sandbox-act.ts#L60) Sandbox event stream → typed output the rubric scores. @@ -1229,7 +1379,7 @@ Sandbox event stream → typed output the rubric scores. > `optional` **compose?**: (`persona`) => `SandboxActComposeOverrides` -Defined in: [agent/sandbox-act.ts:60](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/sandbox-act.ts#L60) +Defined in: [agent/sandbox-act.ts:65](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/sandbox-act.ts#L65) Per-persona profile overrides (workspace-augmented system prompt, extra file mounts, tool flags, MCP connections). Overlaid onto `baseProfile`. @@ -1248,7 +1398,7 @@ file mounts, tool flags, MCP connections). Overlaid onto `baseProfile`. > `optional` **sandboxOverrides?**: `Partial`\<`Omit`\<`CreateSandboxOptions`, `"backend"`\>\> & `object` -Defined in: [agent/sandbox-act.ts:62](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/sandbox-act.ts#L62) +Defined in: [agent/sandbox-act.ts:67](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/sandbox-act.ts#L67) Sandbox-SDK overrides forwarded to `createSandboxForSpec`. @@ -1258,11 +1408,19 @@ Sandbox-SDK overrides forwarded to `createSandboxForSpec`. > `optional` **backend?**: `Omit`\<`BackendConfig`, `"profile"`\> +##### requiredProfileAxes? + +> `optional` **requiredProfileAxes?**: readonly [`AgentProfileMaterializationAxis`](#agentprofilematerializationaxis)[] + +Defined in: [agent/sandbox-act.ts:69](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/sandbox-act.ts#L69) + +Optional changed axes the caller expects this path to carry. + ##### name? > `optional` **name?**: `string` -Defined in: [agent/sandbox-act.ts:64](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/sandbox-act.ts#L64) +Defined in: [agent/sandbox-act.ts:71](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/sandbox-act.ts#L71) Stable run name surfaced in mapped `llm_call` events. @@ -1270,7 +1428,7 @@ Stable run name surfaced in mapped `llm_call` events. > `optional` **mapEvent?**: (`event`, `opts`) => [`RuntimeStreamEvent`](index.md#runtimestreamevent) \| `undefined` -Defined in: [agent/sandbox-act.ts:66](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/sandbox-act.ts#L66) +Defined in: [agent/sandbox-act.ts:73](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/sandbox-act.ts#L73) Override the `SandboxEvent → RuntimeStreamEvent` mapper. @@ -1459,6 +1617,64 @@ Defined in: [agent/surfaces.ts:193](https://github.com/tangle-network/agent-runt Defined in: [agent/surfaces.ts:194](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/surfaces.ts#L194) +## Type Aliases + +### KnownAgentProfileMaterializationAxis + +> **KnownAgentProfileMaterializationAxis** = *typeof* [`AGENT_PROFILE_MATERIALIZATION_AXES`](#agent_profile_materialization_axes)\[`number`\] + +Defined in: [agent/profile-materialization.ts:31](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/profile-materialization.ts#L31) + +*** + +### AgentProfileMaterializationAxis + +> **AgentProfileMaterializationAxis** = [`KnownAgentProfileMaterializationAxis`](#knownagentprofilematerializationaxis) \| `` `custom:${string}` `` + +Defined in: [agent/profile-materialization.ts:35](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/profile-materialization.ts#L35) + +AgentProfile axis name, with `custom:` reserved for caller-owned extensions. + +## Variables + +### AGENT\_PROFILE\_MATERIALIZATION\_AXES + +> `const` **AGENT\_PROFILE\_MATERIALIZATION\_AXES**: readonly \[`"identity"`, `"name"`, `"model"`, `"prompt"`, `"systemPrompt"`, `"instructions"`, `"resources"`, `"files"`, `"resourceInstructions"`, `"skills"`, `"resourceTools"`, `"resourceAgents"`, `"commands"`, `"tools"`, `"permissions"`, `"mcp"`, `"mcpConnections"`, `"connections"`, `"subagents"`, `"hooks"`, `"modes"`, `"confidential"`, `"metadata"`, `"extensions"`\] + +Defined in: [agent/profile-materialization.ts:4](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/profile-materialization.ts#L4) + +Known AgentProfile axes a run path may or may not carry into execution. + +*** + +### sandboxActProfileMaterialization + +> `const` **sandboxActProfileMaterialization**: [`ProfileMaterializationContract`](#profilematerializationcontract) + +Defined in: [agent/profile-materialization.ts:91](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/profile-materialization.ts#L91) + +Materialization contract for `createSandboxAct`, which forwards the full AgentProfile. + +*** + +### promptOnlyProfileMaterialization + +> `const` **promptOnlyProfileMaterialization**: [`ProfileMaterializationContract`](#profilematerializationcontract) + +Defined in: [agent/profile-materialization.ts:112](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/profile-materialization.ts#L112) + +Materialization contract for a run path that only injects prompt text. + +*** + +### promptResourceProfileMaterialization + +> `const` **promptResourceProfileMaterialization**: [`ProfileMaterializationContract`](#profilematerializationcontract) + +Defined in: [agent/profile-materialization.ts:118](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/profile-materialization.ts#L118) + +Materialization contract for a run path that injects prompt text plus inline resources. + ## Functions ### unimplementedAgentRun() @@ -1656,11 +1872,95 @@ optional on the type; missing means no measurement was wired). *** +### defineProfileMaterializationContract() + +> **defineProfileMaterializationContract**(`options`): [`ProfileMaterializationContract`](#profilematerializationcontract) + +Defined in: [agent/profile-materialization.ts:124](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/profile-materialization.ts#L124) + +Define the profile axes a concrete run path actually carries into execution. + +#### Parameters + +##### options + +[`DefineProfileMaterializationContractOptions`](#defineprofilematerializationcontractoptions) + +#### Returns + +[`ProfileMaterializationContract`](#profilematerializationcontract) + +*** + +### validateProfileMaterialization() + +> **validateProfileMaterialization**(`options`): readonly [`ProfileMaterializationIssue`](#profilematerializationissue)[] + +Defined in: [agent/profile-materialization.ts:138](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/profile-materialization.ts#L138) + +Return every changed profile axis that the selected run path would drop. + +#### Parameters + +##### options + +[`ValidateProfileMaterializationOptions`](#validateprofilematerializationoptions) + +#### Returns + +readonly [`ProfileMaterializationIssue`](#profilematerializationissue)[] + +*** + +### assertProfileMaterialization() + +> **assertProfileMaterialization**(`options`): `void` + +Defined in: [agent/profile-materialization.ts:159](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/profile-materialization.ts#L159) + +Throw when a candidate changes axes the selected run path cannot carry. + +#### Parameters + +##### options + +[`AssertProfileMaterializationOptions`](#assertprofilematerializationoptions) + +#### Returns + +`void` + +*** + +### renderProfileMaterializationIssues() + +> **renderProfileMaterializationIssues**(`issues`, `context?`): `string` + +Defined in: [agent/profile-materialization.ts:166](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/profile-materialization.ts#L166) + +Format profile-axis drop issues into a concise operator-facing error. + +#### Parameters + +##### issues + +readonly [`ProfileMaterializationIssue`](#profilematerializationissue)[] + +##### context? + +`string` + +#### Returns + +`string` + +*** + ### createSandboxAct() > **createSandboxAct**\<`TPersona`, `TRunOutput`\>(`options`): (`persona`, `ctx`) => [`AgentRunInvocation`](#agentruninvocation)\<`TRunOutput`\> -Defined in: [agent/sandbox-act.ts:78](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/sandbox-act.ts#L78) +Defined in: [agent/sandbox-act.ts:85](https://github.com/tangle-network/agent-runtime/blob/main/src/agent/sandbox-act.ts#L85) Build an `AgentRuntime.act` implementation backed by a single prod-profile sandbox run. The returned function honours the `act` contract: it returns diff --git a/docs/api/primitive-catalog.md b/docs/api/primitive-catalog.md index ea71e5e..5dedff7 100644 --- a/docs/api/primitive-catalog.md +++ b/docs/api/primitive-catalog.md @@ -147,31 +147,45 @@ Import from `@tangle-network/agent-runtime` — 211 exports. ### Vertical agent — manifest + improvement adapter -Import from `@tangle-network/agent-runtime/agent` — 33 exports. +Import from `@tangle-network/agent-runtime/agent` — 48 exports. | Symbol | Kind | Summary | |---|---|---| +| `assertProfileMaterialization` | function | Throw when a candidate changes axes the selected run path cannot carry. | | `collectAgentRun` | function | Drain `act`'s `events` into an array AND await its `output`. Useful for | | `createSandboxAct` | function | Build an `AgentRuntime.act` implementation backed by a single prod-profile | | `createSurfaceImprovementAdapter` | function | The substrate-default `ImprovementAdapter`: resolve each finding's subject to a real surface path, LLM-draft a unified-diff patch, then auto-apply or open a PR. | | `createSurfaceKnowledgeAdapter` | function | Wire a surface-based `KnowledgeAdapter` that writes analyst proposals to agent surface files. | | `defineAgent` | function | Construct a validated agent manifest. Throws `AgentManifestError` | +| `defineProfileMaterializationContract` | function | Define the profile axes a concrete run path actually carries into execution. | | `measureOutcome` | function | Run `runAnalystLoop` and stamp an `OutcomeMeasurement` onto the | +| `renderProfileMaterializationIssues` | function | Format profile-axis drop issues into a concise operator-facing error. | | `renderSurfaceIssues` | function | Format a list of surface validation issues into a human-readable error string. | | `resolveSubjectPath` | function | Resolve a parsed `FindingSubject` to the file path the substrate | | `unimplementedAgentRun` | function | Stub for agents whose `runtime.act` is not yet wired to the substrate's | +| `validateProfileMaterialization` | function | Return every changed profile axis that the selected run path would drop. | | `validateSurfaces` | function | Validate an `AgentSurfaces` map on disk — missing paths fail loud at `defineAgent` time instead of silently skipping self-improvement edits. | +| `AGENT_PROFILE_MATERIALIZATION_AXES` | const | Known AgentProfile axes a run path may or may not carry into execution. | +| `promptOnlyProfileMaterialization` | const | Materialization contract for a run path that only injects prompt text. | +| `promptResourceProfileMaterialization` | const | Materialization contract for a run path that injects prompt text plus inline resources. | +| `sandboxActProfileMaterialization` | const | Materialization contract for `createSandboxAct`, which forwards the full AgentProfile. | | `AgentManifestError` | class | Thrown when `defineAgent` finds a required surface missing on disk. | | `AgentManifest` | interface | The full agent manifest. Each agent ships ONE of these. | | `AgentSurfaces` | interface | Surface declarations. Every path is repo-relative (or absolute) at | +| `AssertProfileMaterializationOptions` | interface | Input for throwing on dropped profile axes. | | `CreateSurfaceKnowledgeAdapterOpts` | interface | Substrate-default `KnowledgeAdapter` — wraps agent-knowledge's | +| `DefineProfileMaterializationContractOptions` | interface | Input for declaring a run path's profile-axis support. | | `KnowledgeAdapterDeps` | interface | Build the adapter. We accept the agent-knowledge functions as DI so | | `OutcomeMeasurement` | interface | `OutcomeMeasurement` — the missing metric that turns the analyst | +| `ProfileMaterializationContract` | interface | Declares which AgentProfile axes a concrete run path really carries. | +| `ProfileMaterializationIssue` | interface | One changed AgentProfile axis that would be dropped by a run path. | | `SurfaceImprovementEdit` | interface | Substrate-default `ImprovementAdapter` — surfaces-driven, LLM-drafted | | `SurfaceLifecycle` | interface | One profile surface's artifact-lifecycle wiring — the declarative config a | | `SurfaceValidationIssue` | interface | Validate that every declared surface exists on disk under `repoRoot`. | +| `ValidateProfileMaterializationOptions` | interface | Input for checking a candidate diff against a run path. | +| `AgentProfileMaterializationAxis` | type | AgentProfile axis name, with `custom:` reserved for caller-owned extensions. | -**Undocumented supporting types** (add a TSDoc line at the declaration to earn a table row): `AgentRubric`, `AgentRunContext`, `AgentRunInvocation`, `AgentRuntime`, `AnalystConfig`, `AutoApplyPolicy`, `CreateSandboxActOptions`, `CreateSurfaceImprovementAdapterOpts`, `DraftPatchInput`, `DraftPatchOutput`, `JudgeConfig`, `OutcomeMeasurementOpts`, `ResolvedSurface`, `RubricDimension`. +**Undocumented supporting types** (add a TSDoc line at the declaration to earn a table row): `AgentRubric`, `AgentRunContext`, `AgentRunInvocation`, `AgentRuntime`, `AnalystConfig`, `AutoApplyPolicy`, `CreateSandboxActOptions`, `CreateSurfaceImprovementAdapterOpts`, `DraftPatchInput`, `DraftPatchOutput`, `JudgeConfig`, `OutcomeMeasurementOpts`, `ResolvedSurface`, `RubricDimension`, `KnownAgentProfileMaterializationAxis`. ### Intelligence SDK — Observe + provable-OFF billing diff --git a/src/agent/index.ts b/src/agent/index.ts index b88f4a2..0b8c7b0 100644 --- a/src/agent/index.ts +++ b/src/agent/index.ts @@ -40,6 +40,25 @@ export type { export { createSurfaceKnowledgeAdapter } from './knowledge-adapter' export type { OutcomeMeasurement, OutcomeMeasurementOpts } from './outcome' export { measureOutcome } from './outcome' +export type { + AgentProfileMaterializationAxis, + AssertProfileMaterializationOptions, + DefineProfileMaterializationContractOptions, + KnownAgentProfileMaterializationAxis, + ProfileMaterializationContract, + ProfileMaterializationIssue, + ValidateProfileMaterializationOptions, +} from './profile-materialization' +export { + AGENT_PROFILE_MATERIALIZATION_AXES, + assertProfileMaterialization, + defineProfileMaterializationContract, + promptOnlyProfileMaterialization, + promptResourceProfileMaterialization, + renderProfileMaterializationIssues, + sandboxActProfileMaterialization, + validateProfileMaterialization, +} from './profile-materialization' export type { CreateSandboxActOptions } from './sandbox-act' export { createSandboxAct } from './sandbox-act' export type { AgentSurfaces, ResolvedSurface, SurfaceValidationIssue } from './surfaces' diff --git a/src/agent/profile-materialization.ts b/src/agent/profile-materialization.ts new file mode 100644 index 0000000..3411bcd --- /dev/null +++ b/src/agent/profile-materialization.ts @@ -0,0 +1,227 @@ +import { ValidationError } from '../errors' + +/** Known AgentProfile axes a run path may or may not carry into execution. */ +export const AGENT_PROFILE_MATERIALIZATION_AXES = [ + 'identity', + 'name', + 'model', + 'prompt', + 'systemPrompt', + 'instructions', + 'resources', + 'files', + 'resourceInstructions', + 'skills', + 'resourceTools', + 'resourceAgents', + 'commands', + 'tools', + 'permissions', + 'mcp', + 'mcpConnections', + 'connections', + 'subagents', + 'hooks', + 'modes', + 'confidential', + 'metadata', + 'extensions', +] as const + +export type KnownAgentProfileMaterializationAxis = + (typeof AGENT_PROFILE_MATERIALIZATION_AXES)[number] + +/** AgentProfile axis name, with `custom:` reserved for caller-owned extensions. */ +export type AgentProfileMaterializationAxis = + | KnownAgentProfileMaterializationAxis + | `custom:${string}` + +/** Declares which AgentProfile axes a concrete run path really carries. */ +export interface ProfileMaterializationContract { + /** Human-readable run path, e.g. `createSandboxAct` or `prompt-only-message`. */ + name: string + /** Profile axes this run path actually carries into execution. */ + axes: readonly AgentProfileMaterializationAxis[] +} + +/** One changed AgentProfile axis that would be dropped by a run path. */ +export interface ProfileMaterializationIssue { + contract: string + axis: AgentProfileMaterializationAxis + reason: 'unsupported-axis' + supportedAxes: readonly AgentProfileMaterializationAxis[] +} + +/** Input for declaring a run path's profile-axis support. */ +export interface DefineProfileMaterializationContractOptions { + name: string + axes: readonly AgentProfileMaterializationAxis[] +} + +/** Input for checking a candidate diff against a run path. */ +export interface ValidateProfileMaterializationOptions { + contract: ProfileMaterializationContract + changedAxes: readonly AgentProfileMaterializationAxis[] +} + +/** Input for throwing on dropped profile axes. */ +export interface AssertProfileMaterializationOptions extends ValidateProfileMaterializationOptions { + /** Extra label included in the thrown error, usually the caller or run id. */ + context?: string +} + +const KNOWN_AXIS_SET = new Set(AGENT_PROFILE_MATERIALIZATION_AXES) + +const AXIS_PARENTS: Partial< + Record +> = { + name: 'identity', + systemPrompt: 'prompt', + instructions: 'prompt', + files: 'resources', + resourceInstructions: 'resources', + skills: 'resources', + resourceTools: 'resources', + resourceAgents: 'resources', + commands: 'resources', + mcpConnections: 'mcp', +} + +/** Materialization contract for `createSandboxAct`, which forwards the full AgentProfile. */ +export const sandboxActProfileMaterialization = defineProfileMaterializationContract({ + name: 'createSandboxAct', + axes: [ + 'identity', + 'model', + 'prompt', + 'resources', + 'tools', + 'permissions', + 'mcp', + 'connections', + 'subagents', + 'hooks', + 'modes', + 'confidential', + 'metadata', + 'extensions', + ], +}) + +/** Materialization contract for a run path that only injects prompt text. */ +export const promptOnlyProfileMaterialization = defineProfileMaterializationContract({ + name: 'prompt-only-message', + axes: ['prompt'], +}) + +/** Materialization contract for a run path that injects prompt text plus inline resources. */ +export const promptResourceProfileMaterialization = defineProfileMaterializationContract({ + name: 'prompt-resource-attachment', + axes: ['prompt', 'resources'], +}) + +/** Define the profile axes a concrete run path actually carries into execution. */ +export function defineProfileMaterializationContract( + options: DefineProfileMaterializationContractOptions, +): ProfileMaterializationContract { + const name = options.name.trim() + if (!name) { + throw new ValidationError('defineProfileMaterializationContract: name is required') + } + return { + name, + axes: normalizeAxes(options.axes, `${name}.axes`), + } +} + +/** Return every changed profile axis that the selected run path would drop. */ +export function validateProfileMaterialization( + options: ValidateProfileMaterializationOptions, +): readonly ProfileMaterializationIssue[] { + const changedAxes = normalizeAxes(options.changedAxes, 'changedAxes') + const supported = new Set( + normalizeAxes(options.contract.axes, `${options.contract.name}.axes`), + ) + const issues: ProfileMaterializationIssue[] = [] + for (const axis of changedAxes) { + if (isAxisSupported(axis, supported)) continue + issues.push({ + contract: options.contract.name, + axis, + reason: 'unsupported-axis', + supportedAxes: [...supported] as AgentProfileMaterializationAxis[], + }) + } + return issues +} + +/** Throw when a candidate changes axes the selected run path cannot carry. */ +export function assertProfileMaterialization(options: AssertProfileMaterializationOptions): void { + const issues = validateProfileMaterialization(options) + if (issues.length === 0) return + throw new ValidationError(renderProfileMaterializationIssues(issues, options.context)) +} + +/** Format profile-axis drop issues into a concise operator-facing error. */ +export function renderProfileMaterializationIssues( + issues: readonly ProfileMaterializationIssue[], + context?: string, +): string { + if (issues.length === 0) return '' + const contract = issues[0]?.contract ?? '' + const prefix = context ? `${context}: ` : '' + const droppedAxes = issues.map((issue) => issue.axis).join(', ') + const supportedAxes = issues[0]?.supportedAxes.join(', ') || '' + return [ + `${prefix}profile materialization would drop axis changes on "${contract}": ${droppedAxes}.`, + `Supported axes: ${supportedAxes}.`, + 'Use a run path that carries those AgentProfile axes, or remove them from the candidate.', + ].join('\n') +} + +function normalizeAxes( + axes: readonly AgentProfileMaterializationAxis[], + label: string, +): AgentProfileMaterializationAxis[] { + const out: AgentProfileMaterializationAxis[] = [] + const seen = new Set() + for (const raw of axes) { + const axis = normalizeAxis(raw, label) + if (seen.has(axis)) continue + seen.add(axis) + out.push(axis) + } + return out +} + +function normalizeAxis( + raw: AgentProfileMaterializationAxis, + label: string, +): AgentProfileMaterializationAxis { + if (typeof raw !== 'string') { + throw new ValidationError(`${label}: profile axis must be a string`) + } + const axis = raw.trim() + if (!axis) { + throw new ValidationError(`${label}: profile axis must be non-empty`) + } + if (!KNOWN_AXIS_SET.has(axis) && !axis.startsWith('custom:')) { + throw new ValidationError( + `${label}: unknown profile axis "${axis}". Use a known axis or custom:.`, + ) + } + return axis as AgentProfileMaterializationAxis +} + +function isAxisSupported( + axis: AgentProfileMaterializationAxis, + supported: ReadonlySet, +): boolean { + if (supported.has(axis)) return true + let parent = AXIS_PARENTS[axis as KnownAgentProfileMaterializationAxis] + while (parent) { + if (supported.has(parent)) return true + parent = AXIS_PARENTS[parent] + } + return false +} diff --git a/src/agent/sandbox-act.ts b/src/agent/sandbox-act.ts index cb4930f..0010f1a 100644 --- a/src/agent/sandbox-act.ts +++ b/src/agent/sandbox-act.ts @@ -28,6 +28,11 @@ import { mapSandboxEvent } from '../runtime' import { createSandboxForSpec } from '../runtime/run-loop' import type { RuntimeStreamEvent } from '../types' import type { AgentRunContext, AgentRunInvocation } from './define-agent' +import { + type AgentProfileMaterializationAxis, + assertProfileMaterialization, + sandboxActProfileMaterialization, +} from './profile-materialization' /** Per-persona profile-merge slots applied over the base profile (§1.5: the caller authors the * per-persona profile). Each slot overlays the base; an absent slot leaves the base untouched. */ @@ -60,6 +65,8 @@ export interface CreateSandboxActOptions { compose?: (persona: TPersona) => SandboxActComposeOverrides /** Sandbox-SDK overrides forwarded to `createSandboxForSpec`. */ sandboxOverrides?: AgentRunSpec['sandboxOverrides'] + /** Optional changed axes the caller expects this path to carry. */ + requiredProfileAxes?: readonly AgentProfileMaterializationAxis[] /** Stable run name surfaced in mapped `llm_call` events. */ name?: string /** Override the `SandboxEvent → RuntimeStreamEvent` mapper. */ @@ -78,6 +85,11 @@ export interface CreateSandboxActOptions { export function createSandboxAct( options: CreateSandboxActOptions, ): (persona: TPersona, ctx: AgentRunContext) => AgentRunInvocation { + assertProfileMaterialization({ + contract: sandboxActProfileMaterialization, + changedAxes: options.requiredProfileAxes ?? [], + context: 'createSandboxAct', + }) const mapEvent = options.mapEvent ?? mapSandboxEvent return (persona: TPersona, ctx: AgentRunContext): AgentRunInvocation => { diff --git a/tests/profile-materialization.test.ts b/tests/profile-materialization.test.ts new file mode 100644 index 0000000..fa37907 --- /dev/null +++ b/tests/profile-materialization.test.ts @@ -0,0 +1,98 @@ +import { describe, expect, it } from 'vitest' +import { + assertProfileMaterialization, + defineProfileMaterializationContract, + promptOnlyProfileMaterialization, + promptResourceProfileMaterialization, + sandboxActProfileMaterialization, + validateProfileMaterialization, +} from '../src/agent' + +describe('profile materialization contracts', () => { + it('lets a broad prompt contract carry prompt sub-axes', () => { + expect( + validateProfileMaterialization({ + contract: promptOnlyProfileMaterialization, + changedAxes: ['systemPrompt', 'instructions'], + }), + ).toEqual([]) + }) + + it('lets a broad resources contract carry resource sub-axes', () => { + expect( + validateProfileMaterialization({ + contract: promptResourceProfileMaterialization, + changedAxes: ['files', 'skills', 'resourceTools', 'commands'], + }), + ).toEqual([]) + }) + + it('reports profile axes a limited path would drop', () => { + const issues = validateProfileMaterialization({ + contract: promptResourceProfileMaterialization, + changedAxes: ['prompt', 'tools', 'permissions', 'mcp'], + }) + + expect(issues.map((issue) => issue.axis)).toEqual(['tools', 'permissions', 'mcp']) + expect(issues[0]?.supportedAxes).toEqual(['prompt', 'resources']) + }) + + it('throws before a limited path can silently drop tool or permission changes', () => { + expect(() => + assertProfileMaterialization({ + contract: promptResourceProfileMaterialization, + changedAxes: ['tools', 'permissions'], + context: 'candidate:r348-tools', + }), + ).toThrow( + [ + 'candidate:r348-tools: profile materialization would drop axis changes on "prompt-resource-attachment": tools, permissions.', + 'Supported axes: prompt, resources.', + 'Use a run path that carries those AgentProfile axes, or remove them from the candidate.', + ].join('\n'), + ) + }) + + it('declares createSandboxAct as the full profile path', () => { + expect( + validateProfileMaterialization({ + contract: sandboxActProfileMaterialization, + changedAxes: [ + 'name', + 'model', + 'systemPrompt', + 'files', + 'skills', + 'tools', + 'permissions', + 'mcpConnections', + 'connections', + 'subagents', + 'hooks', + 'modes', + 'confidential', + 'metadata', + 'extensions', + ], + }), + ).toEqual([]) + }) + + it('deduplicates axes while preserving first-seen order', () => { + const contract = defineProfileMaterializationContract({ + name: 'custom-path', + axes: ['prompt', 'prompt', 'tools'], + }) + + expect(contract.axes).toEqual(['prompt', 'tools']) + }) + + it('rejects misspelled axes instead of treating them as carried', () => { + expect(() => + defineProfileMaterializationContract({ + name: 'bad-path', + axes: ['toolz' as never], + }), + ).toThrow('unknown profile axis "toolz"') + }) +}) diff --git a/tests/sandbox-act.test.ts b/tests/sandbox-act.test.ts index 300b2c5..62d74f7 100644 --- a/tests/sandbox-act.test.ts +++ b/tests/sandbox-act.test.ts @@ -138,4 +138,17 @@ describe('createSandboxAct — prod-profile eval parity', () => { }) await expect(collectAgentRun(act('p', ctx()))).rejects.toThrow('stream boom') }) + + it('fails at construction when the caller requires an unsupported custom profile axis', () => { + const { client } = fakeClient(SCRIPT) + expect(() => + createSandboxAct({ + baseProfile: BASE, + sandboxClient: client, + buildPrompt: () => 'x', + output, + requiredProfileAxes: ['custom:side-channel'], + }), + ).toThrow('profile materialization would drop axis changes') + }) })