@dudousxd/nestjs-agent 0.11.1 → 0.12.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/README.md CHANGED
@@ -41,6 +41,66 @@ The module mounts SSE + REST endpoints under `/agent` (`POST /agent/chat`, tool-
41
41
  threads, quota). Add `durable: true` + `AgentDurableModule` for the durable runner;
42
42
  `AgentModule.forFeature([…])` for multi-agent orchestration.
43
43
 
44
+ ### Message attachments
45
+
46
+ A chat turn attaches a file by **`mediaId` only**:
47
+
48
+ ```jsonc
49
+ POST /agent/chat
50
+ { "message": "what is in this?", "attachments": [{ "mediaId": "med_8f21…" }] }
51
+ ```
52
+
53
+ Every other field a client sends with it is discarded. The url the model provider fetches is read
54
+ back from the `AttachmentStagingStore` bound to `AGENT_ATTACHMENT_STAGING`, whose `resolve({ mediaId,
55
+ actor })` returns the attachment or `null` when the id is unknown or is not that actor's — so a
56
+ caller can never point the server at a url it chose (link-local metadata, an internal service), and
57
+ never at another actor's file. Resolution happens per turn, so a short-lived presigned url is minted
58
+ fresh rather than replayed from the stored message.
59
+
60
+ With nothing bound to `AGENT_ATTACHMENT_STAGING` there is no way to tell whose media an id is, so a
61
+ turn carrying attachments is refused with `501`. Text-only turns are unaffected. At most 10
62
+ attachments per turn.
63
+
64
+ The optional `POST /agent/attachments` upload route (`attachments: { upload: true }`) aborts a body
65
+ larger than `HARD_MAX_ATTACHMENT_BYTES` (32 MiB) while it streams; `attachments.maxBytes` narrows
66
+ that ceiling at request time and cannot raise it.
67
+
68
+ ### Sweeping attachments nobody sent
69
+
70
+ `stage()` writes bytes before any message exists, so a user who attaches a file and then never sends
71
+ it leaves media with nothing pointing at it. Two optional methods make that findable — `list` on
72
+ your `AttachmentStagingStore` (you stored the bytes, so only you can enumerate them) and
73
+ `referencedMediaIds` on your `AgentStore` (only it can see which media a live message carries).
74
+
75
+ ```ts
76
+ // a job the host runs; nothing here is reachable over HTTP
77
+ const olderThan = new Date(Date.now() - 48 * 60 * 60 * 1000);
78
+ for (const entry of await agents.collectableAttachments(actor, { olderThan })) {
79
+ await myMediaStore.delete(entry.mediaId); // the library never deletes your bytes
80
+ }
81
+ ```
82
+
83
+ `olderThan` is required and has no default: freshly staged media is an upload in flight, not
84
+ garbage, and only you know how long one of your composers may sit open. It is pushed down to `list`
85
+ and re-applied to the result, so a store that ignores the hint still cannot delete a live upload.
86
+
87
+ References are re-derived on every call rather than latched, because `truncateFrom` — which is what
88
+ regenerating a turn does — deletes messages and frees their media again. If either method is
89
+ missing the call raises `501` rather than reporting everything as collectable.
90
+
91
+ `GET /agent/attachments` returns the caller's own staged files as metadata (no urls — those are
92
+ minted per turn by `resolve`), bounded to `ATTACHMENT_PAGE_SIZE`, and rides the same
93
+ `attachments: { upload: true }` flag. It takes no `threadId` filter: a thread's attachments already
94
+ ride on its messages in `GET /agent/threads/:id`.
95
+
96
+ ### Who may read a run
97
+
98
+ `GET /agent/chat/:runId/stream` and `POST /agent/chat/:runId/cancel` both resolve the acting actor
99
+ and require they own the run currently streaming — `403` for another actor's run, `404` for a run
100
+ nobody is streaming (including one that has already finished). In-process callers that are
101
+ authorized elsewhere use `AgentService.subscribe(runId)`; anything reachable from a request must go
102
+ through `AgentService.subscribeAs(actor, runId)`.
103
+
44
104
  ### Deploying split API/worker pods
45
105
 
46
106
  By default `AgentModule`/`AgentDurableModule` wire everything: every controller, the `agent.run`
@@ -1,4 +1,5 @@
1
- import { ModelProvider, AgentStore, ToolRegistry, RolesPolicy, QuotaStore, TokenStreamSink, PromptBuilder, PromptContributor, ToolTransientRetrySetting, Retriever, AgentPricingStore, ToolSpec, ToolHandler, ActorResolver, AgentRegistry } from '@dudousxd/nestjs-agent-core';
1
+ import { ModelProvider, AgentStore, ToolRegistry, RolesPolicy, QuotaStore, TokenStreamSink, PromptBuilder, PromptContributor, ToolTransientRetrySetting, Retriever, AgentPricingStore, HistoryPolicy, InputProcessor, OutputProcessor, AgentIntake, SkillsConfig, MemoryConfig, ToolSpec, ToolHandler, ActorResolver, AgentHistoryWindow, SkillProvider, ScopeResolver, MemoryProvider, AgentRegistry } from '@dudousxd/nestjs-agent-core';
2
+ import { StandardSchemaV1 } from '@standard-schema/spec';
2
3
  import { Provider, FactoryProvider, Type, CanActivate, ModuleMetadata, InjectionToken, OptionalFactoryDependency } from '@nestjs/common';
3
4
 
4
5
  /** Everything `runAgentLoop` needs, minus the per-run `day` the runner stamps. */
@@ -15,6 +16,10 @@ interface AgentDeps {
15
16
  /** App-wide `@SystemPromptContributor()` sections appended after the agent's base prompt. */
16
17
  promptContributors: PromptContributor[];
17
18
  maxSteps: number;
19
+ /** Nesting ceiling for delegation below this agent. Undefined → the loop's default. */
20
+ maxDelegationDepth?: number;
21
+ /** How many times one agent may appear on a chain. Undefined → the loop's default. */
22
+ maxAgentAppearances?: number;
18
23
  /** Agent-level tool allow-list. Undefined → all tools (after role filtering). */
19
24
  toolAllowList?: string[];
20
25
  /** Per-tool execution timeout in ms (from module options). Undefined → no timeout. */
@@ -32,6 +37,30 @@ interface AgentDeps {
32
37
  retrievalTopK?: number;
33
38
  /** Bound `AGENT_PRICING_STORE`, when a module (e.g. a store's) provides one. Undefined → no pricing. */
34
39
  pricingStore?: AgentPricingStore;
40
+ /** The agent's history ceiling, resolved from `@Agent({ history })` / module options. Undefined → unbounded. */
41
+ historyPolicy?: HistoryPolicy;
42
+ /** Module-wide prompt rewriters, applied before every model call. Empty → none. */
43
+ inputProcessors: InputProcessor[];
44
+ /** Module-wide answer gates. NON-EMPTY MEANS THE TURN'S MODEL OUTPUT IS BUFFERED, not streamed. */
45
+ outputProcessors: OutputProcessor[];
46
+ /** The agent's `@Agent({ outputSchema })`, resolved from DI — never over the wire. Undefined → free text. */
47
+ outputSchema?: StandardSchemaV1;
48
+ /** Extra model calls allowed to repair an answer that failed `outputSchema`. Undefined → 1. */
49
+ outputRepairAttempts?: number;
50
+ /** The agent's `@Agent({ intake })` question set, asked before the turn starts. Undefined → none. */
51
+ intake?: AgentIntake;
52
+ /** Whether the model may call the built-in `ask` tool this turn. Undefined/false → it never sees it. */
53
+ ask?: boolean;
54
+ /**
55
+ * The resolved skills seam (`forRoot({ skills })` + discovered `@Skill` providers). Undefined →
56
+ * no catalog, no `skill` tool, and the turn spends no checkpoint on either.
57
+ */
58
+ skills?: SkillsConfig;
59
+ /**
60
+ * The resolved memory seam (`forRoot({ memory })`). Undefined → no block, no `remember` tool, and
61
+ * the turn spends no checkpoint on either.
62
+ */
63
+ memory?: MemoryConfig;
35
64
  }
36
65
  declare function utcDay(date?: Date): string;
37
66
 
@@ -96,6 +125,61 @@ interface AgentAttachmentsOptions {
96
125
  */
97
126
  upload?: boolean;
98
127
  }
128
+ /**
129
+ * `AgentModuleOptions.skills` — where a turn's skills come from, and which scopes an actor draws
130
+ * them from. Its PRESENCE turns skills on: `skills: {}` offers whatever the `@Skill`-decorated
131
+ * providers declare, resolved against the actor's own, their tenant's and the deployment's scopes.
132
+ */
133
+ interface AgentSkillsOptions {
134
+ /**
135
+ * A provider of your own — skills a host stores and administers itself (per user, per sector, per
136
+ * base). Merged with the discovered `@Skill` classes, and outranking them for a name published at
137
+ * the same scope. Omit → the decorated classes are the whole catalog.
138
+ */
139
+ provider?: SkillProvider;
140
+ /**
141
+ * Which scope tokens an actor draws from, most specific first — the precedence order. Omit → the
142
+ * actor's own (`actor:<id>`), their tenant's (`tenant:<ref>`, when they have one), and `global`.
143
+ * Supply one to add an axis this library has no key for: a sector, a squadron, a shift.
144
+ */
145
+ scopes?: ScopeResolver;
146
+ /**
147
+ * How many skills the catalog block offers before it starts leaving the widest ones out. Omit →
148
+ * 20. A ceiling on what the SYSTEM PROMPT carries, not on how many a deployment may hold.
149
+ */
150
+ maxSkills?: number;
151
+ }
152
+ /**
153
+ * `AgentModuleOptions.memory` — where what the assistant concluded about a person is stored, and how
154
+ * much of it a turn may carry. Its PRESENCE turns memory on.
155
+ *
156
+ * Unlike `skills`, there is no decorator and no zero-config form: a memory is by definition something
157
+ * the agent worked out about someone, so a memory a developer wrote in a class would be an
158
+ * instruction wearing a memory's clothes — which is what `@Skill` and `@SystemPromptContributor()`
159
+ * are for. The rows are the host's from the start.
160
+ */
161
+ interface AgentMemoryOptions {
162
+ /**
163
+ * Where the rows live. `forget` is required on it; `write` is what decides the `remember` tool;
164
+ * `search` is what decides whether a turn's block is selected by relevance or read whole. Supply
165
+ * `search` once the applicable set outgrows `maxMemories` — without it the ceiling selects by
166
+ * scope, and the widest scopes are the ones it starves.
167
+ */
168
+ provider: MemoryProvider;
169
+ /**
170
+ * Which scope tokens an actor draws from, most specific first — the precedence order, and the same
171
+ * seam `skills` uses. Omit → the actor's own, their tenant's, and `global`.
172
+ */
173
+ scopes?: ScopeResolver;
174
+ /**
175
+ * How many memories the block carries. Omit → 20. A budget on the PROMPT, never on the store: with
176
+ * a provider that can `search`, this is how many matter right now rather than how many a person
177
+ * may have. Always-on (`pinned`) memories are taken from it first.
178
+ */
179
+ maxMemories?: number;
180
+ /** How long one memory may be, checked when it is written. Omit → 240. */
181
+ maxFactChars?: number;
182
+ }
99
183
  interface AgentModuleOptions {
100
184
  /** The LLM provider (e.g. a Vercel AI SDK wrapper). Required. */
101
185
  model: ModelProvider;
@@ -191,6 +275,75 @@ interface AgentModuleOptions {
191
275
  retriever: Retriever;
192
276
  topK?: number;
193
277
  };
278
+ /**
279
+ * Bounds how much of a thread rides into each turn. Omit and a turn carries EVERY message the
280
+ * thread holds, so a long-lived thread costs more each time and eventually exceeds the model's
281
+ * context limit outright. `{ maxMessages }` and/or `{ maxTokens }` keep the newest that fit;
282
+ * `{ summarize: true }` folds what they left out into a leading summary, at the price of one extra
283
+ * model call per run (recorded as `history_summary` usage). An agent can override this for itself
284
+ * with `@Agent({ history })`.
285
+ */
286
+ history?: AgentHistoryWindow;
287
+ /**
288
+ * A {@link HistoryPolicy} of your own — for a window the built-in can't express (pinning the
289
+ * thread's opening brief, keeping every message that carries a tool result, a budget that varies
290
+ * by actor). A convenience-vs-custom pair like `quotaLimitTokens`/`quota`: this outranks a
291
+ * module-wide `history`, and `@Agent({ history })` outranks both for the agent that declares it.
292
+ * `select` MUST be pure — see the SPI's determinism contract.
293
+ */
294
+ historyPolicy?: HistoryPolicy;
295
+ /**
296
+ * Rewrites the prompt before every model call — masking identifiers, stamping a policy preamble,
297
+ * trimming an oversized tool result. Applies to EVERY agent: a transformation one persona can opt
298
+ * out of is not a control. Transformation only; `history`/`historyPolicy` still own which messages
299
+ * are there to transform. A processor needing DI-resolved dependencies is built in
300
+ * `forRootAsync`'s `useFactory` (which has `inject`), the same way `retrieval.retriever` is.
301
+ */
302
+ inputProcessors?: InputProcessor[];
303
+ /**
304
+ * Gates every model answer before the stream, the store or the next step sees it — redact,
305
+ * replace, or refuse the turn outright (which fails it with an `output_rejected` stream error).
306
+ *
307
+ * REGISTERING ONE TURNS OFF LIVE TOKEN STREAMING. Reading the whole answer and streaming it as it
308
+ * is generated cannot both be true, so the turn's model output is buffered and released in one
309
+ * frame once the chain passes. That is the price of a gate that actually gates; see
310
+ * `AgentLoopDeps.outputProcessors` for exactly what a subscriber still receives live.
311
+ */
312
+ outputProcessors?: OutputProcessor[];
313
+ /**
314
+ * Offer every agent the built-in `ask` tool, so the model can put its own clarifying question set
315
+ * to the user when it judges the scope is missing. The turn parks on the answers exactly as it
316
+ * parks on a HITL approval, and they arrive through `POST /agent/tool-call/answer`.
317
+ *
318
+ * `ask` is not a registered tool and has no handler — the loop settles it against a person — so no
319
+ * `RolesPolicy` gates it: asking a question performs nothing. An `@Agent({ ask })` overrides this
320
+ * for one persona. Omit → the model never sees it, and nothing about a turn changes.
321
+ */
322
+ ask?: boolean;
323
+ /**
324
+ * Authored procedures any agent may pull in when a task calls for one — per-user, per-tenant or
325
+ * deployment-wide, with the most specific winning. Declared as `@Skill`-decorated providers and/or
326
+ * served by a `provider` of your own; offered to the model as a one-line-each catalog it loads
327
+ * from on demand, so an instruction costs a prompt only on the turns that need it.
328
+ *
329
+ * Omit → no catalog, no `skill` tool, and a turn's checkpoint sequence is byte-identical to one
330
+ * that never had the option. See {@link AgentSkillsOptions}.
331
+ */
332
+ skills?: AgentSkillsOptions;
333
+ /**
334
+ * What the assistant has concluded about the actor and their organisation, carried into every turn
335
+ * as a bounded block of one-line facts and written by the model through a built-in `remember` tool.
336
+ *
337
+ * NOT the same thing as retrieval, and not a second name for it: `retrieval` answers from documents
338
+ * a person curated and can fix at the source, memory answers from the agent's own inferences about
339
+ * someone who never saw them written. That is why the rows carry an origin, why the block tells the
340
+ * model they may be wrong, and why `GET /agent/memories` + `DELETE /agent/memories/:id` are part of
341
+ * the feature rather than an optional console.
342
+ *
343
+ * Omit → no block, no `remember` tool, and a turn's checkpoint sequence is byte-identical to one
344
+ * that never had the option. See {@link AgentMemoryOptions}.
345
+ */
346
+ memory?: AgentMemoryOptions;
194
347
  /**
195
348
  * The name of the agent a turn uses when the caller doesn't select one. Omit → the single
196
349
  * discovered `@Agent` (when there is exactly one), else `'default'` (a bare assistant if no
@@ -273,8 +426,18 @@ interface AgentModuleAsyncOptions extends Pick<ModuleMetadata, 'imports'> {
273
426
  surface?: AgentSurface;
274
427
  }
275
428
 
276
- /** The synthesized `agent`-kind tool name an orchestrator uses to hand off to `target`. */
277
- declare function delegateToolName(target: string): string;
429
+ /**
430
+ * The synthesized `agent`-kind tool name an orchestrator uses to hand off to `target`.
431
+ *
432
+ * A detached edge gets its own verb — `start_research` next to `ask_research` — for two reasons.
433
+ * One agent can be both awaited AND backgrounded by the same orchestrator, and one name cannot
434
+ * carry both. And the name is the part of a tool the model reads first: `ask` promises an answer,
435
+ * `start` does not.
436
+ */
437
+ declare function delegateToolName(args: {
438
+ target: string;
439
+ detached?: boolean;
440
+ }): string;
278
441
  /** Builds the per-agent loop deps. The single-agent case is just the one registered `@Agent`. */
279
442
  declare class AgentDepsFactory {
280
443
  private readonly options;
@@ -287,7 +450,13 @@ declare class AgentDepsFactory {
287
450
  private readonly promptContributors;
288
451
  private readonly quota;
289
452
  private readonly pricingStore;
290
- constructor(options: AgentModuleOptions, model: ModelProvider, store: AgentStore, sink: TokenStreamSink, rolesPolicy: RolesPolicy, registry: ToolRegistry, agents: AgentRegistry, promptContributors: PromptContributor[], quota: QuotaStore | undefined, pricingStore: AgentPricingStore | undefined);
453
+ private readonly skills;
454
+ private readonly memory;
455
+ constructor(options: AgentModuleOptions, model: ModelProvider, store: AgentStore, sink: TokenStreamSink, rolesPolicy: RolesPolicy, registry: ToolRegistry, agents: AgentRegistry, promptContributors: PromptContributor[], quota: QuotaStore | undefined, pricingStore: AgentPricingStore | undefined, skills: SkillsConfig | undefined, memory: MemoryConfig | undefined);
456
+ /** The turn's memory seam, shared with the read-back endpoint. Undefined → memory is not configured. */
457
+ memoryConfig(): MemoryConfig | undefined;
458
+ /** The turn's skills seam, shared with the listing endpoint. Undefined → skills are not configured. */
459
+ skillsConfig(): SkillsConfig | undefined;
291
460
  /**
292
461
  * The agent a turn uses when the caller names none: the explicit `defaultAgent` option, else the
293
462
  * sole registered `@Agent` when there is exactly one, else `'default'` (a bare assistant).
@@ -295,8 +464,16 @@ declare class AgentDepsFactory {
295
464
  defaultAgentName(): string;
296
465
  private effectiveTools;
297
466
  forAgent(agentName?: string): AgentDeps;
467
+ /**
468
+ * The ceiling on how much of a thread this agent's turns carry, most specific wins: the agent's
469
+ * own `@Agent({ history })`, else a module-wide custom `historyPolicy`, else the module-wide
470
+ * `history` window. Undefined → unbounded, the behaviour of a module that configures none.
471
+ */
472
+ private historyPolicy;
473
+ /** Build the built-in window policy from the declarative `{ maxMessages, maxTokens, summarize }`. */
474
+ private window;
298
475
  /** Normalize the `followUps` option (`true` → 3, `{ count }` → count) to a number, or undefined. */
299
476
  private followUpsCount;
300
477
  }
301
478
 
302
- export { AgentDepsFactory as A, type FunctionalTool as F, type AgentSurface as a, type AgentModuleOptions as b, type AgentModuleAsyncOptions as c, AGENT_TOOL_BRAND as d, type AgentAttachmentsOptions as e, type AgentDeps as f, delegateToolName as g, provideAgentTool as p, utcDay as u };
479
+ export { AgentDepsFactory as A, type FunctionalTool as F, type AgentSurface as a, type AgentModuleOptions as b, type AgentModuleAsyncOptions as c, type AgentSkillsOptions as d, AGENT_TOOL_BRAND as e, type AgentAttachmentsOptions as f, type AgentDeps as g, type AgentMemoryOptions as h, delegateToolName as i, provideAgentTool as p, utcDay as u };
@@ -1,4 +1,5 @@
1
- import { ModelProvider, AgentStore, ToolRegistry, RolesPolicy, QuotaStore, TokenStreamSink, PromptBuilder, PromptContributor, ToolTransientRetrySetting, Retriever, AgentPricingStore, ToolSpec, ToolHandler, ActorResolver, AgentRegistry } from '@dudousxd/nestjs-agent-core';
1
+ import { ModelProvider, AgentStore, ToolRegistry, RolesPolicy, QuotaStore, TokenStreamSink, PromptBuilder, PromptContributor, ToolTransientRetrySetting, Retriever, AgentPricingStore, HistoryPolicy, InputProcessor, OutputProcessor, AgentIntake, SkillsConfig, MemoryConfig, ToolSpec, ToolHandler, ActorResolver, AgentHistoryWindow, SkillProvider, ScopeResolver, MemoryProvider, AgentRegistry } from '@dudousxd/nestjs-agent-core';
2
+ import { StandardSchemaV1 } from '@standard-schema/spec';
2
3
  import { Provider, FactoryProvider, Type, CanActivate, ModuleMetadata, InjectionToken, OptionalFactoryDependency } from '@nestjs/common';
3
4
 
4
5
  /** Everything `runAgentLoop` needs, minus the per-run `day` the runner stamps. */
@@ -15,6 +16,10 @@ interface AgentDeps {
15
16
  /** App-wide `@SystemPromptContributor()` sections appended after the agent's base prompt. */
16
17
  promptContributors: PromptContributor[];
17
18
  maxSteps: number;
19
+ /** Nesting ceiling for delegation below this agent. Undefined → the loop's default. */
20
+ maxDelegationDepth?: number;
21
+ /** How many times one agent may appear on a chain. Undefined → the loop's default. */
22
+ maxAgentAppearances?: number;
18
23
  /** Agent-level tool allow-list. Undefined → all tools (after role filtering). */
19
24
  toolAllowList?: string[];
20
25
  /** Per-tool execution timeout in ms (from module options). Undefined → no timeout. */
@@ -32,6 +37,30 @@ interface AgentDeps {
32
37
  retrievalTopK?: number;
33
38
  /** Bound `AGENT_PRICING_STORE`, when a module (e.g. a store's) provides one. Undefined → no pricing. */
34
39
  pricingStore?: AgentPricingStore;
40
+ /** The agent's history ceiling, resolved from `@Agent({ history })` / module options. Undefined → unbounded. */
41
+ historyPolicy?: HistoryPolicy;
42
+ /** Module-wide prompt rewriters, applied before every model call. Empty → none. */
43
+ inputProcessors: InputProcessor[];
44
+ /** Module-wide answer gates. NON-EMPTY MEANS THE TURN'S MODEL OUTPUT IS BUFFERED, not streamed. */
45
+ outputProcessors: OutputProcessor[];
46
+ /** The agent's `@Agent({ outputSchema })`, resolved from DI — never over the wire. Undefined → free text. */
47
+ outputSchema?: StandardSchemaV1;
48
+ /** Extra model calls allowed to repair an answer that failed `outputSchema`. Undefined → 1. */
49
+ outputRepairAttempts?: number;
50
+ /** The agent's `@Agent({ intake })` question set, asked before the turn starts. Undefined → none. */
51
+ intake?: AgentIntake;
52
+ /** Whether the model may call the built-in `ask` tool this turn. Undefined/false → it never sees it. */
53
+ ask?: boolean;
54
+ /**
55
+ * The resolved skills seam (`forRoot({ skills })` + discovered `@Skill` providers). Undefined →
56
+ * no catalog, no `skill` tool, and the turn spends no checkpoint on either.
57
+ */
58
+ skills?: SkillsConfig;
59
+ /**
60
+ * The resolved memory seam (`forRoot({ memory })`). Undefined → no block, no `remember` tool, and
61
+ * the turn spends no checkpoint on either.
62
+ */
63
+ memory?: MemoryConfig;
35
64
  }
36
65
  declare function utcDay(date?: Date): string;
37
66
 
@@ -96,6 +125,61 @@ interface AgentAttachmentsOptions {
96
125
  */
97
126
  upload?: boolean;
98
127
  }
128
+ /**
129
+ * `AgentModuleOptions.skills` — where a turn's skills come from, and which scopes an actor draws
130
+ * them from. Its PRESENCE turns skills on: `skills: {}` offers whatever the `@Skill`-decorated
131
+ * providers declare, resolved against the actor's own, their tenant's and the deployment's scopes.
132
+ */
133
+ interface AgentSkillsOptions {
134
+ /**
135
+ * A provider of your own — skills a host stores and administers itself (per user, per sector, per
136
+ * base). Merged with the discovered `@Skill` classes, and outranking them for a name published at
137
+ * the same scope. Omit → the decorated classes are the whole catalog.
138
+ */
139
+ provider?: SkillProvider;
140
+ /**
141
+ * Which scope tokens an actor draws from, most specific first — the precedence order. Omit → the
142
+ * actor's own (`actor:<id>`), their tenant's (`tenant:<ref>`, when they have one), and `global`.
143
+ * Supply one to add an axis this library has no key for: a sector, a squadron, a shift.
144
+ */
145
+ scopes?: ScopeResolver;
146
+ /**
147
+ * How many skills the catalog block offers before it starts leaving the widest ones out. Omit →
148
+ * 20. A ceiling on what the SYSTEM PROMPT carries, not on how many a deployment may hold.
149
+ */
150
+ maxSkills?: number;
151
+ }
152
+ /**
153
+ * `AgentModuleOptions.memory` — where what the assistant concluded about a person is stored, and how
154
+ * much of it a turn may carry. Its PRESENCE turns memory on.
155
+ *
156
+ * Unlike `skills`, there is no decorator and no zero-config form: a memory is by definition something
157
+ * the agent worked out about someone, so a memory a developer wrote in a class would be an
158
+ * instruction wearing a memory's clothes — which is what `@Skill` and `@SystemPromptContributor()`
159
+ * are for. The rows are the host's from the start.
160
+ */
161
+ interface AgentMemoryOptions {
162
+ /**
163
+ * Where the rows live. `forget` is required on it; `write` is what decides the `remember` tool;
164
+ * `search` is what decides whether a turn's block is selected by relevance or read whole. Supply
165
+ * `search` once the applicable set outgrows `maxMemories` — without it the ceiling selects by
166
+ * scope, and the widest scopes are the ones it starves.
167
+ */
168
+ provider: MemoryProvider;
169
+ /**
170
+ * Which scope tokens an actor draws from, most specific first — the precedence order, and the same
171
+ * seam `skills` uses. Omit → the actor's own, their tenant's, and `global`.
172
+ */
173
+ scopes?: ScopeResolver;
174
+ /**
175
+ * How many memories the block carries. Omit → 20. A budget on the PROMPT, never on the store: with
176
+ * a provider that can `search`, this is how many matter right now rather than how many a person
177
+ * may have. Always-on (`pinned`) memories are taken from it first.
178
+ */
179
+ maxMemories?: number;
180
+ /** How long one memory may be, checked when it is written. Omit → 240. */
181
+ maxFactChars?: number;
182
+ }
99
183
  interface AgentModuleOptions {
100
184
  /** The LLM provider (e.g. a Vercel AI SDK wrapper). Required. */
101
185
  model: ModelProvider;
@@ -191,6 +275,75 @@ interface AgentModuleOptions {
191
275
  retriever: Retriever;
192
276
  topK?: number;
193
277
  };
278
+ /**
279
+ * Bounds how much of a thread rides into each turn. Omit and a turn carries EVERY message the
280
+ * thread holds, so a long-lived thread costs more each time and eventually exceeds the model's
281
+ * context limit outright. `{ maxMessages }` and/or `{ maxTokens }` keep the newest that fit;
282
+ * `{ summarize: true }` folds what they left out into a leading summary, at the price of one extra
283
+ * model call per run (recorded as `history_summary` usage). An agent can override this for itself
284
+ * with `@Agent({ history })`.
285
+ */
286
+ history?: AgentHistoryWindow;
287
+ /**
288
+ * A {@link HistoryPolicy} of your own — for a window the built-in can't express (pinning the
289
+ * thread's opening brief, keeping every message that carries a tool result, a budget that varies
290
+ * by actor). A convenience-vs-custom pair like `quotaLimitTokens`/`quota`: this outranks a
291
+ * module-wide `history`, and `@Agent({ history })` outranks both for the agent that declares it.
292
+ * `select` MUST be pure — see the SPI's determinism contract.
293
+ */
294
+ historyPolicy?: HistoryPolicy;
295
+ /**
296
+ * Rewrites the prompt before every model call — masking identifiers, stamping a policy preamble,
297
+ * trimming an oversized tool result. Applies to EVERY agent: a transformation one persona can opt
298
+ * out of is not a control. Transformation only; `history`/`historyPolicy` still own which messages
299
+ * are there to transform. A processor needing DI-resolved dependencies is built in
300
+ * `forRootAsync`'s `useFactory` (which has `inject`), the same way `retrieval.retriever` is.
301
+ */
302
+ inputProcessors?: InputProcessor[];
303
+ /**
304
+ * Gates every model answer before the stream, the store or the next step sees it — redact,
305
+ * replace, or refuse the turn outright (which fails it with an `output_rejected` stream error).
306
+ *
307
+ * REGISTERING ONE TURNS OFF LIVE TOKEN STREAMING. Reading the whole answer and streaming it as it
308
+ * is generated cannot both be true, so the turn's model output is buffered and released in one
309
+ * frame once the chain passes. That is the price of a gate that actually gates; see
310
+ * `AgentLoopDeps.outputProcessors` for exactly what a subscriber still receives live.
311
+ */
312
+ outputProcessors?: OutputProcessor[];
313
+ /**
314
+ * Offer every agent the built-in `ask` tool, so the model can put its own clarifying question set
315
+ * to the user when it judges the scope is missing. The turn parks on the answers exactly as it
316
+ * parks on a HITL approval, and they arrive through `POST /agent/tool-call/answer`.
317
+ *
318
+ * `ask` is not a registered tool and has no handler — the loop settles it against a person — so no
319
+ * `RolesPolicy` gates it: asking a question performs nothing. An `@Agent({ ask })` overrides this
320
+ * for one persona. Omit → the model never sees it, and nothing about a turn changes.
321
+ */
322
+ ask?: boolean;
323
+ /**
324
+ * Authored procedures any agent may pull in when a task calls for one — per-user, per-tenant or
325
+ * deployment-wide, with the most specific winning. Declared as `@Skill`-decorated providers and/or
326
+ * served by a `provider` of your own; offered to the model as a one-line-each catalog it loads
327
+ * from on demand, so an instruction costs a prompt only on the turns that need it.
328
+ *
329
+ * Omit → no catalog, no `skill` tool, and a turn's checkpoint sequence is byte-identical to one
330
+ * that never had the option. See {@link AgentSkillsOptions}.
331
+ */
332
+ skills?: AgentSkillsOptions;
333
+ /**
334
+ * What the assistant has concluded about the actor and their organisation, carried into every turn
335
+ * as a bounded block of one-line facts and written by the model through a built-in `remember` tool.
336
+ *
337
+ * NOT the same thing as retrieval, and not a second name for it: `retrieval` answers from documents
338
+ * a person curated and can fix at the source, memory answers from the agent's own inferences about
339
+ * someone who never saw them written. That is why the rows carry an origin, why the block tells the
340
+ * model they may be wrong, and why `GET /agent/memories` + `DELETE /agent/memories/:id` are part of
341
+ * the feature rather than an optional console.
342
+ *
343
+ * Omit → no block, no `remember` tool, and a turn's checkpoint sequence is byte-identical to one
344
+ * that never had the option. See {@link AgentMemoryOptions}.
345
+ */
346
+ memory?: AgentMemoryOptions;
194
347
  /**
195
348
  * The name of the agent a turn uses when the caller doesn't select one. Omit → the single
196
349
  * discovered `@Agent` (when there is exactly one), else `'default'` (a bare assistant if no
@@ -273,8 +426,18 @@ interface AgentModuleAsyncOptions extends Pick<ModuleMetadata, 'imports'> {
273
426
  surface?: AgentSurface;
274
427
  }
275
428
 
276
- /** The synthesized `agent`-kind tool name an orchestrator uses to hand off to `target`. */
277
- declare function delegateToolName(target: string): string;
429
+ /**
430
+ * The synthesized `agent`-kind tool name an orchestrator uses to hand off to `target`.
431
+ *
432
+ * A detached edge gets its own verb — `start_research` next to `ask_research` — for two reasons.
433
+ * One agent can be both awaited AND backgrounded by the same orchestrator, and one name cannot
434
+ * carry both. And the name is the part of a tool the model reads first: `ask` promises an answer,
435
+ * `start` does not.
436
+ */
437
+ declare function delegateToolName(args: {
438
+ target: string;
439
+ detached?: boolean;
440
+ }): string;
278
441
  /** Builds the per-agent loop deps. The single-agent case is just the one registered `@Agent`. */
279
442
  declare class AgentDepsFactory {
280
443
  private readonly options;
@@ -287,7 +450,13 @@ declare class AgentDepsFactory {
287
450
  private readonly promptContributors;
288
451
  private readonly quota;
289
452
  private readonly pricingStore;
290
- constructor(options: AgentModuleOptions, model: ModelProvider, store: AgentStore, sink: TokenStreamSink, rolesPolicy: RolesPolicy, registry: ToolRegistry, agents: AgentRegistry, promptContributors: PromptContributor[], quota: QuotaStore | undefined, pricingStore: AgentPricingStore | undefined);
453
+ private readonly skills;
454
+ private readonly memory;
455
+ constructor(options: AgentModuleOptions, model: ModelProvider, store: AgentStore, sink: TokenStreamSink, rolesPolicy: RolesPolicy, registry: ToolRegistry, agents: AgentRegistry, promptContributors: PromptContributor[], quota: QuotaStore | undefined, pricingStore: AgentPricingStore | undefined, skills: SkillsConfig | undefined, memory: MemoryConfig | undefined);
456
+ /** The turn's memory seam, shared with the read-back endpoint. Undefined → memory is not configured. */
457
+ memoryConfig(): MemoryConfig | undefined;
458
+ /** The turn's skills seam, shared with the listing endpoint. Undefined → skills are not configured. */
459
+ skillsConfig(): SkillsConfig | undefined;
291
460
  /**
292
461
  * The agent a turn uses when the caller names none: the explicit `defaultAgent` option, else the
293
462
  * sole registered `@Agent` when there is exactly one, else `'default'` (a bare assistant).
@@ -295,8 +464,16 @@ declare class AgentDepsFactory {
295
464
  defaultAgentName(): string;
296
465
  private effectiveTools;
297
466
  forAgent(agentName?: string): AgentDeps;
467
+ /**
468
+ * The ceiling on how much of a thread this agent's turns carry, most specific wins: the agent's
469
+ * own `@Agent({ history })`, else a module-wide custom `historyPolicy`, else the module-wide
470
+ * `history` window. Undefined → unbounded, the behaviour of a module that configures none.
471
+ */
472
+ private historyPolicy;
473
+ /** Build the built-in window policy from the declarative `{ maxMessages, maxTokens, summarize }`. */
474
+ private window;
298
475
  /** Normalize the `followUps` option (`true` → 3, `{ count }` → count) to a number, or undefined. */
299
476
  private followUpsCount;
300
477
  }
301
478
 
302
- export { AgentDepsFactory as A, type FunctionalTool as F, type AgentSurface as a, type AgentModuleOptions as b, type AgentModuleAsyncOptions as c, AGENT_TOOL_BRAND as d, type AgentAttachmentsOptions as e, type AgentDeps as f, delegateToolName as g, provideAgentTool as p, utcDay as u };
479
+ export { AgentDepsFactory as A, type FunctionalTool as F, type AgentSurface as a, type AgentModuleOptions as b, type AgentModuleAsyncOptions as c, type AgentSkillsOptions as d, AGENT_TOOL_BRAND as e, type AgentAttachmentsOptions as f, type AgentDeps as g, type AgentMemoryOptions as h, delegateToolName as i, provideAgentTool as p, utcDay as u };