diff --git a/backend/app/config.py b/backend/app/config.py index c5f785e..ef8aea4 100644 --- a/backend/app/config.py +++ b/backend/app/config.py @@ -104,6 +104,15 @@ class Settings(BaseSettings): WEB_SEARCH_DEFAULT_LIMIT: int = 5 WEB_SEARCH_TIMEOUT_SECONDS: int = 10 + # === Hermes 风格升级开关 === + ENABLE_RETROSPECTIVE: bool = True + ENABLE_SESSION_RETROSPECTIVE_SEARCH: bool = True + ENABLE_RUNTIME_SKILL_SHORTLIST: bool = True + ENABLE_LEARNING_SIGNALS: bool = True + ENABLE_SKILL_PROMOTION: bool = True + ENABLE_LEARNED_SKILL_LOADING: bool = True + ENABLE_PARALLEL_TASK_GRAPH: bool = True + settings = Settings() settings.DATABASE_URL = settings.DATABASE_URL.replace("./data", _resolve_path("./data"), 1) diff --git a/backend/app/database.py b/backend/app/database.py index fbd1552..5b2b074 100644 --- a/backend/app/database.py +++ b/backend/app/database.py @@ -39,10 +39,12 @@ async def init_db(): await ensure_message_columns(conn) await ensure_conversation_columns(conn) await ensure_document_columns(conn) + await ensure_memory_columns(conn) await ensure_user_columns(conn) await ensure_forum_columns(conn) await ensure_agent_columns(conn) await ensure_skill_columns(conn) + await ensure_learning_artifact_tables(conn) async def ensure_log_columns(conn): @@ -115,6 +117,28 @@ async def ensure_document_columns(conn): await conn.execute(text(ddl)) +async def ensure_memory_columns(conn): + rows = await _get_table_info(conn, 'user_memories') + if not rows: + return + + columns = {row[1] for row in rows} + required_columns = { + 'frequency_count': "ALTER TABLE user_memories ADD COLUMN frequency_count INTEGER DEFAULT 0", + 'emotion_tags': "ALTER TABLE user_memories ADD COLUMN emotion_tags JSON", + 'importance_score': "ALTER TABLE user_memories ADD COLUMN importance_score FLOAT DEFAULT 0.5", + 'importance_level': "ALTER TABLE user_memories ADD COLUMN importance_level VARCHAR(20) DEFAULT 'medium'", + 'associated_topics': "ALTER TABLE user_memories ADD COLUMN associated_topics JSON", + 'decay_score': "ALTER TABLE user_memories ADD COLUMN decay_score FLOAT DEFAULT 1.0", + 'is_archived': "ALTER TABLE user_memories ADD COLUMN is_archived BOOLEAN DEFAULT 0", + 'last_accessed_at': "ALTER TABLE user_memories ADD COLUMN last_accessed_at DATETIME", + 'archive_at': "ALTER TABLE user_memories ADD COLUMN archive_at DATETIME", + } + for column, ddl in required_columns.items(): + if column not in columns: + await conn.execute(text(ddl)) + + async def ensure_user_columns(conn): rows = await _get_table_info(conn, 'users') if not rows: @@ -181,6 +205,14 @@ async def ensure_skill_columns(conn): 'output_format': "ALTER TABLE skills ADD COLUMN output_format TEXT", 'is_builtin': "ALTER TABLE skills ADD COLUMN is_builtin BOOLEAN DEFAULT 0 NOT NULL", 'team_id': "ALTER TABLE skills ADD COLUMN team_id VARCHAR(36)", + 'status': "ALTER TABLE skills ADD COLUMN status VARCHAR(20) DEFAULT 'active' NOT NULL", + 'scope': "ALTER TABLE skills ADD COLUMN scope JSON DEFAULT '[]' NOT NULL", + 'effectiveness': "ALTER TABLE skills ADD COLUMN effectiveness FLOAT DEFAULT 0.0 NOT NULL", + 'review_after': "ALTER TABLE skills ADD COLUMN review_after DATETIME", + 'candidate_count': "ALTER TABLE skills ADD COLUMN candidate_count INTEGER DEFAULT 0 NOT NULL", + 'candidate_source_hashes': "ALTER TABLE skills ADD COLUMN candidate_source_hashes JSON DEFAULT '[]' NOT NULL", + 'activation_count': "ALTER TABLE skills ADD COLUMN activation_count INTEGER DEFAULT 0 NOT NULL", + 'last_activated_at': "ALTER TABLE skills ADD COLUMN last_activated_at DATETIME", } for column, ddl in required_columns.items(): if column not in columns: @@ -205,6 +237,48 @@ async def ensure_skill_columns(conn): ) +async def ensure_learning_artifact_tables(conn): + await conn.execute( + text( + """ + CREATE TABLE IF NOT EXISTS learning_artifacts ( + id VARCHAR(36) PRIMARY KEY, + created_at DATETIME NOT NULL, + updated_at DATETIME NOT NULL, + user_id VARCHAR(36) NOT NULL, + conversation_id VARCHAR(36) NOT NULL, + retrospective_id VARCHAR(36), + artifact_type VARCHAR(32) NOT NULL, + artifact_key VARCHAR(128), + summary_text TEXT NOT NULL, + payload JSON NOT NULL, + recorded_at DATETIME NOT NULL + ) + """ + ) + ) + await conn.execute( + text( + "CREATE INDEX IF NOT EXISTS ix_learning_artifacts_user_id ON learning_artifacts (user_id)" + ) + ) + await conn.execute( + text( + "CREATE INDEX IF NOT EXISTS ix_learning_artifacts_conversation_id ON learning_artifacts (conversation_id)" + ) + ) + await conn.execute( + text( + "CREATE INDEX IF NOT EXISTS ix_learning_artifacts_retrospective_id ON learning_artifacts (retrospective_id)" + ) + ) + await conn.execute( + text( + "CREATE INDEX IF NOT EXISTS ix_learning_artifacts_artifact_type ON learning_artifacts (artifact_type)" + ) + ) + + async def _backfill_usernames(conn): result = await conn.execute(text("SELECT id, email, username FROM users ORDER BY created_at, id")) users = result.fetchall() diff --git a/backend/app/models/__init__.py b/backend/app/models/__init__.py index 7c4ea5a..979c8ce 100644 --- a/backend/app/models/__init__.py +++ b/backend/app/models/__init__.py @@ -7,6 +7,7 @@ from app.models.forum import ForumPost, ForumReply from app.models.agent import Agent, AgentMessage from app.models.conversation import Conversation, Message from app.models.knowledge_graph import KGNode, KGEdge +from app.models.learning import LearningArtifactRecord, SessionRetrospectiveRecord from app.models.memory import MemorySummary, UserMemory from app.models.brain import ( BrainEvent, @@ -20,6 +21,7 @@ from app.models.brain import ( from app.models.todo import DailyTodo, TodoSource from app.models.reminder import Reminder, ReminderStatus from app.models.goal import Goal, GoalStatus +from app.models.skill import Skill from app.models.log import Log, LogType, LogLevel __all__ = [ @@ -38,6 +40,8 @@ __all__ = [ "Message", "KGNode", "KGEdge", + "LearningArtifactRecord", + "SessionRetrospectiveRecord", "MemorySummary", "UserMemory", "BrainEvent", @@ -53,6 +57,7 @@ __all__ = [ "ReminderStatus", "Goal", "GoalStatus", + "Skill", "Log", "LogType", "LogLevel", diff --git a/backend/app/models/skill.py b/backend/app/models/skill.py index 2dbfbc0..a65ebf4 100644 --- a/backend/app/models/skill.py +++ b/backend/app/models/skill.py @@ -1,4 +1,4 @@ -from sqlalchemy import Column, String, Text, Boolean, JSON, ForeignKey +from sqlalchemy import Column, String, Text, Boolean, JSON, ForeignKey, Float, Integer, DateTime from sqlalchemy.orm import relationship from app.models.base import BaseModel @@ -17,6 +17,14 @@ class Skill(BaseModel): is_builtin = Column(Boolean, default=False, nullable=False) team_id = Column(String(36), ForeignKey("users.id"), nullable=True) is_active = Column(Boolean, default=True) + status = Column(String(20), default="active", nullable=False, index=True) # candidate/shadow/active/deprecated/retired + scope = Column(JSON, default=list, nullable=False) + effectiveness = Column(Float, default=0.0, nullable=False) + review_after = Column(DateTime, nullable=True) + candidate_count = Column(Integer, default=0, nullable=False) + candidate_source_hashes = Column(JSON, default=list, nullable=False) + activation_count = Column(Integer, default=0, nullable=False) + last_activated_at = Column(DateTime, nullable=True) owner_id = Column(String(36), ForeignKey("users.id"), nullable=False) owner = relationship("User", foreign_keys=[owner_id]) diff --git a/data/jarvis.db b/data/jarvis.db index 16ba667..0f526df 100644 Binary files a/data/jarvis.db and b/data/jarvis.db differ