diff --git a/backend/README.md b/backend/README.md
index 4c8e01c..e94debd 100644
--- a/backend/README.md
+++ b/backend/README.md
@@ -2,6 +2,7 @@
DeerFlow is a LangGraph-based AI agent system that provides a powerful "super agent" with sandbox execution capabilities. The backend enables AI agents to execute code, browse the web, manage files, and perform complex multi-step tasks in isolated environments.
+---
## Features
- **LangGraph Agent Runtime**: Built on LangGraph for robust multi-agent workflow orchestration
@@ -13,6 +14,7 @@ DeerFlow is a LangGraph-based AI agent system that provides a powerful "super ag
- **Context Summarization**: Automatic conversation summarization for long conversations
- **Plan Mode**: TodoList middleware for complex multi-step task tracking
+---
## Architecture
```
@@ -33,6 +35,7 @@ DeerFlow is a LangGraph-based AI agent system that provides a powerful "super ag
- `/api/*` (other) → Gateway API (models, MCP, skills, artifacts, uploads)
- `/` (non-API) → Frontend (web interface)
+---
## Quick Start
### Prerequisites
@@ -101,6 +104,7 @@ Direct access:
- LangGraph: http://localhost:2024
- Gateway: http://localhost:8001
+---
## Project Structure
```
@@ -129,6 +133,7 @@ backend/
└── Dockerfile # Container build
```
+---
## API Reference
### LangGraph API (via `/api/langgraph/*`)
@@ -164,6 +169,7 @@ backend/
**Artifacts**:
- `GET /api/threads/{thread_id}/artifacts/{path}` - Download generated artifacts
+---
## Configuration
### Main Configuration (`config.yaml`)
@@ -209,6 +215,7 @@ MCP servers and skills are configured in `extensions_config.json`:
- Model API keys: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `DEEPSEEK_API_KEY`, etc.
- Tool API keys: `TAVILY_API_KEY`, `GITHUB_TOKEN`, etc.
+---
## Development
### Commands
@@ -234,6 +241,7 @@ make format # Format code (ruff)
uv run pytest
```
+---
## Documentation
- [Configuration Guide](docs/CONFIGURATION.md) - Detailed configuration options
@@ -243,6 +251,7 @@ uv run pytest
- [Summarization](docs/summarization.md) - Context summarization feature
- [Plan Mode](docs/plan_mode_usage.md) - TodoList middleware usage
+---
## Technology Stack
### Core Frameworks
@@ -266,10 +275,12 @@ uv run pytest
- `firecrawl-py` - Web scraping
- `ddgs` - DuckDuckGo image search
+---
## License
See the [LICENSE](../LICENSE) file in the project root.
+---
## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md) for contribution guidelines.
diff --git a/frontend/src/components/landing/hero.tsx b/frontend/src/components/landing/hero.tsx
index a9baa7c..2fde97b 100644
--- a/frontend/src/components/landing/hero.tsx
+++ b/frontend/src/components/landing/hero.tsx
@@ -58,11 +58,12 @@ export function Hero({ className }: { className?: string }) {
DeerFlow is an open-source SuperAgent that researches, codes, and
+ creates.
- creates. With the help of sandboxes, tools and skills, it handles
+ With the help of sandboxes, memories, tools and skills, it handles
different levels of tasks that could take minutes to hours.
diff --git a/frontend/src/components/landing/sections/whats-new-section.tsx b/frontend/src/components/landing/sections/whats-new-section.tsx
index ac37029..cca4c98 100644
--- a/frontend/src/components/landing/sections/whats-new-section.tsx
+++ b/frontend/src/components/landing/sections/whats-new-section.tsx
@@ -11,12 +11,7 @@ const features: BentoCardProps[] = [
color: COLOR,
label: "Context Engineering",
title: "Long/Short-term Memory",
- description: (
-
-
Now the agent can better understand you
-
Coming soon
-
- ),
+ description: "Now the agent can better understand you",
},
{
color: COLOR,
diff --git a/frontend/src/components/workspace/settings/memory-settings-page.tsx b/frontend/src/components/workspace/settings/memory-settings-page.tsx
new file mode 100644
index 0000000..860aef3
--- /dev/null
+++ b/frontend/src/components/workspace/settings/memory-settings-page.tsx
@@ -0,0 +1,177 @@
+"use client";
+
+import { Streamdown } from "streamdown";
+
+import { useI18n } from "@/core/i18n/hooks";
+import { useMemory } from "@/core/memory/hooks";
+import type { UserMemory } from "@/core/memory/types";
+import { streamdownPlugins } from "@/core/streamdown/plugins";
+import { pathOfThread } from "@/core/threads/utils";
+import { formatTimeAgo } from "@/core/utils/datetime";
+
+import { SettingsSection } from "./settings-section";
+
+function confidenceToLevelKey(confidence: unknown): {
+ key: "veryHigh" | "high" | "normal" | "unknown";
+ value?: number;
+} {
+ if (typeof confidence !== "number" || !Number.isFinite(confidence)) {
+ return { key: "unknown" };
+ }
+
+ // Clamp to [0, 1] since confidence is expected to be a probability-like score.
+ const value = Math.min(1, Math.max(0, confidence));
+
+ // 3 levels:
+ // - veryHigh: [0.85, 1]
+ // - high: [0.65, 0.85)
+ // - normal: [0, 0.65)
+ if (value >= 0.85) return { key: "veryHigh", value };
+ if (value >= 0.65) return { key: "high", value };
+ return { key: "normal", value };
+}
+
+function memoryToMarkdown(
+ memory: UserMemory,
+ t: ReturnType["t"],
+) {
+ const parts: string[] = [];
+
+ console.info(memory);
+
+ parts.push(`## ${t.settings.memory.markdown.overview}`);
+ parts.push(`- **${t.common.version}**: \`${memory.version}\``);
+ parts.push(
+ `- **${t.common.lastUpdated}**: \`${formatTimeAgo(memory.lastUpdated)}\``,
+ );
+
+ parts.push(`\n## ${t.settings.memory.markdown.userContext}`);
+ parts.push(
+ [
+ `### ${t.settings.memory.markdown.work}`,
+ memory.user.workContext.summary || "-",
+ "",
+ memory.user.workContext.updatedAt &&
+ `> ${t.settings.memory.markdown.updatedAt}: \`${formatTimeAgo(memory.user.workContext.updatedAt)}\``,
+ ].join("\n"),
+ );
+ parts.push(
+ [
+ `### ${t.settings.memory.markdown.personal}`,
+ memory.user.personalContext.summary || "-",
+ "",
+ memory.user.personalContext.updatedAt &&
+ `> ${t.settings.memory.markdown.updatedAt}: \`${formatTimeAgo(memory.user.personalContext.updatedAt)}\``,
+ ].join("\n"),
+ );
+ parts.push(
+ [
+ `### ${t.settings.memory.markdown.topOfMind}`,
+ memory.user.topOfMind.summary || "-",
+ "",
+ memory.user.topOfMind.updatedAt &&
+ `> ${t.settings.memory.markdown.updatedAt}: \`${formatTimeAgo(memory.user.topOfMind.updatedAt)}\``,
+ ].join("\n"),
+ );
+
+ parts.push(`\n## ${t.settings.memory.markdown.historyBackground}`);
+ parts.push(
+ [
+ `### ${t.settings.memory.markdown.recentMonths}`,
+ memory.history.recentMonths.summary || "-",
+ "",
+ memory.history.recentMonths.updatedAt &&
+ `> ${t.settings.memory.markdown.updatedAt}: \`${formatTimeAgo(memory.history.recentMonths.updatedAt)}\``,
+ ].join("\n"),
+ );
+ parts.push(
+ [
+ `### ${t.settings.memory.markdown.earlierContext}`,
+ memory.history.earlierContext.summary || "-",
+ "",
+ memory.history.earlierContext.updatedAt &&
+ `> ${t.settings.memory.markdown.updatedAt}: \`${formatTimeAgo(memory.history.earlierContext.updatedAt)}\``,
+ ].join("\n"),
+ );
+ parts.push(
+ [
+ `### ${t.settings.memory.markdown.longTermBackground}`,
+ memory.history.longTermBackground.summary || "-",
+ "",
+ memory.history.longTermBackground.updatedAt &&
+ `> ${t.settings.memory.markdown.updatedAt}: \`${formatTimeAgo(memory.history.longTermBackground.updatedAt)}\``,
+ ].join("\n"),
+ );
+
+ parts.push(`\n## ${t.settings.memory.markdown.facts}`);
+ if (memory.facts.length === 0) {
+ parts.push(`_${t.settings.memory.markdown.empty}_`);
+ } else {
+ parts.push(
+ [
+ `| ${t.settings.memory.markdown.table.category} | ${t.settings.memory.markdown.table.confidence} | ${t.settings.memory.markdown.table.content} | ${t.settings.memory.markdown.table.source} | ${t.settings.memory.markdown.table.createdAt} |`,
+ "|---|---|---|---|---|",
+ ...memory.facts.map((f) => {
+ const { key, value } = confidenceToLevelKey(f.confidence);
+ const levelLabel =
+ t.settings.memory.markdown.table.confidenceLevel[key];
+ const confidenceText =
+ typeof value === "number" ? `${levelLabel}` : levelLabel;
+ return `| ${upperFirst(f.category)} | ${confidenceText} | ${f.content} | [${t.settings.memory.markdown.table.view}](${pathOfThread(f.source)}) | ${formatTimeAgo(f.createdAt)} |`;
+ }),
+ ].join("\n"),
+ );
+ }
+
+ const markdown = parts.join("\n\n");
+
+ // Ensure every level-2 heading (##) is preceded by a horizontal rule.
+ const lines = markdown.split("\n");
+ const out: string[] = [];
+ let i = 0;
+ for (const line of lines) {
+ i++;
+ if (i !== 1 && line.startsWith("## ")) {
+ if (out.length === 0 || out[out.length - 1] !== "---") {
+ out.push("---");
+ }
+ }
+ out.push(line);
+ }
+
+ return out.join("\n");
+}
+
+export function MemorySettingsPage() {
+ const { t } = useI18n();
+ const { memory, isLoading, error } = useMemory();
+ return (
+
+ {isLoading ? (
+ {t.common.loading}
+ ) : error ? (
+ Error: {error.message}
+ ) : !memory ? (
+
+ {t.settings.memory.empty}
+
+ ) : (
+
+
+ {memoryToMarkdown(memory, t)}
+
+
+ )}
+
+ );
+}
+
+function upperFirst(str: string) {
+ return str.charAt(0).toUpperCase() + str.slice(1);
+}
diff --git a/frontend/src/components/workspace/settings/settings-dialog.tsx b/frontend/src/components/workspace/settings/settings-dialog.tsx
index d3ede84..bd9f942 100644
--- a/frontend/src/components/workspace/settings/settings-dialog.tsx
+++ b/frontend/src/components/workspace/settings/settings-dialog.tsx
@@ -1,6 +1,12 @@
"use client";
-import { BellIcon, PaletteIcon, SparklesIcon, WrenchIcon } from "lucide-react";
+import {
+ BellIcon,
+ BrainIcon,
+ PaletteIcon,
+ SparklesIcon,
+ WrenchIcon,
+} from "lucide-react";
import { useMemo, useState } from "react";
import {
@@ -12,6 +18,7 @@ import {
import { ScrollArea } from "@/components/ui/scroll-area";
import { AcknowledgePage } from "@/components/workspace/settings/acknowledge-page";
import { AppearanceSettingsPage } from "@/components/workspace/settings/appearance-settings-page";
+import { MemorySettingsPage } from "@/components/workspace/settings/memory-settings-page";
import { NotificationSettingsPage } from "@/components/workspace/settings/notification-settings-page";
import { SkillSettingsPage } from "@/components/workspace/settings/skill-settings-page";
import { ToolSettingsPage } from "@/components/workspace/settings/tool-settings-page";
@@ -20,6 +27,7 @@ import { cn } from "@/lib/utils";
type SettingsSection =
| "appearance"
+ | "memory"
| "tools"
| "skills"
| "notification"
@@ -29,11 +37,8 @@ type SettingsDialogProps = React.ComponentProps & {
defaultSection?: SettingsSection;
};
-export function SettingsDialog({
- defaultSection = "appearance",
- onOpenChange,
- ...dialogProps
-}: SettingsDialogProps) {
+export function SettingsDialog(props: SettingsDialogProps) {
+ const { defaultSection = "appearance", ...dialogProps } = props;
const { t } = useI18n();
const [activeSection, setActiveSection] =
useState(defaultSection);
@@ -50,18 +55,27 @@ export function SettingsDialog({
label: t.settings.sections.notification,
icon: BellIcon,
},
+ {
+ id: "memory",
+ label: t.settings.sections.memory,
+ icon: BrainIcon,
+ },
{ id: "tools", label: t.settings.sections.tools, icon: WrenchIcon },
{ id: "skills", label: t.settings.sections.skills, icon: SparklesIcon },
],
[
t.settings.sections.appearance,
+ t.settings.sections.memory,
t.settings.sections.tools,
t.settings.sections.skills,
t.settings.sections.notification,
],
);
return (
-