Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion hindsight-docs/docs-integrations/openclaw.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ Optional settings in `~/.openclaw/openclaw.json`:
- `enableKnowledgeTools` - Register `agent_knowledge_*` tools for explicit agent-driven lookup, reflection, ingest, and knowledge-page management (default: `false`).
- `debug` - Enable debug logging (default: `false`).

When using `agent_knowledge_recall` manually, pass `max_tokens` to control how much memory text the recall response may contain. The tool has no `max_results` parameter — to cap the number of automatically injected memories, use `recallTopK` on auto-recall instead.
When using `agent_knowledge_recall` manually, pass `max_tokens` to control how much memory text the recall response may contain. The tool has no `max_results` parameter — to cap the number of automatically injected memories, use `recallTopK` on auto-recall instead. When the answer needs verbatim wording or an exact number rather than an extracted fact, pass `include_chunks: true` to also get the raw source text those memories came from, and `max_chunk_tokens` to bound it (default `8192`).

When using `agent_knowledge_reflect`, keep the default conservative settings unless you intentionally need a deeper synthesis: `budget` defaults to `low`, `max_tokens` defaults to `1024`, and `fact_types` defaults to `world`, `experience`, and `observation`. Reflect calls can be more expensive than recall because they retrieve memories and then call the configured Reflect LLM to generate an answer. For production banks, set a finite bank-level `reflect_source_facts_max_tokens` value (for example `4096` or `8192`) instead of leaving it unlimited, so ad-hoc reflection cannot pull an unbounded amount of source facts into the LLM context.

Expand Down
21 changes: 21 additions & 0 deletions hindsight-tools/hindsight-agent-sdk/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,14 @@ function parseRecallMaxTokens(input: unknown): number {
return n;
}

/** Undefined on absent/invalid input so the client's own chunk budget default applies. */
function parseChunkMaxTokens(input: unknown): number | undefined {
if (input === undefined || input === null) return undefined;
const n = Math.floor(Number(input));
if (!Number.isFinite(n) || n < 1) return undefined;
return n;
}

// ── Factory ────────────────────────────────────────────

/**
Expand Down Expand Up @@ -261,6 +269,17 @@ export function createKnowledgeTools(opts: CreateKnowledgeToolsOptions): Knowled
items: { type: "string", enum: ["world", "experience", "observation"] },
description: "Alias for fact_types.",
},
include_chunks: {
type: "boolean",
description:
"Also return the raw source text the memories were extracted from (default false). " +
"Use only when the answer needs verbatim wording, an exact quote, or an exact number " +
"that an extracted fact may have rounded or paraphrased — it costs significantly more tokens.",
},
max_chunk_tokens: {
type: "number",
description: "Token budget for the returned source chunks (default 8192).",
},
},
required: ["query"],
},
Expand All @@ -273,6 +292,8 @@ export function createKnowledgeTools(opts: CreateKnowledgeToolsOptions): Knowled
const result = await client.recall(bankId, params.query as string, {
maxTokens,
types,
includeChunks: params.include_chunks === true,
maxChunkTokens: parseChunkMaxTokens(params.max_chunk_tokens),
});
return ok(result);
},
Expand Down
57 changes: 57 additions & 0 deletions hindsight-tools/hindsight-agent-sdk/tests/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,63 @@ describe("createKnowledgeTools", () => {
expect(body.max_tokens).toBe(512);
});

it("recall omits source chunks unless include_chunks is exactly true", async () => {
const tool = tools.find((t) => t.name === "agent_knowledge_recall")!;

// Omitted, false, and a truthy-string "false" must all leave chunks off.
for (const params of [
{ query: "no chunks" },
{ query: "no chunks", include_chunks: false },
{ query: "no chunks", include_chunks: "false" },
// A chunk budget alone must not switch chunks on.
{ query: "no chunks", max_chunk_tokens: 2048 },
]) {
mockFetch.mockReset();
mockFetch.mockReturnValueOnce(mockResponse({ results: [{ text: "found" }] }));
await tool.execute(params);
const body = await getBody(mockFetch.mock.calls[0]);
expect(body.include?.chunks, JSON.stringify(params)).toBeUndefined();
}
});

it("recall include_chunks requests source chunks and returns them to the agent", async () => {
mockFetch.mockReturnValueOnce(
mockResponse({
results: [{ text: "found" }],
chunks: { c1: { chunk_text: "the fee is 4.375%", truncated: false } },
})
);

const tool = tools.find((t) => t.name === "agent_knowledge_recall")!;
const result = await tool.execute({ query: "exact wording", include_chunks: true });

const body = await getBody(mockFetch.mock.calls[0]);
expect(body.include.chunks).toEqual({ max_tokens: 8192 });
// The chunks must survive into the tool output, not just the request.
expect(JSON.parse(result.content[0].text).chunks.c1.chunk_text).toBe("the fee is 4.375%");
});

it("recall falls back to the client's chunk budget on unusable max_chunk_tokens", async () => {
const tool = tools.find((t) => t.name === "agent_knowledge_recall")!;

for (const [maxChunkTokens, expected] of [
[2048, 2048],
[0, 8192],
[-5, 8192],
["not-a-number", 8192],
] as const) {
mockFetch.mockReset();
mockFetch.mockReturnValueOnce(mockResponse({ results: [{ text: "found" }] }));
await tool.execute({
query: "exact wording",
include_chunks: true,
max_chunk_tokens: maxChunkTokens,
});
const body = await getBody(mockFetch.mock.calls[0]);
expect(body.include.chunks.max_tokens, String(maxChunkTokens)).toBe(expected);
}
});

it("reflect sends POST with conservative defaults", async () => {
mockFetch.mockReturnValueOnce(mockResponse({ text: "answer" }));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ Optional settings in `~/.openclaw/openclaw.json`:
- `enableKnowledgeTools` - Register `agent_knowledge_*` tools for explicit agent-driven lookup, reflection, ingest, and knowledge-page management (default: `false`).
- `debug` - Enable debug logging (default: `false`).

When using `agent_knowledge_recall` manually, pass `max_tokens` to control how much memory text the recall response may contain. The tool has no `max_results` parameter — to cap the number of automatically injected memories, use `recallTopK` on auto-recall instead.
When using `agent_knowledge_recall` manually, pass `max_tokens` to control how much memory text the recall response may contain. The tool has no `max_results` parameter — to cap the number of automatically injected memories, use `recallTopK` on auto-recall instead. When the answer needs verbatim wording or an exact number rather than an extracted fact, pass `include_chunks: true` to also get the raw source text those memories came from, and `max_chunk_tokens` to bound it (default `8192`).

When using `agent_knowledge_reflect`, keep the default conservative settings unless you intentionally need a deeper synthesis: `budget` defaults to `low`, `max_tokens` defaults to `1024`, and `fact_types` defaults to `world`, `experience`, and `observation`. Reflect calls can be more expensive than recall because they retrieve memories and then call the configured Reflect LLM to generate an answer. For production banks, set a finite bank-level `reflect_source_facts_max_tokens` value (for example `4096` or `8192`) instead of leaving it unlimited, so ad-hoc reflection cannot pull an unbounded amount of source facts into the LLM context.

Expand Down