tinker-agent 2.8.0 → 2.10.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.
Files changed (66) hide show
  1. package/CHANGELOG.md +79 -1
  2. package/README.md +81 -11
  3. package/package.json +5 -3
  4. package/src/agent/runtime-context-capabilities.ts +19 -0
  5. package/src/agent/runtime-context-events.ts +127 -0
  6. package/src/agent/runtime-context-maintenance.ts +780 -0
  7. package/src/agent/runtime-hosted-session.ts +443 -0
  8. package/src/agent/runtime-interactions.ts +291 -0
  9. package/src/agent/runtime-prompt-scheduler.ts +182 -0
  10. package/src/agent/runtime-session-contracts.ts +317 -0
  11. package/src/agent/runtime-session.ts +250 -2130
  12. package/src/agent/runtime-skills.ts +544 -0
  13. package/src/cli/command-line.ts +26 -2
  14. package/src/cli/connect-runner.tsx +26 -0
  15. package/src/cli/main.ts +26 -0
  16. package/src/cli/output.ts +1 -1
  17. package/src/cli/public-cli-contract.ts +18 -0
  18. package/src/cli/public-config-contract.ts +1 -1
  19. package/src/cli/runner-dependencies.ts +6 -5
  20. package/src/cli/serve-runner.ts +45 -0
  21. package/src/cli/serve-runtime.ts +100 -0
  22. package/src/context/context-automation-policy.ts +12 -118
  23. package/src/context/context-swap-renderer.ts +14 -0
  24. package/src/events/types.ts +12 -0
  25. package/src/memory/memory-get-tool.ts +1 -1
  26. package/src/observation/observation-builder.ts +128 -48
  27. package/src/remote/client.ts +350 -0
  28. package/src/remote/config.ts +95 -0
  29. package/src/remote/http-server.ts +240 -0
  30. package/src/remote/protocol.ts +228 -0
  31. package/src/remote/service-store.ts +175 -0
  32. package/src/remote/service.ts +219 -0
  33. package/src/remote/sync-hub.ts +95 -0
  34. package/src/session/remote-history-reader.ts +143 -0
  35. package/src/session/resume-projection.ts +47 -21
  36. package/src/session/session-history-access.ts +238 -0
  37. package/src/session/session-store-context-readers.ts +183 -0
  38. package/src/session/session-store-ledger-writer.ts +315 -0
  39. package/src/session/session-store-record-writer.ts +318 -0
  40. package/src/session/session-store-recovery.ts +225 -0
  41. package/src/session/session-store-revisions.ts +1004 -0
  42. package/src/session/session-store-sql.ts +40 -0
  43. package/src/session/session-store-validation.ts +657 -0
  44. package/src/session/session-store.ts +756 -3186
  45. package/src/tools/bash-task.ts +46 -18
  46. package/src/tools/bash.ts +44 -2
  47. package/src/tools/glob.ts +107 -19
  48. package/src/tools/grep-output.ts +130 -0
  49. package/src/tools/grep-pagination.ts +73 -0
  50. package/src/tools/grep-path.ts +11 -0
  51. package/src/tools/grep-snippets.ts +111 -0
  52. package/src/tools/grep.ts +139 -154
  53. package/src/tools/read.ts +0 -9
  54. package/src/tools/recall.ts +106 -50
  55. package/src/tools/registry.ts +4 -6
  56. package/src/tools/ripgrep.ts +19 -26
  57. package/src/tools/shell-process.ts +30 -4
  58. package/src/tools/task-output-range.ts +146 -0
  59. package/src/tools/task-output-tool.ts +35 -5
  60. package/src/tools/task-output.ts +35 -0
  61. package/src/tools/task-stop.ts +2 -1
  62. package/src/tools/task-tool-args.ts +34 -0
  63. package/src/tools/terminal-screen.ts +11 -2
  64. package/src/tools/types.ts +39 -2
  65. package/src/tui/event-store.ts +23 -5
  66. package/src/tui/remote-app.tsx +210 -0
@@ -47,6 +47,24 @@ export const PUBLIC_CLI_CONTRACT = Object.freeze({
47
47
  description: "Start the interactive terminal interface.",
48
48
  profileOption: PROFILE_OPTION,
49
49
  }),
50
+ serve: Object.freeze({
51
+ command: "serve",
52
+ description: "Run the local daemon for paired remote clients.",
53
+ configOption: Object.freeze({
54
+ flags: "--config <path>",
55
+ description: "Read the service JSON configuration.",
56
+ valueName: "path",
57
+ } satisfies PublicCliOption),
58
+ }),
59
+ connect: Object.freeze({
60
+ command: "connect",
61
+ description: "Attach a terminal client to a service; exiting detaches only.",
62
+ configOption: Object.freeze({
63
+ flags: "--config <path>",
64
+ description: "Read the paired client JSON configuration.",
65
+ valueName: "path",
66
+ } satisfies PublicCliOption),
67
+ }),
50
68
  run: Object.freeze({
51
69
  command: "run [prompt]",
52
70
  description: "Run one prompt non-interactively.",
@@ -174,7 +174,7 @@ export const PUBLIC_CONFIG_FIELDS = Object.freeze([
174
174
  valueKind: "positive-integer",
175
175
  requiredIn: "never",
176
176
  appliesIn: "always",
177
- defaultValue: 5_000,
177
+ defaultValue: 60_000,
178
178
  secret: false,
179
179
  section: "tooling",
180
180
  description: "Default Bash foreground timeout in milliseconds.",
@@ -34,13 +34,14 @@ Use WebSearch, when it is available, to look up current information on the web s
34
34
  Use WebFetch to read the content of a specific URL, such as documentation pages found via WebSearch.
35
35
  Prefer Read for reading files instead of using cat on large files.
36
36
  Prefer Write or Edit for changing files instead of shell redirection.
37
- Use run_in_background=true for dev servers, watch commands, long-running builds, and long-running test services.
37
+ Use run_in_background=true for persistent processes such as dev servers and watch commands, or when you have independent work to do while a command runs.
38
+ For finite commands whose result is needed next, such as builds, tests, and checks, prefer foreground execution when no independent work remains. Set a sufficient foreground timeout; the call returns as soon as the command finishes.
38
39
  Do not add & to Bash commands; background execution is handled by the Bash tool.
39
40
  Use Bash with tty=true for REPLs, debuggers, interactive prompts, and terminal applications that require a controlling terminal.
40
- Use TaskList to list background shell tasks in the current session.
41
- Use TaskOutput to inspect a task's current status, latest output, or current terminal screen.
42
- Use TaskInput with the returned task ID to send characters to a PTY task. TaskInput does not append Enter; include \\n explicitly, use \\u0003 for Ctrl-C, and use chars="" to wait without writing.
43
- Use TaskStop to stop a background task that is no longer needed.
41
+ TaskList lists background shell tasks in the current session.
42
+ TaskOutput reports a task's current status, latest output, or current terminal screen. For non-PTY logs, offset (1-based) and limit select consecutive lines instead of the default head/tail preview; PTY tasks ignore them. Range truncated=true means byte limits shortened requested content, not that lines outside the range exist. The last observed line of a running log may still be growing; rereading it when polling captures further changes to that line.
43
+ TaskInput sends characters to a PTY task identified by the returned task ID. TaskInput does not append Enter; an explicit \\n sends Enter, \\u0003 sends Ctrl-C, and chars="" waits without writing.
44
+ TaskStop stops a background task that is no longer needed.
44
45
  Do not use ad-hoc kill commands to manage tasks created by Bash.
45
46
  Bash and TaskOutput return outputFilePath. Use Read on outputFilePath when you need complete or paginated output.
46
47
  Do not send passwords, tokens, or other secrets through TaskInput because tool arguments are stored in session history.
@@ -0,0 +1,45 @@
1
+ import { loadRemoteConfig } from "../remote/config";
2
+ import { startRemoteHttpServer } from "../remote/http-server";
3
+ import { RemoteService } from "../remote/service";
4
+ import { RemoteServiceStore } from "../remote/service-store";
5
+ import { defaultHomeRoot } from "../session/workspace-storage";
6
+ import { createHostedRuntimeFactory } from "./serve-runtime";
7
+ import { writeCliOutput, type CliOutputWriter } from "./output";
8
+
9
+ export async function runServe(input: {
10
+ configPath: string;
11
+ env: NodeJS.ProcessEnv;
12
+ stdout: CliOutputWriter;
13
+ }): Promise<number> {
14
+ const config = await loadRemoteConfig(input.configPath);
15
+ const homeRoot = defaultHomeRoot(input.env);
16
+ const store = await RemoteServiceStore.open(config.stateDirectory);
17
+ const service = new RemoteService(
18
+ store,
19
+ config.workspaces,
20
+ createHostedRuntimeFactory(config.workspaces, input.env, homeRoot),
21
+ homeRoot,
22
+ );
23
+ let transport: ReturnType<typeof startRemoteHttpServer> | undefined;
24
+ try {
25
+ await service.initialize();
26
+ transport = startRemoteHttpServer(service, config);
27
+ await writeCliOutput(
28
+ input.stdout,
29
+ `Tinker service listening on https://${config.hostname}:${transport.port}; ${config.workspaces.length} workspace(s).\nClient disconnects detach only. Stop the process to shut down hosted sessions.\n`,
30
+ );
31
+ await new Promise<void>((resolve) => {
32
+ const stop = () => {
33
+ process.off("SIGINT", stop);
34
+ process.off("SIGTERM", stop);
35
+ resolve();
36
+ };
37
+ process.once("SIGINT", stop);
38
+ process.once("SIGTERM", stop);
39
+ });
40
+ return 0;
41
+ } finally {
42
+ await transport?.stopTransport();
43
+ await service.close();
44
+ }
45
+ }
@@ -0,0 +1,100 @@
1
+ import { createRuntimeSession } from "../agent/runtime-session";
2
+ import { parseSessionId } from "../ids/runtime-id";
3
+ import {
4
+ buildSystemPrompt,
5
+ loadProjectInstructions,
6
+ projectInstructionManifest,
7
+ } from "../instructions/project-instructions";
8
+ import { createReasoningEffortController } from "../model/reasoning-effort";
9
+ import { resolveSessionDatabasePath } from "../session/session-store";
10
+ import { SessionCatalog } from "../session/session-catalog";
11
+ import { loadSkillCatalog } from "../skills/skill-loader";
12
+ import type { RemoteWorkspaceConfig } from "../remote/config";
13
+ import type { HostedRuntimeFactory } from "../agent/runtime-hosted-session";
14
+ import { deriveRunnerConfig, resolvePublicConfig } from "./config";
15
+ import { resolveSessionProfileName } from "./model-profiles";
16
+ import {
17
+ createRunnerModelClient,
18
+ createWebFetchRefiner,
19
+ RUNTIME_INSTRUCTIONS,
20
+ } from "./runner-dependencies";
21
+
22
+ /** Service composition reuses the existing configuration/provider/runtime contracts. */
23
+ export function createHostedRuntimeFactory(
24
+ workspaces: readonly RemoteWorkspaceConfig[],
25
+ env: NodeJS.ProcessEnv,
26
+ homeRoot?: string,
27
+ ): HostedRuntimeFactory {
28
+ return async ({ record, sink }) => {
29
+ const workspace = workspaces.find((entry) => entry.id === record.workspaceId);
30
+ if (!workspace || workspace.path !== record.workspacePath)
31
+ throw new Error("Managed workspace configuration changed.");
32
+ const sessionId = parseSessionId(record.id);
33
+ const publicConfig = await resolvePublicConfig({
34
+ env: { ...env, TINKER_WORKSPACE: workspace.path },
35
+ cwd: workspace.path,
36
+ });
37
+ let profileName = workspace.profile;
38
+ if (record.initialized && publicConfig.mode === "profile") {
39
+ const summary = await new SessionCatalog({
40
+ workspaceRoot: workspace.path,
41
+ homeRoot,
42
+ }).get(sessionId);
43
+ profileName = resolveSessionProfileName(publicConfig.profiles, summary);
44
+ }
45
+ const config = deriveRunnerConfig(publicConfig, {
46
+ sessionId,
47
+ ...(profileName ? { profileName } : {}),
48
+ });
49
+ const reasoning = createReasoningEffortController(config.reasoning);
50
+ const projectInstructions = await loadProjectInstructions(workspace.path);
51
+ const runtime = await createRuntimeSession({
52
+ workspaceRoot: workspace.path,
53
+ ...(homeRoot === undefined ? {} : { homeRoot }),
54
+ ...(record.initialized
55
+ ? { selection: { mode: "resume" as const, sessionId } }
56
+ : { selection: { mode: "new" as const, sessionId } }),
57
+ modelName: config.modelName,
58
+ profileName: config.profileName,
59
+ maxIterations: config.maxIterations,
60
+ includeReasoningContent: config.includeReasoningContent,
61
+ contextProfile: config.contextProfile,
62
+ contextBudget: config.contextBudget,
63
+ modelClient: createRunnerModelClient(config, undefined, env, reasoning),
64
+ systemPrompt: buildSystemPrompt({
65
+ workspaceRoot: workspace.path,
66
+ runtimeInstructions: RUNTIME_INSTRUCTIONS(workspace.path),
67
+ projectInstructions,
68
+ }),
69
+ projectInstruction: projectInstructionManifest(projectInstructions),
70
+ skillCatalog: await loadSkillCatalog({ workspaceRoot: workspace.path }),
71
+ presentationSinks: [sink],
72
+ assistantTextDeltaSink: sink,
73
+ toolingConfig: publicConfig.tooling,
74
+ webFetchRefiner: createWebFetchRefiner(config, env, reasoning),
75
+ enableAskUser: true,
76
+ bashGuard: {
77
+ mode: config.bashGuardMode,
78
+ source: config.bashGuardSource,
79
+ surface: "tui",
80
+ },
81
+ });
82
+ try {
83
+ return {
84
+ runtime,
85
+ databasePath: await resolveSessionDatabasePath(
86
+ workspace.path,
87
+ sessionId,
88
+ homeRoot,
89
+ ),
90
+ modelName: config.modelName,
91
+ };
92
+ } catch (error) {
93
+ await runtime.dispose({
94
+ type: "initialization_failed",
95
+ error: "Cannot open remote history reader.",
96
+ });
97
+ throw error;
98
+ }
99
+ };
100
+ }
@@ -1,122 +1,16 @@
1
- import { stableJsonStringify, sha256 } from "../model/model-request-preflight";
2
- import { RECALL_TOOL_DEFINITIONS } from "../tools/recall";
3
- import type { ToolDefinition } from "../tools/types";
4
- import type { StoredContextSurfaceV8 } from "./context-surface";
5
- import {
6
- CURRENT_RECALL_RETIREMENT_CONTRACT_VERSION,
7
- renderRecallRetirementContract,
8
- } from "./recall-retirement-contract";
9
-
10
- export const I4_ACTIVE_RECALL_QUALIFICATION = Object.freeze({
11
- qualificationId: "deepseek-v4-flash-floor-v1",
12
- evaluatedProfile: "deepseek-v4-flash",
13
- manifestVersion: "active-recall-manifest-v1",
14
- manifestSha256: "093679e221e02b71ba5acf54693faa7a299d05dd25f6faabbeb84645d4db4d2d",
15
- graderVersion: "active-recall-deterministic-grader-v1",
16
- fixtureVersion: "active-recall-long-session-fixture-v1",
17
- policyVersion: "active-recall-qualification-policy-v1",
18
- policySha256: "77ca611594d4e9b7b5a597a3a33e35fcaaffae284dc2ac9953ce9a63cce1c009",
19
- positiveReportSha256:
20
- "e827e5e94171328bb2dd7fcaeff91881f04bdfc78361a45e2548da323229b02a",
21
- negativeReportSha256:
22
- "ed379843aee0f193f398a4dff9a18ed338f0edab2cbaf9b628d0dfc402d925a4",
23
- resolvedModel: "deepseek-v4-flash",
24
- recallContractVersion: CURRENT_RECALL_RETIREMENT_CONTRACT_VERSION,
25
- recallContractSha256:
26
- "3b6d1a452efea1db5920eb13542571b038667ef4f635551bea375bda6562a39f",
27
- recallToolDefinitionSha256:
28
- "e63ada7cdf9591d1e933cf5e190ea30586e02bbf73aeb5e648db449d75aae009",
29
- metrics: Object.freeze({
30
- fullHistoryTaskSuccessRate: 0.9667,
31
- swapOnlyTaskSuccessRate: 0.9667,
32
- recallOnlyTaskSuccessRate: 1,
33
- recallOnlyActiveRecallRate: 1,
34
- recallOnlySearchGetSuccessRate: 0.3333,
35
- minimumCounterfactualGroupTaskSuccessRate: 1,
36
- invalidRecallCallsPerRecallOnlyTrial: 0,
37
- negativeUnnecessaryRecallRate: 0,
38
- recallOnlyTokenRatioToFullHistory: 1.3739,
39
- recallOnlyLatencyRatioToFullHistory: 1.207,
40
- }),
41
- passed: true,
42
- } as const);
43
-
44
- export const I4_SWAP_ONLY_QUALIFICATION_ID = "swap-only-engineering-v1";
45
-
46
- export type ActiveRecallQualificationEvidence = {
47
- readonly qualificationId: string;
48
- readonly recallContractVersion: string;
49
- readonly recallContractSha256: string;
50
- readonly recallToolDefinitionSha256: string;
51
- readonly passed: boolean;
52
- };
53
-
54
- export type ContextAutomationDecision = {
55
- readonly automaticSwapOnly: boolean;
1
+ /** Product defaults only. Evaluation results and model identities never select these flags.
2
+ * Planners and session boundaries independently validate each operation before execution.
3
+ */
4
+ export type ContextAutomationPolicy = {
5
+ readonly policyId: string;
6
+ readonly automaticSwap: boolean;
56
7
  readonly automaticPrefixRetirement: boolean;
57
- readonly reason:
58
- | "qualified"
59
- | "swap_only_qualified"
60
- | "qualification_pending"
61
- | "unprofiled_model"
62
- | "recall_contract_mismatch"
63
- | "recall_tool_mismatch";
64
- readonly qualificationId?: string;
65
8
  };
66
9
 
67
- export function selectContextAutomation(
68
- input: {
69
- readonly profileName?: string;
70
- readonly surface: StoredContextSurfaceV8;
71
- },
72
- evidence: ActiveRecallQualificationEvidence = I4_ACTIVE_RECALL_QUALIFICATION,
73
- ): ContextAutomationDecision {
74
- if (input.profileName === undefined) {
75
- return disabled("unprofiled_model");
76
- }
77
- if (
78
- input.surface.recallContractVersion !== evidence.recallContractVersion ||
79
- sha256(renderRecallRetirementContract()) !== evidence.recallContractSha256
80
- ) {
81
- return disabled("recall_contract_mismatch");
82
- }
83
- const recallTools = input.surface.toolDefinitions.filter(
84
- (definition) =>
85
- definition.name === "RecallSearch" || definition.name === "RecallGet",
86
- );
87
- if (
88
- recallTools.length !== 2 ||
89
- toolDefinitionsHash(recallTools) !== evidence.recallToolDefinitionSha256 ||
90
- toolDefinitionsHash(RECALL_TOOL_DEFINITIONS) !== evidence.recallToolDefinitionSha256
91
- ) {
92
- return disabled("recall_tool_mismatch");
93
- }
94
- if (!evidence.passed) {
95
- return Object.freeze({
96
- automaticSwapOnly: true,
97
- automaticPrefixRetirement: false,
98
- reason: "swap_only_qualified",
99
- qualificationId: I4_SWAP_ONLY_QUALIFICATION_ID,
100
- });
101
- }
102
- return Object.freeze({
103
- automaticSwapOnly: true,
10
+ export const DEFAULT_CONTEXT_AUTOMATION_POLICY: ContextAutomationPolicy = Object.freeze(
11
+ {
12
+ policyId: "context-automation-v1",
13
+ automaticSwap: true,
104
14
  automaticPrefixRetirement: true,
105
- reason: "qualified",
106
- qualificationId: evidence.qualificationId,
107
- });
108
- }
109
-
110
- function disabled(
111
- reason: Exclude<ContextAutomationDecision["reason"], "qualified">,
112
- ): ContextAutomationDecision {
113
- return Object.freeze({
114
- automaticSwapOnly: false,
115
- automaticPrefixRetirement: false,
116
- reason,
117
- });
118
- }
119
-
120
- function toolDefinitionsHash(definitions: readonly ToolDefinition[]): string {
121
- return sha256(stableJsonStringify(definitions));
122
- }
15
+ },
16
+ );
@@ -193,6 +193,11 @@ function metadataEntries(
193
193
  ["pattern", raw.pattern],
194
194
  ["searchPath", raw.searchPath],
195
195
  ["matchCount", raw.matchCount],
196
+ ["totalMatches", raw.totalMatches],
197
+ ["returnedCount", raw.returnedCount],
198
+ ["appliedOffset", raw.appliedOffset],
199
+ ["hasMore", raw.hasMore],
200
+ ["nextOffset", raw.nextOffset],
196
201
  ];
197
202
  case "grep":
198
203
  return [
@@ -200,6 +205,15 @@ function metadataEntries(
200
205
  ["searchPath", raw.searchPath],
201
206
  ["mode", raw.mode],
202
207
  ["numMatches", raw.numMatches],
208
+ ["numLines", raw.numLines],
209
+ ["paginationUnit", raw.paginationUnit],
210
+ ["totalResults", raw.totalResults],
211
+ ["returnedResults", raw.returnedResults],
212
+ ["appliedOffset", raw.appliedOffset],
213
+ ["hasMore", raw.hasMore],
214
+ ["nextOffset", raw.nextOffset],
215
+ ["searchIncomplete", raw.searchIncomplete],
216
+ ["contextMayBeIncomplete", raw.contextMayBeIncomplete],
203
217
  ["truncated", raw.truncated],
204
218
  ];
205
219
  case "bash":
@@ -56,6 +56,8 @@ export type ContextRevisionStartedData =
56
56
  reason: "manual" | "runtime_pressure" | "model_directed";
57
57
  policyVersion: "swap-only-v1";
58
58
  rendererFormat: "swap-observation-v1";
59
+ automationPolicyId?: string;
60
+ /** Legacy event metadata only; never controls runtime automation. */
59
61
  qualificationId?: string;
60
62
  }
61
63
  | {
@@ -69,6 +71,8 @@ export type ContextRevisionStartedData =
69
71
  reason: "manual" | "runtime_pressure";
70
72
  policyVersion: "recall-first-retirement-v1";
71
73
  baseRevisionNumber: number;
74
+ automationPolicyId?: string;
75
+ /** Legacy event metadata only; never controls runtime automation. */
72
76
  qualificationId?: string;
73
77
  }
74
78
  | {
@@ -102,6 +106,8 @@ export type ContextRevisionFinishedData =
102
106
  targetTokens: number;
103
107
  planHash?: string;
104
108
  durationMs: number;
109
+ automationPolicyId?: string;
110
+ /** Legacy event metadata only; never controls runtime automation. */
105
111
  qualificationId?: string;
106
112
  }
107
113
  | {
@@ -143,6 +149,8 @@ export type ContextRevisionFinishedData =
143
149
  transactionDurationMs?: number;
144
150
  activationDurationMs?: number;
145
151
  durationMs: number;
152
+ automationPolicyId?: string;
153
+ /** Legacy event metadata only; never controls runtime automation. */
146
154
  qualificationId?: string;
147
155
  }
148
156
  | {
@@ -166,6 +174,8 @@ export type ContextRevisionFailedData =
166
174
  stage: "snapshot" | "plan" | "validate" | "commit" | "activate";
167
175
  errorCode: string;
168
176
  error: string;
177
+ automationPolicyId?: string;
178
+ /** Legacy event metadata only; never controls runtime automation. */
169
179
  qualificationId?: string;
170
180
  }
171
181
  | {
@@ -183,6 +193,8 @@ export type ContextRevisionFailedData =
183
193
  errorCode: string;
184
194
  error: string;
185
195
  committed: boolean;
196
+ automationPolicyId?: string;
197
+ /** Legacy event metadata only; never controls runtime automation. */
186
198
  qualificationId?: string;
187
199
  }
188
200
  | {
@@ -10,7 +10,7 @@ import { MAX_MEMORY_ID_BYTES, MEMORY_GET_TOOL_NAME } from "./contracts";
10
10
  export const MEMORY_GET_TOOL_DEFINITION: ToolDefinition = Object.freeze({
11
11
  name: MEMORY_GET_TOOL_NAME,
12
12
  description:
13
- "Read one stored memory in full by its memoryId from a MemorySearch result. Use this when a search hit's summary is truncated or you need its exact stored text, summary, and source metadata. The record is a derived historical summary that may be stale or wrong; verify current workspace facts with current tools, and use RecallSearch on its source session for the full original context.",
13
+ "Read one stored memory in full by its memoryId from a MemorySearch result. Use this when a search hit's summary is truncated or you need its exact stored text, summary, and source metadata. The record is a derived historical summary that may be stale or wrong; verify current workspace facts with current tools.",
14
14
  parameters: {
15
15
  type: "object",
16
16
  additionalProperties: false,