auto-sync: 2026-05-17 00:34:18

This commit is contained in:
cfdaily
2026-05-17 00:34:18 +08:00
parent 766ec11081
commit 662945f579
+292
View File
@@ -0,0 +1,292 @@
"""黑板数据库连接管理(per-project SQLite"""
from __future__ import annotations
import sqlite3
from pathlib import Path
from typing import Optional
def init_db(db_path: Path) -> None:
"""初始化数据库,创建全部表"""
db_path.parent.mkdir(parents=True, exist_ok=True)
conn = _connect(db_path)
try:
conn.executescript(SCHEMA_SQL)
conn.commit()
finally:
conn.close()
def get_connection(db_path: Path) -> sqlite3.Connection:
"""获取数据库连接(WAL + busy_timeout + foreign_keys"""
return _connect(db_path)
def _connect(db_path: Path) -> sqlite3.Connection:
conn = sqlite3.connect(str(db_path))
conn.row_factory = sqlite3.Row
conn.execute("PRAGMA journal_mode=WAL")
conn.execute("PRAGMA foreign_keys=ON")
conn.execute("PRAGMA busy_timeout=5000")
return conn
# ---------------------------------------------------------------------------
# 状态机
# ---------------------------------------------------------------------------
VALID_STATUSES = frozenset({
"pending", "claimed", "working", "review",
"done", "failed", "blocked", "cancelled",
})
TERMINAL_STATUSES = frozenset({"done", "cancelled"})
VALID_TRANSITIONS = {
"pending": {"claimed", "cancelled"},
"claimed": {"working", "pending", "cancelled"},
"working": {"review", "blocked", "failed", "cancelled"},
"review": {"done", "pending", "failed", "cancelled"},
"blocked": {"pending", "cancelled"},
"done": set(),
"failed": {"pending"},
"cancelled": set(),
}
COMMENT_TYPES = frozenset({
"general", "handoff", "observation", "rebuttal",
"rebuttal_response", "debate_argument", "debate_rebuttal", "debate_judgment",
})
SEVERITY_LEVELS = frozenset({"blocking", "warning", "info", "audit"})
EVENT_TYPES = frozenset({
"task_created", "task_claimed", "task_started", "task_completed",
"task_failed", "task_blocked", "task_unblocked", "task_reviewed",
"task_cancelled", "task_retried",
"comment_added", "output_written", "observation_added", "decision_recorded",
"agent_spawned", "agent_completed", "agent_zombie_detected",
"session_spawned", "session_archived", "session_cleanup",
"daemon_tick", "daemon_manual_tick",
})
OUTPUT_TYPES = frozenset({"code", "document", "data", "config", "other"})
REVIEW_TYPES = frozenset({"plan_review", "output_review", "guardrail", "final_review"})
VERDICT_TYPES = frozenset({"approved", "rejected", "needs_revision"})
EXPERIENCE_SOURCES = frozenset({
"task_completion", "error_correction", "review_finding", "manual",
})
EXPERIENCE_CATEGORIES = frozenset({
"pitfall", "best_practice", "pattern", "anti_pattern",
})
EXPERIENCE_STATUSES = frozenset({"draft", "active", "deprecated"})
ATTEMPT_OUTCOMES = frozenset({
"completed", "blocked", "crashed", "timed_out", "spawn_failed", "reclaimed",
})
# ---------------------------------------------------------------------------
# Schema SQL
# ---------------------------------------------------------------------------
SCHEMA_SQL = """
-- ===== 任务表 =====
CREATE TABLE IF NOT EXISTS tasks (
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
description TEXT,
status TEXT NOT NULL DEFAULT 'pending',
CHECK (status IN ('pending','claimed','working','review','done','failed','blocked','cancelled')),
assignee TEXT,
assigned_by TEXT,
depends_on TEXT,
parent_task TEXT,
priority INTEGER NOT NULL DEFAULT 5,
task_type TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
claimed_at TEXT,
started_at TEXT,
completed_at TEXT,
deadline TEXT,
retry_count INTEGER NOT NULL DEFAULT 0,
max_retries INTEGER NOT NULL DEFAULT 2,
must_haves TEXT,
risk_level TEXT DEFAULT 'standard',
estimated_duration_minutes INTEGER,
escalated INTEGER DEFAULT 0
);
CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
CREATE INDEX IF NOT EXISTS idx_tasks_assignee ON tasks(assignee);
CREATE INDEX IF NOT EXISTS idx_tasks_parent ON tasks(parent_task);
-- ===== 评论线程表 =====
CREATE TABLE IF NOT EXISTS comments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
task_id TEXT NOT NULL,
author TEXT NOT NULL,
comment_type TEXT NOT NULL DEFAULT 'general',
body TEXT NOT NULL,
mentions TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (task_id) REFERENCES tasks(id),
CHECK (comment_type IN ('general','handoff','observation','rebuttal','rebuttal_response','debate_argument','debate_rebuttal','debate_judgment'))
);
CREATE INDEX IF NOT EXISTS idx_comments_task ON comments(task_id);
CREATE INDEX IF NOT EXISTS idx_comments_type ON comments(task_id, comment_type);
CREATE INDEX IF NOT EXISTS idx_comments_author ON comments(author);
-- ===== 产出表 =====
CREATE TABLE IF NOT EXISTS outputs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
task_id TEXT NOT NULL,
agent TEXT NOT NULL,
output_type TEXT NOT NULL,
title TEXT NOT NULL,
content_path TEXT,
summary TEXT,
metadata TEXT,
attempt_number INTEGER DEFAULT 1,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (task_id) REFERENCES tasks(id),
CHECK (output_type IN ('code','document','data','config','other'))
);
CREATE INDEX IF NOT EXISTS idx_outputs_task ON outputs(task_id);
-- ===== 决策记录表 =====
CREATE TABLE IF NOT EXISTS decisions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
task_id TEXT NOT NULL,
decider TEXT NOT NULL,
decision TEXT NOT NULL,
rationale TEXT NOT NULL,
alternatives TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (task_id) REFERENCES tasks(id)
);
CREATE INDEX IF NOT EXISTS idx_decisions_task ON decisions(task_id);
-- ===== 观察表 =====
CREATE TABLE IF NOT EXISTS observations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
task_id TEXT NOT NULL,
observer TEXT NOT NULL,
severity TEXT NOT NULL DEFAULT 'info',
CHECK (severity IN ('blocking','warning','info','audit')),
body TEXT NOT NULL,
resolved_by TEXT,
resolved_at TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (task_id) REFERENCES tasks(id)
);
-- ===== 事件日志 =====
CREATE TABLE IF NOT EXISTS events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
task_id TEXT,
agent TEXT,
event_type TEXT NOT NULL,
detail TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_events_task ON events(task_id);
CREATE INDEX IF NOT EXISTS idx_events_time ON events(created_at);
-- ===== Agent 注册表 =====
CREATE TABLE IF NOT EXISTS agents (
agent_id TEXT PRIMARY KEY,
role TEXT,
current_status TEXT DEFAULT 'idle',
current_task TEXT,
last_active TEXT,
capabilities TEXT
);
-- ===== 任务尝试记录 =====
CREATE TABLE IF NOT EXISTS task_attempts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
task_id TEXT NOT NULL,
attempt_number INTEGER NOT NULL,
agent TEXT NOT NULL,
outcome TEXT NOT NULL,
CHECK (outcome IN ('completed','blocked','crashed','timed_out','spawn_failed','reclaimed')),
exit_code INTEGER,
log_path TEXT,
summary TEXT,
metadata TEXT,
started_at TEXT NOT NULL DEFAULT (datetime('now')),
completed_at TEXT,
FOREIGN KEY (task_id) REFERENCES tasks(id)
);
CREATE INDEX IF NOT EXISTS idx_attempts_task ON task_attempts(task_id);
-- ===== 评审表 =====
CREATE TABLE IF NOT EXISTS reviews (
id TEXT PRIMARY KEY,
task_id TEXT NOT NULL,
output_id TEXT,
reviewer TEXT NOT NULL,
review_type TEXT NOT NULL,
CHECK (review_type IN ('plan_review','output_review','guardrail','final_review')),
verdict TEXT NOT NULL,
CHECK (verdict IN ('approved','rejected','needs_revision')),
confidence REAL,
round INTEGER NOT NULL DEFAULT 1,
max_rounds INTEGER NOT NULL DEFAULT 3,
consensus_reached INTEGER DEFAULT 0,
summary TEXT NOT NULL,
detail_path TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (task_id) REFERENCES tasks(id)
);
CREATE INDEX IF NOT EXISTS idx_reviews_task ON reviews(task_id);
CREATE INDEX IF NOT EXISTS idx_reviews_output ON reviews(output_id);
-- ===== 经验表 =====
CREATE TABLE IF NOT EXISTS experiences (
experience_id TEXT PRIMARY KEY,
source TEXT NOT NULL,
CHECK (source IN ('task_completion','error_correction','review_finding','manual')),
task_id TEXT,
summary TEXT NOT NULL,
category TEXT NOT NULL,
CHECK (category IN ('pitfall','best_practice','pattern','anti_pattern')),
confidence REAL DEFAULT 0.8,
status TEXT DEFAULT 'active',
CHECK (status IN ('draft','active','deprecated')),
skill_id TEXT,
usage_count INTEGER DEFAULT 0,
last_used_at TEXT,
created_at TEXT NOT NULL,
created_by TEXT NOT NULL,
updated_at TEXT,
deprecated_reason TEXT
);
-- ===== 经验标签关联表 =====
CREATE TABLE IF NOT EXISTS experience_tags (
experience_id TEXT NOT NULL REFERENCES experiences(experience_id),
tag TEXT NOT NULL,
PRIMARY KEY (experience_id, tag)
);
CREATE INDEX IF NOT EXISTS idx_exptags_tag ON experience_tags(tag);
"""