Commit Graph
56 Commits
Author SHA1 Message Date
Omar Sobh 5c63ef0ed3 missions: phase-completion summary card (Claude Opus 4.8 synthesized)
ci / gates (push) Successful in 6s
ci / rust (push) Failing after 9s
ci / frontend (push) Successful in 26s
ci / e2e (push) Skipped
ci / publish (push) Skipped
New phase_summarizer background worker fires on any mission_phase
transition to a terminal state (completed/failed). Aggregates every
topology_runs.checkpoint.outputs[] + mission_tasks + mission_artifacts
bound to that phase and asks Claude Opus 4.8 to produce a structured
JSON card:

  { narrative, metrics, sources, tooling, next_actions }

Rendered inline on the mission page under each completed phase via
new PhaseSummaryCard component. Metrics grid is kind-specific:
research surfaces insights/sources/int_cards/artifacts, coding
surfaces cards_picked_up/commits/tests/issues, benchmark surfaces
regressions/improvements, security surfaces findings-by-severity.

New table: mission_phase_summaries (migration 0060), unique per
phase_id — regenerates on retry.
New endpoint: GET /api/missions/{id}/phases/{phase_id}/summary.

Model overridable via CLAWMATES_SUMMARIZER_MODEL. Reuses the
ANTHROPIC_API_KEY prod already carries for mission_refiner.
2026-07-23 16:54:38 -07:00
Omar Sobh b569688e04 fix(mission_runtime): per-mission auto-pair via container log scrape (C3 auth)
ci / gates (push) Successful in 10s
ci / e2e (push) Skipped
ci / publish (push) Skipped
ci / rust (push) Failing after 23s
ci / frontend (push) Successful in 38s
The seed-mount approach didnt work: even with the shared runtimes
data dir bind-mounted, a fresh gateway instance mints a new pairing
key and requires re-pairing. The topology_worker connect returned
401 forever.

New approach — per-mission gateways self-pair:
- Provisioner tails container logs after start, extracts the
  X-Pairing-Code from the boot banner
- Persists it on missions.runtime_pairing_code (migration 0059)
- topology_worker constructs ZeroClawDriveExecutor with THAT code
  via from_env_for_gateway_with_code, which triggers the lazy
  /pair handshake on first turn and caches the returned bearer

Drops the shared-runtime data-dir mount — each per-mission gateway
now owns its own state, restoring the C3 isolation guarantee.
2026-07-22 13:06:25 -07:00
Omar Sobh 5d24fd3460 missions: schema + provisioner skeleton for per-mission runtime containers (C3 slice 1)
ci / e2e (push) Skipped
ci / publish (push) Skipped
ci / gates (push) Successful in 7s
ci / rust (push) Failing after 14s
ci / frontend (push) Successful in 29s
- migration 0058: adds missions.runtime_container_name + runtime_endpoint
- new mission_runtime module (bollard): ensure_container /
  teardown_container. Container is spawned on clawmates_core +
  clawmates_edge networks with just /var/lib/clawmates-missions/{id}
  bind-mounted so agents scoped to /mission/repo can only see this
  missions repo.
- provider API keys forwarded from the server envs so per-mission
  runtimes inherit them.
- Mission struct + repo helpers updated for the two new columns +
  set_runtime_binding().
- Unit tests cover container naming determinism + entropy.

Not wired to the orchestrator yet — that lands in slice 2.
2026-07-21 22:17:41 -07:00
Omar Sobh f0dd0147f6 templates: 5 research team templates + category filtering
ci / gates (push) Successful in 5s
ci / frontend (push) Successful in 26s
ci / rust (push) Successful in 4m28s
ci / e2e (push) Skipped
ci / publish (push) Successful in 2m45s
Adds the operator's five categorized research team archetypes:

  1. codebase_research — code archeologist, architecture mapper,
     flow tracer, vault scribe. Produces Obsidian vault entries
     under Codebases/<repo>/ that make future missions faster.
  2. papers_research — domain scout, paper reader, library curator.
     Pulls arXiv / Semantic Scholar / conference proceedings, keeps
     a structured local library under Papers/<topic>/.
  3. insight_research — implementation tracker, novelty hunter,
     publication drafter. Bidirectional loop that spots
     publication-worthy novelty in our own implementations of
     external papers.
  4. continuous_research — signal harvester, ranker, digest writer.
     Standing sweep of RSS + arXiv daily + GitHub trending; produces
     a rolling ContinuousResearch/<date>/digest.md.
  5. continuous_improvement — brain inspector, improvement proposer,
     improvement evaluator. Standing self-audit that files level-up
     proposals for the operator to review + measures the outcome.

Each template ships with role system_prompts + brain_seeds authored
in the same voice as the existing backend/frontend/etc templates —
evidence-first, redlines called out, no invention.

Schema + code:
  - 0057_team_templates_category.sql — new column with
    CHECK (research | development | security | ops). Existing rows
    default to 'development'.
  - team_templates::UpsertBuiltin + TeamTemplate carry category
    (with default_category = 'development' fallback for
    Serialize/Deserialize compatibility).
  - team_template_loader reads `category = "..."` from the TOML;
    absent defaults to 'development' so old templates keep working.
  - Wizard step 3 filters:
      Research teams panel → templates.filter(t.category==='research')
      Development teams panel → templates.filter(t.category==='development')
    Operator can no longer accidentally pick backend as their
    "research team".

Test fixture updated with category="development".

The templates ship in the server image via the existing
`COPY templates /etc/clawmates/templates` line — no Dockerfile
change needed.
2026-07-21 04:54:52 -07:00
Omar Sobh b8b8cb452e missions: multi-team model — pick research + development teams
ci / frontend (push) Successful in 37s
ci / gates (push) Successful in 5s
ci / rust (push) Failing after 1m41s
ci / e2e (push) Skipped
ci / publish (push) Skipped
Directly addresses "we want to pick one or more teams to assign to a
mission, first screen research teams, next screen dev teams." A
mission now materializes N teams, each tagged with a phase purpose.

Backend:
  - 0056_mission_teams.sql — new join table
    mission_teams(mission_id, team_id, purpose). team_id PK because a
    team belongs to one mission-purpose. missions.team_id kept as
    legacy pointer to the first minted team for single-team surfaces.
  - mission_orchestrator::on_launch — reads mission.config.phase_teams
    (JSONB shape { research: [tid,...], coding: [tid,...] }), mints
    one team per (purpose, template) pair, records each in
    mission_teams, binds the first to mission.team_id. Legacy fallback:
    if config.phase_teams is absent, uses missions.team_template_id.
    Hard error if both are absent.
  - GET /api/missions/{id}/teams — returns
    [{ team_id, purpose, team_name }], sorted by created_at asc.

Frontend wizard (step 3 rewrite):
  - researchTeamIds / devTeamIds — Set<string> multi-selects
  - Reusable TeamMultiSelect component (checkbox-style cards)
  - Panels rendered conditionally by preset:
    hasResearchPhase → "Research teams" panel
    hasCodingPhase → "Development teams" panel
    neither → "Teams" panel (bench/security-only missions)
  - canNext enforces at least one pick in every visible panel
  - submit builds config.phase_teams and passes it via CreateMissionRequest
  - Review step shows both selections by name

MissionTeamTab:
  - Fetches /api/missions/{id}/teams and groups by purpose
  - Each purpose renders a section with per-team cards
  - Falls back to a single "mission" pseudo-row for legacy missions
    that only have missions.team_id (no mission_teams rows)

CreateMissionRequest no longer sends team_template_id from the wizard
— the multi-team config.phase_teams path supersedes it. The backend
still accepts team_template_id for API callers.

Verified: cargo check --workspace + tsc + eslint --quiet all green.
2026-07-20 19:25:07 -07:00
Omar Sobh d6dbd044c8 herdr phase 1a: missions runtime_kind + target_node schema
First slice of the second-runtime path. Missions now carry
runtime_kind ('zeroclaw' | 'local_herdr') + target_node_id (FK to
nodes) so the mission_orchestrator + phase executors can dispatch
differently depending on where the operator wants execution.

Migration:
  - 0055_missions_runtime_kind.sql — adds runtime_kind (NOT NULL
    DEFAULT 'zeroclaw' + CHECK), target_node_id (nullable FK ON
    DELETE SET NULL). All existing missions backfill to 'zeroclaw'
    so behavior is unchanged.
  - topology_runs also grows herdr_workspace_id / herdr_tab_id /
    herdr_pane_id text columns so a resumed run can reattach to the
    same Herdr pane instead of spawning a duplicate.

Code:
  - cm-db::repo::missions — Mission + NewMission carry the two new
    fields; all SELECTs updated; INSERT COALESCE-defaults
    runtime_kind to 'zeroclaw' when unspecified.
  - routes::missions::create — validates runtime_kind and requires
    target_node_id when kind='local_herdr' (400 otherwise).
  - lib/api/missions.ts — RuntimeKind type; Mission carries both;
    CreateMissionRequest optional fields.

Behavior is opt-in: no path exists yet to actually create a
local_herdr mission — that lands in Phase 1c (wizard picker). This
commit just makes the schema + validation in place so Phase 1b's
fleet_herdr dispatch module can key on it.

Tests: mission_orchestrator integration test still green.
2026-07-20 09:45:15 -07:00
Omar Sobh 854a617777 task #23: retire per-team ZeroClaw container coords (Option A)
Missions never populated teams.zeroclaw_container /
teams.zeroclaw_gateway_url — those were research/loops-era columns
for long-lived per-team containers. Every mission-materialized team
runs inside the SHARED runtime as claws-as-agents provisioned via
RuntimeProvisioner. Reading zeroclaw_container on a mission row
always came up NULL, making security_scan + benchmark_runner
silently fail with "mission has no team container yet."

Changes:
  - migrations/0054_drop_teams_zeroclaw_columns.sql — DROP both
    columns.
  - cm-db/src/repo/teams.rs — delete dead helpers
    team_container_coords + set_team_container_coords.
  - cm-api/src/security_scan.rs — replace team_container_for_mission
    with exec_target(pool, mission_id): container from env
    CLAWMATES_RUNTIME_CONTAINER (default clawmates-runtime); workdir
    from env CLAWMATES_MISSIONS_ROOT + /{mission_id}/repo
    (same convention pdf_renderer uses); precondition that mission
    must have repo_id bound.
  - cm-api/src/benchmark_runner.rs — same shape.

Follow-up (not in this commit): mission_orchestrator + compose stack
still need to wire a per-mission repo checkout under
CLAWMATES_MISSIONS_ROOT before scan/bench actually produce findings.
Columns cleanup here removes the misleading silent-fail; the
missing-checkout gap is now surfaced with a clear error.

Verified: SQLX_OFFLINE=true cargo check --workspace + cargo test
-p cm-api --test mission_orchestrator both green.

Closes task #23.
2026-07-19 23:18:28 -07:00
Omar Sobh fdb8cfeecc slice 9 cleanup: drop legacy research/loops backend + tables
Retires the legacy research/loops backend after the missions arc
(slices 1-9) fully replaced it. Frontend cutover was 4663348; this
commit finishes the job on the backend + database.

Migration:
  - 0053_drop_legacy_research_loops.sql — drops the 8 legacy tables
    (research_topics, research_topic_agents, research_outcomes,
    research_publish_approvals, loops, loop_agents, loop_orgs,
    loop_teams) and the 3 topology_runs FK columns
    (research_topic_id, loop_id, iteration). parent_run_id stays;
    recursive_exec still uses it.

Files deleted (11):
  - crates/cm-api/src/routes/{research,loops,research_setup,
    research_pipeline,wizard_repo,probe}.rs
  - crates/cm-api/src/research_container.rs
  - crates/cm-db/src/repo/{research_topics,research_outcomes,
    research_publish_approvals,loops}.rs
  - crates/cm-runtime/src/loops.rs
  - crates/cm-api/tests/research_publish_role.rs

Files edited:
  - crates/cm-api/src/lib.rs — dropped 20 legacy route registrations
    (all /api/research/* + /api/loops/* + /webhooks/loops + probe)
    and module decls
  - crates/cm-api/src/topology_worker.rs — deleted legacy dispatch
    (freeze_research_outcome, advance_loop_after_completion,
    continue_initial_burst, maybe_transition_research_topic,
    parse_reorder_rationale, per-topic/loop gateway resolver).
    reap_stuck_runs now keys on mission_id (not topic_id).
    Executor path unconditionally uses ZeroClawDriveExecutor::from_env
    — mission_orchestrator provisions each claw as an agent inside
    the shared runtime via RuntimeProvisioner, so per-team gateway
    resolution is no longer applicable.
  - crates/cm-api/src/routes/topology.rs — deleted container-log SSE
    endpoint (research/loop-specific), dropped loop_id filter and
    iteration field from ListRunsQuery/RunSummary
  - crates/cm-api/src/routes/world.rs — removed
    active_research_topics/active_loops/preseed_repo_paths;
    World SSE no longer emits repo:{topic}/loop:{id} landmark orbs
    (follow-up task #21 tracks adding mission:{id} equivalents)
  - crates/cm-api/src/runtime_provision.rs — removed now-unused
    mint_workspace_service_token
  - crates/cm-db/src/repo/topology_runs.rs — removed 9 legacy
    helpers (research_topic_id lookup, loop_id_for_run,
    iteration_for_run, active_runs_for_research_topic, etc.)
  - crates/cm-db/src/repo/teams.rs — removed 4 dead helpers
    (team_for_loop, team_for_research_topic + setters)
  - crates/cm-api/tests/topology_jobs.rs — removed loop/topic
    tests, dropped enqueue_run_with_topic helper
  - crates/bins/clawmates-server/src/main.rs — removed
    spawn_loop_scheduler call
  - crates/cm-api/src/routes/mod.rs, crates/cm-db/src/repo/mod.rs,
    crates/cm-runtime/src/lib.rs — module decls stripped

sqlx cache: regenerated against post-migration schema
  (71 files changed, ~+70 / -8896 net)

Test/build: SQLX_OFFLINE=true cargo check --workspace clean;
cargo test --workspace --no-run clean.

Follow-up (task #21): World view lost the in-flight-work landmarks
when repo:{topic} / loop:{id} orbs disappeared. Add mission:{id}
orbs as the missions-era replacement.
2026-07-19 18:37:24 -07:00
Omar SobhandClaude Opus 4.7 9b5e63cbb7 slice 8.5: per-agent + per-team level-up endpoints
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
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
Omar SobhandClaude Opus 4.7 f40ec075a5 slice 5: task-card parser + background worker
ci / frontend (push) Successful in 25s
ci / rust (push) Successful in 4m26s
ci / e2e (push) Skipped
ci / gates (push) Successful in 6s
ci / publish (push) Successful in 2m42s
Watches topology_runs' event stream for the INT-XX marker protocol
(see skills/foundation/int-xx-marker-protocol.md) and materializes
mission_tasks rows with typed status so the canvas Tasks tab renders
a live timeline instead of raw agent chatter.

Migration 0051 adds mission_id + mission_phase_id columns to
topology_runs (nullable) so runs enqueued by a mission phase can be
attributed. Populated by future phase executors; NULL for legacy
research/loops runs (parser skips them cleanly).

New Rust surface:
  - task_card_parser::parse(text) — line-scanner over TASK/WORK/
    HANDOFF/TEST_PASS/TEST_FAIL/REVIEW_APPROVE/REVIEW_BLOCK/COMPLETED
    markers. Strict: exact kind + colon + INT- prefix, no in-prose
    matches, no bold/code-fence wrappers.
  - task_card_parser::apply_for_run(pool, run_id) — reads the run's
    mission binding, walks its event payloads, extracts text/output/
    content/message string fields (matching every ZeroClaw event
    shape we see), parses markers, UPSERTs mission_tasks via the
    (phase_id, external_id) unique key from Slice 1.
  - task_card_worker::spawn — 15s poller over runs updated in the
    last 5 minutes. Idempotent + generous window survives server
    restarts + task-scheduling jitter.

Boot wires the worker after the content loaders. Silent no-op when
mission wiring isn't populated yet.

MarkerKind → status mapping (monotonic-forward):
  TASK           → created
  WORK           → working
  HANDOFF        → validating
  TEST_PASS      → validating
  TEST_FAIL      → failed
  REVIEW_APPROVE → validating
  REVIEW_BLOCK   → failed
  COMPLETED      → complete

Follow-ups:
  - Wire phase executor to populate topology_runs.mission_id +
    mission_phase_id (Slice 6/7/8 work)
  - Assign assigned_agent_id via the event's producing agent alias
    (currently always None)
  - SSE stream on /api/missions/{id}/tasks for live canvas updates
    (currently the canvas polls via mission GET)

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-07-19 15:04:57 -07:00
Omar SobhandClaude Opus 4.7 85a97dffca slice 3.5d: agent_template_link + brain seed helper + skills merge
ci / gates (push) Successful in 4s
ci / frontend (push) Successful in 38s
ci / rust (push) Failing after 3m1s
ci / e2e (push) Skipped
ci / publish (push) Skipped
Ships the lineage layer that ties agents back to their team template
and wires the MCP skills server to actually merge template default
skills with per-agent overrides.

Migration 0050 adds `agent_template_link` (agent_id PK, template_id,
template_version, role_slot, seeded_at, created_at + indexes for
template/role lookups). Populated at agent-materialization time by
Slice 4's mission-launch orchestrator; read here by the skills MCP
server and by future level-up (Slice 8.5).

New Rust surface:
  - cm_db::repo::agent_template_link  (upsert / get / mark_seeded /
    agents_for_template — the last is what level-up's "prompt upgrade
    on template N+1" query needs)
  - cm_api::brain_seed::ingest(claw_id, seed_md, identity_prompt)
    opens cm_brain::ClawBrain on spawn_blocking, sets system_prompt
    on first touch, writes seed as agent_md, commits. Idempotent —
    skips when agent_md already populated.
  - cm_api::mcp_skills::mcp_skills tools/call now resolves the caller
    agent's template + role via agent_template_link and merges
    template default skills with per-agent overrides (was overrides-
    only in Slice 3.5b).
  - cm_api::team_template_loader now binds template_role_skills after
    upserting each template — looks up each declared skill by name,
    attaches with pin_in_context=true for foundation skills and the
    first two role skills. Missing skills log + skip.
  - Boot ordering: skills load BEFORE team templates so the binding
    lookup resolves.

Follow-up (Slice 4): mission-launch orchestrator calls brain_seed::ingest
+ agent_template_link::upsert when minting a team from a template.
Until that lands, the link is populated only by manual writes; the
MCP merge is silent-no-op for agents without a link (falls through
to overrides-only), which matches the pre-3.5d behavior.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-07-19 14:16:16 -07:00
Omar SobhandClaude Opus 4.7 1153d72d00 slice 3.5a: skills catalog — the "how to think" layer
ci / gates (push) Successful in 4s
ci / frontend (push) Successful in 38s
ci / rust (push) Failing after 1m28s
ci / e2e (push) Skipped
ci / publish (push) Skipped
Introduce the skills catalog: the second half of the two-layer agent
model (skills teach agents HOW to think about a problem; MCP servers
give them the ABILITY to act). Delivery via MCP resources lands in
Slice 3.5b; this slice ships the data model + API surface.

Migration 0049 extends the legacy `skills` table (from 0001_init.sql,
originally a workspace catalog of markdown snippets) with the richer
typing we need — name, when_to_use, tags, source_kind, current_version
— rather than duplicating tables. Also adds:
  - skill_versions       (version history for level-up promotions +
                          rollback; back-pointer via promoted_from
                          JSONB records agent_id / research artifact /
                          brain memory that produced it)
  - template_role_skills (m2m binding skills to team-template roles
                          with pin_in_context + order_idx)
  - agent_skills_ext     (per-agent overlay: include=true adds a skill
                          to the bundle; include=false prunes a
                          template default for this specific agent)

Rust surface:
  - cm_db::repo::skills_catalog with typed Skill/SkillVersion/
    AgentSkillBinding structs + upsert_builtin (idempotent — bumps
    version + appends to skill_versions ONLY when body changes) +
    list_visible/get/get_by_name reads + template + agent binding
    helpers + effective_for_agent (merges template defaults with
    agent overrides, applies exclude precedence, batch-fetches skill
    bodies)
  - cm_api::routes::skills_catalog with:
      GET /api/skills             — list visible
      GET /api/skills/{id}        — detail
      GET /api/claws/{id}/skills  — effective binding (accepts
        template_id + role_slot as query args to merge in template
        defaults)

Follow-ups:
  - Slice 3.5b: clawmates_skills MCP server exposes catalog as MCP
    resources, honoring pin_in_context for auto-injection
  - Slice 3.5c: seed ~40-60 builtin skills across the 6 stacks

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-07-19 13:00:56 -07:00
Omar SobhandClaude Opus 4.7 9ba5c06a1a slice 3: 6 team templates seeded from TOML recipes
ci / rust (push) Failing after 11s
ci / gates (push) Successful in 6s
ci / frontend (push) Successful in 28s
ci / e2e (push) Skipped
ci / publish (push) Skipped
Team templates are the canonical rosters + tool bundles that mint
concrete teams for a mission. Every builtin ships as a TOML recipe
under templates/teams/*.toml, loaded into the DB at server boot.

Migration 0048 adds:
  - team_templates    (id, key, name, stack, default_topology,
                       risk_profile, mcp_bundles, version, source,
                       workspace_id)
  - template_roles    (m2m: template_id + slot; system_prompt,
                       skills[], brain_seed)
  - teams gets template_id + template_version for level-up lineage

Ships 6 builtins:
  - rust_sdlc  — planner/coder/tester/reviewer/committer for Rust
  - backend    — api_designer/db_engineer/coder/tester/committer
                 (Postgres, DuckDB, graph DBs, wire protocols)
  - frontend   — designer/coder/tester/committer (React + Tailwind + ShadCN)
  - mobile     — designer/coder/tester/committer (Expo, RN, iOS, Android)
  - gpu        — arch_analyst/kernel_author/bench_engineer/coder/committer
                 (CUDA, Metal, ROCm from Rust)
  - threejs    — scene_designer/coder/shader_author/perf_engineer/
                 committer (three.js, WebGL, WebGPU)

Each role has a versioned system_prompt + skill list + brain_seed
markdown. Skills column is a name array today; Slice 3.5a promotes it
to a typed m2m join with the real skills catalog.

Server boot:
  - team_template_loader::load_builtins reads TOML from
    /etc/clawmates/templates/teams (container) or templates/teams (dev),
    upserts idempotently. Deterministic uuid per template key (sha256
    of a fixed namespace + key) so ids are stable across boots.
  - Dockerfile copies templates/ to /etc/clawmates/templates.

Read API:
  - GET /api/team-templates       — list all
  - GET /api/team-templates/{id}  — detail with roles

Wizard:
  - Step 3 rewired from a raw team_id text field to a template picker
    with "LLM auto-provision" as the default option + one card per
    builtin, showing stack, topology, risk profile, and description.
  - Mission create now passes team_template_id (not team_id) so phase
    execution knows which template to mint from.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-07-19 12:41:15 -07:00
Omar SobhandClaude Opus 4.7 fbefc67878 slice 1: missions data model + migration
ci / gates (push) Successful in 6s
ci / rust (push) Failing after 9s
ci / frontend (push) Successful in 25s
ci / e2e (push) Skipped
ci / publish (push) Skipped
Introduce the unified `missions` tier that will replace the current
research_topics + loops split. This slice ships the data model +
backfill + skeleton REST surface; the old wizards keep working in
parallel until Slice 9's big-bang cutover.

Migration 0047 adds:
  - missions              (top-level workflow: template_kind + team +
                           schedule + status + config)
  - mission_phases        (ordered {research|coding|benchmark|
                           security_scan} phases per mission)
  - mission_tasks         (typed units of work, e.g. INT-XX cards,
                           UPSERT-keyed on (phase_id, external_id))
  - mission_artifacts     (MD/PDF/benchmark/security/diff files with
                           a pending queue for the PDF renderer worker)
  - benchmark_snapshots   (before/after pairs per iteration)

Backfill copies existing research_topics + loops rows into the new
tables as one-shot missions with the appropriate template_kind, so
Slice 2's UI can render the full history immediately.

New Rust surface:
  - cm_domain: MissionId, MissionPhaseId, MissionTaskId, MissionArtifactId
  - cm_db::repo::missions: Mission/MissionPhase/MissionTask/
    MissionArtifact structs + insert (txn-wrapped)/get/list/set_status/
    phases_for/set_phase_status/upsert_task/tasks_for/register_artifact/
    artifacts_for/next_pdf_pending/set_pdf_result
  - cm_api::routes::missions: skeleton list/create/get/set_status
    routes registered at /api/missions/*

Follow-up slices layer richer behavior (template dispatch, phase
execution, task parsing, artifact rendering) on this foundation.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-07-19 11:40:51 -07:00
Omar Sobh d687a00524 teams: zeroclaw container coords + coding_readwrite risk profile
ci / publish (push) Successful in 2m46s
ci / gates (push) Successful in 5s
ci / frontend (push) Successful in 37s
ci / rust (push) Successful in 2m58s
ci / e2e (push) Skipped
Slice 3a of the per-loop-team arc — prerequisites for the runtime
spawn hookup that lands in 3b:

0046 migration
- ALTER TABLE teams ADD zeroclaw_container TEXT
- ALTER TABLE teams ADD zeroclaw_gateway_url TEXT
Both NULL until the runtime's spawn_team fn (3b) provisions the
container and persists its coordinates. Mirrors the shape already on
research_topics (0038) so the resolver code path can generalize.

cm-db
- team_container_coords / set_team_container_coords: dynamic
  sqlx::query() readers/writers for the new columns.

Runtime template (gw-04, out-of-band edit on
/var/lib/clawmates-runtime-template/config.toml + shared runtime
/root/clawmates-runtime/data/.zeroclaw/config.toml)
- New [risk_profiles.coding_readwrite]: adds file_write + shell on
  top of the research_readonly baseline. Still excludes http_request /
  browser / composio (egress stays behind the MCP door).

Slice 3b will add spawn_team (bind-mounts paired-topic repo, uses
team-scoped state dir, injects team.risk_profile into the config
template) and rewire topology_worker to resolve gateway URL through
team_id when the loop has one.
2026-07-16 22:02:58 -07:00
Omar Sobh 6066e93889 teams: per-team runtime posture (risk_profile + mcp_bundles) + FK from loops/topics
ci / gates (push) Successful in 6s
ci / publish (push) Successful in 2m19s
ci / frontend (push) Successful in 26s
ci / rust (push) Successful in 3m24s
ci / e2e (push) Skipped
Foundation slice for letting a coding loop bring its own team instead
of reusing the paired research topic's team. Turns out the teams
table already exists (0010_teams.sql) with full CRUD — this scales
back to the minimal missing bits:

Schema (0045_teams.sql)
- ALTER TABLE teams ADD risk_profile TEXT (NULL = template default)
- ALTER TABLE teams ADD mcp_bundles JSONB DEFAULT '[]'
- ALTER TABLE loops ADD team_id UUID REFERENCES teams ON DELETE SET NULL
- ALTER TABLE research_topics ADD team_id UUID REFERENCES teams
- Two partial indexes (team_id NOT NULL) for the future cascade queries

cm-db (dynamic sqlx::query so the existing get_team's compile-time
cache doesn't need regenerating):
- TeamRuntimeConfig struct
- get_team_runtime_config / set_team_runtime_config
- team_for_loop / team_for_research_topic (resolvers)
- set_team_for_loop / set_team_for_research_topic (binders)

cm-api
- GET /api/teams/{id} now surfaces risk_profile + mcp_bundles
- PATCH /api/teams/{id}/runtime-config sets them

Not touched (comes in follow-up slices):
- Wizard picker exposing 'reuse research team' vs 'fresh coding team'
- Runtime container spawn keyed on team_id
- Migration of existing paired coding loops onto their own team
2026-07-16 18:16:28 -07:00
Omar Sobh 4ada5557f2 loops: kind column + initial_burst + on_artifact_update trigger fan-out
ci / gates (push) Successful in 6s
ci / publish (push) Has been skipped
ci / frontend (push) Successful in 28s
ci / rust (push) Failing after 1m0s
ci / e2e (push) Has been skipped
Foundation for folding research into loops as a first-class kind.
This commit ships the plumbing; the research-kind dispatch itself
lands next. Behavior for existing exec-kind loops is unchanged unless
they opt into the new trigger fields.

Migration 0044:
- kind TEXT NOT NULL DEFAULT 'exec' CHECK ('exec' | 'research'). New
  research-kind will run the research pipeline each iteration (next
  commit); 'exec' preserves today's behavior.
- initial_burst_remaining INT NOT NULL DEFAULT 0 — countdown for the
  triggers.initial_burst quota. Decremented CAS-safely on each
  completion until it hits 0.
- Two partial indexes: (kind, source_research_topic_id) for kind-
  aware lookups, and (source_research_topic_id) filtered on
  on_artifact_update=true + enabled=true for the fan-out hook.

Trigger schema extended with two optional fields:
- initial_burst: N — fire N iterations back-to-back at create time.
  create_loop enqueues the first iteration inline (subject to
  empty-roster gate), sets remaining=N-1, and the completion hook
  continues the chain until exhausted.
- on_artifact_update: true — when a bound research_outcomes row is
  inserted for the source topic, wake up one iteration of this loop.
  Coalesced against has_active_run so a burst of rapid revisions
  doesn't queue duplicates.

Backend:
- cm_db::repo::loops helpers (all dynamic sqlx, no offline cache
  regen needed):
  - set_initial_burst_remaining
  - take_initial_burst_slot (CAS UPDATE returning prev value; 0 on
    exhausted or race loss)
  - loops_awaiting_topic (fan-out query: kind=exec + enabled +
    on_artifact_update=true bound to the given topic)
  - has_active_run (queued|running iteration existence check)
  - get_any_workspace (bypasses the workspace scope guard; used by
    the completion hook where the run row is authoritative)
- routes/loops::compose_iteration_task made pub so the completion
  hook can build the same enriched task string as run_now.
- topology_worker::freeze_research_outcome now fans out to awakened
  loops after the outcome insert, using compose_iteration_task and
  coalescing on has_active_run.
- topology_worker::continue_initial_burst runs on every completion:
  · take_initial_burst_slot (CAS) — no-op if already exhausted
  · has_active_run coalesce guard
  · re-fetches the loop via get_any_workspace + compose_iteration_task
  · enqueues via loops::enqueue_iteration with parent_run_id set

Follow-ups already queued:
- kind='research' dispatch in run_job — build the research
  coordinator task from the topic config, run the research pipeline
  each iteration. Requires factoring start_topic's task-build.
- ResearchWizard "When should this run?" step (Just once / Nightly /
  Manual) creating the topic + paired research-kind loop.
- LoopsWizard trigger UI matching the design proposal (burst count,
  cron, on-artifact checkbox).
2026-07-09 22:50:01 -07:00
Omar Sobh 0c17de52dd loops: reorder rationale extraction — REORDER: markers logged per iteration
ci / gates (push) Successful in 7s
ci / frontend (push) Successful in 27s
ci / rust (push) Failing after 43s
ci / e2e (push) Has been skipped
ci / publish (push) Has been skipped
Coordinator can now log WHY it worked on an INT-XX out of order
("REORDER: INT-05 before INT-04 because prereq X is unmet") and the
completion hook captures each rationale as an append-only event on
the loop. Sets up a reviewable timeline of when the plan was
adjusted, independent of the underlying `consumed_int_ids` advance.

Migration 0043:
- loops.reorder_events JSONB NOT NULL DEFAULT '[]'::jsonb — append-
  only array of {run_id, iteration, text, ts}. Kept on the loop row
  (rather than a dedicated table) so the mini-timeline is one read
  away from the loop card.

Backend:
- topology_worker::parse_reorder_rationale — line matcher symmetric
  with parse_completed_int_ids. Tolerates list dashes / prefixes /
  markdown emphasis; case-insensitive marker match, preserves case of
  the rationale text.
- cm_db::repo::loops::append_reorder_event — one INSERT-like append
  per rationale, uses jsonb_build_object with postgres now() so ts is
  wall-clock canonical (no client-clock skew).
- topology_runs::iteration_for_run — new helper so events carry the
  iteration index.
- routes::loops::compose_iteration_task — coordinator prompt now
  explicitly asks for `REORDER: <one-sentence>` at the top of the
  first substantive turn when working out of order, AND spells out
  that both markers must appear literally with colons (no bold, no
  code fence) so the line parser doesn't miss them.

Non-loop and standalone-loop runs are unaffected — the hook only
fires when the run belongs to a source-bound loop.

Follow-up: expose reorder_events on the loops list endpoint + render
a small collapsed timeline on the LoopsList card.
2026-07-09 18:58:55 -07:00
Omar Sobh ce73abe5ab loops: bridge research artifact into loop iterations (option C + b)
ci / rust (push) Failing after 13m41s
ci / e2e (push) Has been skipped
ci / publish (push) Has been skipped
ci / gates (push) Successful in 7s
ci / frontend (push) Successful in 37s
The bridge lets a coding loop "consume" an integrations research
artifact one INT-XX item per iteration. Options b (order-sequential
iteration) and C (snapshot in task_template + save the pointer for
future refresh) from the design discussion.

Migration 0042 — three new loops columns:
- source_research_topic_id — nullable pointer to research_topics.
- consumed_int_ids TEXT[]   — INT-XX ids the loop has completed.
  Advances when topology_worker parses "COMPLETED: INT-<NN>" markers
  from the run's final output (wired in a follow-up commit).
- current_int_index INT     — monotonic pointer for order-sequential
  iteration. Coordinator addresses INT-<current+1> unless prereqs are
  unmet, in which case it works on the smallest unblocking INT-XX and
  logs the reorder rationale.

Backend:
- cm_db::repo::loops::set_source_research_topic — bind/unbind pointer.
- cm_db::repo::loops::source_research_context   — read pointer + state.
- routes::loops::compose_iteration_task         — new caller-side helper
  that reads the pointer, fetches the topic's latest research_outcome,
  and prepends the artifact + focus instruction to task_template.
- run_now + webhook_receive both pass task_template through
  compose_iteration_task before enqueue. Standalone loops (no pointer)
  behave identically to before.
- CreateLoopRequest accepts `source_research_topic_id`, ownership-
  checked via research_topics::get before persist.

Frontend:
- New ResearchArtifactPicker modal — lists published topics, fetches
  the artifact on pick, returns (topic_id, markdown) to caller.
- LoopsWizard task_template step gains "Import from research artifact"
  button (right-aligned). Click opens the picker. On pick: task
  populates with the artifact markdown, pointer saved, textarea
  expands to 8 rows, small info strip shows "Loop is bound to topic
  <id>. Each iteration will focus on the next unconsumed INT-XX."
- Unlink button reverts to standalone loop mode.

Follow-up (next commit):
- topology_worker completion hook — parse "COMPLETED: INT-<NN>" out
  of the run's final output + update consumed_int_ids +
  current_int_index atomically. Without this, current_int_index stays
  at 0 forever and every iteration works on the same INT.
- Loop card refresh button — re-read source topic's latest outcome
  (useful after a reject-with-revision cycle on the source topic).
2026-07-09 18:47:17 -07:00
Omar Sobh 3ed1d03d2b research: integrations outcome + rich wizard cards + coordinator template
ci / e2e (push) Has been skipped
ci / rust (push) Successful in 22m15s
ci / publish (push) Successful in 2m40s
ci / gates (push) Successful in 6s
ci / frontend (push) Successful in 39s
Adds a fifth outcome kind ('integrations') tuned for the "audit repo,
survey papers, propose a menu of concrete integrations" use case.
Every INT-XX item is self-contained (what, how, where, prereqs, effort,
risk, testing, rollback, acceptance) so a downstream loop can execute
one per iteration.

Backend:
- Migration 0041 drops + re-adds the outcome_kind CHECK constraint
  with 'integrations' allowed. Existing rows unaffected.
- VALID_OUTCOMES gains 'integrations'.
- New deliverable_template(kind) returns the canonical section shape
  for each outcome — spec, prod_plan, roadmap, paper, integrations all
  get first-class treatment (prior: all shared a bare label).
- build_coordinator_task injects an ARTIFACT SHAPE block from the
  template into the coordinator prompt, so the final synthesis
  actually matches the promise the wizard made.

Frontend:
- OutcomeKind gains 'integrations'.
- ResearchWizard OUTCOMES list carries a `sections` array per kind.
- Selected card renders an "ARTIFACT WILL CONTAIN" preview so users
  pick by seeing what they'll get, not by reading a one-line hint.
- Integrations card gets the fullest preview (executive summary +
  INT-XX card shape) since it's the most structured deliverable.

Follow-ups queued (next commit): loop wizard "Import from research
artifact" bridge + one-INT-per-iteration mode.
2026-07-09 18:42:46 -07:00
Omar Sobh 03f1830d1f loops: Path B container isolation (P2)
ci / gates (push) Successful in 6s
ci / rust (push) Failing after 11s
ci / publish (push) Has been skipped
ci / frontend (push) Successful in 28s
ci / e2e (push) Has been skipped
Symmetric with the research pipeline: every enabled loop can now have
its own per-loop team container so scheduled runs don't share state
with other loops or with research. Same daemon image, same clawmates
network, deterministic name loop-<id>-team.

Backend surface:
- Migration 0040 adds nullable `zeroclaw_container` +
  `zeroclaw_gateway_url` columns to loops (parallel to
  research_topics).
- research_container.rs grows loop_container_name_for(), spawn_loop()
  (state-only mount, no repo), and teardown_loop(). Kept in the same
  module to share the docker connect() + inherited_env() plumbing;
  each pattern gets its own labels (clawmates.role=loop-team) so ps
  filters can tell them apart.
- cm_db::repo::loops gains set_zeroclaw_container() +
  zeroclaw_gateway_url() (dynamic sqlx queries — no offline cache
  regen needed).
- cm_db::repo::topology_runs gets loop_id_for_run(): mirror of
  research_topic_id, used by the worker.

Wiring:
- routes/loops::run_now + webhook_receive call ensure_loop_container()
  before enqueuing an iteration. Idempotent: an already-running
  container is just reattached. Failures are logged and do NOT block
  the enqueue — topology_worker falls back to the workspace gateway
  when the URL isn't set on the loop.
- routes/loops::disable_loop + delete_loop both fire teardown_loop()
  so paused / deleted loops don't hold a docker slot.
- topology_worker's per-run URL resolution: existing research fast
  path unchanged; when it doesn't hit, the worker now looks up
  loop_id and reads the loop's gateway URL.

Deploy step (required on gw-04 for state to persist across container
restarts): add a `/var/lib/clawmates-loops:/var/lib/clawmates-loops`
bind mount + `CLAWMATES_LOOPS_STATE_ROOT=/var/lib/clawmates-loops`
env var to clawmates_server_1 in the compose. Without it, loops still
run — the state dir lives inside the API container's filesystem so
persistence is limited to that container's lifetime.

Follow-up:
- Scheduler-tick fires (cron-driven, not run_now) — they call
  enqueue_iteration in cm-scheduler and don't yet go through
  ensure_loop_container. Add a symmetric spawn there so cron fires
  also land on the isolated daemon.
- Compose file reconciliation — deploy/compose/docker-compose.yml in
  the repo has drifted from prod; when we sync it, add the loops mount
  at the same time.
2026-07-09 16:02:08 -07:00
Omar Sobh e3011ed025 research: reject-with-revision loop (R2)
ci / gates (push) Successful in 6s
ci / rust (push) Failing after 14s
ci / frontend (push) Successful in 28s
ci / e2e (push) Has been skipped
ci / publish (push) Has been skipped
Before: reviewer rejected a publish → audit log flipped, topic stayed
in reviewing, no way to feed the critique back into the run pipeline.
Reviewers with revision notes had to eat them or hand-message the
coordinator.

Now: reject accepts an optional `notes` field. When present:
- Persisted on the research_publish_approvals row (migration 0039).
- Topic flips `reviewing → standby` so the next `start_topic` is legal.
- `start_topic` reads the most recent rejected-approval notes for the
  topic and prepends "PRIOR REVIEW NOTES (address these in this
  revision):\n<notes>\n---" to the coordinator task.

Loop closes through the same run pipeline — no new spawn code path,
which means the reviewer's guidance flows through the same
topology_worker, run_events, outcome-writer chain and lands as a fresh
research_outcomes row (versioned, prior drafts preserved). No notes on
reject = legacy behavior (topic stays in reviewing, publish requests
still allowed).

Migration 0039 adds nullable `notes TEXT` to
research_publish_approvals. `decide()` gains a `notes: Option<&str>`
parameter (only one caller, updated inline). New
`latest_rejection_notes(pool, topic_id)` helper for start_topic.

Frontend:
- rejectPublish(id, notes?) now sends a JSON body when notes are
  provided.
- ResearchCanvas reject button opens an inline form with a textarea +
  Cancel/"Send back for revision" pair. Empty notes → plain reject.
- Button label switches: "Send back for revision" when notes present,
  "Reject without notes" when empty.

Follow-up:
- Notes shown in the review UI on the resulting draft so the next
  reviewer sees what changed.
- Multiple rejection rounds — currently only the LATEST rejection's
  notes surface. Accumulating history is a schema-only tweak.
2026-07-09 15:56:16 -07:00
Omar Sobh 21ac35c8d4 research: spawn per-topic ZeroClaw team container on start (commit 1/3)
ci / frontend (push) Successful in 29s
ci / publish (push) Successful in 2m27s
ci / gates (push) Successful in 6s
ci / rust (push) Successful in 2m40s
ci / e2e (push) Has been skipped
Commit 1 of the path-B (real per-topic isolation) plan. The
container spawns and its coordinates persist — nothing talks to
it yet; commit 2 wires ZeroClawDriveExecutor to prefer the topic's
URL when populated. This split keeps each landing verifiable.

Backend

- Migration 0038: research_topics gets zeroclaw_container_name +
  zeroclaw_gateway_url columns. Both nullable so a topic can exist
  before a spawn and teardown just NULLs them out.

- cm-db: ResearchTopic struct extended; get/list SELECTs updated;
  new set_zeroclaw_container(id, workspace_id, name, url) helper
  used both for spawn (Some/Some) and teardown (None/None).

- cm-api: bollard added as a workspace dep (matches cm-sandbox's
  version). New research_container module:
    · connect() → uses DOCKER_HOST when set (prod's socket-proxy
      at tcp://socket-proxy:2375) else the local socket. Same
      pattern cm-sandbox already uses.
    · container_name_for(topic_id) → "research-<uuid>-team"
      (deterministic so a re-start reattaches to the same
      container instead of orphaning it).
    · inherited_env() → propagates ZEROCLAW_*, OPENAI_*,
      ANTHROPIC_*, GEMINI_*, GROQ_* from the parent server env
      (provider config + tokens), stripping the server's own
      ZEROCLAW_GATEWAY_URL/WORKSPACE so the team runtime doesn't
      loop back on itself. Appends ZEROCLAW_GATEWAY_PORT=42617
      and ZEROCLAW_WORKSPACE=/zeroclaw-data/workspace for the
      team's own listener.
    · spawn(docker, topic_id, repo_host_path, state_host_path):
        - inspect: if the container already exists, start it if
          stopped and return its coordinates (idempotent restart).
        - else create with:
            image  = CLAWMATES_RESEARCH_TEAM_IMAGE or
                     clawmates-runtime:latest
            cmd    = [daemon, --host, 0.0.0.0]
            env    = inherited_env()
            mounts = repo_host_path → /workspace/repo (rw)
                     state_host_path → /zeroclaw-data (rw)
            network = CLAWMATES_RESEARCH_TEAM_NETWORK or
                      clawmates_core
            labels = clawmates.role=research-team,
                     clawmates.research.topic_id=<uuid>
        - creates state_host_path first so bind doesn't ENOENT.
    · stop(docker, name) → stop + remove. Idempotent on 404/304.

- start_topic wires spawn after the clone completes:
    · state root = CLAWMATES_RESEARCH_WORKSPACE_ROOT / <topic> /
      state
    · on success, persists (name, url) on the topic row so commit
      2 can look them up when constructing the executor
    · every failure (docker connect, docker create/start, DB
      persist) is best-effort: logs and continues. A missing team
      container leaves the topic pointing at the workspace-wide
      gateway URL (env), preserving prior behavior.

Deploy prerequisites (not in this commit)

- The compose stack's clawmates_server service needs bind-mounts
  of CLAWMATES_RESEARCH_WORKSPACE_ROOT (e.g.
  /var/lib/clawmates-research:/var/lib/clawmates-research) so
  paths the server writes to are visible on the host and the
  spawned team container mounts the same underlying data.
- socket-proxy's ACL must allow POST + DELETE on /containers
  (already the case in prod per the audited compose file).
2026-07-09 04:14:26 -07:00
Omar Sobh 3465bb7a6d research: persist bound repo + shallow-clone on start_topic
ci / frontend (push) Successful in 35s
ci / e2e (push) Has been skipped
ci / publish (push) Has been skipped
ci / gates (push) Successful in 7s
ci / rust (push) Failing after 38s
This is the minimum viable version of the "agents actually work on
a repo" architecture. Full vision (isolated ZeroClaw container per
topic, dynamic agent provisioning inside, pause/resume, commit
gate) is real weeks of work — this closes the first, most-visible
gap so the ClawHDF5 topic can actually run against its codebase.

Backend

- Migration 0037: research_topics gets repo_id UUID (nullable, FK
  to repos ON DELETE SET NULL) and repo_workspace_path TEXT for
  the on-disk checkout location. Index on repo_id when set.

- research_topics::create takes repo_id: Option<Uuid>. get + list
  select it and repo_workspace_path. set_repo_workspace_path
  persists the path once the first clone lands.

- CreateTopicRequest accepts `repo: Option<TopicRepoRef>` — the
  same denormalized shape the wizard already sends. Only repo_id
  is authoritative; other fields are ignored (dead_code-allowed
  so serde still deserializes the full body).

- start_topic branches on topic.repo_id. When set, it calls
  ensure_repo_workspace:
    · resolves repo.clone_url + repo.default_branch
    · target path = CLAWMATES_RESEARCH_WORKSPACE_ROOT
                    // <topic_id> // repo (defaults under $TMPDIR)
    · runs `git clone --depth 1 --single-branch --branch <b>` via
      tokio::process. Reuses the checkout if .git already exists.
    · persists the path so re-starts skip the clone
    · runs `git ls-files` to sample the tree (first 60 entries,
      total count reported honestly so the prompt doesn't lie
      about coverage)
  All best-effort — a clone failure logs but still starts the run
  without repo context rather than aborting.

- build_coordinator_task takes Option<&RepoContext>. When present,
  the framing gets a REPO block (slug / path / branch / file
  sample) and a USING THE REPO section instructing the coordinator
  to ground every recommendation in a concrete file reference and
  never fabricate paths. The per-topology bodies are unchanged —
  the repo guidance sits above them so it applies to every shape.

What this unblocks / doesn't unblock

Unblocks: The coordinator prompt now knows the repo exists, where
it lives on disk, and what's in it. Even without file-editing
tools wired to the checkout, the coordinator can point spokes at
concrete modules and the final artifact can reference real files.
For a spec-shaped outcome like ClawHDF5's, that's the difference
between abstract advice and a spec grounded in the actual crates.

Does NOT unblock: The agents themselves editing files, running
tests, or committing. That requires either mounting the checkout
into the ZeroClaw sandbox or exposing a new MCP tool for
repo-scoped file ops — separate follow-up.
2026-07-08 21:38:00 -07:00
Omar Sobh a2d3d85ebe research pipeline v2: topology-aware start + persisted draft
ci / rust (push) Successful in 2m42s
ci / gates (push) Successful in 7s
ci / frontend (push) Successful in 37s
ci / e2e (push) Has been skipped
ci / publish (push) Successful in 4m2s
Three connected changes that turn "Start research" from a status
flip into a real pipeline that produces a reviewable artifact:

- Migration 0036: adds research_topics.topology_kind (default
  'hub_spoke') and a new research_outcomes table
  (id, topic_id, version DESC, body_md, produced_by_run_id, created_at)
  so each run's final synthesis is versioned and persistent.

- Wizard now has a topology picker in the Outcome step —
  hub_spoke / pipeline / hierarchical / star_moe — with copy that
  steers users to the right shape (Pipeline for research → distill
  → analyze → implement rosters, hub_spoke for the coordinator-
  and-specialists default).

- start_topic reads the chosen topology_kind, parses it into a
  cm_topology::TopologyKind, and dispatches a per-shape coordinator
  prompt via build_coordinator_task. Pipeline explicitly tells
  stage 1 not to write the final artifact and propagates a
  "final stage MUST emit a complete markdown document with
  measurable acceptance criteria" instruction downstream. The
  graph builder is called with the topology the user actually
  picked instead of hard-coded HubSpoke.

- topology_worker::freeze_research_outcome fires after every
  successful complete(). It looks up research_topic_id on the run;
  if set and final_output is non-empty, it inserts a new
  research_outcomes row (version auto-derived server-side via
  coalesce(max(version), 0) + 1). Best-effort — a DB hiccup logs
  but doesn't fail the run.

- TopicDetail now includes topology_kind and latest_outcome.
  ResearchCanvas swaps in the outcome's body_md (rendered as
  pre-wrap markdown, versioned header, produced-at timestamp)
  whenever an outcome exists; the original prompt collapses into
  an "Original prompt" <details> below so it's still one click
  away. Pre-run topics still show the description as before.

Follow-ups still open: reject-with-revision loop feeding the
coordinator, publishing → published transition + real artifact
export (md / pdf), and an approvals inbox surface for reviewers.
2026-07-08 17:13:46 -07:00
Omar Sobh a6da19430f loops: repo picker + agent/team/org staffing + sidebar edit/delete
ci / gates (push) Successful in 6s
ci / rust (push) Failing after 12s
ci / frontend (push) Successful in 25s
ci / e2e (push) Has been skipped
ci / publish (push) Has been skipped
Adds the missing pieces the wizard needed and the sidebar controls
around it:

- LoopsWizard is now a 6-step flow (identity → repo → task/topology
  → triggers → repeat → assign agents) plus the existing secrets
  card. ResearchWizard picks up the same repo step and a hard gate
  when the workspace has zero agents.
- New LoopStaffingStep with three tabs — Individual / Team /
  Organization — that mix freely per loop; selections persist via
  new loop_agents / loop_teams / loop_orgs join tables (0035
  migration), each cascading on loop_id so hard-delete stays a
  single-row DELETE.
- Backend CreateLoopRequest / UpdateLoopRequest accept the three
  lists and apply_staffing does a transactional replace-all;
  list_loops / get_loop hydrate the lists via a flattened
  LoopWithStaffing response.
- LoopsList sidebar gains per-row enable/disable, edit (reopens the
  wizard prefilled with the current loop, PATCHes on submit), and
  delete with an inline confirm.
- NoAgentsGate blocks launching a loop or research topic from a
  workspace with no roster; the sidebar `+` buttons also disable
  with a tooltip pointing at the TEAM tier.

Not yet wired: the run driver still fills role slots from the
workspace-wide pool; teaching enqueue_iteration to prefer
loop_agents/loop_teams/loop_orgs is a follow-up.
2026-07-07 22:09:06 -07:00
Omar Sobh 6d087bf537 repos: backend — schema, /api/repos routes + GitHub sync provider
Migration 0034: two tables. repo_connections carries the workspace's
per-provider config (owner, base_url, label, last_synced_at,
last_sync_error) and points at an app_connections row for the PAT.
repos is the per-connection cache with (connection_id, external_id)
unique so upsert is idempotent across re-syncs. Cascading deletes clean
up cleanly on connection removal.

cm-secrets grows a FetchAuthorized op — GET with the stored PAT injected
as bearer, returns status + JSON body without ever exposing the
credential to cm-api. This is the least-privilege door for read-only
provider APIs (list repos), distinct from the InvokeHttp path that still
requires a single-use approval grant for outbound writes.

cm-api::routes::repos wires:
- POST /api/repos/connections (broker store_secret + insert both rows +
  initial sync + mark_synced)
- GET /api/repos/connections
- DELETE /api/repos/connections/:id
- POST /api/repos/connections/:id/sync
- GET /api/repos (500 cap, newest provider_updated first)
- GET /api/repos/:id (full detail incl. clone_url + html_url)

GitHub provider inline for v1 — paginated pull of /orgs/:owner/repos
(when owner set) or /user/repos (when absent), 100/page, capped at 20
pages (~2k repos) to keep first-sync latency bounded. Non-2xx surface
back to the caller as sync_error; parse failures are best-effort per
repo (skipped, logged, don't abort the batch).

Gitea + GitLab providers land in a follow-up — mostly URL swap +
response-shape adapter.
2026-07-07 14:52:47 -07:00
Omar Sobh 806ba869e5 teams: ephemeral lifecycle for Scheduled + Triggered planner modes
ci / gates (push) Successful in 6s
ci / frontend (push) Successful in 38s
ci / rust (push) Successful in 3m6s
ci / e2e (push) Has been skipped
ci / publish (push) Successful in 2m23s
Migration 0033: adds teams.lifecycle ('permanent' | 'ephemeral') and a
topology_runs.team_id back-ref with a partial index for the sibling-in-
flight check.

cm-db repo:
- teams::insert_team_with_lifecycle (insert_team keeps the permanent default)
- topology_runs::enqueue_run_for_team (populates team_id)
- topology_runs::check_ephemeral_teardown — atomic SELECT that only
  returns Some when the team is ephemeral AND no siblings are still
  queued/running; carries the workspace + bound claw ids for cleanup.

cm-api:
- topology_worker post-terminal hook maybe_teardown_ephemeral_team
  runs deprovision_claw on each bound claw (best-effort; failures log
  but don't block Postgres deletion), then hard_purge each agent row,
  then delete_team.
- routes::teams::build_team_with_lifecycle (build_team keeps default);
  run_team enqueues with team_id.
- planner ScaffoldRequest gains mode; lifecycle_for(mode) sets the team
  to ephemeral for scheduled + triggered, permanent otherwise.

Frontend MasterPlannerModal passes mode in the scaffold payload so the
backend can derive lifecycle without duplicating the mode taxonomy.

Tests: 3 new (returns claws when no siblings, holds when siblings queued,
ignores permanent teams). 10/10 topology_jobs green; workspace clippy
--tests clean.
2026-07-07 04:30:07 -07:00
Omar Sobh 973eeb272e research: publish approval gate + explicit state transitions
ci / gates (push) Successful in 6s
ci / frontend (push) Successful in 24s
ci / rust (push) Successful in 3m44s
ci / publish (push) Successful in 2m14s
ci / e2e (push) Failing after 29m59s
Fourth commit of the Research + Loops arc. Completes the state machine
for research topics with the publish approval gate the spec asked for.

Migration 0032 — research_publish_approvals
  Dedicated small table (id, workspace_id, topic_id, requested_by,
  status, decided_by/at, created_at). Keeping it separate from the
  existing `approvals` table (0001) because that one is tightly coupled
  to gated tool calls inside an agent run — session_key + run_id +
  action_type + category + payload + preview + requested_by_agent, all
  NOT NULL. Forcing those nullable would ripple through cm_safety;
  cleaner to give publish approvals their own two-transition state
  machine.

New endpoints
  POST /api/research/:id/submit-review               processing → reviewing
                                                     (v1 caller-driven; the
                                                     orchestrator hook comes
                                                     when we wire actual runs)
  POST /api/research/:id/request-publish             creates a pending
                                                     approval. Rejects with
                                                     409 if the topic already
                                                     has one open.
  GET  /api/research/publish-approvals               list workspace's pending
  POST /api/research/publish-approvals/:id/approve   flips approval to
                                                     approved + transitions
                                                     the topic
                                                     reviewing → publishing
                                                     (which stamps
                                                     published_at)
  POST /api/research/publish-approvals/:id/reject    stays in reviewing; new
                                                     requests allowed

The approve/reject write is an atomic UPDATE ... WHERE status = 'pending';
the decide() repo function returns whether the caller won the race so
concurrent double-approves collapse to a single topic transition.

State machine after this commit:
  standby ─POST /start─▶ processing ─POST /submit-review─▶ reviewing
    ─POST /request-publish + approve─▶ publishing ─(future: artifact
    assembly)─▶ published
2026-07-06 06:21:29 -07:00
Omar Sobh 258113e4d8 migrations: 0030 research_topics + 0031 loops schema
ci / gates (push) Successful in 5s
ci / frontend (push) Successful in 24s
ci / rust (push) Successful in 3m44s
ci / publish (push) Successful in 3m6s
ci / e2e (push) Failing after 29m54s
First commit of the Research + Loops feature arc. Schema only — routes,
runtime hooks, and the UI arrive in subsequent commits.

0030 — research_topics
  Container entity with a small state machine (standby → processing →
  reviewing → publishing → published). Owns runs via a nullable
  topology_runs.research_topic_id FK, so all existing SSE/audit/gated-
  approval plumbing surfaces without changes. Many-to-many join table
  captures the role_slot the wizard assigns each agent ("lead", "critic",
  "writer") so the canvas can group avatars sensibly.

0031 — loops
  Durable recurring topology execution. graph + task_template pair with a
  triggers JSONB (any of cron / on_completion / webhook, all can be on
  simultaneously) and a repeat_policy (infinite / N iters / until). Every
  iteration writes a topology_runs row with loop_id, iteration (1-indexed),
  and parent_run_id chained back to N-1 — cross-iteration context comes
  from that hop, no extra state store needed. Webhook auth is HMAC-SHA256
  keyed by webhook_signing_key. Missed cron windows fire once and skip the
  backlog (see comment header).

Both migrations only ADD tables/columns and use ON DELETE SET NULL for the
back-refs, so they're safe to run against prod without downtime. The
existing indexes on topology_runs keep serving legacy (non-research,
non-loop) runs unchanged.

Publish approval extension (approvals.kind for the reviewing → publishing
gate) comes as a separate migration in the publish-gate commit.
2026-07-06 04:14:06 -07:00
Omar Sobh 0c57f52502 migrations: 0029 — targeted indexes for hot queries
Each index added after reading its call site; no just-in-case coverage.
- outbox_queued_idx: partial (created_at) WHERE status='queued'. The
  drainer pops the oldest queued row workspace-agnostically; the existing
  (workspace_id, created_at DESC) index doesn't help.
- audit_log_workspace_event_idx: (workspace_id, event_type, created_at DESC).
  Rate limiting fires on every door tool call and A2A invocation; counts
  scan the recent tail.

Tables whose only access pattern is a PK lookup were left alone.
2026-07-05 18:48:55 -07:00
Omar Sobh e5e049921f migrations: 0028 — backfill ON DELETE clauses on legacy FKs
Retrofits the ON DELETE pattern learned after v1 shipped (CASCADE for
tenant-scoped children, SET NULL for historical references, RESTRICT where
the domain type is non-Option) onto tables from 0001-0006 and 0026. Two
deliberate exceptions kept as NO ACTION: audit_log.workspace_id (audit is
append-only and must outlive workspace deletes) and thread_messages.from_agent
(history stays attributable via agents.deleted_at). Two stay NOT NULL as
RESTRICT (agents.managed_by, installed_skills.installed_by) because the
Rust domain type is UserId, not Option<UserId>.

Also includes the previously-orphan .sqlx cache for the usage_events query
in cm-runtime/tests/run_loop.rs, which needed re-recording after the FK
changes touched the metadata.
2026-07-05 18:48:48 -07:00
Omar SobhandClaude Opus 4.8 cbfa0ff24f feat: agent-to-agent platform on ZeroClaw 0.8.2 — rooms, delegation, A2A ingress
Builds on the v0.8.2 runtime. Four workstreams, all behind the §15 MCP door:

- Group rooms (Phase 1): migration 0026; N-way threads repo with a DM/room
  count-guard; chat.send {room} + room.create/invite/leave tools; RoomMessage
  -> room.message SSE; /api/claw-chat/rooms* APIs; Observer room badge.
- Per-claw door identity: door caller_agent resolves the X-ZeroClaw-Agent
  header (set by the fork) to the specific claw, falling back to roster[0].
- Gated delegation bridge (Phase 3): clawmates__delegate door tool drives a
  sibling via the existing /ws/chat ZeroClawDriveExecutor (not A2A); self-deny,
  per-workspace hourly budget, audit trail, untrusted-banner result. Native
  in-daemon delegation stays off (it would bypass the door).
- A2A tenant ingress (Phase 2): migration 0027 (workspace_a2a + a2a_tokens);
  runtime_provision enable_a2a_server/publish_claw; routes/a2a.rs tenant-aware
  proxy (per-workspace tokens, injected internal bearer, daemon stays internal,
  cards URL-rewritten to the cm-api edge); a2a.invoked taxonomy.

Tests: cm-db room repos, cm-runtime chat tools, door units. sqlx cache updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-28 16:11:01 -07:00
Omar SobhandClaude Opus 4.8 9263418fcb Fleet: per-node dev-tool version cards + nightly latest-check (Phase 1, read-only)
ci / gates (push) Successful in 5s
ci / rust (push) Failing after 7s
ci / frontend (push) Successful in 23s
ci / e2e (push) Has been skipped
Each node card now shows installed versions of Docker / Claude Code / Kimi / GLM /
Ollama (conditional per node) under the ssh card, with an "update available" badge.

- daemon: probe_tools() finds docker/claude/kimi-cli/ollama across candidate bin dirs,
  extracts semver from --version, reports {"t":"node_tools",...} on connect + every 15m.
- migration node_tools + tool_latest; cm-db repo node_tools (upsert/list/latest).
- cm-api: fleet.rs NodeTools uplink → upsert; tool_versions.rs spawn_latest_checker
  (24h, npm/pypi/github; docker display-only); GET /api/nodes/{id}/tools (glm mirrors
  claude). Spawned in clawmates-server.
- frontend: NodeTools cards on each HostCard with the ↑latest badge.

Phase 2 (one-click update execution) intentionally deferred.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-27 06:01:51 -07:00
Omar SobhandClaude Opus 4.8 c94784bab2 Fleet: actionable executions — rules engine + metrics-aware placement (Phase 2)
ci / gates (push) Failing after 16s
ci / rust (push) Has been skipped
ci / sandbox-k8s (push) Has been skipped
ci / frontend (push) Has been skipped
ci / e2e (push) Has been skipped
Turn the Beszel-tapped metrics into a self-managing loop.

- migration node_rules (workspace/node-scoped: metric op threshold, for_seconds,
  action JSONB, last_fired).
- cm-db: repo/node_rules.rs (CRUD + list_enabled); node_metrics::eval_all merges
  Beszel + heartbeat scalars per node + a headroom() heuristic; nodes::status_of;
  heartbeat now PRESERVES a `draining` status across heartbeats (so a cordon sticks).
- cm-api: node_rules.rs evaluator (spawn_evaluator, 20s) — when a metric condition
  holds for the rule's window it fires drain / undrain / alert (in-memory sustained
  + cooldown tracking, modeled on the node sweeper); routes/beszel.rs rules CRUD
  (GET/POST/PATCH/DELETE /api/fleet/rules); spawned in clawmates-server.
- cm-runtime: placement_node() is metrics-aware — a `draining` node stops receiving
  new agent sandboxes (falls back to local), so the drain rule is actionable.
- frontend: FleetRules section in the Local view — build rules (node · metric · op ·
  threshold · duration → action), toggle/delete, with fired-history.

The loop: hot/overloaded node → rule drains it → placement avoids it → recovers →
undrain rule brings it back. Deployed; node_rules migration applied.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-25 23:43:49 -07:00
Omar SobhandClaude Opus 4.8 36a227566b Fleet: Beszel hub integration — rich per-node metrics + per-node monitor (Phase 1)
ci / gates (push) Failing after 5s
ci / rust (push) Has been skipped
ci / sandbox-k8s (push) Has been skipped
ci / frontend (push) Has been skipped
ci / e2e (push) Has been skipped
Tap each node's Beszel metrics (GPU/temps/disk-IO/network/per-container — beyond
our basic heartbeat) by reading the workspace's Beszel hub. The agents run in
WS-only mode with no locally-readable socket, so (per the de-risk) the server taps
the hub's PocketBase API instead of the daemon reading agents — no daemon changes.

- migrations: workspace_beszel (BYO hub URL + login, server-side only, mirrors the
  Tailscale BYO pattern) + node_metrics (latest scalar columns + JSONB blob).
- cm-db: repo/fleet_beszel.rs, repo/node_metrics.rs; nodes SELECT joins node_metrics
  (gpu_pct/temp_max surfaced on node_json for the live cards).
- cm-api: beszel.rs client (auth-with-password, poll `systems`, map to nodes by
  hostname, upsert metrics) + a 15s spawn_poller; routes/beszel.rs (connect/status/
  disconnect + GET /api/nodes/{id}/metrics with history proxied live from the hub).
- frontend: HostCard gains a GPU/temp readout + a Monitor button; NodeMonitor is a
  full-width per-node page (current panel + CPU/mem/GPU/temp/net/disk charts from the
  hub's 1m history); a "Beszel monitoring" connect form in the Local view.

Reachability confirmed: gw-04 → the hub over the tailnet (100.123.224.84:8090). Needs
the user to connect their hub login to activate the poller.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-25 23:28:29 -07:00
Omar SobhandClaude Opus 4.8 cf6c331b02 Fleet: node hostname/IP on register + node terminal in the infra computer
ci / gates (push) Failing after 5s
ci / rust (push) Has been skipped
ci / sandbox-k8s (push) Has been skipped
ci / frontend (push) Has been skipped
ci / e2e (push) Has been skipped
Hostname/IP:
- Daemon reports the machine's hostname (sysinfo) + primary outbound IPv4 on each
  heartbeat. migrations/0021 adds nodes.hostname/local_ip; cm-db heartbeat stores
  them; node JSON exposes them. Cards now title on the real hostname (falling back
  to name) + show the IP, instead of the "New node" placeholder. `name` stays
  user-overridable (rename).

Terminal moved into the pull-out computer (no more per-card modal):
- New infra computer app NodeTerminalApp (computer/apps/infra) — xterm bridged to
  a node's host shell over the node control channel, filling the app window
  (mirrors the agent Terminal's layout + ResizeObserver). Added "terminal" to the
  INFRA_CATALOG grid; a ?node= panel param targets a specific node (picker when
  unset). Clicking Terminal on a node card now opens the infra computer to that
  node's shell instead of a separate full-screen window. Deleted NodeTerminal.tsx.

Rebuilt + re-hosted both daemon binaries (hostname change).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-24 20:13:15 -07:00
Omar SobhandClaude Opus 4.8 fb59378aa2 Fleet P2b: run agent sandboxes on connected nodes (RemoteDriver + placement)
ci / gates (push) Failing after 5s
ci / rust (push) Has been skipped
ci / sandbox-k8s (push) Has been skipped
ci / frontend (push) Has been skipped
ci / e2e (push) Has been skipped
Agents can now provision their sandbox on a connected fleet node instead of the
gateway host. Local stays the strict default, so existing agents are byte-for-
byte unaffected until explicitly placed elsewhere.

Security parity: the daemon links the REAL cm-sandbox DockerDriver and runs the
typed container ops (sb_provision/sb_exec/sb_destroy/sb_health/sb_list) through
it — identical hardening (cap-drop ALL, seccomp, no-net, read-only, non-root) to
local sandboxes. cm-sandbox spec types are now Serialize/Deserialize so the spec
crosses the channel.

- cm-api: RemoteDriver (impl SandboxDriver over the node channel) + HubDriverProvider
  (impl cm_runtime::NodeDriverProvider, hands out a driver only for connected
  nodes via a sync online set) + NodeHub.call/is_connected. AppState.with_node_hub
  so the hub is shared with the placement provider.
- cm-runtime SandboxManager: driver_for(node_id) routes by the recorded
  agent_containers.node_id (local default = existing driver, identical path);
  placement_node() reads the workspace setting and falls back to local if the
  node is offline; exec/release route accordingly. NodeDriverProvider trait.
- DB: 0020_workspace_placement + repo (for_agent/get/set/clear).
- main.rs: build the NodeHub first; inject HubDriverProvider into the agent
  manager + share the hub with AppState.
- API+UI: GET/PUT /api/fleet/placement + a "Run agents on: Local / <node>"
  selector in the Fleet overview.

Note: a node must be able to pull the agent image (the daemon docker-pulls it);
interactive PTY for agent containers on remote nodes is not wired (Terminal app
stays local) — the in-dashboard node shell already covers host access.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-24 12:48:50 -07:00
Omar SobhandClaude Opus 4.8 7332d69f8a Fleet P1: BYO Tailscale + network metrics, Tailscale SSH, exec hardening
ci / gates (push) Failing after 6s
ci / rust (push) Has been skipped
ci / sandbox-k8s (push) Has been skipped
ci / frontend (push) Has been skipped
ci / e2e (push) Has been skipped
Security hardening:
- The gateway no longer sends arbitrary shell to nodes. The WSS exec op is
  replaced by a typed `verify` op the daemon runs itself (fixed host+docker
  check); future container ops are typed too. cm-api NodeHub.verify() + the
  daemon's handle_command only dispatches vetted ops.

BYO Tailscale:
- migrations/0019_workspace_tailscale.sql + cm-db fleet_tailscale repo (store the
  user's Tailscale API key + tailnet, server-side only).
- cm-api routes/tailscale.rs: POST/GET/DELETE /api/fleet/tailscale + GET
  /api/fleet/tailscale/devices (proxies api.tailscale.com device list).
- Daemon: --tailscale-authkey → `tailscale up --authkey … --ssh` (enables
  Tailscale SSH for keyless user access); else `tailscale set --ssh=true`. Reports
  its tailscale IP (already).

UI:
- Fleet overview gains a Tailscale section: connect (key+tailnet) + live tailnet
  device status (online/last-seen/IP/os). Node cards show a copyable Tailscale SSH
  target (ssh <ip>).

Remaining: P2 — RemoteDriver + placement (run agents on nodes) and the in-UI
remote terminal (PTY proxied over the WSS channel).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-24 10:27:58 -07:00
Omar SobhandClaude Opus 4.8 2bdd0a23e8 Fleet P0: node registry + daemon + health + connect-host wizard
ci / gates (push) Failing after 5s
ci / rust (push) Has been skipped
ci / sandbox-k8s (push) Has been skipped
ci / frontend (push) Has been skipped
ci / e2e (push) Has been skipped
Users can connect their own local-hardware nodes into a fleet. Each node runs a
new Rust daemon that dials home over an outbound WebSocket, reports host health,
and runs commands we send.

Backend:
- migrations/0018_fleet_nodes.sql: nodes + node_health tables + agent_containers
  (node_id, workspace_id) index. cm-domain NodeId.
- cm-db repo/nodes.rs: create/auth/list+health/get/heartbeat/set_status/delete
  (unchecked sqlx, no .sqlx regen).
- cm-api fleet.rs NodeHub: live daemon channels (node_id→sender) + the WS channel
  runner (heartbeat→DB upsert, exec request/response framing). routes/nodes.rs:
  POST /pair, GET /nodes, SSE /nodes/live, POST /{id}/exec-test, DELETE /{id},
  WS /nodes/agent (token-auth). Wired into AppState + router.

Daemon (new crate crates/bins/clawmates-node):
- sysinfo host metrics (cpu/mem/pressure/swap/disk/load/containers), outbound WSS
  dial + reconnect, heartbeat loop, exec command handling, tailscale-ip probe.
  install.sh convenience installer.

Frontend:
- Fleet sidebar item + FleetOverview + LocalHardware node-health cards (live via
  /api/nodes, 3s poll) + ConnectHostWizard (install → verify connection →
  exec-test). InfraStage dispatches fleet/local; default selection = fleet.

Deferred: P1 (BYO Tailscale + network metrics), P2 (RemoteDriver + placement so
agents actually run on connected nodes).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-24 08:09:14 -07:00
Omar SobhandClaude Opus 4.8 e9ce368ec1 Scaling Phase 1: multi-tenant onboarding + replica-safe coordination
Decouples "many users" + "many server replicas" from "many machines" so the
platform is tenant-isolated and horizontally safe on the current single node.

- Per-signup workspaces (cm-auth): a new hosted-identity sign-in provisions and
  owns its own workspace instead of joining the first. Config-gated by
  auth.per_signup_workspace (default off); concurrent first-logins serialized by
  a per-subject advisory lock so no duplicate workspaces.
- Terminal tickets in Postgres (migration 0016, hashed, single-use): any replica
  can redeem a ticket minted by another. Drops the in-process ticket map.
- Container registry in Postgres (migration 0017, agent_containers): Terminal
  and Sandbox managers resolve an agent's container through a shared registry,
  so a 2nd replica reuses it instead of spawning a duplicate. node_id recorded
  as 'local' (Phase 2 hook). Boot reconcile removes only true orphans, so
  terminals now survive a redeploy (tmux sessions resume).
- Per-workspace quotas (cm-api/quota.rs): plan-tier caps on agents + live
  containers, enforced at agent create + terminal spin-up (reconnects allowed),
  returned as HTTP 402. New GET /api/quota surfaces usage vs limits.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-23 18:24:51 -07:00
Omar SobhandClaude Opus 4.8 e61724ff82 Agent computer: terminal (tmux + drives + tabs), Obsidian vault, UI polish
Terminal app (xterm ⇄ WebSocket ⇄ per-agent themed container):
- zsh + oh-my-zsh + powerlevel10k image (agent-terminal), runs as uid 65532 to
  share read-write ownership of the file-drive volume with the server.
- Interactive PTY in cm-sandbox (bollard exec tty/attach + resize) + a
  TerminalManager; ticket-authed WS bridge routed straight to the backend via a
  Traefik PathRegexp(/ws) rule. MOTD greets the user by name.
- tmux resumable sessions; multi-tab (one tmux session per tab, same container),
  drag-to-reorder, rename, and a Save that persists named tabs to the server
  (terminal_tabs, migration 0014) so they survive logout / a new device.
- Files drives mounted per-agent (subpath) at ~/drives/{documents,received,
  shared}; a reconciler keeps the Files app's index in sync with terminal writes.
  Storage moved to a shared `filedata` volume (CLAWMATES_STORAGE__DATA_DIR).

Obsidian vault (a markdown "second brain" per agent):
- New `vault` FileDrive (migration 0015) mounted into the terminal at ~/obsidian;
  a file-content read route; a purple Obsidian tile + a vault viewer app.

Computer UI:
- Draggable computer-panel width (min = phone preset) keeping the size presets.
- Green Terminal glyph, "Claw Chat" → "Chat", colored gradient-outline app icons.
- Agent page: avatar↔activity-grid spacing + larger, uniform section fonts with
  colored section-tinted tag chips.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-23 16:52:35 -07:00
Omar SobhandClaude Opus 4.8 34f744734b Large World graph, agent platform, brain stack & dashboard rebuild
Frontend
- Large World: collapse org/company/team tiers into one expandable React Flow
  hierarchy (WorldFlow) with per-click expand, persisted node positions, a
  compact tree sidebar, wrench multi-select delete across levels, and a sized
  right slide-out (phone/tablet/full) showing an agent summary + drill button.
- Agent page: GitHub-style animated contribution grid (VitalsCard), collapsible
  System Prompt + Personality cards, restructured anatomy cards, bigger avatar
  with name/title header row, Markdown/JSON-aware rendering, brain registry +
  history, avatar generate/upload.
- User-icon menu (Infrastructure/Brains/Tools/Profile/Credits) + ToolPanel;
  Master Planner deploy wizard (Specialists/Swarm/Scheduled/Triggered);
  Team Runs view; reap-progress modal; dashboard is the single live interface.

Backend
- cm-brain crate (.brain as the agent definition) + brain apply/history.
- Hard-purge reap (FK-ordered) + sandbox release + SSE batch-delete.
- Swarm self-verifying loop, mode-aware planner, web.search tool, webhooks
  (migration 0013), org/company/team delete endpoints, scheduler sweeps.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-22 23:21:54 -07:00
Omar SobhandClaude Opus 4.8 540c74f42e Adopt design comps: dark system, new landing/auth, dashboard shell + canvas
Re-skins the whole app to the dark design comps and wires the new surfaces to
the backend (the /api proxy + auth + schemas are unchanged).

Design system:
- globals.css: remapped @theme tokens to the comp palette (#08080a base, coral
  #ff6f61, status cyan/green/amber/purple/teal); token names preserved
- MeshMark: triangle + 3-node brand glyph; cm-flow/cm-blink/cm-halo keyframes
- marketing flipped light → dark

Backend (migration 0012):
- agents.model_binding (persisted on team deploy) + GET /api/claws/{id}/runtime-config
- routine_runs table + scheduler journaling + GET /api/routines/runs
- GET /api/claws/{id}/compartments (anatomy aggregate)
- GET /api/structure/stats (workspace counts)

Frontend:
- Landing: full dark marketing page (hero constellation, deploy ladder,
  12-topology taxonomy, recursive execution, compare/Pareto, safety, self-host)
- Auth: dark split-panel AuthShell + comp LoginForm + Clerk SignIn themed dark
- Dashboard shell: TopBar (breadcrumb + live stats + deploy + user) + StatusBar
  (runner/sandbox/doors); rail slimmed to 60px + 252px context column
- ConstellationCanvas (radial recursive) replaces the graph view in StructureCanvas;
  selecting a claw opens ComputerPanel (apps/now-running/dock); RoutinesPanel
- Claw anatomy view (/claws/[id]/anatomy) from compartments + runtime-config

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-19 04:12:16 -07:00
Omar SobhandClaude Opus 4.8 3eca4ed70c Recursive deploy ladder: Company + Org tiers, mesh mark, two-tier rail
Completes the scale ladder (single → team → company → org). Every tier is a
topology whose nodes are the tier below; running a parent recursively runs each
child's sub-topology down to the leaf claws.

Backend:
- migration 0011: companies/company_teams, orgs/org_companies, topology_runs.tier
- cm-db repos for companies + orgs (mirror teams)
- TurnRequest.attrs (forwarded from node.attrs) for child-id binding
- SubTopologyExecutor (recursive_exec.rs): a parent "turn" runs the child's
  sub-topology; durability via parent updated_at keepalive + cancel propagation
  + depth cap; boxed future breaks the org→company recursion
- topology_worker selects executor by job.tier
- routes: /api/companies, /api/orgs (create/list/get/run) + unified
  /api/structure/{level}/{id} for the zoom canvas

Frontend:
- MeshMark: node-mesh brand glyph (replaces the claw PNG), tier variants
- TopologyGraphView: optional onNodeClick/nodeMeta + dark-token theming
- StructureCanvas + Breadcrumb: one recursive zoom view for every tier
  (drill down on node click, breadcrumb up); TeamRunPanel extracted + shared
- two-tier Discord-style rail: StructureRail (mesh mark + org/company/team
  glyphs + tools popover + deploy + user) | RosterColumn (selected group's
  children, or your claws); SecondaryNav for cross-cutting tools
- ComposeWizard (company/org) wired into DeployWizard; /companies + /orgs pages

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-18 14:25:06 -07:00
Omar SobhandClaude Opus 4.8 8123a27bcf Teams (deploy ladder rung 1): schema + provisioning + API
A team = a baseline topology staffed with real claws. Migration 0010 (teams +
team_members node→claw bindings) + cm-db repo/teams.rs. runtime_provision.rs
turns a claw into a live runtime agent claw_<id> via the synced gateway config
API (#7468): create agent + bind model_provider (mapped from chosen model) +
risk_profile=toolfree + clawmates_door bundle — atomic, immediately drivable.

routes/teams.rs: POST /api/teams (create claws + provision + build(kind,roles)
+ bind node.attrs["agent"]=claw_<id> + persist), GET /api/teams[/{id}],
POST /api/teams/{id}/run (enqueue a durable run of the team graph — reuses the
topology worker + SSE). v1 persona = topology role via the prompt builder; the
claw's system_prompt stays its chat identity.

Spike confirmed: runtime agent provisioning works; IDENTITY.md persona works
for API models (Gemini/Groq), masked by CLI models (Claude/Kimi Code). 16
cm-api tests + provision unit tests pass, clippy clean.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-18 13:13:14 -07:00
Omar SobhandClaude Opus 4.8 8dee01c77b Durable topology jobs (1/4): job-table schema + repo
Evolve topology_runs into a durable-job table (migration 0009): status state
machine (queued/running/completed/failed/cancelled), kind, input graph,
per-step checkpoint, error, last_event_id, timestamps. comparison becomes
nullable (the result blob, absent until completion). Back-compat: existing
rows default to completed/compare.

cm-db repo gains the durable-job ops: enqueue_run, claim_next_queued (CAS via
FOR UPDATE SKIP LOCKED), checkpoint, complete, fail, requeue_stale (resume
sweep), and status(). Regenerated .sqlx cache. Also fix two pre-existing test
RuntimeConfig literals missing the providers field (from the registry work).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-17 19:02:32 -07:00
Omar SobhandClaude Opus 4.8 7baf2082d0 feat(topology): persist comparison runs + history
ci / gates (push) Has been cancelled
ci / rust (push) Has been cancelled
ci / sandbox-k8s (push) Has been cancelled
ci / frontend (push) Has been cancelled
ci / e2e (push) Has been cancelled
Save each comparison and let users reload past ones.
- migration 0008: topology_runs (workspace-scoped; full comparison as JSONB).
- cm-db repo::topology_runs (insert / list_recent / get) + regenerated .sqlx.
- cm-api: compare persists best-effort (never loses the LLM result on a DB
  hiccup); GET /api/topology-runs (recent) + GET /api/topology-runs/{id}.
  Integration test asserts persist → list → get.
- frontend: "Recent comparisons" list on the Compare tab; click to reload a
  saved run. e2e p8 green (39 suite); offline build + clippy clean.

Server self-migrates at boot (cm_db::MIGRATOR), so 0008 applies on deploy.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-16 05:11:52 -07:00
Omar SobhandClaude Fable 5 add4f79fed Rebrand: TeamClaw -> Clawmates (clawmates.work)
Full-depth rename per the approved plan; the 'claw' product vocabulary
(claws, /claws routes, clawId, Claw Chat) stays — it is now the brand.

- Display brand: Clawmates (manifest, titles, hero, login/rail logo
  'clawmates'); default host app.clawmates.work; registry
  ghcr.io/clawmates
- Crates tc-* -> cm-* (16 crates + all imports); binaries
  clawmates-server/broker/bundler; images clawmates/*; env prefix
  CLAWMATES_* (+ CM_TEST_DATABASE_URL / CM_LIVE_LLM); config
  clawmates.toml; helm chart deploy/helm/clawmates with clawmates-*
  resources; db names clawmates*; sockets /run/clawmates; cookie
  cm_session; kind cluster clawmates-test; seccomp node profile
  clawmates-agent-profile.json
- All 9 Playwright brand assertions updated in lockstep; historical
  spec document left untouched as the only remaining 'TeamClaw'
- Local env migrated: dev pg clawmates-dev-pg/clawmates_dev, shared
  test server clawmates-test-pg, kind cluster recreated with image +
  profile, compose images rebuilt under clawmates/*

Verified end to end: 161 Rust + 68 frontend tests, 29 Playwright
journeys, 4 live kind tests, helm/install/LOC/placeholder gates, and
the clean-room install rehearsal serving the clawmates login page from
a signed bundle of the rebuilt images.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-10 12:31:25 -05:00
Omar SobhandClaude Fable 5 cbc8d35a2e Clerk authentication: hosted-identity session JWTs as a first-class mode
- tc-auth JwtVerifier: OIDC discovery -> JWKS, RS256 with the issuer
  pinned, 5s leeway (the crate's default 60s would double the life of
  Clerk's 60s session tokens), key cache with one refresh on unknown kid
  (Clerk rotates). Serves auth.mode = clerk AND generic oidc — a Clerk
  instance IS an OIDC issuer, so one verifier covers both
- AuthService.authenticate dispatches: JWT-shaped bearers take the
  hosted-identity path, everything else stays a local opaque session.
  External users JIT-provision keyed by the stable sub claim
  (users.auth_subject, unique partial index in migration 0007); an
  existing local account with the same email is LINKED, not duplicated;
  role tracks the issuer claim every request (org:admin -> Owner)
- Config auth.mode = "clerk" (requires issuer_url; validated), server
  pins the issuer at boot, Helm values/configmap accept mode=clerk
- Tests with REAL crypto, no mocks: fresh RSA keypairs, a live local
  issuer publishing real discovery + JWKS docs, Clerk-shaped tokens —
  JIT + role mapping, repeat-subject no-dup, expired refused (leeway
  regression), wrong-key forgery refused, foreign issuer refused, and
  the full router round trip with Authorization: Bearer <session JWT>
- docs/clerk.md: dashboard session-token customization (email + org
  role claims), config, @clerk/nextjs getToken() wiring, what CI proves

157 Rust + 63 frontend tests + 29 journeys. Air-gapped installs keep
local auth — Clerk is a cloud-only alternative, not a replacement.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-10 10:29:27 -05:00