Files
clawmates/migrations/0052_level_up_proposals.sql
T
Omar SobhandClaude Opus 4.7 9b5e63cbb7
ci / gates (push) Successful in 4s
ci / frontend (push) Successful in 40s
ci / rust (push) Successful in 3m30s
ci / e2e (push) Skipped
ci / publish (push) Successful in 2m30s
slice 8.5: per-agent + per-team level-up endpoints
Level-up analyzes an agent's brain + recent run outcomes (or a
whole team's aggregate state), calls Gemini 2.5 Flash for structured
JSON proposals, and persists them as pending level_up_proposals
rows. Reviewer approves a subset via /apply; the applier commits
only those items.

Migration 0052 adds level_up_proposals (id, workspace_id, agent_id
XOR team_id via CHECK constraint, status, payload JSONB,
applied_items[], model, created_by, approved_by, created_at,
applied_at) + workspace/pending/agent/team indexes.

Rust surface:
  - cm_db::repo::level_up::{insert, get, list_pending, mark_applied,
    mark_rejected}
  - cm_api::level_up::{propose_agent, propose_team, apply}
    Item kinds handled by apply():
      identity_refinement    → UPDATE agents.system_prompt
      skill_add              → agent_skills_ext INSERT
      skill_candidate        → workspace-scoped skills INSERT
                              (deterministic id per (workspace, name))
      brain_consolidation    → set_agent_md on the brain (unlike
                              brain_seed::ingest, this overwrites)
      roster_change / mcp_bundle_change — logged as
                              "not auto-applied, human runs
                              team-wizard" (structural changes need
                              human review of side effects).

API:
  - POST /api/claws/{id}/level-up   → { proposal_id }
  - POST /api/teams/{id}/level-up   → { proposal_id }
  - GET  /api/level-up-proposals    → pending list
  - GET  /api/level-up-proposals/{id}
  - POST /api/level-up-proposals/{id}/apply  { approved_item_ids }
  - POST /api/level-up-proposals/{id}/reject

Uses Gemini 2.5 Flash with response_mime_type: "application/json"
so the model returns structured JSON directly (no ```json fence
stripping needed). Configurable via CLAWMATES_LEVEL_UP_MODEL.

Follow-ups:
  - Frontend diff-review UI (pick items, approve/reject)
  - roster_change / mcp_bundle_change appliers (currently manual)
  - Anthropic + OpenAI proposer variants
  - Promote workspace-scoped skills to builtin via a curator flow

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-07-19 16:36:29 -07:00

75 lines
3.1 KiB
SQL

-- Slice 8.5 — level-up proposals.
--
-- A proposal is an LLM-generated diff of "current state → suggested
-- state" for either a specific agent or a whole team. Human approves
-- (all or per-item) via a diff-review UI; the applier commits the
-- approved subset.
--
-- Proposal payload (JSONB) shape — kept schemaless because proposal
-- kinds evolve. Documented shapes:
--
-- agent proposal:
-- {
-- "kind": "agent",
-- "current": { "system_prompt": "…", "skills": ["…"] },
-- "suggested_items": [
-- { "id": "u1", "kind": "brain_consolidation",
-- "rationale": "…", "brain_md_diff": "…" },
-- { "id": "u2", "kind": "identity_refinement",
--- "rationale": "…", "new_system_prompt": "…" },
-- { "id": "u3", "kind": "skill_add", "skill_id": "…",
-- "rationale": "…" },
-- { "id": "u4", "kind": "skill_candidate",
-- "rationale": "…", "draft": { "name": "…",
-- "description": "…",
-- "when_to_use": "…",
-- "body": "…",
-- "tags": ["…"] } }
-- ]
-- }
--
-- team proposal — same shape plus:
-- { "id": "u5", "kind": "roster_change",
-- "op": "add"|"drop"|"rename", "slot": "…", "rationale": "…" }
-- { "id": "u6", "kind": "mcp_bundle_change",
-- "op": "add"|"drop", "bundle": "…", "rationale": "…" }
--
-- `applied_items` is the array of `id`s that the reviewer approved;
-- the applier only touches those.
CREATE TABLE level_up_proposals (
id UUID PRIMARY KEY,
workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
-- Exactly one of these is set; enforced by the CHECK below.
agent_id UUID REFERENCES agents(id) ON DELETE CASCADE,
team_id UUID REFERENCES teams(id) ON DELETE CASCADE,
-- 'pending' | 'applied' | 'rejected' | 'partial' (applied a subset)
status TEXT NOT NULL DEFAULT 'pending',
-- Full proposal JSONB (see header comment for shape).
payload JSONB NOT NULL,
-- Ids from payload.suggested_items[] that reviewer approved.
applied_items TEXT[] NOT NULL DEFAULT '{}',
-- Model used for the LLM analysis pass (audit trail).
model TEXT,
created_by UUID REFERENCES users(id) ON DELETE SET NULL,
approved_by UUID REFERENCES users(id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
applied_at TIMESTAMPTZ,
CONSTRAINT level_up_target_one CHECK (
(agent_id IS NOT NULL AND team_id IS NULL) OR
(agent_id IS NULL AND team_id IS NOT NULL)
)
);
CREATE INDEX level_up_workspace_idx
ON level_up_proposals (workspace_id, created_at DESC);
CREATE INDEX level_up_pending_idx
ON level_up_proposals (status, created_at DESC)
WHERE status = 'pending';
CREATE INDEX level_up_agent_idx
ON level_up_proposals (agent_id, created_at DESC)
WHERE agent_id IS NOT NULL;
CREATE INDEX level_up_team_idx
ON level_up_proposals (team_id, created_at DESC)
WHERE team_id IS NOT NULL;