@robota-sdk/agent-session 3.0.0-beta.83 → 3.0.0-beta.84

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/CHANGELOG.md CHANGED
@@ -1,5 +1,43 @@
1
1
  # @robota-sdk/agent-session
2
2
 
3
+ ## 3.0.0-beta.84
4
+
5
+ ### Minor Changes
6
+
7
+ - 9f46375: A session can save a pending tool approval and answer it after it is reopened.
8
+ `Session.runRecoverable()` runs a prompt with a host-owned execution journal from
9
+ `@robota-sdk/agent-core`. When a tool call needs approval, it returns `waiting` with the saved
10
+ request (the exact action and its arguments) instead of asking a live approver.
11
+ `Session.resumeRecoverable()` takes the answer and continues that execution without adding another
12
+ user message; a session recreated with the same session ID, workspace, provider and model can do the
13
+ same. Ordinary `run()` keeps asking for approval live.
14
+
15
+ An approval covers only the action and arguments it was given; it never grants session-wide consent.
16
+ Current permissions, hooks and task restrictions are still checked when the tool runs, and a saved
17
+ denial holds even if the policy has since become more permissive. Reusing a response ID with
18
+ different content is refused.
19
+
20
+ While an execution waits, the session refuses new prompts. `Session.getPendingExecution()` returns
21
+ its `executionId` and saved requests, also after a cancellation that ended the turn once the wait
22
+ was saved. A resume refused before it continues, such as a stale response or a storage failure,
23
+ leaves the execution pending for another attempt; once it has continued, a later failure ends it
24
+ like an ordinary turn. `Session.abandonPendingExecution(executionId)` gives it up so the session
25
+ accepts new input: nothing runs and nothing is written to the journal, its open tool calls are
26
+ closed as failed in history, and that execution can no longer be resumed. The same journal also
27
+ records compaction's model call before history changes, and a journal write failure stays a failure
28
+ even when the turn is being cancelled.
29
+
30
+ ### Patch Changes
31
+
32
+ - Updated dependencies [9f46375]
33
+ - Updated dependencies [e8d70ac]
34
+ - Updated dependencies [9c6a8db]
35
+ - Updated dependencies [9c6a8db]
36
+ - @robota-sdk/agent-core@3.0.0-beta.84
37
+ - @robota-sdk/agent-interface-session@3.0.0-beta.84
38
+ - @robota-sdk/agent-interface-execution@3.0.0-beta.84
39
+ - @robota-sdk/agent-file-authority@3.0.0-beta.84
40
+
3
41
  ## 3.0.0-beta.83
4
42
 
5
43
  ### Minor Changes
package/README.md CHANGED
@@ -1,6 +1,12 @@
1
1
  # @robota-sdk/agent-session
2
2
 
3
- Session lifecycle management for the Robota SDK. Wraps a `Robota` agent instance with permission-gated tool execution, hook-based lifecycle events, context window tracking, conversation compaction, and optional persistence.
3
+ Session lifecycle for the Robota SDK. A `Session` wraps a `Robota` agent from
4
+ `@robota-sdk/agent-core` and adds permission-gated tool execution, lifecycle hooks, context-window
5
+ tracking, conversation compaction, session logs and optional persistence.
6
+
7
+ Most applications use `InteractiveSession` or `createQuery()` from `@robota-sdk/agent-framework`,
8
+ which build a `Session` with tools, provider and system prompt already wired. Use this package
9
+ directly when you assemble a session yourself.
4
10
 
5
11
  ## Installation
6
12
 
@@ -8,11 +14,14 @@ Session lifecycle management for the Robota SDK. Wraps a `Robota` agent instance
8
14
  npm install @robota-sdk/agent-session @robota-sdk/agent-core
9
15
  ```
10
16
 
17
+ Requires Node.js 22.12 or later.
18
+
11
19
  ## Quick Start
12
20
 
13
21
  ```typescript
14
22
  import { Session } from '@robota-sdk/agent-session';
15
- import type { IAIProvider, IToolWithEventService, ITerminalOutput } from '@robota-sdk/agent-core';
23
+ import type { ITerminalOutput } from '@robota-sdk/agent-session';
24
+ import type { IAIProvider, IToolWithEventService } from '@robota-sdk/agent-core';
16
25
 
17
26
  declare const tools: IToolWithEventService[];
18
27
  declare const provider: IAIProvider;
@@ -23,9 +32,7 @@ const session = new Session({
23
32
  provider,
24
33
  systemMessage: 'You are a helpful assistant.',
25
34
  terminal,
26
- // ARCH-010: required. The session's execution root feeds every hook input, CLAUDE_PROJECT_DIR, the
27
- // permission root and the persisted record — it is not read from the process any more, so a
28
- // subagent runs in its own workspace rather than its parent's.
35
+ // Required. The session's root: hook inputs, the permission workspace and the stored record use it.
29
36
  cwd: process.cwd(),
30
37
  permissions: { allow: ['Read(*)'], deny: [] },
31
38
  autoCompactThreshold: 0.75,
@@ -35,178 +42,214 @@ const response = await session.run('Hello!');
35
42
 
36
43
  // Context tracking
37
44
  const state = session.getContextState();
38
- console.log(`${state.usedPercentage.toFixed(1)}% context used`);
45
+ console.log(`${state.usedPercentage.toFixed(1)}% context used`, response);
39
46
 
40
47
  // Manual compaction
41
48
  await session.compact('Focus on the API changes');
49
+
50
+ await session.shutdown();
42
51
  ```
43
52
 
44
- ## Replay log validation
53
+ `ITerminalOutput` is a small terminal I/O interface (`write`, `writeLine`, `prompt`, `select`,
54
+ `spinner`, …). The session writes notices to it and hands it to an injected `promptForApproval`
55
+ function. A tool call that needs approval goes to `permissionHandler` or `promptForApproval`; with
56
+ neither, it is denied.
57
+
58
+ ## Checkpointed approval
59
+
60
+ Use `runRecoverable` when the host must save an approval request and answer it after reopening the
61
+ same Session. Supply an `IRecoverableExecutionJournal` from `@robota-sdk/agent-core`; the host owns
62
+ exclusive access and durable, idempotent record storage. Ordinary `run` keeps live approval handling.
63
+
64
+ ```typescript
65
+ import type { IRecoverableExecutionJournal } from '@robota-sdk/agent-core';
66
+
67
+ declare const journal: IRecoverableExecutionJournal;
68
+ declare const approvedByHost: boolean;
69
+ declare const hostResponseId: string; // Persist this ID and reuse it for redelivery.
70
+
71
+ const result = await session.runRecoverable('Perform the requested task', {
72
+ executionJournal: journal,
73
+ });
74
+ if (result.status === 'waiting') {
75
+ const request = result.requests.find((value) => value.kind === 'robota-session/approval');
76
+ if (request) {
77
+ // Authenticate the responder and show the exact request.data.arguments before answering.
78
+ const continued = await session.resumeRecoverable({
79
+ executionId: request.executionId,
80
+ journal,
81
+ toolResponses: [
82
+ {
83
+ requestId: request.requestId,
84
+ responseId: hostResponseId,
85
+ response: { approved: approvedByHost },
86
+ },
87
+ ],
88
+ });
89
+ // continued may wait again, including for another action with identical arguments.
90
+ }
91
+ }
92
+ ```
45
93
 
46
- `FileSessionLogger` writes versioned JSONL. `loadSessionLogEntries` and
47
- `decodeSessionLogEntries` validate every declared event before replay, including nested messages.
48
- Unknown events, malformed fields, and unsupported versions raise `SessionLogDecodeError` with safe
49
- field/line diagnostics. No malformed message is silently dropped or given an invented ID or date.
50
- Unversioned legacy logs are not accepted; persisted session snapshots keep their existing format.
94
+ `resumeRecoverable` continues execution without adding another user message. A recreated Session
95
+ must use the same session ID, canonical workspace, provider and model. Current hooks, permissions,
96
+ task restrictions and peer restrictions are checked again; approval never grants session-wide consent.
97
+ A response with a reused ID and different content is refused. Existing saved decisions also bind
98
+ ordinary `resume`.
99
+
100
+ While an execution waits, `run` refuses new input. `getPendingExecution()` returns its
101
+ `executionId` and `requests`, including after a cancellation that ended the turn once the wait was
102
+ saved. A journaled `run`, `runRecoverable` or resume that fails (a storage failure, an unreconciled
103
+ effect) while its tool calls are open in history also leaves the execution pending, with only the
104
+ waits whose effect never started in `requests`; resume it once the journal is readable, or abandon
105
+ it. A failure after its rounds settled ends it like an ordinary turn.
106
+ `abandonPendingExecution(executionId)` gives it up without running anything: its open tool calls
107
+ are closed as failed in this Session's history and this Session can no longer resume it, but the
108
+ journal is left untouched. Discard that execution's journal records yourself; otherwise a Session
109
+ without this history, such as a fresh one, can still resume the execution from them.
110
+
111
+ Waiting is returned only after request writes are durable and tools already running beside the
112
+ waiting one finish; calls not yet started stay pending. Cancellation interrupts running tools but
113
+ still waits for them to settle. Storage failures propagate, and uncertain effects raise a
114
+ reconciliation error rather than being replayed. Turn-level lifecycle hooks are omitted on
115
+ continuation because their effects lack durable receipts; structured-output recovery is unsupported.
116
+ This API alone does not provide arbitrary-effect recovery or durable Roundtable execution.
51
117
 
52
118
  ## Features
53
119
 
54
- | Feature | Description |
55
- | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
56
- | **Permission enforcement** | Tool calls gated by 3-step policy (deny list, allow list, mode policy) |
57
- | **Hook execution** | PreToolUse, PostToolUse, PreModelCall, PostModelCall, PreCompact, PostCompact, SessionStart, Stop; model-call hooks include selected effort (`auto` when unset) |
58
- | **Context tracking** | Effective token usage from the shared core estimator, configurable auto-compact threshold (default ~83.5%) |
59
- | **Compaction** | LLM-generated conversation summary to free context space; an invalid summary throws `CompactionError` and leaves history untouched |
60
- | **Persistence** | `IInteractiveSessionStore` injection; each completed `run()` and shutdown persist through the store; explicit `NodeSessionStore` uses atomic temp-file + rename writes |
61
- | **Abort** | Cancel via `session.abort()` — propagates AbortSignal to `robota.run()`, throws `AbortError` to caller |
62
- | **One turn at a time** | A concurrent `run()` is refused with `SessionBusyError` (RUNTIME-003); `isRunning()` is authoritative — see SPEC § Turn Identity |
63
- | **Session logging** | `FileSessionLogger` writes JSONL through an injected neutral sink; `NodeSessionLogSink` is the explicit host adapter |
64
- | **Replay events** | Provider/tool execution boundary events are forwarded from core into append-only session logs |
65
- | **Usage observations** | Content-free top-level turn outcomes and invocation-scoped provider usage identities are preserved for cross-session analytics |
66
- | **Provider capabilities** | Generic native web capability setup is requested through the provider contract, not provider-name branches |
120
+ | Feature | Description |
121
+ | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
122
+ | **Permission enforcement** | Every tool call is decided by `evaluatePermission` from `agent-core` (deny, ask and allow rules plus the permission mode). Approvals go to `permissionHandler` or `promptForApproval`; with neither, the call is denied. |
123
+ | **`auto` mode** | `AutoModeGate` sends calls the mode would ask about to a `permissionClassifier`; after repeated blocks it falls back to asking a person |
124
+ | **Hooks** | Fires `PreToolUse`, `PostToolUse`, `PermissionDecision`, `SessionStart`, `SessionEnd`, `UserPromptSubmit`, `Stop`, `StopFailure`, `PreCompact`, `PostCompact`, `PreModelCall` and `PostModelCall`. See the [hook event catalog](../agent-core/docs/HOOK-CATALOG.md). |
125
+ | **Context tracking** | Token usage from the shared core estimator, with a configurable auto-compact threshold (default 83.5% of the context window) |
126
+ | **Compaction** | A model-written summary replaces the history to free context space; an invalid summary throws `CompactionError` and leaves the history untouched |
127
+ | **Persistence** | Inject an `IInteractiveSessionStore`; each completed `run()` and `shutdown()` save the record. `NodeSessionStore` writes JSON files atomically (temp file + rename) |
128
+ | **Abort** | `session.abort()` stops the running turn; `run()` then rejects with an `AbortError` |
129
+ | **One turn at a time** | A concurrent `run()` is refused with `SessionBusyError`; `isRunning()` is true until the running turn has fully unwound |
130
+ | **Session logging** | `FileSessionLogger` writes versioned JSONL through an injected sink; `NodeSessionLogSink` is the file-system sink |
131
+ | **Replay events** | Provider and tool execution events from core are written to the log so a session can be replayed and validated |
67
132
 
68
133
  ## Key Methods
69
134
 
70
- | Method | Description |
71
- | ------------------------------------------------- | ----------------------------------------------------------------------- |
72
- | `constructor(options)` (with `sessionId`) | Accepts optional `sessionId` for deterministic IDs |
73
- | `run(message)` | Send a message, returns AI response |
74
- | `injectMessage(message)` | Inject a message into history without running the agent |
75
- | `compact(instructions?)` | Compress conversation via LLM summary |
76
- | `getContextState()` | Effective token usage: `{ usedTokens, maxTokens, usedPercentage }` |
77
- | `getAutoCompactThreshold()` | Auto-compact threshold fraction, or `false` if disabled |
78
- | `getPermissionMode()` / `setPermissionMode(mode)` | Read/change permission mode |
79
- | `getModelEffort()` | Read the model-effort selection for the next call (`auto` when unset) |
80
- | `getHistory()` / `clearHistory()` | Access or clear conversation history |
81
- | `abort()` | Signal the running turn to stop (it holds the session until it unwinds) |
82
- | `isRunning()` | True while a turn is in flight, including one aborted and unwinding |
83
- | `getSessionId()` | Returns the stable session identifier |
84
- | `getMessageCount()` | Returns the number of completed `run()` calls |
85
- | `getSessionAllowedTools()` | Tools approved for this session |
86
- | `getRecentPermissionDenials()` | Calls this session refused, most recent first, with the reason |
87
- | `clearSessionAllowedTools()` | Clears all session-scoped allow rules |
88
-
89
- ## Public API Surface
90
-
91
- | Export | Kind | Description |
92
- | ------------------------------------------ | --------- | -------------------------------------------------------------------------- |
93
- | `Session` | Class | Wraps Robota with permissions, hooks, streaming, persistence |
94
- | `PermissionEnforcer` | Class | Tool permission checking, hook execution, output truncation |
95
- | `ContextWindowTracker` | Class | Effective token usage tracking and auto-compact threshold |
96
- | `CompactionOrchestrator` | Class | Conversation compaction via LLM summary |
97
- | `NodeSessionStore` | Class | Explicit host-filesystem JSON persistence adapter |
98
- | `FileSessionLogger` | Class | Sink-driven JSONL session event logger |
99
- | `NodeSessionLogSource` | Class | Explicit host adapter for a JSONL log and relative payload sidecars |
100
- | `NodeSessionLogSink` | Class | Explicit host adapter for JSONL append and payload sidecars |
101
- | `NodeExternalPayloadSource` | Class | Linux stable-handle host adapter for budget-bounded sidecar reads |
102
- | `createSessionLogExternalPayloadReference` | Function | Validates and constructs the canonical content-addressed sidecar reference |
103
- | `SilentSessionLogger` | Class | No-op session logger |
104
- | `ISessionOptions` | Interface | Constructor options for Session |
105
- | `TAutoCompactThreshold` | Type | Auto-compact threshold fraction, or `false` to disable |
106
- | `TPermissionHandler` | Type | Custom permission approval callback |
107
- | `TPermissionResult` | Type | Permission decision result (`boolean \| 'allow-session'`) |
108
- | `ITerminalOutput` | Interface | Terminal I/O abstraction (write, prompt, select, spinner) |
109
- | `ISpinner` | Interface | Spinner handle |
110
- | `ISessionLogger` | Interface | Pluggable session event logger interface |
111
- | `TSessionLogData` | Type | Structured log event data |
112
- | `resolveSessionLogExternalPayloads` | Function | Bounded, integrity-checked hydration of JSON sidecar references |
113
- | `SessionLogPayloadResolutionError` | Class | Typed sidecar resolution failure with a stable error code |
114
- | `IInteractiveSessionRecord` | Interface | Canonical persisted session record (owned by agent-interface-transport) |
115
- | `IInteractiveSessionStore` | Interface | Canonical persistence port implemented by `NodeSessionStore` |
116
- | `ISessionLogSource` / `ISessionLogSink` | Interface | Neutral log read/write ports used by framework authority adapters |
117
- | `ISessionRecord` / `ISessionStore` | Type | Compatibility-only renamed re-exports of the canonical contracts |
118
- | `IContextWindowState` | Type | Context window usage state (re-exported from agent-core) |
119
-
120
- Note: `IPermissionEnforcerOptions` is an internal type and is not exported from the public API.
121
-
122
- ## Sub-Components
123
-
124
- | Component | Purpose |
125
- | ------------------------ | ------------------------------------------------------------------- |
126
- | `PermissionEnforcer` | Tool wrapping, permission checks, hook execution, output truncation |
127
- | `ContextWindowTracker` | Token usage tracking, auto-compact threshold |
128
- | `CompactionOrchestrator` | Conversation summarization via LLM |
135
+ | Method | Description |
136
+ | --------------------------------------------------------- | ------------------------------------------------------------------------------- |
137
+ | `constructor(options)` | `ISessionOptions`; pass `sessionId` for a deterministic ID |
138
+ | `run(message)` | Send a message and return the response |
139
+ | `runRecoverable(message, options)` | Run with a recovery journal; returns a completed response or saved waits |
140
+ | `resumeRecoverable(options)` | Answer saved waits and continue that execution without new input |
141
+ | `getPendingExecution()` | The execution whose saved waits block new input, if any |
142
+ | `abandonPendingExecution(executionId)` | Give up that execution without running it, so new input is accepted |
143
+ | `injectMessage(role, content)` | Add a message to the history without running the agent |
144
+ | `compact(instructions?)` | Summarize the conversation to free context space |
145
+ | `getContextState()` | `{ usedTokens, maxTokens, usedPercentage, remainingPercentage }` |
146
+ | `getAutoCompactThreshold()` | Auto-compact threshold fraction, or `false` when disabled |
147
+ | `getPermissionMode()` / `setPermissionMode(mode)` | Read or change the permission mode |
148
+ | `getModelEffort()` | The model-effort selection for the next call (`auto` when unset) |
149
+ | `getHistory()` / `clearHistory()` | Read or clear the conversation history |
150
+ | `addTools(tools)` | Register more tools on a live session |
151
+ | `getProvider()` / `swapProvider(provider, model)` | Read or replace the provider the session uses |
152
+ | `abort()` | Signal the running turn to stop (it holds the session until it unwinds) |
153
+ | `isRunning()` | True while a turn is in flight, including one that was aborted and is unwinding |
154
+ | `getSessionId()` | The stable session identifier |
155
+ | `getMessageCount()` | Number of completed `run()` calls |
156
+ | `getSessionAllowedTools()` / `clearSessionAllowedTools()` | Approvals remembered for this session |
157
+ | `getRecentPermissionDenials()` | Calls this session refused, most recent first, with the reason |
158
+ | `shutdown()` | Fire `SessionEnd`, save the record and release the agent |
159
+
160
+ ## Public API
161
+
162
+ | Export | Kind | Description |
163
+ | ------------------------------------------------------------------------------------------------------------ | ------------------- | ---------------------------------------------------------------------------------------- |
164
+ | `Session` | Class | Wraps `Robota` with permissions, hooks, compaction, logging and persistence |
165
+ | `ISessionOptions`, `ISessionRunOptions`, `ISessionShutdownOptions` | Types | Constructor, run and shutdown options |
166
+ | `SessionBusyError`, `TurnClaim` | Classes | The refusal of a concurrent turn, and the identity of the running turn |
167
+ | `PermissionEnforcer` | Class | Tool permission checks, hook execution and output truncation |
168
+ | `consentScopeFor` | Function | The permission pattern an "allow for this session/project" answer grants |
169
+ | `AutoModeGate`, `IPermissionClassifier` | Class, type | The `auto` permission mode and the classifier port it calls |
170
+ | `ContextWindowTracker`, `AUTO_COMPACT_THRESHOLD` | Class, const | Token usage tracking and the default auto-compact threshold |
171
+ | `CompactionOrchestrator`, `CompactionError`, `DEFAULT_COMPACTION_PROMPT` | Class, error, const | Conversation compaction |
172
+ | `TPermissionHandler`, `TPermissionResult` | Types | Approval callback and its answer (`true`, `false`, `'allow-session'`, `'allow-project'`) |
173
+ | `ITerminalOutput`, `ISpinner` | Types | Terminal I/O used for approval prompts |
174
+ | `NodeSessionStore` | Class | File-system JSON store for session records |
175
+ | `IInteractiveSessionRecord`, `IInteractiveSessionStore` | Types | The persisted session record and store port (from `@robota-sdk/agent-interface-session`) |
176
+ | `decodeInteractiveSessionRecord`, `decodeVersionedInteractiveSessionRecord` | Functions | Validate a stored record before use |
177
+ | `serializeSessionArtifact`, `deserializeSessionArtifact` | Functions | Export a session record to a portable artifact and import it again |
178
+ | `scrubSensitiveKeys`, `isSensitiveKey` | Functions | Redact secret-looking keys (used by the logger; optional for artifacts) |
179
+ | `CheckpointTree` | Class | In-memory branching tree of checkpoint IDs (fork and switch between branches) |
180
+ | `FileSessionLogger`, `SilentSessionLogger`, `ISessionLogger` | Class, type | JSONL session logger and the logger port |
181
+ | `NodeSessionLogSink`, `NodeSessionLogSource`, `ISessionLogSink`, `ISessionLogSource` | Classes, types | File-system log writer and reader, and their ports |
182
+ | `loadSessionLogEntries`, `decodeSessionLogEntries`, `SessionLogDecodeError` | Functions, error | Read and validate a session log |
183
+ | `replaySessionLogEntries`, `validateSessionReplayLogEntries` | Functions | Rebuild history from a log and report gaps |
184
+ | `SESSION_LOG_EVENT`, `SESSION_LOG_SCHEMA_VERSION` | Consts | The log event vocabulary and schema version |
185
+ | `resolveSessionLogExternalPayloads`, `NodeExternalPayloadSource`, `createSessionLogExternalPayloadReference` | Functions, class | Large log fields stored as separate payload files |
186
+ | `NodeToolResultSpillStore` | Class | Stores oversized tool results in files for the life of the session |
187
+ | `NodePromptHistoryFile` | Class | Append-only prompt history file that can be read newest-first |
188
+
189
+ `IContextWindowState` (the `getContextState()` result) is exported by `@robota-sdk/agent-core`.
129
190
 
130
191
  ## Session vs Robota
131
192
 
132
- - **`Robota`** (agent-core): Raw agent — conversation + tools + plugins. No permissions, no hooks.
133
- - **`Session`** (this package): Wraps Robota with permissions, hooks, compaction, and persistence. Used by the CLI and SDK.
134
-
135
- ### Interactive session record
136
-
137
- `IInteractiveSessionRecord` is owned by `@robota-sdk/agent-interface-transport` and carries the full conversation and resumable state. `NodeSessionStore` persists this record without inspecting its payload. It is a conspicuously named host adapter: passing a directory does not establish workspace trust. Framework project composition instead adapts an accepted project-authority state facet to the same neutral store port. When a raw `Session` re-saves an existing record, it preserves fields it does not own and refreshes only its live conversation, history, prompt, schema, path, and timestamp fields.
138
-
139
- When a raw `Session` re-saves an existing record, it preserves fields it does not own and refreshes
140
- only its live conversation, history, prompt, schema, path, and timestamp fields. A resumed session
141
- must reuse the record ID when its new turns are intended to update that record; sessions without a
142
- store remain transient.
143
-
144
- Session-log parsing is source-driven. `loadSessionLogEntries(source)` consumes an explicit
145
- `ISessionLogSource`; it never converts a filename into filesystem authority. Use
146
- `NodeSessionLogSource` only when the application deliberately owns the host path, or provide a
147
- framework authority-backed source for project logs. Empty or whitespace-only Node log paths are
148
- rejected before sidecar authority is derived. Externalized sidecars use a bounded, stable
149
- root-relative reader on qualified Linux x64/arm64, macOS x64/arm64, and Windows x64 hosts. Parent or
150
- final symlink/reparse replacement is refused rather than retried through an ambient pathname, and an
151
- unsupported native capability is reported as `STABLE_PAYLOAD_READ_UNAVAILABLE`.
152
-
153
- Streaming text deltas are written to append-only JSONL session logs as `text_delta` events. Consumers should store high-frequency streaming chunks in JSONL logs/transcripts and keep session JSON focused on resumable snapshots and references.
154
-
155
- ### Replay-Oriented JSONL Events
156
-
157
- `Session.run()` forwards core execution events into the session logger through `onExecutionEvent`. Current events include:
158
-
159
- - `provider_request`
160
- - `provider_native_raw_payload`
161
- - `provider_stream_raw_delta`
162
- - `provider_response_raw`
163
- - `provider_response_normalized`
164
- - `assistant_message_committed`
165
- - `tool_batch_started`
166
- - `tool_execution_request`
167
- - `tool_execution_result`
168
- - `tool_message_committed`
169
- - `history_mutation`
170
-
171
- `SESSION_LOG_EVENT` is the complete production and replay-reader vocabulary. Direct logger calls and core
172
- execution-event literals must be members of that shared list, and the coverage test scans every source so a
173
- new event cannot silently become writer-only or reader-only.
174
-
175
- Manual and automatic compaction also share one session-owned trigger value. The same `manual` or `auto`
176
- value reaches PreCompact, PostCompact, the `context_compact` log entry, and `onCompactEvent`; instructions
177
- do not cause the compaction orchestrator to reclassify the trigger.
178
-
179
- `FileSessionLogger` redacts common secret fields before writing logs and stores large fields as
180
- content-addressed JSON payload references under `{sessionId}.payloads/`. `loadSessionLogEntries()`
181
- hydrates those sidecars before replay and fails closed on malformed references, path/symlink escape,
182
- missing or unreadable files, byte-length/hash mismatch, invalid JSON, cycles, or configured depth/byte
183
- limits. Each source read receives the remaining aggregate byte budget; the Node adapter checks it before
184
- allocation and reads from the same no-follow descriptor it validated. `session-log-replay` exports replay
185
- readers and validators that reconstruct chat history from
186
- `history_mutation` and report missing provider/tool terminal events; an unresolved history message or
187
- normalized provider response is replay-incomplete. Replay validation also requires provider-native raw
188
- response or stream payload coverage for each `provider_request`. Direct `NodeSessionLogSink` calls reject
189
- unsafe session path components and reject payload digests that are malformed or do not hash the supplied
190
- serialized content. Host and authority-backed sinks share
191
- `createSessionLogExternalPayloadReference()` as the validation and reference-construction SSOT.
192
-
193
- A migration script is available for upgrading session records from older formats. See the package source for details.
194
-
195
- ## Assembly
196
-
197
- Most users should use `InteractiveSession` or `createQuery()` from `@robota-sdk/agent-framework` — or `createAgentRuntime().createSession()` for multi-session runtimes — instead of constructing `Session` directly. (`createSession()` itself is an internal assembly factory and is not part of the public entry.) The SDK wires tools, provider, and system prompt automatically from config and context.
193
+ - **`Robota`** (`agent-core`): the raw agent — conversation, tools and plugins. No permissions, no
194
+ hooks.
195
+ - **`Session`** (this package): `Robota` plus permissions, hooks, compaction, logging and
196
+ persistence. `@robota-sdk/agent-framework` builds its sessions from it.
197
+
198
+ ## Persistence
199
+
200
+ `IInteractiveSessionRecord` carries the full conversation and resumable state. `NodeSessionStore`
201
+ saves the record without inspecting its payload. It is a host adapter: passing it a directory does
202
+ not establish workspace trust. `@robota-sdk/agent-framework` instead adapts a trusted project's
203
+ state storage to the same store port.
204
+
205
+ When a `Session` re-saves an existing record, it keeps the fields it does not own and refreshes only
206
+ its live conversation, history, prompt, schema, path and timestamp fields. A resumed session must
207
+ reuse the record ID for its new turns to update that record; a session without a store stays
208
+ transient.
209
+
210
+ ## Session logs and replay
211
+
212
+ `FileSessionLogger` writes versioned JSONL through an injected `ISessionLogSink`. It redacts common
213
+ secret fields and stores large fields as content-addressed JSON payload files under
214
+ `{sessionId}.payloads/`. Streaming text deltas go to the log as `text_delta` events; keep
215
+ high-frequency output in logs and the session record focused on resumable state.
216
+
217
+ `Session.run()` forwards core execution events into the log, including `provider_request`,
218
+ `provider_native_raw_payload`, `provider_stream_raw_delta`, `provider_response_raw`,
219
+ `provider_response_normalized`, `assistant_message_committed`, `tool_batch_started`,
220
+ `tool_execution_request`, `tool_execution_result`, `tool_message_committed` and `history_mutation`.
221
+ `SESSION_LOG_EVENT` is the complete vocabulary that writers and readers share.
222
+
223
+ Reading is source-driven: `loadSessionLogEntries(source)` takes an explicit `ISessionLogSource` and
224
+ never turns a filename into file-system access on its own. Use `NodeSessionLogSource` when the
225
+ application owns the path. Every declared event is validated before replay, nested messages
226
+ included; unknown events, malformed fields and unsupported versions raise `SessionLogDecodeError`,
227
+ and unversioned logs are not accepted. Payload files are read with an aggregate byte budget and
228
+ checked for path escape, symlink replacement, size and hash mismatch; a failure is reported rather
229
+ than skipped. `replaySessionLogEntries` rebuilds the chat history from `history_mutation` events, and
230
+ `validateSessionReplayLogEntries` reports missing provider or tool events.
231
+
232
+ Manual and automatic compaction carry one trigger value (`manual` or `auto`) to `PreCompact`,
233
+ `PostCompact`, the `context_compact` log entry and `onCompactEvent`.
234
+
235
+ ## Legacy session migration
236
+
237
+ From `packages/agent-session` in the repository, `node scripts/migrate-session-history.mjs
238
+ --sessions-dir <absolute-directory>` backfills history in legacy session files. It rewrites the files
239
+ in that directory, so back them up first. `node examples/verify-session-history-migration.mjs`
240
+ checks the conversion on disposable files without touching your sessions.
198
241
 
199
242
  ## Dependencies
200
243
 
201
- - `@robota-sdk/agent-core` (production) — Robota agent, permission system, hook system, core types
244
+ - `@robota-sdk/agent-core` — the `Robota` agent, permissions, hooks and core types
245
+ - `@robota-sdk/agent-interface-session` — the session record and store contracts
246
+ - `@robota-sdk/agent-interface-execution` — background task contracts stored in the session record
247
+ - `@robota-sdk/agent-file-authority` — bounded, root-relative file reads for log payloads
202
248
 
203
- ## Legacy Session Migration
249
+ ## Documentation
204
250
 
205
- For legacy session history, run `node scripts/migrate-session-history.mjs --sessions-dir
206
- <absolute-directory>` from `packages/agent-session` in the repository. This writes the selected
207
- legacy session files; back them up first. The disposable example
208
- `node examples/verify-session-history-migration.mjs` checks conversion without using your stored
209
- sessions. See [Session Data Migration](./docs/SPEC.md) for the exact policy.
251
+ - [docs/SPEC.md](./docs/SPEC.md) — package contract, invariants and design decisions
252
+ - [Hook event catalog](../agent-core/docs/HOOK-CATALOG.md)
210
253
 
211
254
  ## License
212
255