refactor(monorepo): extract coord-core

This commit is contained in:
2026-03-20 13:01:16 +08:00
parent 3cf7a15626
commit 1938eb8f07
65 changed files with 6586 additions and 85 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ import (
"time"
"ai-workflow-skill/internal/app"
"ai-workflow-skill/internal/db"
"ai-workflow-skill/packages/coord-core/db"
"ai-workflow-skill/internal/httpapi"
)
+8 -3
View File
@@ -38,6 +38,7 @@ As of now:
- the first real Phase 2 read-only operator UI is now implemented in `apps/web`, including routed runs list, run detail, blocked queue, and thread timeline views backed by the existing `orchd` HTTP API, plus Tailwind v4 consumer wiring so the source-owned Cadence UI components render correctly in the app
- a repository-level skill workspace monorepo migration plan now exists under `docs/skill-workspace-monorepo.md`, defining the target split between runtime packages under `packages/`, agent-facing skill bundles under `skills/`, support apps under `apps/`, and package-based skill packaging flows
- the first migration phase for the skill workspace monorepo is now complete: root `go.work` exists, `pnpm-workspace.yaml` now discovers `packages/*`, empty runtime module roots now exist under `packages/`, and a declarative `scripts/skill-bundles.json` plus `scripts/package_skill_runtimes.sh` scaffold now define package-oriented skill bundle metadata from the repo root
- `packages/coord-core` now exists as the first real extracted runtime package, containing shared coordination DB/schema, protocol, and store code, and the active coordination runtimes now import `coord-core` instead of root `internal/db`, `internal/store`, and `internal/protocol`
- a repo-local `scripts/package_skill_clis.sh` packaging flow now builds bundled skill CLI assets for `inbox`, `orch`, and `council-review`
- `orch` now implements `run init/show`, `task add`, `dep add`, `ready`, `dispatch`, `reconcile`, `wait`, `blocked`, `answer`, `retry`, `reassign`, `cancel`, `cleanup`, and `status`
- `orch` can create runs, gate tasks through dependencies, dispatch work through `inbox`, reconcile worker thread state back into task state, answer blocked tasks, retry or reassign work, cancel tasks or runs, clean attempt worktrees, and create per-attempt Git worktrees during strict dispatch
@@ -495,10 +496,14 @@ Completed so far:
- initial module roots now exist for `packages/coord-core`, `packages/inbox-runtime`, `packages/orch-runtime`, `packages/orchd-runtime`, and `packages/repo-memory-runtime`
- `scripts/skill-bundles.json` now records the first package-oriented skill bundle metadata, including the future `repo-memory` runtime mapping
- `scripts/package_skill_runtimes.sh` now provides a declarative bundle plan/validate scaffold that targets package paths rather than hardcoded root runtime paths
- `packages/coord-core/db` now owns the shared SQLite open, pragmas, migrations, and coordination schema files
- `packages/coord-core/protocol` now owns the shared JSON and CLI error helpers used across the coordination stack
- `packages/coord-core/store` now owns the shared inbox, orch, and council store logic plus its coordination-domain tests
- root coordination runtimes under `cmd/`, `internal/cli/`, `internal/app/`, `internal/httpapi/`, and `internal/query/` now import `coord-core` instead of depending on root `internal/db`, `internal/store`, or root `internal/protocol`
- `go test ./...` still passes for the root module, and `go test ./...` passes inside `packages/coord-core`
Remaining:
- extract shared coordination code into `packages/coord-core`
- extract `inbox`, `orch`, and `orchd` into package-owned runtimes
- import `repo-memory` as its own runtime package and add the corresponding skill bundle
- graduate the bundle scaffold into the primary packaging flow once package-owned runtime entrypoints exist
@@ -509,12 +514,12 @@ If a new agent is taking over now, the next concrete step should be:
1. treat `Milestone 9: Web Product Phase 2 Read-Only Operator UI` as complete for the initial operator surface and do not expand web feature scope further until the workspace split is decided package-by-package
2. treat the Phase 1 workspace bootstrap for `Milestone 10` as complete and keep the new `go.work`, `packages/`, and declarative bundle metadata as the baseline for all further migration steps
3. extract the shared coordination kernel into `packages/coord-core` before moving `inbox`, `orch`, or `orchd` into package-owned runtimes
3. treat the shared coordination kernel extraction into `packages/coord-core` as complete and move `inbox` plus `orch` into package-owned runtimes next
4. keep the authored skill forward-test plans under `docs/tests/*-skill/` synchronized as runtime ownership moves from root paths to package paths
5. keep the legacy hardcoded packaging flow working temporarily, but evolve the new declarative bundle scaffold into the primary packaging path before adding `repo-memory`
6. import `repo-memory` only after the package-based runtime and skill packaging pattern exists
The inbox implementation and its human-readable test-plan set are already in place, `orch` supports the main scheduler loop plus the complete council start/wait/tally/report workflow, the web product now has its first real operator-facing read surfaces, and the repository has finished the first workspace-bootstrap phase of the skill monorepo migration, so the next step should be package extraction rather than continuing to accrete new root-owned runtimes.
The inbox implementation and its human-readable test-plan set are already in place, `orch` supports the main scheduler loop plus the complete council start/wait/tally/report workflow, the web product now has its first real operator-facing read surfaces, and the repository has completed both the workspace bootstrap and the shared coordination-kernel extraction phases of the skill monorepo migration, so the next step should be runtime extraction rather than continuing to accrete new root-owned runtimes.
## Recommended Driver Choices
@@ -26,7 +26,7 @@
- [x] create or adopt an active execution roadmap for the migration workstream
- [x] Phase 1: bootstrap `go.work`, expanded workspace manifests, package roots, and declarative skill bundle metadata
- [ ] Phase 2: extract shared coordination code into `packages/coord-core`
- [x] Phase 2: extract shared coordination code into `packages/coord-core`
- [ ] Phase 3: extract `inbox-runtime` and `orch-runtime`
- [ ] Phase 4: extract `orchd-runtime`
- [ ] Phase 5: import `repo-memory-runtime` and add `skills/repo-memory`
@@ -54,4 +54,4 @@
## Next Step
- start Phase 2 by extracting the shared coordination kernel into `packages/coord-core`, using the new workspace and bundle metadata scaffold as the stable base for subsequent package moves
- start Phase 3 by moving `inbox` and `orch` into package-owned runtimes on top of the now-shared `packages/coord-core` kernel
+1 -1
View File
@@ -5,7 +5,7 @@ import (
"database/sql"
"ai-workflow-skill/internal/query"
"ai-workflow-skill/internal/store"
"ai-workflow-skill/packages/coord-core/store"
)
type WebService struct {
+2 -2
View File
@@ -3,8 +3,8 @@ package inbox
import (
"strings"
"ai-workflow-skill/internal/protocol"
"ai-workflow-skill/internal/store"
"ai-workflow-skill/packages/coord-core/protocol"
"ai-workflow-skill/packages/coord-core/store"
"github.com/spf13/cobra"
)
+1 -1
View File
@@ -3,7 +3,7 @@ package inbox
import (
"os"
"ai-workflow-skill/internal/protocol"
"ai-workflow-skill/packages/coord-core/protocol"
)
func resolveBodyValue(body, bodyFile string) (string, error) {
+2 -2
View File
@@ -3,8 +3,8 @@ package inbox
import (
"fmt"
"ai-workflow-skill/internal/protocol"
"ai-workflow-skill/internal/store"
"ai-workflow-skill/packages/coord-core/protocol"
"ai-workflow-skill/packages/coord-core/store"
"github.com/spf13/cobra"
)
+2 -2
View File
@@ -4,8 +4,8 @@ import (
"errors"
"fmt"
"ai-workflow-skill/internal/protocol"
"ai-workflow-skill/internal/store"
"ai-workflow-skill/packages/coord-core/protocol"
"ai-workflow-skill/packages/coord-core/store"
"github.com/spf13/cobra"
)
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"context"
"database/sql"
"ai-workflow-skill/internal/db"
"ai-workflow-skill/packages/coord-core/db"
)
func openInboxDB(ctx context.Context, dbPath string) (*sql.DB, error) {
+2 -2
View File
@@ -3,8 +3,8 @@ package inbox
import (
"fmt"
"ai-workflow-skill/internal/protocol"
"ai-workflow-skill/internal/store"
"ai-workflow-skill/packages/coord-core/protocol"
"ai-workflow-skill/packages/coord-core/store"
"github.com/spf13/cobra"
)
+2 -2
View File
@@ -6,8 +6,8 @@ import (
"io"
"strings"
"ai-workflow-skill/internal/protocol"
"ai-workflow-skill/internal/store"
"ai-workflow-skill/packages/coord-core/protocol"
"ai-workflow-skill/packages/coord-core/store"
)
func Execute(args []string, stdout, stderr io.Writer) int {
+2 -2
View File
@@ -4,8 +4,8 @@ import (
"fmt"
"strings"
"ai-workflow-skill/internal/protocol"
"ai-workflow-skill/internal/store"
"ai-workflow-skill/packages/coord-core/protocol"
"ai-workflow-skill/packages/coord-core/store"
"github.com/spf13/cobra"
)
+1 -1
View File
@@ -3,7 +3,7 @@ package inbox
import (
"fmt"
"ai-workflow-skill/internal/protocol"
"ai-workflow-skill/packages/coord-core/protocol"
"github.com/spf13/cobra"
)
+2 -2
View File
@@ -3,8 +3,8 @@ package inbox
import (
"fmt"
"ai-workflow-skill/internal/protocol"
"ai-workflow-skill/internal/store"
"ai-workflow-skill/packages/coord-core/protocol"
"ai-workflow-skill/packages/coord-core/store"
"github.com/spf13/cobra"
)
+2 -2
View File
@@ -3,8 +3,8 @@ package inbox
import (
"fmt"
"ai-workflow-skill/internal/protocol"
"ai-workflow-skill/internal/store"
"ai-workflow-skill/packages/coord-core/protocol"
"ai-workflow-skill/packages/coord-core/store"
"github.com/spf13/cobra"
)
+2 -2
View File
@@ -3,8 +3,8 @@ package inbox
import (
"fmt"
"ai-workflow-skill/internal/protocol"
"ai-workflow-skill/internal/store"
"ai-workflow-skill/packages/coord-core/protocol"
"ai-workflow-skill/packages/coord-core/store"
"github.com/spf13/cobra"
)
+2 -2
View File
@@ -3,8 +3,8 @@ package inbox
import (
"fmt"
"ai-workflow-skill/internal/protocol"
"ai-workflow-skill/internal/store"
"ai-workflow-skill/packages/coord-core/protocol"
"ai-workflow-skill/packages/coord-core/store"
"github.com/spf13/cobra"
)
+2 -2
View File
@@ -3,8 +3,8 @@ package inbox
import (
"fmt"
"ai-workflow-skill/internal/protocol"
"ai-workflow-skill/internal/store"
"ai-workflow-skill/packages/coord-core/protocol"
"ai-workflow-skill/packages/coord-core/store"
"github.com/spf13/cobra"
)
+2 -2
View File
@@ -3,8 +3,8 @@ package inbox
import (
"fmt"
"ai-workflow-skill/internal/protocol"
"ai-workflow-skill/internal/store"
"ai-workflow-skill/packages/coord-core/protocol"
"ai-workflow-skill/packages/coord-core/store"
"github.com/spf13/cobra"
)
+2 -2
View File
@@ -4,8 +4,8 @@ import (
"fmt"
"time"
"ai-workflow-skill/internal/protocol"
"ai-workflow-skill/internal/store"
"ai-workflow-skill/packages/coord-core/protocol"
"ai-workflow-skill/packages/coord-core/store"
"github.com/spf13/cobra"
)
+2 -2
View File
@@ -4,8 +4,8 @@ import (
"fmt"
"time"
"ai-workflow-skill/internal/protocol"
"ai-workflow-skill/internal/store"
"ai-workflow-skill/packages/coord-core/protocol"
"ai-workflow-skill/packages/coord-core/store"
"github.com/spf13/cobra"
)
+2 -2
View File
@@ -3,8 +3,8 @@ package orch
import (
"fmt"
"ai-workflow-skill/internal/protocol"
"ai-workflow-skill/internal/store"
"ai-workflow-skill/packages/coord-core/protocol"
"ai-workflow-skill/packages/coord-core/store"
"github.com/spf13/cobra"
)
+2 -2
View File
@@ -3,8 +3,8 @@ package orch
import (
"fmt"
"ai-workflow-skill/internal/protocol"
"ai-workflow-skill/internal/store"
"ai-workflow-skill/packages/coord-core/protocol"
"ai-workflow-skill/packages/coord-core/store"
"github.com/spf13/cobra"
)
+1 -1
View File
@@ -3,7 +3,7 @@ package orch
import (
"os"
"ai-workflow-skill/internal/protocol"
"ai-workflow-skill/packages/coord-core/protocol"
)
func resolveBodyValue(body, bodyFile string) (string, error) {
+2 -2
View File
@@ -3,8 +3,8 @@ package orch
import (
"fmt"
"ai-workflow-skill/internal/protocol"
"ai-workflow-skill/internal/store"
"ai-workflow-skill/packages/coord-core/protocol"
"ai-workflow-skill/packages/coord-core/store"
"github.com/spf13/cobra"
)
+2 -2
View File
@@ -3,8 +3,8 @@ package orch
import (
"fmt"
"ai-workflow-skill/internal/protocol"
"ai-workflow-skill/internal/store"
"ai-workflow-skill/packages/coord-core/protocol"
"ai-workflow-skill/packages/coord-core/store"
"github.com/spf13/cobra"
)
+2 -2
View File
@@ -6,8 +6,8 @@ import (
"path/filepath"
"strings"
"ai-workflow-skill/internal/protocol"
"ai-workflow-skill/internal/store"
"ai-workflow-skill/packages/coord-core/protocol"
"ai-workflow-skill/packages/coord-core/store"
"github.com/spf13/cobra"
)
+2 -2
View File
@@ -3,8 +3,8 @@ package orch
import (
"fmt"
"ai-workflow-skill/internal/protocol"
"ai-workflow-skill/internal/store"
"ai-workflow-skill/packages/coord-core/protocol"
"ai-workflow-skill/packages/coord-core/store"
"github.com/spf13/cobra"
)
+2 -2
View File
@@ -3,8 +3,8 @@ package orch
import (
"fmt"
"ai-workflow-skill/internal/protocol"
"ai-workflow-skill/internal/store"
"ai-workflow-skill/packages/coord-core/protocol"
"ai-workflow-skill/packages/coord-core/store"
"github.com/spf13/cobra"
)
+2 -2
View File
@@ -4,8 +4,8 @@ import (
"fmt"
"time"
"ai-workflow-skill/internal/protocol"
"ai-workflow-skill/internal/store"
"ai-workflow-skill/packages/coord-core/protocol"
"ai-workflow-skill/packages/coord-core/store"
"github.com/spf13/cobra"
)
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"context"
"database/sql"
"ai-workflow-skill/internal/db"
"ai-workflow-skill/packages/coord-core/db"
)
func openOrchDB(ctx context.Context, dbPath string) (*sql.DB, error) {
+2 -2
View File
@@ -3,8 +3,8 @@ package orch
import (
"fmt"
"ai-workflow-skill/internal/protocol"
"ai-workflow-skill/internal/store"
"ai-workflow-skill/packages/coord-core/protocol"
"ai-workflow-skill/packages/coord-core/store"
"github.com/spf13/cobra"
)
+2 -2
View File
@@ -3,8 +3,8 @@ package orch
import (
"fmt"
"ai-workflow-skill/internal/protocol"
"ai-workflow-skill/internal/store"
"ai-workflow-skill/packages/coord-core/protocol"
"ai-workflow-skill/packages/coord-core/store"
"github.com/spf13/cobra"
)
+2 -2
View File
@@ -6,8 +6,8 @@ import (
"io"
"strings"
"ai-workflow-skill/internal/protocol"
"ai-workflow-skill/internal/store"
"ai-workflow-skill/packages/coord-core/protocol"
"ai-workflow-skill/packages/coord-core/store"
)
func Execute(args []string, stdout, stderr io.Writer) int {
+2 -2
View File
@@ -3,8 +3,8 @@ package orch
import (
"fmt"
"ai-workflow-skill/internal/protocol"
"ai-workflow-skill/internal/store"
"ai-workflow-skill/packages/coord-core/protocol"
"ai-workflow-skill/packages/coord-core/store"
"github.com/spf13/cobra"
)
+2 -2
View File
@@ -3,8 +3,8 @@ package orch
import (
"fmt"
"ai-workflow-skill/internal/protocol"
"ai-workflow-skill/internal/store"
"ai-workflow-skill/packages/coord-core/protocol"
"ai-workflow-skill/packages/coord-core/store"
"github.com/spf13/cobra"
)
+2 -2
View File
@@ -3,8 +3,8 @@ package orch
import (
"fmt"
"ai-workflow-skill/internal/protocol"
"ai-workflow-skill/internal/store"
"ai-workflow-skill/packages/coord-core/protocol"
"ai-workflow-skill/packages/coord-core/store"
"github.com/spf13/cobra"
)
+2 -2
View File
@@ -3,8 +3,8 @@ package orch
import (
"fmt"
"ai-workflow-skill/internal/protocol"
"ai-workflow-skill/internal/store"
"ai-workflow-skill/packages/coord-core/protocol"
"ai-workflow-skill/packages/coord-core/store"
"github.com/spf13/cobra"
)
+2 -2
View File
@@ -3,8 +3,8 @@ package orch
import (
"fmt"
"ai-workflow-skill/internal/protocol"
"ai-workflow-skill/internal/store"
"ai-workflow-skill/packages/coord-core/protocol"
"ai-workflow-skill/packages/coord-core/store"
"github.com/spf13/cobra"
)
+2 -2
View File
@@ -3,8 +3,8 @@ package orch
import (
"fmt"
"ai-workflow-skill/internal/protocol"
"ai-workflow-skill/internal/store"
"ai-workflow-skill/packages/coord-core/protocol"
"ai-workflow-skill/packages/coord-core/store"
"github.com/spf13/cobra"
)
+2 -2
View File
@@ -3,8 +3,8 @@ package orch
import (
"fmt"
"ai-workflow-skill/internal/protocol"
"ai-workflow-skill/internal/store"
"ai-workflow-skill/packages/coord-core/protocol"
"ai-workflow-skill/packages/coord-core/store"
"github.com/spf13/cobra"
)
+2 -2
View File
@@ -5,8 +5,8 @@ import (
"strings"
"time"
"ai-workflow-skill/internal/protocol"
"ai-workflow-skill/internal/store"
"ai-workflow-skill/packages/coord-core/protocol"
"ai-workflow-skill/packages/coord-core/store"
"github.com/spf13/cobra"
)
+2 -2
View File
@@ -9,8 +9,8 @@ import (
"path/filepath"
"strings"
"ai-workflow-skill/internal/protocol"
"ai-workflow-skill/internal/store"
"ai-workflow-skill/packages/coord-core/protocol"
"ai-workflow-skill/packages/coord-core/store"
"github.com/spf13/cobra"
)
+1 -1
View File
@@ -6,7 +6,7 @@ import (
"fmt"
"net/http"
"ai-workflow-skill/internal/store"
"ai-workflow-skill/packages/coord-core/store"
)
type errorEnvelope struct {
+1 -1
View File
@@ -9,7 +9,7 @@ import (
"github.com/go-chi/chi/v5/middleware"
"ai-workflow-skill/internal/query"
"ai-workflow-skill/internal/store"
"ai-workflow-skill/packages/coord-core/store"
)
type readService interface {
+2 -2
View File
@@ -10,8 +10,8 @@ import (
"time"
"ai-workflow-skill/internal/app"
dbpkg "ai-workflow-skill/internal/db"
"ai-workflow-skill/internal/store"
dbpkg "ai-workflow-skill/packages/coord-core/db"
"ai-workflow-skill/packages/coord-core/store"
)
func TestRouterExposesReadOnlyWebEndpoints(t *testing.T) {
+1 -1
View File
@@ -6,7 +6,7 @@ import (
"fmt"
"time"
"ai-workflow-skill/internal/store"
"ai-workflow-skill/packages/coord-core/store"
)
type ReadService struct {
+50
View File
@@ -0,0 +1,50 @@
package db
import (
"context"
"database/sql"
"embed"
"fmt"
"sort"
)
//go:embed schema/*.sql
var schemaFS embed.FS
func ApplyMigrations(ctx context.Context, db *sql.DB) error {
files, err := schemaFS.ReadDir("schema")
if err != nil {
return fmt.Errorf("read embedded schema directory: %w", err)
}
names := make([]string, 0, len(files))
for _, file := range files {
if file.IsDir() {
continue
}
names = append(names, file.Name())
}
sort.Strings(names)
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return fmt.Errorf("begin schema transaction: %w", err)
}
defer tx.Rollback()
for _, name := range names {
content, err := schemaFS.ReadFile("schema/" + name)
if err != nil {
return fmt.Errorf("read embedded schema file %q: %w", name, err)
}
if _, err := tx.ExecContext(ctx, string(content)); err != nil {
return fmt.Errorf("apply schema file %q: %w", name, err)
}
}
if err := tx.Commit(); err != nil {
return fmt.Errorf("commit schema transaction: %w", err)
}
return nil
}
+38
View File
@@ -0,0 +1,38 @@
package db
import (
"context"
"database/sql"
"fmt"
"os"
"path/filepath"
_ "modernc.org/sqlite"
)
func Open(ctx context.Context, dbPath string) (*sql.DB, error) {
if err := ensureParentDir(dbPath); err != nil {
return nil, err
}
db, err := sql.Open("sqlite", dbPath)
if err != nil {
return nil, fmt.Errorf("open sqlite database: %w", err)
}
if err := applyPragmas(ctx, db); err != nil {
_ = db.Close()
return nil, err
}
return db, nil
}
func ensureParentDir(dbPath string) error {
parent := filepath.Dir(dbPath)
if parent == "." || parent == "" {
return nil
}
return os.MkdirAll(parent, 0o755)
}
+23
View File
@@ -0,0 +1,23 @@
package db
import (
"context"
"database/sql"
"fmt"
)
func applyPragmas(ctx context.Context, db *sql.DB) error {
pragmas := []string{
"PRAGMA foreign_keys = ON;",
"PRAGMA journal_mode = WAL;",
"PRAGMA busy_timeout = 5000;",
}
for _, pragma := range pragmas {
if _, err := db.ExecContext(ctx, pragma); err != nil {
return fmt.Errorf("apply pragma %q: %w", pragma, err)
}
}
return nil
}
@@ -0,0 +1,51 @@
CREATE TABLE IF NOT EXISTS threads (
thread_id TEXT PRIMARY KEY,
run_id TEXT NOT NULL,
task_id TEXT NOT NULL,
subject TEXT NOT NULL,
created_by TEXT NOT NULL,
assigned_to TEXT NOT NULL,
status TEXT NOT NULL,
priority TEXT NOT NULL DEFAULT 'normal',
latest_message_id TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS messages (
message_id TEXT PRIMARY KEY,
thread_id TEXT NOT NULL,
from_agent TEXT NOT NULL,
to_agent TEXT NOT NULL,
kind TEXT NOT NULL,
summary TEXT NOT NULL,
body TEXT NOT NULL DEFAULT '',
payload_json TEXT NOT NULL DEFAULT '{}',
created_at TEXT NOT NULL,
FOREIGN KEY(thread_id) REFERENCES threads(thread_id)
);
CREATE TABLE IF NOT EXISTS leases (
thread_id TEXT PRIMARY KEY,
agent_id TEXT NOT NULL,
lease_token TEXT NOT NULL,
claimed_at TEXT NOT NULL,
expires_at TEXT NOT NULL,
released_at TEXT
);
CREATE TABLE IF NOT EXISTS artifacts (
artifact_id TEXT PRIMARY KEY,
message_id TEXT NOT NULL,
path TEXT NOT NULL,
kind TEXT NOT NULL,
metadata_json TEXT NOT NULL DEFAULT '{}',
created_at TEXT NOT NULL,
FOREIGN KEY(message_id) REFERENCES messages(message_id)
);
CREATE INDEX IF NOT EXISTS idx_threads_status_assigned
ON threads(status, assigned_to, updated_at);
CREATE INDEX IF NOT EXISTS idx_messages_thread_created
ON messages(thread_id, created_at);
@@ -0,0 +1,52 @@
CREATE TABLE IF NOT EXISTS runs (
run_id TEXT PRIMARY KEY,
goal TEXT NOT NULL,
summary TEXT NOT NULL DEFAULT '',
status TEXT NOT NULL DEFAULT 'active',
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS tasks (
run_id TEXT NOT NULL,
task_id TEXT NOT NULL,
title TEXT NOT NULL,
summary TEXT NOT NULL DEFAULT '',
status TEXT NOT NULL,
default_to TEXT,
priority TEXT NOT NULL DEFAULT 'normal',
acceptance_json TEXT NOT NULL DEFAULT '[]',
latest_attempt_no INTEGER,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
PRIMARY KEY (run_id, task_id),
FOREIGN KEY(run_id) REFERENCES runs(run_id)
);
CREATE TABLE IF NOT EXISTS task_dependencies (
run_id TEXT NOT NULL,
task_id TEXT NOT NULL,
depends_on_task_id TEXT NOT NULL,
PRIMARY KEY (run_id, task_id, depends_on_task_id)
);
CREATE TABLE IF NOT EXISTS task_attempts (
run_id TEXT NOT NULL,
task_id TEXT NOT NULL,
attempt_no INTEGER NOT NULL,
assigned_to TEXT NOT NULL,
thread_id TEXT NOT NULL,
base_ref TEXT,
base_commit TEXT,
branch_name TEXT,
worktree_path TEXT,
workspace_status TEXT,
result_commit TEXT,
status TEXT NOT NULL,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
PRIMARY KEY (run_id, task_id, attempt_no)
);
CREATE INDEX IF NOT EXISTS idx_tasks_run_status
ON tasks(run_id, status, priority, updated_at);
@@ -0,0 +1,18 @@
CREATE TABLE IF NOT EXISTS events (
event_id INTEGER PRIMARY KEY AUTOINCREMENT,
run_id TEXT NOT NULL,
task_id TEXT NOT NULL,
thread_id TEXT,
source TEXT NOT NULL,
event_type TEXT NOT NULL,
message_id TEXT,
summary TEXT NOT NULL DEFAULT '',
payload_json TEXT NOT NULL DEFAULT '{}',
created_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_events_run_event
ON events(run_id, event_id);
CREATE INDEX IF NOT EXISTS idx_events_thread_event
ON events(thread_id, event_id);
@@ -0,0 +1,45 @@
CREATE TABLE IF NOT EXISTS council_runs (
run_id TEXT PRIMARY KEY,
mode TEXT NOT NULL,
target_type TEXT NOT NULL,
output_mode TEXT NOT NULL,
only_unanimous INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS council_reviewers (
run_id TEXT NOT NULL,
reviewer_role TEXT NOT NULL,
task_id TEXT NOT NULL,
status TEXT NOT NULL,
PRIMARY KEY (run_id, reviewer_role)
);
CREATE TABLE IF NOT EXISTS council_findings (
run_id TEXT NOT NULL,
reviewer_role TEXT NOT NULL,
finding_id TEXT NOT NULL,
title TEXT NOT NULL,
summary TEXT NOT NULL,
proposal TEXT NOT NULL,
rationale TEXT NOT NULL,
confidence TEXT NOT NULL,
tags_json TEXT NOT NULL DEFAULT '[]',
target_refs_json TEXT NOT NULL DEFAULT '{}',
PRIMARY KEY (run_id, reviewer_role, finding_id)
);
CREATE TABLE IF NOT EXISTS council_groups (
run_id TEXT NOT NULL,
group_id TEXT NOT NULL,
proposal TEXT NOT NULL,
bucket TEXT NOT NULL,
support_count INTEGER NOT NULL,
supporters_json TEXT NOT NULL DEFAULT '[]',
dissenters_json TEXT NOT NULL DEFAULT '[]',
rationale_summary TEXT NOT NULL DEFAULT '',
tags_json TEXT NOT NULL DEFAULT '[]',
source_finding_ids_json TEXT NOT NULL DEFAULT '[]',
PRIMARY KEY (run_id, group_id)
);
@@ -0,0 +1,12 @@
CREATE TABLE IF NOT EXISTS thread_reads (
thread_id TEXT NOT NULL,
agent_id TEXT NOT NULL,
last_read_message_id TEXT NOT NULL,
last_read_at TEXT NOT NULL,
PRIMARY KEY(thread_id, agent_id),
FOREIGN KEY(thread_id) REFERENCES threads(thread_id),
FOREIGN KEY(last_read_message_id) REFERENCES messages(message_id)
);
CREATE INDEX IF NOT EXISTS idx_thread_reads_agent
ON thread_reads(agent_id, last_read_at);
@@ -0,0 +1,9 @@
CREATE TABLE IF NOT EXISTS council_inputs (
run_id TEXT PRIMARY KEY,
prompt TEXT NOT NULL DEFAULT '',
target_file TEXT NOT NULL DEFAULT '',
repo_path TEXT NOT NULL DEFAULT '',
target_task_id TEXT NOT NULL DEFAULT '',
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
@@ -0,0 +1,8 @@
CREATE TABLE IF NOT EXISTS council_reports (
run_id TEXT PRIMARY KEY,
show_json TEXT NOT NULL DEFAULT '[]',
summary_json TEXT NOT NULL DEFAULT '{}',
markdown_path TEXT NOT NULL DEFAULT '',
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
+5
View File
@@ -1,3 +1,8 @@
module ai-workflow-skill/packages/coord-core
go 1.26
require (
github.com/google/uuid v1.6.0
modernc.org/sqlite v1.40.1
)
+33
View File
@@ -0,0 +1,33 @@
package protocol
type CLIError struct {
Code string
ExitCode int
Message string
Err error
}
func (e *CLIError) Error() string {
return e.Message
}
func (e *CLIError) Unwrap() error {
return e.Err
}
func NewCLIError(code string, exitCode int, message string, err error) error {
return &CLIError{
Code: code,
ExitCode: exitCode,
Message: message,
Err: err,
}
}
func InvalidInput(message string, err error) error {
return NewCLIError("invalid_input", 30, message, err)
}
func NoMatchingWork(message string) error {
return NewCLIError("no_matching_work", 10, message, nil)
}
+28
View File
@@ -0,0 +1,28 @@
package protocol
import (
"encoding/json"
"io"
)
type Success struct {
OK bool `json:"ok"`
Command string `json:"command"`
Data map[string]any `json:"data,omitempty"`
}
type Error struct {
OK bool `json:"ok"`
Error ErrorPayload `json:"error"`
}
type ErrorPayload struct {
Code string `json:"code"`
Message string `json:"message"`
}
func WriteJSON(w io.Writer, v any) error {
enc := json.NewEncoder(w)
enc.SetIndent("", " ")
return enc.Encode(v)
}
File diff suppressed because it is too large Load Diff
+3
View File
@@ -0,0 +1,3 @@
package store
// Package store contains higher-level database access helpers.
File diff suppressed because it is too large Load Diff
+107
View File
@@ -0,0 +1,107 @@
package store
import (
"context"
"errors"
"path/filepath"
"testing"
"time"
dbpkg "ai-workflow-skill/packages/coord-core/db"
)
func TestClaimThreadReturnsLeaseConflictAfterBusyWrite(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
sqlDB, err := dbpkg.Open(ctx, dbPath)
if err != nil {
t.Fatalf("open base db: %v", err)
}
defer sqlDB.Close()
if err := dbpkg.ApplyMigrations(ctx, sqlDB); err != nil {
t.Fatalf("apply migrations: %v", err)
}
baseStore := NewInboxStore(sqlDB)
thread, _, err := baseStore.Send(ctx, SendInput{
FromAgent: "leader",
ToAgent: "worker-a",
Subject: "race claim",
Summary: "race claim",
})
if err != nil {
t.Fatalf("seed thread: %v", err)
}
lockerDB, err := dbpkg.Open(ctx, dbPath)
if err != nil {
t.Fatalf("open locker db: %v", err)
}
defer lockerDB.Close()
lockTx, err := lockerDB.BeginTx(ctx, nil)
if err != nil {
t.Fatalf("begin locker tx: %v", err)
}
now := nowUTC()
if _, err := lockTx.ExecContext(
ctx,
`INSERT INTO leases (
thread_id, agent_id, lease_token, claimed_at, expires_at, released_at
) VALUES (?, ?, ?, ?, ?, NULL)`,
thread.ThreadID,
"worker-a",
"lease_locked",
formatTime(now),
formatTime(now.Add(5*time.Minute)),
); err != nil {
t.Fatalf("seed active lease in tx: %v", err)
}
if _, err := lockTx.ExecContext(
ctx,
`UPDATE threads
SET status = ?, assigned_to = ?, latest_message_id = ?, updated_at = ?
WHERE thread_id = ?`,
"claimed",
"worker-a",
"msg_locked",
formatTime(now),
thread.ThreadID,
); err != nil {
t.Fatalf("seed claimed thread in tx: %v", err)
}
commitDone := make(chan error, 1)
go func() {
time.Sleep(100 * time.Millisecond)
commitDone <- lockTx.Commit()
}()
claimDB, err := dbpkg.Open(ctx, dbPath)
if err != nil {
t.Fatalf("open claim db: %v", err)
}
defer claimDB.Close()
claimStore := NewInboxStore(claimDB)
_, err = claimStore.ClaimThread(ctx, ClaimInput{
ThreadID: thread.ThreadID,
Agent: "worker-b",
LeaseSeconds: 300,
})
if !errors.Is(err, ErrLeaseConflict) {
t.Fatalf("expected lease conflict after busy retry, got %v", err)
}
if err := <-commitDone; err != nil {
t.Fatalf("commit locker tx: %v", err)
}
}
File diff suppressed because it is too large Load Diff