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
11 changes: 11 additions & 0 deletions src/aish/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
import yaml
from pydantic import BaseModel, ConfigDict, Field, field_validator

from aish.memory.config import MemoryConfig


class ToolArgPreviewSettingsDict(TypedDict):
enabled: bool
Expand Down Expand Up @@ -234,6 +236,11 @@ class ConfigModel(BaseModel):
description="Whether the current configuration uses a free API key",
)

memory: MemoryConfig = Field(
default_factory=MemoryConfig,
description="Long-term memory configuration",
)

@field_validator("tool_arg_preview", mode="before")
@classmethod
def normalize_tool_arg_preview(cls, v: Any) -> dict[str, ToolArgPreviewSettings]:
Expand Down Expand Up @@ -379,6 +386,10 @@ def _load_config(self) -> ConfigModel:
if "prompt_theme" not in config_data:
config_data["prompt_theme"] = "compact"
need_save = True
# Add memory section if missing (new field migration)
if "memory" not in config_data:
config_data["memory"] = {"enabled": True}
need_save = True

if need_save:
self._save_config_data(config_data)
Expand Down
12 changes: 11 additions & 1 deletion src/aish/llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ def __init__(
env_manager=None,
interruption_manager=None,
history_manager=None,
memory_manager=None,
): # noqa: F821
self.config = config
self.model = config.model
Expand Down Expand Up @@ -389,6 +390,13 @@ def __init__(
self.system_diagnose_agent.name: self.system_diagnose_agent,
self.skill_tool.name: self.skill_tool,
}

# Register memory tool if memory manager is provided
if memory_manager is not None:
from aish.tools.memory_tool import MemoryTool

self.memory_tool = MemoryTool(memory_manager=memory_manager)
self.tools[self.memory_tool.name] = self.memory_tool
else:
# Use the provided tool set
self.tools = tools_override
Expand Down Expand Up @@ -1142,7 +1150,9 @@ def _get_messages_with_system(
messages = context_manager.as_messages()
if system_message:
if messages and messages[0]["role"] == "system":
messages[0]["content"] = system_message
# Merge: keep knowledge context, append system prompt
existing = messages[0]["content"]
messages[0]["content"] = f"{existing}\n\n{system_message}"
else:
messages.insert(0, {"role": "system", "content": system_message})
reminder = self._build_skills_reminder_message()
Expand Down
4 changes: 4 additions & 0 deletions src/aish/memory/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from aish.memory.manager import MemoryManager
from aish.memory.models import MemoryCategory, MemoryEntry

__all__ = ["MemoryManager", "MemoryCategory", "MemoryEntry"]
38 changes: 38 additions & 0 deletions src/aish/memory/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from __future__ import annotations

import os
from pathlib import Path
from pydantic import BaseModel, Field


def _default_data_dir() -> str:
"""Resolve default memory directory, following the same pattern as skills.

Uses AISH_CONFIG_DIR if set, otherwise ~/.config/aish/memory/
"""
config_dir = os.environ.get("AISH_CONFIG_DIR")
if config_dir:
return str(Path(config_dir) / "memory")
return str(Path.home() / ".config" / "aish" / "memory")


class MemoryConfig(BaseModel):
"""Configuration for long-term memory system."""

enabled: bool = Field(default=True, description="Enable long-term memory")
data_dir: str = Field(
default_factory=_default_data_dir,
description="Directory for memory files and database",
)
recall_limit: int = Field(
default=5, gt=0, description="Max memories returned per recall"
)
recall_token_budget: int = Field(
default=512, gt=0, description="Max tokens injected per recall"
)
daily_retention_days: int = Field(
default=30, gt=0, description="Days to keep daily notes before auto-cleanup"
)
auto_recall: bool = Field(
default=True, description="Automatically inject relevant memories before AI turns"
)
Loading
Loading