world viz: agents grow with their brain (log-curve dot size)
ci / gates (push) Successful in 6s
ci / frontend (push) Successful in 35s
ci / rust (push) Successful in 2m38s
ci / e2e (push) Has been skipped
ci / publish (push) Successful in 2m40s

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.
This commit is contained in:
Omar Sobh
2026-07-09 11:52:45 -07:00
parent aed21b654c
commit aa941b72a1
4 changed files with 69 additions and 7 deletions
+8 -1
View File
@@ -18,6 +18,11 @@ export interface TaxonomyEvents {
};
/** Observe → REASONING STREAM (append). */
"agent.reasoning.delta": { agentId: string; text: string; channel?: "think" | "say" | "tool" };
/** World → pawn scales with brain-file size (log curve). Sent by the
* server periodically for each agent whose `.brain` file has changed.
* Fresh agents start small ("size of their letters"); loaded agents
* visibly bloom out. */
"agent.memory": { agentId: string; bytes?: number; count?: number };
/** Observe → the live computer-screen tile. */
"agent.computer.frame": {
agentId: string;
@@ -81,6 +86,7 @@ export const TAXONOMY_TYPES: TaxonomyType[] = [
"agent.status",
"agent.task.update",
"agent.reasoning.delta",
"agent.memory",
"agent.computer.frame",
"agent.tool.call",
"door.request",
@@ -102,6 +108,7 @@ export const TAXONOMY_TYPES: TaxonomyType[] = [
export const STATEFUL_TYPES = new Set<TaxonomyType>([
"agent.status",
"agent.task.update",
"agent.memory",
"node.activity",
"telemetry",
"topology.update",
@@ -110,7 +117,7 @@ export const STATEFUL_TYPES = new Set<TaxonomyType>([
/** The replay key per stateful event (one retained value per agent/node/routine). */
export function stateKey<T extends TaxonomyType>(type: T, d: TaxonomyPayload<T>): string {
if (type === "agent.status" || type === "agent.task.update")
if (type === "agent.status" || type === "agent.task.update" || type === "agent.memory")
return `${type}:${(d as TaxonomyEvents["agent.status"]).agentId}`;
if (type === "node.activity") return `${type}:${(d as TaxonomyEvents["node.activity"]).nodeId}`;
if (type === "routine.update") return `${type}:${(d as TaxonomyEvents["routine.update"]).routineId}`;