feat: give the curator continuous context and durable idea history
This commit is contained in:
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user