feat: give the curator continuous context and durable idea history

This commit is contained in:
wuyang
2026-07-28 17:31:37 +08:00
parent 5c483536b4
commit af7bb68268
10 changed files with 967 additions and 114 deletions
+159 -6
View File
@@ -103,12 +103,18 @@ class Database:
id TEXT PRIMARY KEY,
idea_id TEXT NOT NULL REFERENCES ideas(id) ON DELETE CASCADE,
source_fragment_id TEXT REFERENCES fragments(id) ON DELETE SET NULL,
title TEXT,
summary TEXT,
maturity_ai REAL NOT NULL,
maturity_override REAL,
confidence REAL,
motion TEXT NOT NULL,
position TEXT NOT NULL,
tension TEXT NOT NULL,
trajectory TEXT NOT NULL,
possible_moves TEXT NOT NULL,
change_kind TEXT NOT NULL DEFAULT 'legacy_partial',
fragment_ids TEXT NOT NULL DEFAULT '[]',
created_at TEXT NOT NULL
);
@@ -185,6 +191,26 @@ class Database:
self._ensure_column(connection, "fragments", "user_id", "TEXT")
self._ensure_column(connection, "ideas", "user_id", "TEXT")
self._ensure_column(connection, "sessions", "user_id", "TEXT")
self._ensure_column(connection, "idea_snapshots", "title", "TEXT")
self._ensure_column(connection, "idea_snapshots", "summary", "TEXT")
self._ensure_column(
connection, "idea_snapshots", "maturity_override", "REAL"
)
self._ensure_column(
connection, "idea_snapshots", "confidence", "REAL"
)
self._ensure_column(
connection,
"idea_snapshots",
"change_kind",
"TEXT NOT NULL DEFAULT 'legacy_partial'",
)
self._ensure_column(
connection,
"idea_snapshots",
"fragment_ids",
"TEXT NOT NULL DEFAULT '[]'",
)
now = utc_now()
for seed in seeds:
@@ -457,8 +483,10 @@ class Database:
).fetchall()
snapshots = connection.execute(
"""
SELECT s.maturity_ai, s.motion, s.position, s.tension,
s.trajectory, s.possible_moves, s.created_at
SELECT s.title, s.summary, s.maturity_ai,
s.maturity_override, s.confidence, s.motion,
s.position, s.tension, s.trajectory, s.possible_moves,
s.change_kind, s.fragment_ids, s.created_at
FROM idea_snapshots s
JOIN ideas i ON i.id = s.idea_id
WHERE s.idea_id = ? AND i.user_id = ?
@@ -471,6 +499,7 @@ class Database:
{
**dict(row),
"possible_moves": _loads(row["possible_moves"], []),
"fragment_ids": _loads(row["fragment_ids"], []),
}
for row in snapshots
]
@@ -588,23 +617,55 @@ class Database:
now,
),
)
version_state = connection.execute(
"""
SELECT maturity_override FROM ideas
WHERE id = ? AND user_id = ?
""",
(idea_id, user_id),
).fetchone()
fragment_ids = [
row["fragment_id"]
for row in connection.execute(
"""
SELECT l.fragment_id
FROM idea_fragments l
JOIN fragments f ON f.id = l.fragment_id
WHERE l.idea_id = ? AND f.user_id = ?
ORDER BY f.created_at ASC, f.rowid ASC
""",
(idea_id, user_id),
).fetchall()
]
connection.execute(
"""
INSERT INTO idea_snapshots (
id, idea_id, source_fragment_id, maturity_ai, motion,
position, tension, trajectory, possible_moves, created_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
id, idea_id, source_fragment_id, title, summary,
maturity_ai, maturity_override, confidence, motion,
position, tension, trajectory, possible_moves,
change_kind, fragment_ids, created_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
str(uuid.uuid4()),
idea_id,
fragment_id,
assessment["title"],
assessment["summary"],
assessment["maturity"],
(
version_state["maturity_override"]
if version_state
else None
),
assessment["confidence"],
assessment["motion"],
assessment["position"],
assessment["tension"],
assessment["trajectory"],
_json(assessment["possible_moves"]),
"analysis",
_json(fragment_ids),
now,
),
)
@@ -622,16 +683,66 @@ class Database:
def update_idea_override(
self, user_id: str, idea_id: str, maturity_override: float | None
) -> dict[str, Any] | None:
now = utc_now()
with self.connect() as connection:
result = connection.execute(
"""
UPDATE ideas SET maturity_override = ?, updated_at = ?
WHERE id = ? AND user_id = ?
""",
(maturity_override, utc_now(), idea_id, user_id),
(maturity_override, now, idea_id, user_id),
)
if result.rowcount == 0:
return None
idea = connection.execute(
"""
SELECT title, summary, maturity_ai, maturity_override,
confidence, motion, position, tension, trajectory,
possible_moves
FROM ideas WHERE id = ? AND user_id = ?
""",
(idea_id, user_id),
).fetchone()
fragment_ids = [
row["fragment_id"]
for row in connection.execute(
"""
SELECT l.fragment_id
FROM idea_fragments l
JOIN fragments f ON f.id = l.fragment_id
WHERE l.idea_id = ? AND f.user_id = ?
ORDER BY f.created_at ASC, f.rowid ASC
""",
(idea_id, user_id),
).fetchall()
]
connection.execute(
"""
INSERT INTO idea_snapshots (
id, idea_id, source_fragment_id, title, summary,
maturity_ai, maturity_override, confidence, motion,
position, tension, trajectory, possible_moves,
change_kind, fragment_ids, created_at
) VALUES (?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
str(uuid.uuid4()),
idea_id,
idea["title"],
idea["summary"],
idea["maturity_ai"],
idea["maturity_override"],
idea["confidence"],
idea["motion"],
idea["position"],
idea["tension"],
idea["trajectory"],
idea["possible_moves"],
"manual_calibration",
_json(fragment_ids),
now,
),
)
return self.get_idea(user_id, idea_id)
@staticmethod
@@ -928,6 +1039,48 @@ class Database:
).hexdigest()[:16]
return run
def prepare_agent_retry(self, run_id: str) -> dict[str, str] | None:
with self.connect() as connection:
row = connection.execute(
"""
SELECT r.user_id, r.fragment_id, r.status AS run_status,
f.analysis_status
FROM agent_runs r
JOIN fragments f ON f.id = r.fragment_id
WHERE r.id = ? AND f.user_id = r.user_id
""",
(run_id,),
).fetchone()
if (
not row
or row["run_status"] != "error"
or row["analysis_status"] != "error"
):
return None
newer = connection.execute(
"""
SELECT 1 FROM agent_runs
WHERE fragment_id = ? AND user_id = ?
AND id != ? AND status IN ('running', 'success')
LIMIT 1
""",
(row["fragment_id"], row["user_id"], run_id),
).fetchone()
if newer:
return None
connection.execute(
"""
UPDATE fragments
SET analysis_status = 'pending', analysis_error = NULL
WHERE id = ? AND user_id = ?
""",
(row["fragment_id"], row["user_id"]),
)
return {
"user_id": row["user_id"],
"fragment_id": row["fragment_id"],
}
def list_agent_events(self, run_id: str) -> list[dict[str, Any]]:
with self.connect() as connection:
rows = connection.execute(