@stratusagent/core 0.11.6 → 0.11.7

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/dist/index.d.ts CHANGED
@@ -169,6 +169,44 @@ export interface Message {
169
169
  * — the boundary provenance draws, here at the point text enters.
170
170
  */
171
171
  overheard?: boolean;
172
+ /**
173
+ * Appended by `observe`, so it started no turn. `overheard` alone cannot
174
+ * say so: a turn dispatched with `addressed: false` is overheard too, and
175
+ * is a turn. Present only when true; a message observed before this was
176
+ * recorded reads as a turn, which errs toward attributing nothing to an
177
+ * earlier one (`isLatestTurn`).
178
+ */
179
+ observed?: boolean;
180
+ /**
181
+ * The caller's idempotency key for the dispatch that brought this user
182
+ * message (`RunInput.idempotencyKey`). Written in the same save as the
183
+ * message, so a key is never durable without its message or the other
184
+ * way round; `workItemState` reads it back.
185
+ */
186
+ idempotencyKey?: string;
187
+ /**
188
+ * Why the turn this user message started failed, written by
189
+ * `recordTurnFailure` in the save that fails it. The session's
190
+ * `lastError` is the latest turn's and the next turn replaces it; this
191
+ * stays, so `turnFailureFor` can answer for any turn a key names.
192
+ */
193
+ turnError?: string;
194
+ /**
195
+ * This message's turn was continued once after the process running it
196
+ * died (`AgentRunner.continueTurn`). A host fails a second such turn
197
+ * instead of continuing it again: a turn that takes the process down
198
+ * with it would otherwise do so on every start. Present only when true.
199
+ */
200
+ continuedAfterCrash?: boolean;
201
+ /**
202
+ * The turn this message started could reach a provider that runs its own
203
+ * tool loop (a harness): what that turn did is not all in this
204
+ * transcript, so a crash mid-turn cannot be continued from it. Recorded
205
+ * at accept time, because the runtime a crashed turn ran on is not the
206
+ * one a restart resolves if the configuration changed in between.
207
+ * Present only when true; see `RunInput.hostedLoop`.
208
+ */
209
+ hostedLoop?: boolean;
172
210
  }
173
211
  export interface AgentDescriptor {
174
212
  id: string;
@@ -369,6 +407,38 @@ export interface ToolResult {
369
407
  */
370
408
  trust?: TrustLevel;
371
409
  }
410
+ /**
411
+ * What the model is told about a result labelled `external`, said once
412
+ * here rather than left for each tool to word — a third-party plugin or an
413
+ * MCP server never has to know the convention for its output to carry it.
414
+ */
415
+ export declare const UNTRUSTED_TOOL_RESULT_NOTE: string;
416
+ /**
417
+ * The text a provider hands the model for a tool result — the one
418
+ * serialization every provider and harness bridge uses, so the trust label
419
+ * reaches the model the same way whichever one carries the turn.
420
+ *
421
+ * An `external` result is wrapped in `{ untrusted, untrustedNote, output }`,
422
+ * the vocabulary `web.search` already used in-band. This marks the boundary
423
+ * and nothing more: a model can still follow text inside the envelope, and
424
+ * it is not a prompt-injection defence. Only `external` is wrapped, not
425
+ * `unknown`: `shell.run` declares `unknown`, and a marker on every command's
426
+ * output would teach the model that the marker is noise. A failed result is
427
+ * wrapped too, because the executor labels failures — a fetched server's
428
+ * error text is that server's text.
429
+ *
430
+ * The envelope is applied whatever the output already says, including
431
+ * `web.search`'s own `untrusted: true`. Skipping on a field of the output
432
+ * would let the output decide whether it is marked, and the output is the
433
+ * party not trusted; the repetition costs a sentence.
434
+ *
435
+ * Never mutates the result: the transcript, the dashboard, and the control
436
+ * API read the raw output. Deterministic for a given result, so a replayed
437
+ * transcript is byte-identical and the prompt cache holds. `Tool failed`
438
+ * stays unpunctuated because the API providers replay it from persisted
439
+ * transcripts, where a changed byte is a cache miss.
440
+ */
441
+ export declare const renderToolResultContent: (result: Pick<ToolResult, "ok" | "output" | "error" | "trust">) => string;
372
442
  export type ProviderPart = {
373
443
  type: 'text';
374
444
  text: string;
@@ -379,7 +449,8 @@ export type ProviderPart = {
379
449
  /**
380
450
  * A streamed fragment of an in-progress provider response. Text deltas carry
381
451
  * output as it is generated; tool-call deltas announce that the model has
382
- * started emitting a call (input may still be incomplete). A reset delta
452
+ * started emitting a call (input may still be incomplete); thinking and
453
+ * progress deltas carry nothing but the fact of activity. A reset delta
383
454
  * tells consumers to DISCARD every fragment streamed so far for this
384
455
  * response — emitted when a provider abandons a partial attempt (e.g. a
385
456
  * fallback wrapper retrying after the primary failed mid-stream), so
@@ -400,6 +471,16 @@ export type ProviderDelta = {
400
471
  */
401
472
  | {
402
473
  type: 'thinking';
474
+ }
475
+ /**
476
+ * The provider is working on this response and has nothing to show for
477
+ * it yet: a retry it is waiting out, a context compaction, a message
478
+ * boundary. Content-free like `thinking`, and for the same reader — an
479
+ * activity watchdog that would otherwise take a busy provider for a
480
+ * stalled one. A renderer has nothing to draw for it.
481
+ */
482
+ | {
483
+ type: 'progress';
403
484
  } | {
404
485
  type: 'reset';
405
486
  /**
@@ -1391,6 +1472,13 @@ export declare class InMemoryAgentMemoryStore implements AgentMemoryStore {
1391
1472
  * Resolves named credentials for an agent. Implementations must enforce the
1392
1473
  * agent's `credentials` allowlist so secrets stay scoped per agent.
1393
1474
  */
1475
+ /** Whose a named credential is: one agent's own, or the whole fleet's. */
1476
+ export type CredentialScope = 'agent' | 'shared';
1477
+ /**
1478
+ * How a credential request reaches a person: a form in the conversation's
1479
+ * own channel, or a one-time link the agent passes on.
1480
+ */
1481
+ export type CredentialDelivery = 'form' | 'link';
1394
1482
  export interface CredentialResolver {
1395
1483
  resolve(agent: AgentDefinition, name: string): Promise<string | undefined>;
1396
1484
  }
@@ -1499,6 +1587,99 @@ export declare const uncachedInputTokens: (total: number, ...cached: Array<numbe
1499
1587
  * nothing at all.
1500
1588
  */
1501
1589
  export declare const totalTokenUsage: (records: readonly TokenUsage[]) => TokenUsage | undefined;
1590
+ /**
1591
+ * Facts about how an agent is run that only the host knows, told to the
1592
+ * agent so it does not have to guess them.
1593
+ *
1594
+ * It does guess otherwise, and guesses wrong: an agent asked to reread its
1595
+ * soul after an edit answered that it had none, because there was no
1596
+ * `SOUL.md` in its workspace, which is where other agent runtimes keep one.
1597
+ * Handed in by the host rather than written into the soul because the
1598
+ * paths are the host's to own, and a copy in every soul would go stale the
1599
+ * next time the layout moved, the way the workspace already did once.
1600
+ */
1601
+ export interface AgentRuntimeContext {
1602
+ /** Absolute path of the soul file this agent's persona was loaded from. */
1603
+ soulPath?: string;
1604
+ /**
1605
+ * True when the host reads the soul again before every turn, so an edit
1606
+ * reaches the agent's next reply. The daemon does; a one-shot run reads
1607
+ * it once, and leaving this out says nothing either way.
1608
+ */
1609
+ soulReloads?: boolean;
1610
+ /** Where this agent's files go: `AgentWorkspaces.forAgent`. */
1611
+ workspace?: string;
1612
+ /**
1613
+ * The language the agent writes in, as a tag `isLanguageTag` accepts,
1614
+ * already resolved by the host: the soul's `language:`, then the config
1615
+ * file's. Absent means {@link DEFAULT_LANGUAGE}. Rendered in the reply
1616
+ * section rather than here, so a host that says nothing else still gets
1617
+ * the default rule.
1618
+ */
1619
+ language?: string;
1620
+ /**
1621
+ * What the host has configured to answer as this agent, and what it has
1622
+ * observed: `onFallback` is true once this conversation has switched to
1623
+ * the fallback, which is sticky, so a model asked what it is can tell a
1624
+ * configured default from the one actually answering. The fallback
1625
+ * wrapper sets it on the request it hands the fallback mid-turn, so the
1626
+ * model that took over is not told it is the default.
1627
+ */
1628
+ model?: AgentModelContext;
1629
+ /**
1630
+ * Who can read this conversation, as the channel that carried the turn
1631
+ * reported it on the turn's metadata (`conversationContextFrom`). Per
1632
+ * turn rather than read off the session, so a thread that started before
1633
+ * this existed, or a channel that grew since, is described as it is now.
1634
+ */
1635
+ conversation?: ConversationContext;
1636
+ }
1637
+ /**
1638
+ * What kind of room a conversation is: one person, a few, a closed
1639
+ * channel, or one the whole workspace can read. An agent that did not
1640
+ * know told a person in a Slack DM they were "talking on the terminal",
1641
+ * and would have posted the same reply to a thousand-person channel.
1642
+ */
1643
+ export type ConversationKind = 'direct' | 'group' | 'private' | 'public';
1644
+ export interface ConversationContext {
1645
+ kind: ConversationKind;
1646
+ /** Members as the channel counts them, the agent included. */
1647
+ members?: number;
1648
+ /**
1649
+ * Who a direct message is with. It reaches the system prompt, so a
1650
+ * channel sets it only for someone the operator vouched for (a Slack
1651
+ * principal), never from a name anyone could have typed.
1652
+ */
1653
+ with?: string;
1654
+ /** The turn is a reply in a thread rather than the channel itself. */
1655
+ thread?: boolean;
1656
+ /**
1657
+ * People outside this workspace read it too: a Slack Connect channel,
1658
+ * or one shared across an organization's workspaces. Never set for a
1659
+ * direct message.
1660
+ */
1661
+ shared?: boolean;
1662
+ }
1663
+ /**
1664
+ * The metadata key a channel adapter records a turn's room under: a
1665
+ * `ConversationContext`-shaped object, read back by
1666
+ * `conversationContextFrom`.
1667
+ */
1668
+ export declare const CONVERSATION_METADATA_KEY = "conversation";
1669
+ /**
1670
+ * The room a turn's metadata describes, keeping only what is safe to put
1671
+ * in front of the model, or undefined when it describes none.
1672
+ */
1673
+ export declare const conversationContextFrom: (metadata: JsonObject | undefined) => ConversationContext | undefined;
1674
+ export interface AgentModelContext {
1675
+ provider: string;
1676
+ model?: string;
1677
+ fallback?: {
1678
+ provider: string;
1679
+ model: string;
1680
+ };
1681
+ onFallback?: boolean;
1682
+ }
1502
1683
  export interface ProviderRequest {
1503
1684
  /**
1504
1685
  * The live session. **Persistable**: a provider that saves it, or hands
@@ -1551,6 +1732,12 @@ export interface ProviderRequest {
1551
1732
  * the model decides a description is relevant.
1552
1733
  */
1553
1734
  skills?: SkillDescriptor[];
1735
+ /**
1736
+ * What the host can say about how this agent is run: where its soul and
1737
+ * workspace are. Rendered as the `runtime` section; see
1738
+ * {@link AgentRuntimeContext}.
1739
+ */
1740
+ runtime?: AgentRuntimeContext;
1554
1741
  /**
1555
1742
  * Streaming sink. Adapters that stream call this per fragment and MUST
1556
1743
  * await the returned promise before the next call (backpressure); the
@@ -1842,6 +2029,82 @@ export interface PendingApprovalRecord {
1842
2029
  * cannot disagree on which message is "the reply".
1843
2030
  */
1844
2031
  export declare const latestTurnReply: (session: Pick<Session, "messages">) => string | undefined;
2032
+ /**
2033
+ * Whether a session's transcript already ends in the turn's answer: its
2034
+ * last response holds no tool call, so the loop owed the model nothing more
2035
+ * and only the save marking the turn completed is missing. A continuation
2036
+ * then completes it without a provider call.
2037
+ *
2038
+ * Judged on the last response as a whole: a response's parts are saved
2039
+ * together and its tool results after them all, so the trailing run of
2040
+ * assistant messages is that one response. Any call in it — even one
2041
+ * followed by text, which a provider may send in that order — means the
2042
+ * model is owed another turn. Read it before `reconcileInterruptedToolCalls`
2043
+ * splices results in.
2044
+ */
2045
+ export declare const lastResponseIsAnswer: (session: Pick<Session, "messages">) => boolean;
2046
+ /**
2047
+ * The end of a turn that answered: marked completed in one save, then
2048
+ * announced. The runner's loop ends here, and so does a turn whose answer
2049
+ * a process saved and then died before marking it completed — which needs
2050
+ * no provider and no current agent definition to finish, so a host can do
2051
+ * it with only its store and bus (`lastResponseIsAnswer` says when).
2052
+ */
2053
+ export declare const completeAnsweredTurn: (finished: Session, store: SessionStore, bus: EventBus) => Promise<Session>;
2054
+ /**
2055
+ * The longest idempotency key a dispatch accepts. Here rather than in the
2056
+ * gateway that enforces it, because a channel composing keys from ids it
2057
+ * does not bound (an agent's, a platform's) has to fit them to it.
2058
+ */
2059
+ export declare const MAX_IDEMPOTENCY_KEY_LENGTH = 256;
2060
+ /**
2061
+ * The reply of the turn a keyed message started (`Message.idempotencyKey`),
2062
+ * where `latestTurnReply` is the newest turn's. A repeated dispatch resolves
2063
+ * with the session as it stands, which may have moved on since: an adapter
2064
+ * answering a redelivered message reads the reply here, or it would answer
2065
+ * the old message with a newer turn's words. Undefined when no message
2066
+ * carries the key, or its turn produced no text.
2067
+ */
2068
+ export declare const turnReplyFor: (session: Pick<Session, "messages">, idempotencyKey: string) => string | undefined;
2069
+ /**
2070
+ * The files the turn a keyed message started produced (`filePathsOf` over
2071
+ * its tool results), the other half of what `turnReplyFor` reads. A
2072
+ * channel uploads a turn's files as the results arrive, so an adapter
2073
+ * answering a repeat of a finished turn — no events, only the session —
2074
+ * has this and nothing else to find them by.
2075
+ *
2076
+ * A path, not the bytes: the transcript never held them. `producedAt` is
2077
+ * when the result naming it was recorded, so a reader can refuse a path
2078
+ * that has been written since — by now it may hold a later turn's file.
2079
+ */
2080
+ export declare const turnFilesFor: (session: Pick<Session, "messages">, idempotencyKey: string) => Array<{
2081
+ path: string;
2082
+ producedAt: string;
2083
+ }>;
2084
+ /**
2085
+ * Fail a session's in-flight turn: `status` and `lastError`, and the error
2086
+ * on the turn's own user message (`Message.turnError`), where the next
2087
+ * turn cannot overwrite it. The one write every path that fails a turn
2088
+ * makes — the runner's and the gateway's sweeps — so `turnFailureFor`
2089
+ * reads the same record whichever failed it. In flight, the turn's message
2090
+ * is the session's newest user message (`observe` refuses to append).
2091
+ */
2092
+ export declare const recordTurnFailure: (session: Session, error: string) => void;
2093
+ /**
2094
+ * Whether the keyed message started the session's latest turn. A message
2095
+ * `observe` appended since started no turn and is passed over
2096
+ * (`Message.observed`); every other user message, keyed or not, addressed
2097
+ * or not, started one.
2098
+ */
2099
+ export declare const isLatestTurn: (session: Pick<Session, "messages">, idempotencyKey: string) => boolean;
2100
+ /**
2101
+ * Why the turn a keyed message started failed, or undefined if it did not.
2102
+ * Read from the message (`recordTurnFailure`), so a turn the session has
2103
+ * moved on from still answers. A transcript written before turns carried
2104
+ * their own failure has only the session's, which is the keyed turn's
2105
+ * while it is the latest (`isLatestTurn`).
2106
+ */
2107
+ export declare const turnFailureFor: (session: Pick<Session, "messages" | "status" | "lastError">, idempotencyKey: string) => string | undefined;
1845
2108
  /**
1846
2109
  * A user message's text as a prompt should carry it. The one place an
1847
2110
  * overheard message is framed, so the API providers' per-message blocks
@@ -1877,6 +2140,22 @@ export declare const promptTextOf: (message: Pick<Message, "content" | "overhear
1877
2140
  * as the answer rather than as a broken endpoint.
1878
2141
  */
1879
2142
  export declare const isUnaddressedTurn: (session: Pick<Session, "messages">) => boolean;
2143
+ /**
2144
+ * Where the dispatch carrying `idempotencyKey` stands in this session:
2145
+ * `undefined` if no message carries the key, `unfinished` if its message
2146
+ * is the one the session's in-flight turn is on, `finished` otherwise.
2147
+ *
2148
+ * Derived, never recorded separately, which is what makes it exact. The
2149
+ * keyed message is written in the same save that sets the status to
2150
+ * `running` (accepted and started are one write), and the save that moves
2151
+ * the status off `running` or `pending_approval` is the turn's final one,
2152
+ * so `finished` is written in the same transaction as the final save by
2153
+ * construction. While a turn is in flight its message is the session's
2154
+ * newest user message — `observe` refuses to append during one, and the
2155
+ * runner's wrap-up notes never reach the store — the same reading
2156
+ * `isUnaddressedTurn` relies on.
2157
+ */
2158
+ export declare const workItemState: (session: Pick<Session, "messages" | "status">, idempotencyKey: string) => "unfinished" | "finished" | undefined;
1880
2159
  export declare const markPromptDelivered: <T>(error: T) => T;
1881
2160
  export declare const promptWasDelivered: (error: unknown) => boolean;
1882
2161
  export interface PromptTextOptions {
@@ -2069,6 +2348,54 @@ export type StratusEvent = {
2069
2348
  reason: ApprovalResolutionReason;
2070
2349
  /** Who decided, when a person did. Channel-native id (a Slack user). */
2071
2350
  actor?: string;
2351
+ }
2352
+ /**
2353
+ * An agent asked for a named credential it does not hold
2354
+ * (`credential.request`), and it has already been put to someone: a form
2355
+ * in the conversation it asked in (`via: 'form'`), or a one-time link the
2356
+ * agent was handed to pass on (`via: 'link'`). The request is delivered
2357
+ * first and announced after, so this is a record of a question that was
2358
+ * asked, never a request for someone to render one. Nothing about it is
2359
+ * a secret, and nothing that answers it ever travels on the bus. `scope` is whose the key would be: the
2360
+ * agent's own (`agent`, the default) or the fleet's (`shared`).
2361
+ *
2362
+ * `metadata` is the session's, for the same reason an approval request
2363
+ * carries it: it says which conversation to ask in.
2364
+ */
2365
+ | {
2366
+ type: 'credential.requested';
2367
+ sessionId: string;
2368
+ agentId: string;
2369
+ requestId: string;
2370
+ name: string;
2371
+ scope: CredentialScope;
2372
+ /** The agent's own words on what it needs the key for. Untrusted text. */
2373
+ reason?: string;
2374
+ /** How it was put to someone. Absent from hosts before links existed, which only had forms. */
2375
+ via?: CredentialDelivery;
2376
+ metadata?: JsonObject;
2377
+ }
2378
+ /**
2379
+ * A requested credential was stored for the agent that asked, and granted
2380
+ * to it. Names only; the value went to the credential store and nowhere
2381
+ * else.
2382
+ *
2383
+ * Emitted once the key is stored, whether or not the grant then landed:
2384
+ * a key added from a chat is a change on disk, and a shared one is
2385
+ * already usable by every agent granted that name, so it is recorded even
2386
+ * when the soul write failed. `grantError` says it did, and why.
2387
+ */
2388
+ | {
2389
+ type: 'credential.provided';
2390
+ sessionId: string;
2391
+ agentId: string;
2392
+ requestId: string;
2393
+ name: string;
2394
+ scope: CredentialScope;
2395
+ /** Who provided it. Channel-native id (a Slack user). */
2396
+ actor?: string;
2397
+ /** Set when the key was stored but could not be added to the soul. */
2398
+ grantError?: string;
2072
2399
  } | {
2073
2400
  type: 'session.completed';
2074
2401
  sessionId: string;
@@ -2549,6 +2876,15 @@ export interface SkillDescriptor {
2549
2876
  * else, per turn and in memory.
2550
2877
  */
2551
2878
  export interface Skill extends SkillDescriptor {
2879
+ /**
2880
+ * Ships with Stratus and is enabled for every agent, whatever its soul's
2881
+ * `skills:` says. Reserved for the system's own documentation (the
2882
+ * `stratus` skill; `SkillRegistry.register` refuses it on any other id): a skill from anywhere else changing how an agent
2883
+ * behaves is opted into, but an agent that cannot read how the system it
2884
+ * runs in works guesses instead, the way one suggested `passEnv` for a
2885
+ * secret after reading the install around it.
2886
+ */
2887
+ builtin?: boolean;
2552
2888
  /**
2553
2889
  * Toolset globs this skill expects (`browser.*`, `fs.read`). Advisory: a
2554
2890
  * skill is prose and degrades, so an agent enabling one without the tools
@@ -2633,6 +2969,12 @@ export declare class SkillRegistry {
2633
2969
  * A read already underway keeps the promise it holds either way. `next`
2634
2970
  * is consumed — emptied, not shared — so nothing can go on mutating this
2635
2971
  * registry through it.
2972
+ *
2973
+ * A built-in skill serving now is never dropped by the swap: a `next`
2974
+ * without one (its file unreadable mid-reload) keeps the one serving,
2975
+ * and a bare alias `next` gave its id yields to it. A reload is
2976
+ * all-or-nothing for what it read; it must not take away from every
2977
+ * agent the one skill no soul can opt back into.
2636
2978
  */
2637
2979
  replaceWith(next: SkillRegistry): void;
2638
2980
  describe(): SkillDescriptor[];
@@ -2644,6 +2986,12 @@ export declare class SkillRegistry {
2644
2986
  read(id: string): Promise<string>;
2645
2987
  }
2646
2988
  export declare const SKILL_READ_TOOL_NAME = "skill.read";
2989
+ /**
2990
+ * The built-in skill's id: how Stratus works, enabled for every agent.
2991
+ * Reserved, so an operator directory or a plugin cannot put a different
2992
+ * procedure under the name every agent is pointed at.
2993
+ */
2994
+ export declare const STRATUS_SKILL_ID = "stratus";
2647
2995
  export interface SkillReadToolOptions {
2648
2996
  /**
2649
2997
  * Which `skills:` entries the session's agent is held to. Defaults to the
@@ -2727,6 +3075,43 @@ export declare const renderMemorySection: (memory: readonly MemoryEntry[] | Memo
2727
3075
  * here may ever include a body.
2728
3076
  */
2729
3077
  export declare const renderSkillsSection: (skills: readonly SkillDescriptor[] | undefined) => string | undefined;
3078
+ /**
3079
+ * The language agents write in when nothing says otherwise: American
3080
+ * English. A soul's `language:` and the config file's `language` override
3081
+ * it; see `resolveRuntimeConfig`, which decides between those two.
3082
+ */
3083
+ export declare const DEFAULT_LANGUAGE = "en-US";
3084
+ /** Whether a `language` setting is a BCP 47 tag; see `canonicalLanguageTag`. */
3085
+ export declare const isLanguageTag: (value: unknown) => value is string;
3086
+ /**
3087
+ * How to reply, told to every agent before its persona.
3088
+ *
3089
+ * Models default to the long, headed, bulleted answer, and an agent is read
3090
+ * mostly in chat, often on a phone, where that is the wrong shape. Souls
3091
+ * were each growing their own copy of these rules, so they live here once.
3092
+ * Short must not turn into incomplete, so the same section says a requested
3093
+ * deliverable arrives whole and a blocker is always said. A long one goes
3094
+ * where the reader can open it: an agent on Slack that answers with a path
3095
+ * on the daemon's disk has handed over nothing, so when sharing is not
3096
+ * possible the content itself goes in the reply.
3097
+ *
3098
+ * The accuracy rules are here for the same reason, each after an incident:
3099
+ * an agent with no Slack tool insisted it had no Slack connection, and
3100
+ * agents reported work as done that had only been drafted. The language
3101
+ * rule is here because the default persona is not: a custom soul replaces
3102
+ * it, and an agent wrote "favour" and "licence" into American website copy
3103
+ * because nothing shared said which English. A pasted British paragraph is
3104
+ * not a setting, so the rule says a task or a client's style guide decides
3105
+ * one deliverable and nothing is inferred from source text.
3106
+ *
3107
+ * The last sentence lets the soul and the person override length, format,
3108
+ * and language, and deliberately nothing else: a style preference never
3109
+ * changes what an agent may do, and "be brief" must not read as "skip the
3110
+ * blocker". Written without em dashes on purpose: a model imitates the
3111
+ * prose it is prompted with, and a rule against them set in them undoes
3112
+ * itself.
3113
+ */
3114
+ export declare const renderReplySection: (language: string | undefined) => string;
2730
3115
  /**
2731
3116
  * Where the conversation is happening, for a session a channel started.
2732
3117
  *
@@ -2734,11 +3119,39 @@ export declare const renderSkillsSection: (skills: readonly SkillDescriptor[] |
2734
3119
  * dispatches (`slack` today). Without this the agent could not tell: an
2735
3120
  * agent with no Slack tool, asked about an attachment in a Slack DM, told
2736
3121
  * the person it had "no Slack connection at all" and argued the point.
2737
- * Rendered from the session rather than handed in per turn so sessions
2738
- * opened before this existed get it too. The name must look like a channel
3122
+ * The channel is read from the session, so sessions opened before this
3123
+ * existed get it too; the room (`ConversationContext`) comes per turn from
3124
+ * the runtime, falling back to what the session's first turn recorded, so
3125
+ * an agent in a Slack DM no longer tells the person they are "talking on
3126
+ * the terminal". The name must look like a channel
2739
3127
  * id, since it is interpolated into the prompt.
3128
+ *
3129
+ * What it says about attachments is what the adapter writes into the
3130
+ * message, not a promise that they arrive: it used to say that what people
3131
+ * attach "reaches you", which read as though every file's contents did,
3132
+ * when most reach the turn as a name only. The Slack adapter marks each
3133
+ * file as read, shown, or unread with its reason; the section sends the
3134
+ * model to that note and nowhere else.
3135
+ */
3136
+ export declare const renderChannelSection: (session: Pick<Session, "metadata">, conversation?: ConversationContext) => string | undefined;
3137
+ /**
3138
+ * How this agent is run, for a host that said (see
3139
+ * {@link AgentRuntimeContext}).
3140
+ *
3141
+ * It says the soul is already in the prompt as firmly as it says where the
3142
+ * file is. An agent told only a path treats it as something to go and read,
3143
+ * and it usually cannot: the soul sits beside its state directory, outside
3144
+ * any root an operator hands `tool-fs`, and it has to stay there, since
3145
+ * the soul is what grants the agent its tools and credentials.
3146
+ *
3147
+ * Credentials get the same treatment, and for the same reason: an agent
3148
+ * whose operator had stored a shared key went looking through its files and
3149
+ * its environment for it. A named credential only ever reaches a plugin tool
3150
+ * that declared it, so the section names what the soul grants and says there
3151
+ * is nothing to find. Names only; a value never comes near a prompt.
3152
+ * Written without em dashes for the reason the reply section is.
2740
3153
  */
2741
- export declare const renderChannelSection: (session: Pick<Session, "metadata">) => string | undefined;
3154
+ export declare const renderRuntimeSection: (runtime: AgentRuntimeContext | undefined, agent: Pick<AgentDefinition, "credentials">) => string | undefined;
2742
3155
  export interface SystemPromptOptions {
2743
3156
  /** Host-level preamble, rendered before the agent's own persona. */
2744
3157
  preamble?: string;
@@ -2749,12 +3162,12 @@ export interface SystemPromptOptions {
2749
3162
  * Which part of what an agent is told a section is.
2750
3163
  *
2751
3164
  * The distinction a caller actually needs is stable versus volatile:
2752
- * `preamble`, `replies`, `persona`, `channel`, and `skills` are byte-identical across every turn of
3165
+ * `preamble`, `replies`, `persona`, `runtime`, `channel`, and `skills` are byte-identical across every turn of
2753
3166
  * an agent's life, while `memory` is rewritten whenever the agent remembers
2754
3167
  * anything. A provider that caches its request prefix has to place those two
2755
3168
  * groups differently, and it cannot tell them apart from rendered strings.
2756
3169
  */
2757
- export type SystemPromptSectionKind = 'preamble' | 'replies' | 'persona' | 'channel' | 'memory' | 'skills';
3170
+ export type SystemPromptSectionKind = 'preamble' | 'replies' | 'persona' | 'runtime' | 'channel' | 'memory' | 'skills';
2758
3171
  export interface SystemPromptSection {
2759
3172
  kind: SystemPromptSectionKind;
2760
3173
  text: string;
@@ -2773,11 +3186,11 @@ export interface SystemPromptSection {
2773
3186
  * The order is the order. This is the one rule about what an agent is told;
2774
3187
  * `renderSystemPromptSections` is the same rule with the labels dropped.
2775
3188
  */
2776
- export declare const renderSystemPromptParts: (request: Pick<ProviderRequest, "session" | "memory" | "skills">, options?: SystemPromptOptions) => SystemPromptSection[];
3189
+ export declare const renderSystemPromptParts: (request: Pick<ProviderRequest, "session" | "memory" | "skills" | "runtime">, options?: SystemPromptOptions) => SystemPromptSection[];
2777
3190
  /** The sections as plain strings, in the same order. */
2778
- export declare const renderSystemPromptSections: (request: Pick<ProviderRequest, "session" | "memory" | "skills">, options?: SystemPromptOptions) => string[];
3191
+ export declare const renderSystemPromptSections: (request: Pick<ProviderRequest, "session" | "memory" | "skills" | "runtime">, options?: SystemPromptOptions) => string[];
2779
3192
  /** The sections joined the way single-string providers send them. */
2780
- export declare const renderSystemPrompt: (request: Pick<ProviderRequest, "session" | "memory" | "skills">, options?: SystemPromptOptions) => string | undefined;
3193
+ export declare const renderSystemPrompt: (request: Pick<ProviderRequest, "session" | "memory" | "skills" | "runtime">, options?: SystemPromptOptions) => string | undefined;
2781
3194
  /**
2782
3195
  * The two producer channels, collected for one call: the tool's standing
2783
3196
  * `outputTrust` declaration and whatever the tool marks through the
@@ -2889,7 +3302,31 @@ export interface RunInput {
2889
3302
  * attachments instead, as an overheard one does.
2890
3303
  */
2891
3304
  addressed?: boolean;
3305
+ /**
3306
+ * The caller's name for this dispatch, stored on the user message
3307
+ * (`Message.idempotencyKey`) in the same write as the message itself.
3308
+ * The runner only records it; refusing a repeat is the host's job — see
3309
+ * `workItemState` and the gateway's `DispatchInput.idempotencyKey`.
3310
+ */
3311
+ idempotencyKey?: string;
3312
+ /**
3313
+ * Whether this turn may run on a provider that hosts its own tool loop,
3314
+ * stored on the user message (`Message.hostedLoop`). The host knows what
3315
+ * it resolved for the turn; the runner only records it.
3316
+ */
3317
+ hostedLoop?: boolean;
2892
3318
  metadata?: JsonObject;
3319
+ /**
3320
+ * What the host can say about how this agent is run, rendered as the
3321
+ * `runtime` section; see {@link AgentRuntimeContext}. Omitted, the
3322
+ * section is not rendered.
3323
+ *
3324
+ * Per run rather than a runner option, because the answer belongs to the
3325
+ * snapshot the host dispatched with: a daemon that re-reads the roster
3326
+ * between two awaits would otherwise tell an agent running on one soul
3327
+ * file that another file is its soul.
3328
+ */
3329
+ runtime?: AgentRuntimeContext;
2893
3330
  /** Aborting fails the turn cleanly; see RunAbortedError. */
2894
3331
  signal?: AbortSignal;
2895
3332
  }
@@ -2900,6 +3337,10 @@ export interface ResumeInput {
2900
3337
  images?: ImageAttachment[];
2901
3338
  /** See `RunInput.addressed`. */
2902
3339
  addressed?: boolean;
3340
+ /** See `RunInput.idempotencyKey`. */
3341
+ idempotencyKey?: string;
3342
+ /** See `RunInput.hostedLoop`. */
3343
+ hostedLoop?: boolean;
2903
3344
  /**
2904
3345
  * This turn's metadata — read for the sender's trust
2905
3346
  * (`SENDER_TRUST_METADATA_KEY`) and not merged into the session's. The
@@ -2908,6 +3349,8 @@ export interface ResumeInput {
2908
3349
  * trust.
2909
3350
  */
2910
3351
  metadata?: JsonObject;
3352
+ /** See `RunInput.runtime`. */
3353
+ runtime?: AgentRuntimeContext;
2911
3354
  /** Aborting fails the turn cleanly; see RunAbortedError. */
2912
3355
  signal?: AbortSignal;
2913
3356
  }
@@ -3104,6 +3547,30 @@ export declare class AgentRunner {
3104
3547
  */
3105
3548
  private labelLegacySession;
3106
3549
  resume(input: ResumeInput): Promise<Session>;
3550
+ /**
3551
+ * Picks up a turn whose process died while it was `running`: its user
3552
+ * message is durable and its reply is not. Nothing is appended — the turn
3553
+ * already has its message — so this is `resume` without the input.
3554
+ * Dangling tool calls are closed as interrupted, telling the model they
3555
+ * may not have run rather than running them again, and the loop goes back
3556
+ * to the provider with the transcript as it stands.
3557
+ *
3558
+ * A transcript that already ends in the reply (the response was saved,
3559
+ * and the process died before the save that marks the turn completed) is
3560
+ * completed as it stands, with no provider call: asking again would send
3561
+ * a second answer to one message.
3562
+ *
3563
+ * Marks the turn's message `continuedAfterCrash` in its first save, so a
3564
+ * host can refuse to do this twice. Resolves `undefined` when the session
3565
+ * is missing or no longer `running`. Never for a turn parked on a human:
3566
+ * `recoverPendingApproval` re-enters the parked call, which reconciling
3567
+ * here would close.
3568
+ */
3569
+ continueTurn(sessionId: string, options?: {
3570
+ /** See `RunInput.runtime`. */
3571
+ runtime?: AgentRuntimeContext;
3572
+ signal?: AbortSignal;
3573
+ }): Promise<Session | undefined>;
3107
3574
  /**
3108
3575
  * A message reaching the session with no turn run on it: something said
3109
3576
  * in a conversation the agent is in, to somebody else. Appended and
@@ -3122,11 +3589,18 @@ export declare class AgentRunner {
3122
3589
  */
3123
3590
  observe(input: ObserveInput): Promise<Session>;
3124
3591
  /**
3125
- * Appends a synthetic failed result directly after every tool call that
3126
- * has none — the durable trace of a turn interrupted between the call's
3127
- * save and its result's. The model sees an honest record ("interrupted,
3128
- * never ran to completion") instead of a wire-format violation, and a
3129
- * resume can decide to retry rather than assume the side effect landed.
3592
+ * Adds a synthetic failed result for every tool call that has none — the
3593
+ * durable trace of a turn interrupted between the call's save and its
3594
+ * result's. The model sees an honest record ("interrupted, never ran to
3595
+ * completion") instead of a wire-format violation, and a resume can
3596
+ * decide to retry rather than assume the side effect landed.
3597
+ *
3598
+ * Placed where an uninterrupted turn would have written it: after the
3599
+ * whole response the call came in — every assistant message that one
3600
+ * save wrote — and after any results already recorded for it. Directly
3601
+ * after the call would split a response whose text followed its call, and
3602
+ * a provider replaying the response's raw turn (which already holds that
3603
+ * text) would then send the text a second time.
3130
3604
  */
3131
3605
  private reconcileInterruptedToolCalls;
3132
3606
  /**
@@ -3163,11 +3637,14 @@ export declare class AgentRunner {
3163
3637
  * the allowlist actually granted (canonical before alias) — the id the
3164
3638
  * model is told is the id `skill.read` will accept.
3165
3639
  *
3166
- * Empty for an agent whose soul has no `skills:` key, which is what keys
3167
- * both `skill.read` gates: no skills, no reader.
3640
+ * A built-in skill is in it for every agent; otherwise it is empty for
3641
+ * an agent whose soul has no `skills:` key, which is what keys both
3642
+ * `skill.read` gates: no skills, no reader.
3168
3643
  */
3169
3644
  private enabledSkillsFor;
3170
3645
  private executeTurns;
3646
+ /** See `completeAnsweredTurn`; the loop's end and `continueTurn`'s. */
3647
+ private completeTurn;
3171
3648
  /**
3172
3649
  * Finishes a turn that was parked on a human when the process died.
3173
3650
  *
@@ -3188,6 +3665,8 @@ export declare class AgentRunner {
3188
3665
  * about at all, so they still face the policy normally.
3189
3666
  */
3190
3667
  denyPending?: boolean;
3668
+ /** See `RunInput.runtime`. */
3669
+ runtime?: AgentRuntimeContext;
3191
3670
  signal?: AbortSignal;
3192
3671
  }): Promise<Session | undefined>;
3193
3672
  /**