feat(FN-3485): align memory scope guidance and dashboard tooling
- Clarify task memory scope behavior across core types, project memory logic, engine tool prompts, and related docs - Add regression coverage for memory scope and runtime plugin alias handling in core/dashboard/engine tests - Add agent avatar API routes and dashboard UI support for avatar display and storage documentation - Add line-number gutter toggle support in FileEditor and Files modal with accompanying component tests - Include changeset for @runfusion/fusion documenting memory scope guidance update Fusion-Task-Id: FN-3485
This commit is contained in:
5
.changeset/fn-3485-memory-scope-guidance.md
Normal file
5
.changeset/fn-3485-memory-scope-guidance.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
Clarify runtime memory guidance so agents explicitly distinguish private `scope="agent"` memory from shared `scope="project"` memory in prompts and tool metadata.
|
||||
@@ -167,6 +167,9 @@ When engine sessions include per-agent memory context, the memory tools operate
|
||||
|
||||
Runtime behavior:
|
||||
|
||||
- `fn_memory_append` supports dual scope writes:
|
||||
- `scope="agent"` for private per-agent operating context (personal playbooks/checklists, self-management notes)
|
||||
- `scope="project"` for shared repo-wide durable knowledge (architecture constraints, conventions, pitfalls)
|
||||
- `fn_memory_search` can surface snippets from:
|
||||
- `.fusion/agent-memory/{agentId}/MEMORY.md` (long-term)
|
||||
- `.fusion/agent-memory/{agentId}/DREAMS.md` (synthesized patterns)
|
||||
|
||||
@@ -16,7 +16,7 @@ These tools are **not** part of the user-invokable extension surface. They are i
|
||||
| `fn_task_document_read` | triage, executor, heartbeat | Read one task document or list all | `key?` (string) |
|
||||
| `fn_memory_search` | triage, executor, heartbeat | Search project memory plus per-agent layered memory snippets | `query` (string), `limit?` (number) |
|
||||
| `fn_memory_get` | triage, executor, heartbeat | Read a bounded memory file window (including bounded per-agent layered paths) | `path` (string), `startLine?` (number), `lineCount?` (number) |
|
||||
| `fn_memory_append` | executor, heartbeat (when writable backend enabled) | Append long-term/daily memory notes | `scope?` (`project` \| `agent`), `layer` (`long-term` \| `daily`), `content` (string) |
|
||||
| `fn_memory_append` | executor, heartbeat (when writable backend enabled) | Append memory notes with explicit scope: `scope="agent"` for private operating context, `scope="project"` for workspace-wide durable knowledge | `scope?` (`project` \| `agent`), `layer` (`long-term` \| `daily`), `content` (string) |
|
||||
| `fn_research_run` | triage, executor | Start a bounded research run (optionally wait for completion) and return structured findings metadata | `query` (string), `wait_for_completion?` (boolean), `max_wait_ms?` (number) |
|
||||
| `fn_research_list` | triage, executor | List recent research runs with status/summary metadata | `status?` (`pending` \| `running` \| `completed` \| `failed` \| `cancelled`), `limit?` (number) |
|
||||
| `fn_research_get` | triage, executor | Read one research run's structured findings/citations payload | `id` (string) |
|
||||
|
||||
@@ -250,6 +250,19 @@ describe("project-memory", () => {
|
||||
expect(instructions).toMatch(/consolidate|update.*refine.*existing|edit.*existing/i);
|
||||
});
|
||||
|
||||
it("includes explicit agent-vs-project memory scope guidance", () => {
|
||||
const instructions = buildExecutionMemoryInstructions(testDir, { memoryBackendType: "file" });
|
||||
expect(instructions).toContain('fn_memory_append(scope="agent")');
|
||||
expect(instructions).toContain('fn_memory_append(scope="project")');
|
||||
expect(instructions).toMatch(/private\/ephemeral|private operating context/i);
|
||||
});
|
||||
|
||||
it("includes layer guidance for long-term vs daily memory", () => {
|
||||
const instructions = buildExecutionMemoryInstructions(testDir, { memoryBackendType: "qmd" });
|
||||
expect(instructions).toContain('layer="long-term"');
|
||||
expect(instructions).toContain('layer="daily"');
|
||||
});
|
||||
|
||||
it("keeps qmd default path-agnostic", () => {
|
||||
const instructions = buildExecutionMemoryInstructions(testDir);
|
||||
expect(instructions).not.toContain("`.fusion/memory/MEMORY.md`");
|
||||
|
||||
@@ -455,20 +455,17 @@ This project has OpenClaw-style memory files:
|
||||
|
||||
**At the end of execution (before calling \`fn_task_done()\`):**
|
||||
1. Review what you learned during this task that would genuinely benefit future runs
|
||||
2. Write durable decisions, conventions, and pitfalls to \`.fusion/memory/MEMORY.md\`
|
||||
3. Write running observations, unresolved context, and open loops to today's \`.fusion/memory/YYYY-MM-DD.md\`
|
||||
4. **If nothing durable was learned, skip the memory update entirely** — do not append trivial or task-specific notes
|
||||
5. Only write when you have genuinely durable, reusable insights such as:
|
||||
- New architectural patterns or module boundaries discovered
|
||||
- Conventions or standards that should be followed
|
||||
- Pitfalls or anti-patterns to avoid in future work
|
||||
- Important constraints or context that affects implementation decisions
|
||||
6. **Avoid** writing task-specific trivia such as:
|
||||
- Per-task implementation logs or changelog entries
|
||||
- Transient failures resolved without broader lessons
|
||||
- One-off file paths, variable names, or minor code changes
|
||||
- Notes about what you did rather than what future agents should know
|
||||
7. **Consolidate when possible**: If an existing entry already covers a concept, update or refine it rather than adding a duplicate. Delete entries that are no longer accurate.
|
||||
2. Choose scope intentionally:
|
||||
- Use \`fn_memory_append(scope="agent")\` for your private operating context (personal checklists, delegation habits, temporary playbooks, self-improvement notes)
|
||||
- Use \`fn_memory_append(scope="project")\` for repository-wide durable knowledge any future agent should know
|
||||
3. Choose layer intentionally:
|
||||
- \`layer="long-term"\` for durable conventions/decisions/pitfalls
|
||||
- \`layer="daily"\` for running observations, unresolved context, and open loops
|
||||
4. If using project scope with file backend, write long-term memory to \`.fusion/memory/MEMORY.md\` and daily notes to today's \`.fusion/memory/YYYY-MM-DD.md\`
|
||||
5. **If nothing durable was learned, skip the memory update entirely** — do not append trivial or task-specific notes
|
||||
6. Only write to **project** memory when the insight is genuinely reusable across the workspace (architecture patterns, shared conventions, durable pitfalls, cross-task constraints)
|
||||
7. **Do not** write private/ephemeral items to project memory, such as personal TODOs, one-off scratch notes, or preferences that only help you as an individual agent
|
||||
8. **Consolidate when possible**: If an existing entry already covers a concept, update or refine it rather than adding a duplicate. Delete entries that are no longer accurate.
|
||||
|
||||
**Format for additions:** Add bullet points under the relevant section heading:
|
||||
- Use \`- \` prefix for list items
|
||||
@@ -490,18 +487,15 @@ This project has a memory system that stores durable project learnings accumulat
|
||||
|
||||
**At the end of execution (before calling \`fn_task_done()\`):**
|
||||
1. Review what you learned during this task that would genuinely benefit future runs
|
||||
2. **If nothing durable was learned, skip the memory update entirely** — do not append trivial or task-specific notes
|
||||
3. Only write when you have genuinely durable, reusable insights such as:
|
||||
- New architectural patterns or module boundaries discovered
|
||||
- Conventions or standards that should be followed
|
||||
- Pitfalls or anti-patterns to avoid in future work
|
||||
- Important constraints or context that affects implementation decisions
|
||||
4. **Avoid** writing task-specific trivia such as:
|
||||
- Per-task implementation logs or changelog entries
|
||||
- Transient failures resolved without broader lessons
|
||||
- One-off file paths, variable names, or minor code changes
|
||||
- Notes about what you did rather than what future agents should know
|
||||
5. Consolidate when possible: refine an existing memory entry instead of adding duplicates.
|
||||
2. Choose scope intentionally:
|
||||
- Use \`fn_memory_append(scope="agent")\` for your private operating context
|
||||
- Use \`fn_memory_append(scope="project")\` only for repo-wide durable knowledge
|
||||
3. Choose layer intentionally:
|
||||
- \`layer="long-term"\` for durable conventions/decisions/pitfalls
|
||||
- \`layer="daily"\` for running observations and open loops
|
||||
4. **If nothing durable was learned, skip the memory update entirely** — do not append trivial or task-specific notes
|
||||
5. **Avoid task-specific trivia** in project scope (for example: personal reminders, one-off scratch thoughts, individual communication preferences)
|
||||
6. Consolidate when possible: refine an existing memory entry instead of adding duplicates.
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -237,11 +237,15 @@ describe("createMemoryTools", () => {
|
||||
});
|
||||
|
||||
it("includes fn_memory_append for writable memory backends", () => {
|
||||
expect(createMemoryTools("/repo", { memoryBackendType: "file" }).map((tool) => tool.name)).toEqual([
|
||||
const tools = createMemoryTools("/repo", { memoryBackendType: "file" });
|
||||
expect(tools.map((tool) => tool.name)).toEqual([
|
||||
"fn_memory_search",
|
||||
"fn_memory_get",
|
||||
"fn_memory_append",
|
||||
]);
|
||||
const appendTool = tools.find((tool) => tool.name === "fn_memory_append");
|
||||
expect(appendTool?.description).toContain('scope="agent"');
|
||||
expect(appendTool?.description).toContain('scope="project"');
|
||||
});
|
||||
|
||||
it("searches per-agent memory through the fn_memory_search tool", async () => {
|
||||
|
||||
@@ -2751,6 +2751,9 @@ describe("buildExecutionPrompt", () => {
|
||||
// Should instruct selective writes, not unconditional appends
|
||||
expect(result).toMatch(/skip.*memory.*update|selectively|durable.*learnings/i);
|
||||
expect(result).toMatch(/end of execution|before calling.*fn_task_done/i);
|
||||
// Should distinguish agent-private vs project-shared memory scope
|
||||
expect(result).toContain('fn_memory_append(scope="agent")');
|
||||
expect(result).toContain('fn_memory_append(scope="project")');
|
||||
// Should forbid task-specific trivia
|
||||
expect(result).toMatch(/avoid.*trivia|task-specific.*trivia|per-task.*log/i);
|
||||
// Should allow consolidation/editing
|
||||
|
||||
@@ -622,6 +622,8 @@ describe("executeHeartbeat", () => {
|
||||
expect(systemPrompt).toContain("fn_send_message");
|
||||
expect(systemPrompt).toContain("fn_memory_search");
|
||||
expect(systemPrompt).toContain("fn_memory_append");
|
||||
expect(systemPrompt).toContain('scope="agent"');
|
||||
expect(systemPrompt).toContain('scope="project"');
|
||||
expect(systemPrompt).toContain("fn_heartbeat_done");
|
||||
});
|
||||
|
||||
|
||||
@@ -319,7 +319,7 @@ You have coding-capable workspace tools (read/write/edit/bash within worktree bo
|
||||
Use this decision rule:
|
||||
- **fn_task_create:** create executable work when ownership is not predetermined.
|
||||
- **fn_delegate_task:** assign immediately when a specific agent should own the work now.
|
||||
- **fn_memory_append:** persist durable conventions/pitfalls; avoid transient run-by-run chatter.
|
||||
- **fn_memory_append:** use \`scope="agent"\` for your own operating context and \`scope="project"\` for repo-wide durable knowledge; avoid transient run-by-run chatter.
|
||||
|
||||
If unsure who should do the work, prefer fn_task_create and let scheduler routing happen naturally.
|
||||
|
||||
|
||||
@@ -851,8 +851,8 @@ export function createMemoryAppendTool(rootDir: string, settings?: MemoryToolSet
|
||||
name: "fn_memory_append",
|
||||
label: "Append Memory",
|
||||
description:
|
||||
"Append concise Markdown to project memory. Use long-term only for durable conventions/decisions/pitfalls; " +
|
||||
"use daily for running observations and open loops. Skip this tool when there is no reusable memory.",
|
||||
"Append concise Markdown to memory. Use scope=\"agent\" for private operating context and scope=\"project\" for workspace-wide durable knowledge. " +
|
||||
"Use layer=\"long-term\" for durable conventions/decisions/pitfalls and layer=\"daily\" for running observations/open loops.",
|
||||
parameters: memoryAppendParams,
|
||||
execute: async (_id: string, params: Static<typeof memoryAppendParams>) => {
|
||||
const content = params.content.trim();
|
||||
|
||||
@@ -363,7 +363,8 @@ async function flushMemoryBeforeSessionCompaction(session: AgentSession): Promis
|
||||
|
||||
const flushPrompt = [
|
||||
"Before context compaction, preserve only unresolved durable memory if needed.",
|
||||
"If fn_memory_append is available and you learned reusable project decisions, conventions, pitfalls, or open loops that are not already saved, append them now.",
|
||||
"If fn_memory_append is available and you learned reusable project decisions/conventions/pitfalls/open loops or private operating context that is not already saved, append it now.",
|
||||
"Use scope=\"project\" for shared workspace knowledge and scope=\"agent\" for private operating context.",
|
||||
"Use layer=\"long-term\" for durable facts and layer=\"daily\" for running notes/open loops.",
|
||||
"If there is nothing durable to save, reply exactly: NONE.",
|
||||
].join("\n");
|
||||
|
||||
Reference in New Issue
Block a user