Repository navigation
chore: promote dev→main — pgserve, fire-and-forget, scheduler #676
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
66dc547
45153d5
74de54c
c8cdcb1
c3f1a58
c461714
02d3d6b
0e82f04
ce5e2f6
a2cb615
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,131 @@ | ||
| -- 001_initial.sql — Core genie schema | ||
| -- Tables: schedules, triggers, runs, heartbeats, audit_events, agent_checkpoints | ||
|
|
||
| -- ============================================================================ | ||
| -- Schedules — cron-like schedule definitions | ||
| -- ============================================================================ | ||
| CREATE TABLE schedules ( | ||
| id TEXT PRIMARY KEY, | ||
| name TEXT NOT NULL, | ||
| cron_expression TEXT NOT NULL, | ||
| timezone TEXT DEFAULT 'UTC', | ||
| command TEXT, | ||
| metadata JSONB DEFAULT '{}', | ||
| status TEXT NOT NULL DEFAULT 'active' | ||
| CHECK (status IN ('active', 'paused', 'deleted')), | ||
| created_at TIMESTAMPTZ NOT NULL DEFAULT now(), | ||
| updated_at TIMESTAMPTZ NOT NULL DEFAULT now() | ||
| ); | ||
|
|
||
| CREATE INDEX idx_schedules_status ON schedules(status); | ||
| CREATE INDEX idx_schedules_name ON schedules(name); | ||
|
|
||
| -- ============================================================================ | ||
| -- Triggers — individual fire instances of a schedule | ||
| -- ============================================================================ | ||
| CREATE TABLE triggers ( | ||
| id TEXT PRIMARY KEY, | ||
| schedule_id TEXT NOT NULL REFERENCES schedules(id) ON DELETE CASCADE, | ||
| due_at TIMESTAMPTZ NOT NULL, | ||
| status TEXT NOT NULL DEFAULT 'pending' | ||
| CHECK (status IN ('pending', 'executing', 'completed', 'failed', 'skipped')), | ||
| created_at TIMESTAMPTZ NOT NULL DEFAULT now(), | ||
| started_at TIMESTAMPTZ, | ||
| completed_at TIMESTAMPTZ | ||
| ); | ||
|
|
||
| CREATE INDEX idx_triggers_schedule_id ON triggers(schedule_id); | ||
| CREATE INDEX idx_triggers_status ON triggers(status); | ||
| CREATE INDEX idx_triggers_due_pending ON triggers(due_at) | ||
| WHERE status = 'pending'; | ||
|
|
||
| -- ============================================================================ | ||
| -- Runs — execution attempts for a trigger | ||
| -- ============================================================================ | ||
| CREATE TABLE runs ( | ||
| id TEXT PRIMARY KEY, | ||
| trigger_id TEXT NOT NULL REFERENCES triggers(id) ON DELETE CASCADE, | ||
| worker_id TEXT, | ||
| status TEXT NOT NULL DEFAULT 'pending' | ||
| CHECK (status IN ('pending', 'leased', 'running', 'completed', 'failed', 'timeout', 'cancelled')), | ||
| output TEXT, | ||
| error TEXT, | ||
| started_at TIMESTAMPTZ, | ||
| completed_at TIMESTAMPTZ, | ||
| created_at TIMESTAMPTZ NOT NULL DEFAULT now() | ||
| ); | ||
|
|
||
| CREATE INDEX idx_runs_trigger_id ON runs(trigger_id); | ||
| CREATE INDEX idx_runs_worker_id ON runs(worker_id); | ||
| CREATE INDEX idx_runs_status ON runs(status); | ||
| CREATE INDEX idx_runs_leased ON runs(status, started_at) | ||
| WHERE status IN ('leased', 'running'); | ||
|
|
||
| -- ============================================================================ | ||
| -- Heartbeats — worker liveness tracking | ||
| -- ============================================================================ | ||
| CREATE TABLE heartbeats ( | ||
| id TEXT PRIMARY KEY, | ||
| worker_id TEXT NOT NULL, | ||
| run_id TEXT REFERENCES runs(id) ON DELETE SET NULL, | ||
| status TEXT NOT NULL DEFAULT 'alive' | ||
| CHECK (status IN ('alive', 'idle', 'busy', 'dead')), | ||
| context JSONB DEFAULT '{}', | ||
| last_seen_at TIMESTAMPTZ NOT NULL DEFAULT now(), | ||
| created_at TIMESTAMPTZ NOT NULL DEFAULT now() | ||
| ); | ||
|
|
||
| CREATE INDEX idx_heartbeats_worker_id ON heartbeats(worker_id); | ||
| CREATE INDEX idx_heartbeats_run_id ON heartbeats(run_id); | ||
| CREATE INDEX idx_heartbeats_last_seen ON heartbeats(last_seen_at); | ||
|
|
||
| -- ============================================================================ | ||
| -- Audit events — immutable log of state transitions | ||
| -- ============================================================================ | ||
| CREATE TABLE audit_events ( | ||
| id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, | ||
| entity_type TEXT NOT NULL, | ||
| entity_id TEXT NOT NULL, | ||
| event_type TEXT NOT NULL, | ||
| actor TEXT, | ||
| details JSONB DEFAULT '{}', | ||
| created_at TIMESTAMPTZ NOT NULL DEFAULT now() | ||
| ); | ||
|
|
||
| CREATE INDEX idx_audit_entity ON audit_events(entity_type, entity_id); | ||
| CREATE INDEX idx_audit_created ON audit_events(created_at); | ||
| CREATE INDEX idx_audit_actor ON audit_events(actor); | ||
|
|
||
| -- ============================================================================ | ||
| -- Agent checkpoints — session resume state for agents | ||
| -- ============================================================================ | ||
| CREATE TABLE agent_checkpoints ( | ||
| worker_id TEXT PRIMARY KEY, | ||
| wish_slug TEXT, | ||
| group_name TEXT, | ||
| phase TEXT CHECK (phase IN ('executing', 'validating', 'reporting')), | ||
| context_summary TEXT, | ||
| dispatch_context TEXT, | ||
| branch_name TEXT, | ||
| last_checkpoint TIMESTAMPTZ NOT NULL DEFAULT now() | ||
| ); | ||
|
|
||
| CREATE INDEX idx_checkpoints_wish ON agent_checkpoints(wish_slug); | ||
| CREATE INDEX idx_checkpoints_group ON agent_checkpoints(group_name); | ||
|
|
||
| -- ============================================================================ | ||
| -- LISTEN/NOTIFY — real-time trigger-due notifications | ||
| -- ============================================================================ | ||
| CREATE OR REPLACE FUNCTION notify_trigger_due() | ||
| RETURNS trigger AS $$ | ||
| BEGIN | ||
| PERFORM pg_notify('genie_trigger_due', NEW.id::text); | ||
| RETURN NEW; | ||
| END; | ||
| $$ LANGUAGE plpgsql; | ||
|
|
||
| CREATE TRIGGER trg_notify_due | ||
| AFTER INSERT ON triggers | ||
| FOR EACH ROW | ||
| WHEN (NEW.status = 'pending') | ||
| EXECUTE FUNCTION notify_trigger_due(); | ||
|
Comment on lines
+119
to
+131
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: find . -type f -name "001_initial.sql" -o -name "*migrations*" -type dRepository: automagik-dev/genie Length of output: 118 🏁 Script executed: wc -l ./src/db/migrations/001_initial.sqlRepository: automagik-dev/genie Length of output: 102 🏁 Script executed: cat -n ./src/db/migrations/001_initial.sqlRepository: automagik-dev/genie Length of output: 6048 🏁 Script executed: rg "status.*=.*['\"]pending['\"]|pending.*status" --type sql --type js --type ts -B 2 -A 2Repository: automagik-dev/genie Length of output: 2985 Add AFTER UPDATE trigger to notify when triggers transition back to pending.
🤖 Prompt for AI Agents |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| -- 002_scheduler_extensions.sql — Scheduler daemon extensions | ||
| -- Adds lease columns to triggers, trace_id to runs, run_spec to schedules | ||
|
|
||
| -- ============================================================================ | ||
| -- Schedules — add interval support and run specification | ||
| -- ============================================================================ | ||
| ALTER TABLE schedules ADD COLUMN IF NOT EXISTS interval_ms BIGINT; | ||
| ALTER TABLE schedules ADD COLUMN IF NOT EXISTS run_spec JSONB DEFAULT '{}'; | ||
|
|
||
| -- ============================================================================ | ||
| -- Triggers — add lease columns and idempotency key | ||
| -- ============================================================================ | ||
| ALTER TABLE triggers ADD COLUMN IF NOT EXISTS idempotency_key TEXT; | ||
| ALTER TABLE triggers ADD COLUMN IF NOT EXISTS leased_by TEXT; | ||
| ALTER TABLE triggers ADD COLUMN IF NOT EXISTS leased_until TIMESTAMPTZ; | ||
|
|
||
| CREATE UNIQUE INDEX IF NOT EXISTS idx_triggers_idempotency | ||
| ON triggers(idempotency_key) | ||
| WHERE idempotency_key IS NOT NULL; | ||
|
|
||
| CREATE INDEX IF NOT EXISTS idx_triggers_leased | ||
| ON triggers(status, leased_until) | ||
| WHERE status = 'executing'; | ||
|
|
||
| -- ============================================================================ | ||
| -- Runs — add trace_id and lease_timeout_ms | ||
| -- ============================================================================ | ||
| ALTER TABLE runs ADD COLUMN IF NOT EXISTS trace_id TEXT; | ||
| ALTER TABLE runs ADD COLUMN IF NOT EXISTS lease_timeout_ms INTEGER DEFAULT 300000; | ||
| ALTER TABLE runs ADD COLUMN IF NOT EXISTS exit_code INTEGER; | ||
|
|
||
| CREATE INDEX IF NOT EXISTS idx_runs_trace_id ON runs(trace_id) | ||
| WHERE trace_id IS NOT NULL; |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,15 @@ | ||||||
| -- 003_machine_snapshots.sql — Machine state snapshots for scheduler observability | ||||||
|
|
||||||
| CREATE TABLE IF NOT EXISTS machine_snapshots ( | ||||||
| id TEXT PRIMARY KEY, | ||||||
| active_workers INTEGER NOT NULL DEFAULT 0, | ||||||
| active_teams INTEGER NOT NULL DEFAULT 0, | ||||||
| tmux_sessions INTEGER NOT NULL DEFAULT 0, | ||||||
| cpu_percent REAL, | ||||||
| memory_mb REAL, | ||||||
| context JSONB DEFAULT '{}', | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial
The ♻️ Add NOT NULL for consistency- context JSONB DEFAULT '{}',
+ context JSONB NOT NULL DEFAULT '{}',📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| created_at TIMESTAMPTZ NOT NULL DEFAULT now() | ||||||
| ); | ||||||
|
|
||||||
| CREATE INDEX IF NOT EXISTS idx_machine_snapshots_created | ||||||
| ON machine_snapshots(created_at); | ||||||
|
Comment on lines
+3
to
+15
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial **Consider retention policy for snapshots.**Machine snapshots can accumulate without bounds. "Having partitions for different time spans makes it more efficient to drop/delete/expire old data." Consider adding a retention cleanup mechanism or documenting expected growth. 🤖 Prompt for AI Agents |
||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -194,11 +194,11 @@ async function createSession( | |||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * Launch Claude Code in a tmux pane with --continue fallback. | ||||||
| * Launch Claude Code in a tmux pane with --resume fallback. | ||||||
| * | ||||||
| * Tries --continue first (preserves conversation history). Waits ~3 seconds, | ||||||
| * then checks #{pane_current_command} — if the pane shows a shell, --continue | ||||||
| * failed (no prior conversation) so we retry fresh without --continue. | ||||||
| * Tries --resume first (preserves conversation history). Waits ~3 seconds, | ||||||
| * then checks #{pane_current_command} — if the pane shows a shell, --resume | ||||||
| * failed (no prior session) so we retry fresh without --resume. | ||||||
| */ | ||||||
| async function launchWithContinueFallback( | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The function name
Suggested change
|
||||||
| target: string, | ||||||
|
|
@@ -209,17 +209,17 @@ async function launchWithContinueFallback( | |||||
| const continueCmd = buildClaudeCommand(windowName, systemPromptFile || undefined, continueName); | ||||||
| const freshCmd = buildClaudeCommand(windowName, systemPromptFile || undefined, undefined); | ||||||
|
|
||||||
| // Try --continue first (preserves conversation history) | ||||||
| // Try --resume first (preserves conversation history) | ||||||
| await tmux.executeTmux(`send-keys -t ${shellQuote(target)} ${shellQuote(continueCmd)} Enter`); | ||||||
|
|
||||||
| // Wait briefly then check if CC is running or fell back to shell | ||||||
| // CC fails fast on missing conversations (~1s), 3s is conservative | ||||||
| // CC fails fast on missing sessions (~1s), 3s is conservative | ||||||
| await new Promise((r) => setTimeout(r, 3000)); | ||||||
| const afterCmd = (await tmux.executeTmux(`display -t ${shellQuote(target)} -p '#{pane_current_command}'`)).trim(); | ||||||
|
|
||||||
| if (['bash', 'zsh', 'sh', 'fish'].includes(afterCmd)) { | ||||||
| // --continue failed, start fresh | ||||||
| console.log('No prior conversation found, starting fresh session...'); | ||||||
| // --resume failed, start fresh | ||||||
| console.log('No prior session found, starting fresh session...'); | ||||||
| await tmux.executeTmux(`send-keys -t ${shellQuote(target)} ${shellQuote(freshCmd)} Enter`); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: automagik-dev/genie
Length of output: 6048
🏁 Script executed:
cat -n src/term-commands/schedule.ts | head -100Repository: automagik-dev/genie
Length of output: 3616
🏁 Script executed:
rg -A 5 "name-based\|by.*name\|WHERE.*name" src/term-commands/schedule.tsRepository: automagik-dev/genie
Length of output: 45
🏁 Script executed:
rg "WHERE\|SELECT.*schedules\|cancel\|retry\|history" src/term-commands/schedule.ts -A 3 -B 1Repository: automagik-dev/genie
Length of output: 45
🏁 Script executed:
rg "UPDATE.*schedules\|UPDATE.*triggers" src/term-commands/schedule.ts -A 3 -B 1Repository: automagik-dev/genie
Length of output: 45
🏁 Script executed:
Repository: automagik-dev/genie
Length of output: 3511
🏁 Script executed:
Repository: automagik-dev/genie
Length of output: 2353
🏁 Script executed:
rg "UPDATE triggers SET status" src/term-commands/schedule.ts -B 3 -A 3Repository: automagik-dev/genie
Length of output: 253
🏁 Script executed:
Repository: automagik-dev/genie
Length of output: 1804
🏁 Script executed:
rg "schedule create\|INSERT INTO schedules" src/term-commands/schedule.ts -B 5 -A 10Repository: automagik-dev/genie
Length of output: 45
🏁 Script executed:
rg "async function scheduleCreateCommand\|INSERT INTO schedules" src/term-commands/schedule.ts -B 5 -A 15Repository: automagik-dev/genie
Length of output: 45
🏁 Script executed:
Repository: automagik-dev/genie
Length of output: 3464
Add partial unique index to enforce active schedule-name uniqueness in the database.
The application checks for duplicate active names before creating a schedule, but this check happens outside the transaction. Two concurrent creates can both pass the check and insert separate active rows with the same name, causing later cancel/history/retry calls by name to be ambiguous or operate on the wrong schedule. Add
CREATE UNIQUE INDEX idx_schedules_active_name ON schedules(name) WHERE status = 'active'to enforce this at the schema level.🤖 Prompt for AI Agents