fix(scope): align trust and input boundaries
Rewrite the agent-scope RFC with executable examples and an explicit security non-goal. Harden subagent scalar and depth validation, and pin live tool-filter semantics across code, tests, and generated docs.
This commit is contained in:
@@ -29,7 +29,7 @@ Unlike the bash seam (one executor per context, second load throws), **multiple
|
||||
- **Start-time features** (`outputSchema`, `depthLimit`, `toolFilter/persona`) are a static `provider.capabilities` descriptor, checked by the service BEFORE a run exists. A request that needs one the provider lacks is **rejected loud** (`UNSUPPORTED_CAPABILITY`), never accepted-then-ignored.
|
||||
- **Runtime features** (steering, resume) are **optional methods** on `SubagentRun` (`sendMessage?`, `resume?`). The method's presence IS the capability; TS narrowing is the discovery mechanism — a consumer cannot call an absent method without narrowing first, so there is no silent degradation path.
|
||||
|
||||
Beside `capabilities` sits one DESCRIPTIVE fact: `provider.inheritsParentContext` — whether a child sees the parent conversation (`fork`: true — seeded with the completed-turn prefix; `spawn`/`acp`: false). The service validates that the descriptor is a boolean but does not interpret or enforce its meaning; the model-facing consumer (`dsh-tool-subagent`) derives truthful tool wording from it.
|
||||
Beside `capabilities` sits one DESCRIPTIVE fact: `provider.inheritsParentContext` — whether a child sees the parent conversation (`fork`: true — seeded with the completed-turn prefix; `spawn`/`acp`: false). “Context” here means conversation history only; it says nothing about tool registrations, injected services, or authority inheritance. The service validates that the descriptor is a boolean but does not interpret or enforce its meaning; the model-facing consumer (`dsh-tool-subagent`) derives truthful tool wording from it.
|
||||
|
||||
## Run lifecycle
|
||||
|
||||
|
||||
@@ -69,9 +69,10 @@ export interface SubagentStartRequest {
|
||||
*/
|
||||
outputSchema?: StructuredOutputSchema
|
||||
/**
|
||||
* Optional recursion cap (max delegation depth below this child). Must be a
|
||||
* non-negative safe integer. Requires {@link SubagentCapabilities.depthLimit};
|
||||
* rejected at start otherwise.
|
||||
* Optional absolute delegation-depth cap for the child being started: its
|
||||
* computed depth must be less than or equal to this non-negative safe
|
||||
* integer. Requires {@link SubagentCapabilities.depthLimit}; rejected at
|
||||
* start otherwise.
|
||||
*/
|
||||
maxDepth?: number
|
||||
/**
|
||||
@@ -195,13 +196,15 @@ export interface SubagentProvider {
|
||||
/** The start-time features this provider supports (see {@link SubagentCapabilities}). */
|
||||
readonly capabilities: SubagentCapabilities
|
||||
/**
|
||||
* The provider's context contract: `true` when a child SEES the parent
|
||||
* The provider's conversation-history descriptor: `true` when a child SEES the parent
|
||||
* conversation (fork — the child is seeded with the parent's completed-turn
|
||||
* prefix), `false` when it starts fresh (spawn, ACP). A DESCRIPTIVE fact,
|
||||
* not a start-time capability: the service validates nothing against it —
|
||||
* the model-facing consumer (`dsh-tool-subagent`) derives truthful tool
|
||||
* wording from it, so a tool bound to a fork provider stops telling the
|
||||
* model the child "does not see this conversation".
|
||||
* model the child "does not see this conversation". This descriptor concerns
|
||||
* conversation history only; it says nothing about tool registrations,
|
||||
* injected services, or authority inheritance.
|
||||
*/
|
||||
readonly inheritsParentContext: boolean
|
||||
/**
|
||||
|
||||
@@ -226,7 +226,7 @@ describe('SubagentService', () => {
|
||||
patch: { capabilities: { ...NO_CAPS, persona: 'yes' } },
|
||||
message: 'capability "persona" must be a boolean',
|
||||
},
|
||||
{ label: 'a non-boolean context descriptor', patch: { inheritsParentContext: 'yes' }, message: 'inheritsParentContext must be a boolean' },
|
||||
{ label: 'a non-boolean conversation-history descriptor', patch: { inheritsParentContext: 'yes' }, message: 'inheritsParentContext must be a boolean' },
|
||||
{ label: 'a non-callable start field', patch: { start: 42 }, message: 'start must be a function' },
|
||||
])('rejects a provider registration with $label before entering the registry', async ({ patch, message }) => {
|
||||
const ctx = new Context()
|
||||
@@ -431,6 +431,7 @@ describe('SubagentService', () => {
|
||||
})
|
||||
|
||||
it.each([
|
||||
{ label: 'null', value: null as unknown as number },
|
||||
{ label: 'a string', value: '1' as unknown as number },
|
||||
{ label: 'NaN', value: Number.NaN },
|
||||
{ label: 'positive infinity', value: Number.POSITIVE_INFINITY },
|
||||
|
||||
Reference in New Issue
Block a user