72ba4ba523de2e87150215a5c86077c6679a2a21
30
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
43436d7181 |
feat(telemetry): push bus for live agent frames
/api/world/live is a 2s database poll, which is right for queryable state and wrong for a token stream: reasoning only became visible after a step finished and its row was written. This adds a process-wide broadcast bus that topology_exec publishes to as the runtime's WebSocket delivers frames, and the SSE handler forwards without waiting for the next tick. Measured: the pushed frame arrived ~2.2s before the polled copy of the same text. Design notes worth keeping: - A global (OnceLock), not an AppState field. The publisher is reached through phase_runner -> topology_worker -> MissionTap, none of which hold AppState; threading a handle through all of them would put a UI concern into four layers that have no other reason to know about one. - Lossy by design. A slow subscriber lags and skips rather than applying backpressure to the agent producing. mission_events remains the durable record; this bus is the fast path, never the source of truth. - Only `claw_<uuid>` aliases are attributed. The governor, door and evaluator drive real turns under other names, and attributing their output to an agent would put words in someone's mouth. Asserted in a test. - The poll no longer emits `reasoning`: with both paths live, every turn arrived TWICE — once pushed, once polled ~2s later. The row is still written; this feed just is not its second mouth. CEILING, measured rather than assumed: turns are not token-level because the runtime is not streaming. zeroclaw's claude_cli provider runs `claude -p --output-format json`, which returns ONE result object when the turn completes — there are no incremental tokens to forward. Making this genuinely token-by-token needs `--output-format stream-json` and incremental parsing in the zeroclaw fork, not here. The bus is in place and will carry them the day it does. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
ba9d7aa185 |
feat(telemetry): WORKING ON NOW shows the mission an agent is on
The last of the three declared-but-never-emitted event types. `agent.task.update` had no producer anywhere in the backend, so the card read "idle — no active task" for an agent that was mid-turn. Derived rather than newly instrumented: an agent is working on its crew's RUNNING mission, and that mission's phases are the steps (completed/skipped → done, running/evaluating → active, else pending). Nothing is emitted for an agent with no running mission, so "idle" stays truthful rather than freezing on a stale last-known task. Verified on a live mission: 116 agent.task.update events observed on /api/world/live, carrying the mission title and phase steps, with the state advancing pending → active as the phase started. That closes the set. Of the seven cards in the command centre, five were dark: three had no emitter at all and two read a table the mission path never wrote. DOORS and LOOPS were correctly wired the whole time and were reporting an honest zero. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
bf40d10064 |
feat(telemetry): the reasoning stream actually streams
`agent.reasoning.delta` and `agent.tool.call` have been declared in the taxonomy and listened for by the command centre since it shipped — and NOTHING ever emitted them. The world feed emitted five types; neither was among them, so REASONING STREAM could not populate no matter what an agent did. The feed is a database poll, not a push bus, so a live card can only show what was persisted. The worker already holds each step's output text and the claw that produced it, so it records a `reasoning` mission_event (truncated — the card renders a tail, not a transcript, and mission_events is capped per phase), and the feed emits it forward from a cursor that starts at the current max so a page load streams rather than replaying history. `tool.call` is emitted from the same place. On the container tier it will stay empty, and that is correct rather than broken: those agents are tool-free behind the §15 door. Tool lines appear where agents actually hold tools. Verified on a live mission: agent.reasoning.delta observed on /api/world/live carrying the agent's own text, keyed by agentId. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
ccbc387f4b |
fix(agents): stop listing soft-deleted agents
Deleting an agent looked like a no-op: it disappeared from the workforce but
stayed on the Team board, and deleting it again did nothing because the row was
already marked. Two queries selected from `agents` without `deleted_at IS NULL`:
routes/team.rs the leaderboard — the surface still showing them
routes/world.rs the "working" set — a deleted agent holding a stale
agent_containers row rendered as live
Observed on this deployment: /api/workforce correctly returned nothing while
/api/team/leaderboard returned two agents soft-deleted back in June.
NOT changed: those rows still exist. Making delete permanent means hard_purge,
which also deletes usage_events — billing history, 6 credits on one of these
two. Discarding that as a side effect of tidying a roster is an explicit
decision, not something a display fix should smuggle in.
.sqlx regenerated: team.rs uses the compile-time-checked query! macro, so the
cached entry no longer matched.
Co-Authored-By: Claude Opus 5 <[email protected]>
|
||
|
|
f8438c32ea |
feat(viz): kind-specific choreography and a finished mission you can read
Security: the pawns already orbited their destination, so homing them at
the security station gave circling for free. This adds the radial
press-and-retreat — an agent closing on the target and backing off reads
as probing it, where a fixed radius reads as waiting — and holds the
stochastic target release while probing, or the circling breaks up into
stray trips that look like distraction rather than a scan.
Findings are `mission_tasks` rows, one orb each, popped once. There is
deliberately no severity anywhere in the path: the scanner keeps
severity, file and line as substrings inside `title`, so a severity
parsed out of prose and rendered as an orb's RADIUS would be the picture
asserting a measurement the data never contained. Count only.
Benchmarks annotate the station, as text. `delta` has no schema —
compute_delta emits `{kind:"opaque"}` whenever the before/after metrics
were not structurally comparable, which is most drivers. The server
formats the shape it can parse and COUNTS the rest; an unparseable driver
reports "3 sample(s)" rather than an invented improvement, and an opaque
delta says nothing at all.
The finished map: the live label rule gates service/event nodes on
`heat > 0.12`, which is exactly backwards once everything has cooled — a
static map would be unlabelled dots. Frozen, the 25 most-touched nodes
label regardless of heat, phase stations carry a second line counting
what they produced, and the camera is released ONCE so it frames the
result even if the user panned during the run.
Every count in a caption is read off the drawn scene rather than a
parallel tally, so the words and the picture cannot disagree.
Co-Authored-By: Claude Opus 5 <[email protected]>
|
||
|
|
9e61e3ba35 |
feat(viz): what the agents actually did, as structured events
The World could draw a mission's shape but nothing about the work. The
detail existed only as prose in checkpoint.log and model output, where a
tool name is indistinguishable from an agent *talking about* a tool — so
it was never parsed, deliberately. `mission_events` is the structured
channel that replaces it.
Three taps, one table:
- Container tier: the `_ => {}` at the end of topology_exec's typed frame
stream now matches `tool_call` and reads the tool's JSON ARGUMENTS for a
path. Never the prose summary — a path scraped from a sentence would put
files on the map that no agent opened, and the test proves a Grep whose
summary says "src/main.rs" produces no file touch. The frame name itself
is unverified, so the same commit ships an unmatched-frame-type
histogram: a tap that matches nothing looks exactly like a mission that
used no tools, and this is how one gw-04 run names the real frame.
- microVM tier: a `PostToolUse` hook, the seam vm_stop_gate already proved
fires under `claude -p`. It copies stdin to /root/tap and exits 0
unconditionally — a non-zero PostToolUse hook talks back to the model,
which would turn the observer into a participant. Drained before collect,
since the VM is destroyed moments later.
- Phase transitions: five identical copies of the pending→running UPDATE
became one `mark_phase_running`, and `close_finished_phases` grew
RETURNING. Its CASE decides each phase's status inside SQL from rows the
statement does not change, so it cannot be re-derived afterwards without
writing that CASE twice — without RETURNING it emits zero phase.completed
and reports success.
The settings.json hazard the plan called out: the stop gate wrote the
WHOLE document, so a second hook writer would have silently erased it and
a coding phase would then complete having written nothing — the exact
failure the gate exists to catch. There is now one composer,
`vm_tool_tap::guest_settings`, one writer, and a source-walk test that
fails if anything else writes a settings document.
`mission_events.run_id` carries no FK on purpose: phase_runner DELETEs
topology_runs on retry, and a cascade would erase a phase's whole history
the moment it retried — silently, since a cascade is not an error.
world.rs streams it with a cursor that separates backfill from motion.
Everything already in the table when a subscriber arrives is drawn as
settled history; only what lands afterwards animates. Otherwise opening a
finished mission replays an hour of tool calls as a burst storm.
Bounded twice: 400 events per phase (enforced inside the INSERT, since
two concurrent taps would each read a count below the cap) and a 7-day
retention sweep in mission_gc.
Co-Authored-By: Claude Opus 5 <[email protected]>
|
||
|
|
c2fa8067e1 |
feat(viz): the coding station fractures into the files it worked on
The engine already turned `file:src/lib/a.ts` into a real dir chain, but both copies of that loop rooted it at the origin — so a mission's files floated beside the map instead of belonging to the work that produced them. One `fileParent` helper now serves both call sites; splitting them was how half the files could end up nesting correctly and half not, decided by whichever code path saw the file first. Files hang under the coding station when there is exactly one, else the single running phase, else the origin. `world.touch` carries no phase id, so with two coding phases any attribution is invented — the fallback is the honest answer. Two ordering hazards, both silent: - the server emitted files BEFORE phases, so on the first pass a file arrived with no station to hang under and first-write-wins pinned its tree at the origin. Loops reordered, with a source-walk guard. - `setFileHome` re-parents trees rooted before the plan landed, for the reconnect case the ordering alone cannot cover. `mission.file` with `source: "tool"` is treated as motion (burst, pawn beams); `"diff"` is end-of-phase truth and only marks the file present and warm — bursting every file of a captured diff would set the whole map alight at once on reconnect. Directories taper in radius and opacity by path depth, so `src` and `src/lib/live` no longer render as identical dots. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
006432c2dc |
feat(viz): the mission becomes a map — centre, stations, and agents at theirs
The clump was structural, not cosmetic. Four causes, each fixed here. `homes` (an agent's resting node) was written only by `seed()`, so every agent homed to the mission centre and orbited the same dot regardless of which phase it was on. `setHome` points each agent at its CURRENT phase, and the existing physics does the rest for free: the pawn rests at its station, the pawn→home line tethers it there, and a touch becomes a visible departure and return. No new motion code. The mission node was seeded as `level: "team"` (my own bug from the focus work). `seed()` casts that straight to a Tier and `ensureNode` is first-write-wins, so it was created as a small teal team dot that the later `node.activity` could never upgrade. That dot at the centre of the scene was one line. `mission`/`phase` replace the retired `repo`/`loop` tiers rather than adding a parallel set. The backend stopped emitting repo:/loop: ids, which left their whole landmark treatment — bigger radius, distinct colour, always-labelled, 60s fade instead of 22s — orphaned on prefixes nothing sends. Missions and phases need exactly that treatment. The two separately-written prefix→tier ternaries in onTouch and onNodeActivity are now one `tierFor`: they agreed only by luck, and whichever path saw a node first fixed its tier forever. Phase stations spring out at 190 rather than the shared 64, or they pack into a rosette around the centre and the point — agents moving BETWEEN stations — is invisible. Idle roam is off under a mission scope: wandering to a random node keeps an idle workspace alive, but inside one mission it sends agents to files nobody opened, which reads as work and isn't. Palette is injected at construction and keyed on template_kind, so a benchmark run and a security sweep no longer render identically to a research mission. It also collapses two uncoordinated kind→colour maps that had drifted: LEVEL_COLOR by tier, and the fireColor if-chain by id prefix. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
5f85dbb718 |
fix(world): missions were never really on the wire
Three bugs in one query, each hiding the next, plus one that made the whole rich layer dead code. `active_missions` joined `team_members` on `missions.team_id` — the LEGACY pointer at the first minted team, superseded by the `mission_teams` junction in 0056. It was an INNER JOIN, and `mission_orchestrator::on_launch` deliberately mints no team for a microVM mission, so the platform's primary execution tier was dropped by a join and the World has been showing nothing at all for it. And it selected only `status='running'`, while missions finish in minutes, so the scene was empty almost always. Now: join `mission_teams`, LEFT so teamless missions survive (their `agent_id` is NULL and no pawn beams at them, which is the truth — nothing on this platform ran that phase except a VM), and include missions finished in the last 24h carrying `status`/`template_kind` so the client can draw a finished map instead of animating a corpse. `?mission=` scopes the feed server-side. The whole phase plan now ships as `mission.phase`, including phases that have not started: a phase list that appeared only as phases began made a five-phase mission look like a one-phase mission until it was nearly over. Attribution reuses `phase_runner::purposes_for` rather than copying it — two copies would let the picture disagree with the machine about who is working on what, which presents as a rendering bug and is really a lie. Deleted the checkpoint tail. It read `topology_runs` keyed by an `agent_runs` id; mission phases live in `topology_runs` under independently generated ids, so it ran every poll and matched nothing, for every mission, forever. That is why no mission has ever shown tool or file activity. Not repointed at `topology_runs`: its only per-step content is the agent's own prose, and a tool name in prose cannot be told from an agent talking about a tool. The a2a `run_events` tail is kept — it genuinely works for the path that writes it. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
0d8db7ff0b |
fix: close the three remaining gaps, and repair a test I silently disabled
FIRST, the self-inflicted one. My edit in |
||
|
|
d8c8793c4a |
ci fixes: cargo fmt, eslint entities, max-lines split
CI on
|
||
|
|
a2bc06d313 |
validation sweep: close residual TODO + strip 'future' stubs
- mission_orchestrator: replace hardcoded cli=\"claude\" with real precedence chain: mission.config.cli → template.config.default_cli → \"claude\". Missions can now A/B by CLI without a schema change. - fleet.rs: scope clippy::too_many_arguments allow on NodeHub::open_pty — 8 params (session address x4 + target address x3) is the actual dimensionality; a struct would be ceremony. - security_scan.rs: delete future_artifact_root — pure hint-stub, no callers, nothing depends on it. - routes/world.rs: delete the empty normalize() seam + its 30-line comment block. normalize_run_event higher up does the real work; the empty stub was pre-cleanup scaffolding. Post-sweep validation across the whole workspace: * cargo check --workspace → clean * cargo test --workspace --no-run → all bins build * cargo test -p cm-api --test mission_orchestrator → 3/3 pass * cargo clippy -p cm-api -p clawmates-node --tests → 0 warnings * tsc --noEmit → 0 errors Wiring audit: every recent route handler is registered in lib.rs. Every recent frontend component has at least one importer. Remaining #[allow(dead_code)] entries are deprovision_claw + unpublish_claw on RuntimeProvisioner — real rollback paths waiting on the team-delete route. Documented future hooks, not stubs. |
||
|
|
9bac84c645 |
world view: restore in-flight landmarks as mission orbs
Slice 9 cleanup removed active_research_topics + active_loops and the
repo:{topic_id} / loop:{id} project orbs they emitted. The World SSE
loop was left with only agents + active runs — no persistent pin for
"this is what the team is working on right now."
Replaces those with a mission-era equivalent: one mission:{id} orb per
running mission, plus world.touch beams from every assigned team
member. Missions outlive individual runs, so the orb persists even
when no run is claimed — matches the UX intent of the legacy
landmarks without the retired research/loops plumbing.
Query: missions ⋈ team_members where m.status='running', grouped by
mission_id for the orb + fanned out per member for the touches.
Closes task #21.
|
||
|
|
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.
|
||
|
|
a49cf86623 |
world: pre-seed repo tree on clone (V3)
Repo focus mode used to open into an empty tree — the file/dir nodes only synthesized as agents touched files via tool calls. Cold-start users saw a lonely repo:<id> orb with nothing under it. Now the tree pre-seeds from the actual cloned repo the moment a client subscribes. Backend: - active_research_topics also returns repo_workspace_path. - preseed_repo_paths(clone_path) runs `git ls-files` (bounded to top 200) against the on-disk clone. Silently returns empty on any failure so a missing clone / git-off-PATH / empty repo just degrades to the pre-V3 behavior (tree still builds on touch). - SSE loop, on first sight of a topic per client, emits one node.activity per pre-seeded path (label = leaf name, heat 0) so the tree is quiet-solid at rest. Frontend: - engine.onNodeActivity now synthesizes the dir:<partial> chain for file: nodeIds the same way onTouch does — otherwise the pre-seed would render as flat leaves under ROOT. - Same 5-line synthesis extracted from onTouch; both paths now agree on the layout. Cap of 200 keeps the SSE payload bounded on huge repos; the tail fills in as agents actually touch files. When we later add per-file heat map (V4), the 200 already-known files get first-class treatment out of the gate. |
||
|
|
5a2340587e |
world: loop:<id> landmark orbs (V1)
Mirror the repo: landmark pattern for scheduled loops. Every enabled loop in the workspace gets a labeled amber orb in the World, whether it's currently running or between fires. Assigned agents converge on it with a soft 0.15 touch — the loop is a persistent landmark, not a transient run. Backend: - active_loops(pool, ws) query joins loops + loop_agents where enabled, returning (loop_id, title, agent_id) — one row per (loop, agent). - SSE loop emits node.activity + world.touch symmetric to the research block. Seen-once set dedupes the label emission across agents. Frontend: - New "loop" tier in the Tier alias, LEVEL_COLOR (#f0b866 warm amber), and ensureNode radius (11 — same landmark size as repo). - engine.onTouch / onNodeActivity preserve the tier from the loop: prefix (previously would have collapsed to service). - Pawn fireColor tinted amber for loop: touches. - WorldCanvas: loop tier joins the struct group for solid-at-rest glow + always-on labels. Focus mode recognizes loop: prefix (click → focused subtree, Esc to exit). Focus pill switches to "LOOP FOCUS" in amber when the selected id is a loop. Contrast with repo: (transient — only appears when a topic is in processing/reviewing/publishing). Loops are persistent because their whole point is recurrence. |
||
|
|
708f45d09b |
world: solid team + project orbs, hide ROOT, per-topic repo landmarks
Three fixes to the Live viz per user feedback:
1) Hide the ROOT sentinel — it was rendering at the origin as a red disc
with no label. It's a physics anchor, not a real orb. Skipping it in
nodes/edges/labels loops removes the mystery red circle.
2) Emit `repo:<topic_id>` project orbs for active research topics from
the world SSE loop. Labeled with the topic title, tier "repo" gets its
own soft sky-blue palette and a landmark radius (r=11 between company
and team). Assigned agents get a low-weight (0.15) convergence touch
so pawns cluster around their project's orb even at rest — no wait
for a file op to see the affiliation.
3) Solid at rest, bloom on interaction. Two engine changes:
- Structural + repo orbs now have a soft 0.08 glow at heat=0 (down
from 0.22 ambient) so the disc reads as solid until agents heat it.
- `world.touch` heat is now weight-scaled (`+w*0.6`) instead of a
flat `+0.5` regardless of intent. Soft convergence stays soft;
file ops still explode.
Click a `repo:` orb → the existing Commit F focus mode already treats
that prefix as a subtree root, so users drop straight into the
Gource-style repo detail view with only the files their agents are
touching.
Follow-ups queued: teardown of repo orbs when a topic reaches 'published'
(currently they persist until the SSE loop's status filter drops them,
which is correct); loops equivalent (loop:<id> landmark orbs).
|
||
|
|
f88e8642d9 |
world viz: repo focus mode — click a file/dir/run to enter the tree
MVP of the repo-detail sub-view. Click any file/dir/run/repo node
in Live and the viz culls to just that node's subtree — you're
now watching agents crawl the repo instead of the whole workspace.
Esc (or the pill button that appears) exits.
Backend
- routes/world.rs: file-op tool events now emit a `file:<path>`
world.touch IN ADDITION to the existing `tool:<name>` touch.
The path is pulled from the tool input's path / target / file
/ filename / url keys (same lookup summarize_input uses, but we
keep the full string so the client can build a real hierarchy).
Non-file tools are unchanged — they still hit tool:<name> nodes
as before.
Engine
- onTouch synthesizes a directory hierarchy when the id starts
with `file:`. Each intermediate path segment gets a `dir:<acc>`
node (label = the segment), parented at the previous dir; the
file itself parents at the innermost dir. Ensures the layout
spring-simulates as a tree naturally, no separate render mode
needed.
- New pawn fireColor for file: touches: coral #ff8a7a. Reads as
"file work" vs #5ec8d8 (tool convergence) vs #5fd08a (run
activity).
WorldCanvas
- Render pass now takes a `visibleNodes: Set<string> | null`.
When the selected id starts with file: / dir: / run: / repo:,
we BFS descendants via parentId and hide every non-descendant
node. Node meshes, glow sprites, hierarchy edges, and labels
all gate on the set. Pawns stay visible (agents still dart to
the focused files).
- ESC handler on window: clears the selection by calling
onSelect("") when a repo-focus id is set.
- Small "REPO FOCUS · <path>" pill lands at top-center with an
Esc button so the exit is discoverable at a glance without
learning the shortcut.
- Dashboard.onWorldSelect now treats empty string as "clear
focus" (setWorldSel(null)) so the same callback handles ESC.
Not yet: the always-on repo:<topic_id> node emitted at run
start when a research topic has a repo bound. Today the focus
works off run:<id> nodes; a repo:<id> anchor would let users
click without waiting for a first file touch. Also skipped:
per-file heat map / call-count visualization tied to touch
weight over time. Both are natural follow-ups on this bones.
|
||
|
|
aa941b72a1 |
world viz: agents grow with their brain (log-curve dot size)
Fresh agents start at "size of their letters" — a small dot in
the live viz — and visibly bloom out as their .brain file fills.
Turns "which of these agents is heavily loaded" into a glance
instead of a menu dive.
Taxonomy
- New agent.memory event: { agentId, bytes?, count? }. STATEFUL, so
a late subscriber sees the last value replayed and pawns arrive
pre-sized. count is included in the schema for a future combo
metric but not emitted yet — bytes carries the visual today.
Backend (routes/world.rs SSE loop)
- Per-agent, per-tick std::fs::metadata() on
brain_dir()/claw_<uuid>.h5. Just the inode stat — no HDF5 open,
no memory count, sub-ms per agent. Emit agent.memory { bytes }
only when the value has changed (or on first sight).
- Tracks last_bytes: HashMap<String, u64> in the SSE-stream scope
alongside the existing status HashMap.
- Missing file (agent never provisioned a brain) reads as 0 bytes
and yields scale = 1.0 downstream — pawn stays small.
Engine
- GPawn gains memoryScale (visible) + memoryScaleTarget (chased).
Base is 1.0; ensurePawn initializes both.
- memoryScaleFromBytes(bytes): 1 + log10(1 + bytes/1MB) * 0.6, cap
MAX_MEMORY_SCALE = 3.5. So 10MB ~ 1.6x, 100MB ~ 2.2x, 1GB ~ 2.8x.
Log curve keeps a heavy brain readable without a lite one being
invisible.
- onMemory(e) sets the target. stepPawns eases the visible scale
toward it at ~3/sec — a big incoming snapshot doesn't pop the
sphere; it swells in like it's inhaling.
Renderer (WorldCanvas)
- Live subscription registers agent.memory alongside the existing
status/touch/reasoning listeners.
- Pawn sphere scale = 5 * p.memoryScale (was hardcoded 8). Halo
scales in proportion (max(24, 4.25 * s)) so a memory-heavy agent
reads as a bigger presence, not a small dot with a huge halo.
- AABB bounds for the frame-camera math updated to use s instead
of 8 so the camera actually frames a big agent when it's the
outlier.
Not yet wired: comm lines between pawns when agents talk to each
other (Commit E next), and the topology-edge overlay that renders
the graph shape dimly at rest. Both build on top of this — bigger
dots make comm beams more visible.
|
||
|
|
ebb5b5780b |
chore: cargo fmt --all — clean up a2a merge's fmt violations
The a2a merge (
|
||
|
|
85b0e1ac33 |
feat(observe): surface delegation + A2A in the live world feed via audit poll
Edge-initiated inter-agent events (gated delegation, A2A ingress) bypass the
run loop, so the world SSE now polls the append-only audit log (cursor on the
BIGINT id, seeded to max on first pass) and emits:
- delegation.invoked -> agent.delegate {fromAgentId,toAgentId,toName,task}
- a2a.invoked -> a2a.invoked {agentId}
TeamObserver renders agent.delegate as an A->B handoff in the team timeline;
adds the agent.delegate taxonomy type. Reliable live (the synthesized-run path
never streamed — active_runs + first-sight cursor jump skip it).
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
||
|
|
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]>
|
||
|
|
66c4a80985 |
Agents: read-only agent-to-agent observer in the chat card + live agent.message
A new "Observe" button (Eye) sits next to "+ New" in the agent chat header; clicking flips the card into a read-only observer of that agent's conversations with other agents, updating live as messages happen. - frontend: AgentObserver (history from /api/claw-chat/* + live agent.message overlay filtered to the agent, read-only banner, no composer); ClawChatSection toggle + flip. - backend: emit a live agent.message run-event when chat.send succeeds — events.rs AgentMessage variant, chat.send returns to_id, runtime emits in both tool paths, world.rs normalizes agent_message → agent.message SSE. No migration, no new table. Roadmap (not built): group/multi-party rooms; A2A protocol (a2a-rs) adoption. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> |
||
|
|
3554a3aaf2 |
CI: remove k8s stages, fix the Docker-level pipeline green
Survey + fixes so the pipeline passes at the Docker level (no k8s).
- Remove k8s: drop the `sandbox-k8s` job (kind/Calico/--features k8s-tests) and the
"Helm chart lints" gate step. release.yml was already k8s-clean.
- Rust job:
- `cargo fmt --all` — fix pre-existing formatting drift (fmt --check was failing).
- clippy -D warnings: fix 3 lib warnings (cm-brain sort_by_key→Reverse, cm-api
fleet.rs doc list indentation, node_rules map_or→is_none_or).
- Regenerate the .sqlx offline cache (was missing the cm-runtime run_loop test
query → offline compile failed). DB-backed tests use testcontainers at runtime.
- Set SQLX_OFFLINE=true on the rust + e2e jobs so query! macros compile against
the committed cache deterministically (no DB needed at compile time).
- Frontend job:
- Fix the 1 ESLint error (useAgentTelemetry: no setState-synchronously-in-effect;
tag the slice with agentId + derive null on mismatch).
- Fix 2 stale panel-params tests (`terminal` is a valid app id now; assert the
current APP_IDS + use a genuinely-unknown id for the reject case).
Verified locally: fmt clean, clippy --all-targets -D warnings clean (offline),
frontend lint 0 errors, tsc clean, 86/86 frontend tests pass, build OK.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
||
|
|
11a1f22daa |
Agents page: reorganize into the Agent Command Center (per-agent live metrics)
Replace the single centered "anatomy profile" + resizable chat split with an
operator command center: compact 62px identity strip → 5-tile per-agent metrics
band → three independently-scrolling LIVE · BRAIN · SURFACE columns. Chat moves to
the computer's Chat app; the right computer pullout (DevicePanel) is untouched.
- backend: cm-api/routes/world.rs emits per-agent `telemetry{agentId,tokensPerMin,
costPerHr,loops,doorsPending}` in the SSE loop, from 4 batched GROUP BY queries
(usage_events tokens/min + credits/hr, active routines, pending approvals) — all
real, no migration. taxonomy `telemetry` gains optional agentId; stateKey now
keys it per-agent so slices don't clobber.
- frontend: new ClawCommandCenter + anatomy-cards (shared cards extracted from
Dashboard); useAgentTelemetry(agentId) feeds the metric band (Doors amber>0/
green=0); LIVE column streams the agent's task.update / reasoning.delta /
tool.call (replaces the mocked VitalsCard heatmap with a live activity chart).
- Dashboard: left region → full-height ClawCommandCenter; chat launcher opens the
computer Chat app; removed the dead anatomy cluster + unused imports.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
||
|
|
3b012b12bf |
World: explosive file sparks + 3 real views (Flat 2D / Live Gource / Brain)
- Explosive sparks on file/project I/O: /api/world/live tags file-ish tools
(read/write/drive/vault/obsidian/edit/save) with touch weight 1; the engine
carries it as a node "burst" and the renderer fires a big fast particle blast.
- The formation tabs are now genuinely different views:
- Flat — the previous 2D React-Flow forest (WorldFlow), overlaid.
- Live — the WebGL Gource clone (glow sprites + sparks + trails + beams).
- Hierarchy — a NEW neural-brain view: agents are neurons in a dormant
two-hemisphere mesh; active agents fire signal particles along pathway edges
to their nearest neighbours (chaining), so the brain lights up as agents work.
- Render groups (gource/brain) + 3D particle system so signals travel in depth;
shared particle update; OrbitControls (rotate + pinch-zoom) across all WebGL views.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
||
|
|
380b95da2a |
World: normalize real run_events into the live taxonomy (the data unlock)
/api/world/live now tails each active run's journal (run_events) and normalizes
the runner's existing events into taxonomy events — no runner change needed:
- text_delta -> agent.reasoning.delta (the live reasoning stream)
- step_started -> agent.tool.call + node.activity + world.touch on the tool
node (agents visibly converge on the tool they're using)
- approval_required -> door.request
Per-run seq cursor streams forward only (skips backlog on first sight). This
lights up REAL data for both the World view and the upcoming Observe surface.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
||
|
|
53279e6339 |
World: Phase C — replay scrubber (Gource-style playback)
- /api/world/replay?hours= — reconstructs a sorted, timestamped taxonomy timeline from the workspace's run history (agent_runs ⋈ sessions) for client playback. - engine.clearWorldNodes() — drop transient world nodes/targets for a loop restart. - WorldCanvas: a WorldClock control (Live ⇄ Replay) feeding the same engine — play/pause, 1x/2x/4x speed, seek bar, loop. Live subscriptions detach in replay. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> |
||
|
|
05c54de47d |
World: real-data convergence — pawns roam the live tree; runs drive world.touch
- engine: when there are no service/event touch-nodes (a quiet feed), pawns roam the real org/company/team structure so the view is always alive with zero synthetic data; real touch-nodes take priority when present. - /api/world/live: emit world.touch + node.activity per currently-running run (agent_runs joined to sessions, state='running') so each working agent visibly converges on its active-run node; fold running agents into working status; telemetry.loops = active run count. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> |
||
|
|
39bacbd1d2 |
World page: live WebGL Gource-style visualization (Phases A+B)
Replaces the static React-Flow world forest with a real-time WebGL view of the swarm working, plus the live event contract that feeds it. Phase A — the live contract + feed: - frontend/src/lib/live/taxonomy.ts — the 13-event Clawmates Event Taxonomy as typed TS (the shared World/Observe contract). - frontend/src/lib/live/useClawmatesLive.ts — native shared-singleton live client (EventSource to /api/world/live, typed fan-out, replay-of-last-state to late subscribers, refcounted, synthetic fallback so views are always alive). - crates/cm-api/src/routes/world.rs — GET /api/world/live, authed + workspace- scoped SSE emitting the taxonomy (real agent.status from live-container state, a topology.update of the workspace's agents, telemetry with real doorsPending), mirroring run_events_sse. normalize() seam documented for run_events->world.touch. Phase B — the WebGL engine: - frontend/src/components/world/engine.ts — Gource-inspired force-directed model: org/company/team tree (sibling repulsion + parent spring + friction), agent pawns that converge on the touched node and beam it, world nodes that glow with heat and fade when idle. Framework-agnostic (renderer-independent) state+math. - frontend/src/components/world/WorldCanvas.tsx — three.js scene (ortho cam, UnrealBloom), render loop syncing engine state, camera auto-fit, HTML labels, raycast click->select, Hierarchy/Flat/Live formation switch. - Dashboard.tsx: swap <WorldFlow/> -> <WorldCanvas/> at the world-tier seam (shared claw-tier pieces untouched; WorldFlow.tsx kept for now). - Adds three (+ @types/three). Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> |