Files
skills/tests/test_hub_space.py
T
Kevin Veen-BirkenbachandClaude Opus 5.5 86b55dbc91 fix(orchestrator): keep subagents behind their agent on the 3D time axis
With depth = time, #agents/3d drew subagents behind the session that
spawned them. A subagent took the mtime of its meta.json, which is
rewritten when it finishes, and a session took the moment the hub first
saw its name, or the window end when it had none.

Sessions now take the first line of their transcript and subagents the
first line of theirs, both from /api/subagents, with the old values as
fallback. SpaceView.notBeforeParent moves a spawned node without a time,
or with one before its parent's, to its parent's time, nested subagents
included, so no subagent lies before the agent that spawned it. The 3D
signature carries the time, so the nodes move once the lifetimes load.

🎯 Fix: 93 %
💥 Break: 2 %
💸 Cost: 100 %
⏱️ Time: –
🛡️ Security: 100 %
🏆 Score: 91
🚦 Gate: ✅ 90/5, cost and time <= 100, security 100 %

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 11:32:16 +00:00

258 lines
17 KiB
Python

from __future__ import annotations
import itertools
import json
import math
import subprocess
import unittest
from hub_support import SPACE
from kvb_orchestrator import settings
SPACE_NOW = 1_800_000_000
SPACE_LOW = SPACE_NOW - 86400
SPACE_PROBE = """
const S = require(process.argv[1]);
const now = Number(process.argv[2]);
const low = Number(process.argv[3]);
let seed = 7;
const next = () => {
seed = (Math.imul(seed, 1664525) + 1013904223) >>> 0;
return seed / 4294967296;
};
const median = (list) => [...list].sort((a, b) => a - b)[Math.floor(list.length / 2)];
const clock = (run) => {
const began = process.hrtime.bigint();
const value = run();
return [Number(process.hrtime.bigint() - began) / 1e6, value];
};
const sessions = Array.from({length: 30}, (_, index) => `s${index}`);
const keys = ["name:Maintainer", ...sessions];
const nodes = [
{id: "Operator", kind: "operator", parent: null, label: "Operator", timeline: [{name: "Operator", since: now}]},
{id: "name:Maintainer", kind: "chair", parent: null, label: "Maintainer", timeline: [{name: "Maintainer", since: low}]},
...sessions.map((id, index) => ({id, kind: "session", parent: null, label: `Renamed ${index}`, timeline: [{name: `Agent ${index}`, since: low - 10}, {name: `Renamed ${index}`, since: low + 43200}]})),
{id: "subagent x @ s1", kind: "subagent", parent: "s1", label: "subagent x", timeline: [{name: "subagent x", since: now}]},
];
const track = Array.from({length: 13}, (_, step) => ({ts: low + 7200 * step, orbit: Object.fromEntries(keys.map((key, at) => [key, {angle: -Math.PI / 2 + at * 0.2 + step * 0.05, radius: 150 + ((at * 37 + step * 11) % 150)}]))}));
const classes = ["direct", "direct", "direct", "broadcast", "security", "mayday", "pan-pan"];
const radio = Array.from({length: 5000}, (_, index) => {
const cls = classes[Math.floor(next() * classes.length)];
return {ts: low + 17.28 * index, source: keys[Math.floor(next() * keys.length)], targets: cls === "broadcast" ? [] : [keys[Math.floor(next() * keys.length)]], class: cls, text: "x"};
});
const grants = Array.from({length: 50}, (_, index) => ({ts: low + 1700 * index, target: sessions[index % sessions.length]}));
const subagents = {s1: [
{id: "root", depth: 1, since: low + 3600, until: low + 7200, started: low + 3600, type: "Explore", description: "scan", finished: true},
{id: "leaf", depth: 2, since: low + 4000, until: null, started: low + 4000, type: "Plan", description: "plan", finished: false},
{id: "late", depth: 1, since: null, until: null, started: low + 80000, type: "", description: "late", finished: false},
]};
const spots = Object.fromEntries(keys.map((key, at) => [key, {x: 200 * Math.cos(at), y: 200 * Math.sin(at)}]));
const input = {axis: "time", range: [low, now], span: [low, now], cursor: now, nodes, track, now: spots, lives: {s2: [low + 600, low + 1200]}, subagents, radio, grants, chair: "name:Maintainer", cap: S.ARC_CAP};
const built = S.scene(input);
const spawnOf = (scene, sub) => scene.arcs.find((arc) => arc.kind === "spawn" && arc.sub === sub);
const flat = (view) => S.camera({yaw: 0, pitch: 0, zoom: 1, pan_x: 0, pan_y: 0, ...view}, 860, 860);
const spawn = S.scene({...input, axis: "spawn"});
const calls = {count: 0};
const ctx = Object.fromEntries(["clearRect", "beginPath", "moveTo", "lineTo", "quadraticCurveTo", "closePath", "stroke", "fill", "arc", "strokeText", "fillText"].map((name) => [name, () => {
calls.count += 1;
}]));
const colors = Object.fromEntries(["line", "accent", "muted", "violet", "direct", "broadcast", "alarm", "security", "grant", "panel", "text"].map((role) => [role, "#123456"]));
const style = {colors, font: "12px sans", strong: "bold 12px sans", level: "Tiefe", cursor: "Stand", selected: "s3", format: String};
const builds = Array.from({length: 15}, () => clock(() => S.scene(input))[0]);
let stats = null;
const paints = Array.from({length: 30}, (_, frame) => {
const [spent, result] = clock(() => S.paint(ctx, built, S.camera({...S.DEFAULTS.graph, yaw: frame * 12}, 1280, 760), style));
stats = result;
return spent;
});
const mini = {...input, nodes: nodes.slice(0, 4), track: [], subagents: {}, lives: {}, grants: [], radio: [{ts: low + 43200, source: "s0", targets: ["s1"], class: "direct", text: "x"}], now: {s0: {x: 200, y: 0}, s1: {x: -200, y: 0}, "name:Maintainer": {x: 0, y: -200}}};
const small = S.scene(mini);
const cam = S.camera(S.DEFAULTS.graph, 860, 860);
const wire = small.arcs[0];
const [a, b, c] = [wire.from, wire.via, wire.to].map((entry) => S.project(cam, entry));
const tail = S.project(cam, small.lines.find((entry) => entry.node === "s0").points[0]);
const hit = (x, y) => {
const found = S.pick(small, cam, x, y, 10);
return found ? `${found.kind}:${found.kind === "line" ? found.line.node : found.arc.kind}` : null;
};
const sight = S.lens(S.DEFAULTS.sky, 1600, 900);
const synthetic = Array.from({length: 5000}, (_, index) => ({tone: ["direct", "broadcast", "alarm", "security", "grant", "spawn"][index % 6], ts: index}));
const capped = S.capArcs(synthetic, 2000);
console.log(JSON.stringify({
defaults: S.DEFAULTS, limits: S.LIMITS, axes: S.AXES, cap: S.ARC_CAP,
matrices: [[0, 0], [30, 20], [-135, 60], [90, -90], [179.5, 45], [12.3, -77.7]].map(([yaw, pitch]) => S.rotation(yaw, pitch)),
projected: [
[flat({}), {x: 0, y: 0, z: 0}], [flat({}), {x: 100, y: 0, z: 0}], [flat({}), {x: 100, y: 50, z: 400}], [flat({yaw: 90}), {x: 100, y: 0, z: 0}],
[flat({pitch: 90}), {x: 0, y: 100, z: 0}], [S.camera({yaw: 0, pitch: 0, zoom: 2, pan_x: 10, pan_y: -20}, 1720, 860), {x: 100, y: 0, z: 0}], [flat({}), {x: 0, y: 0, z: -1500}],
].map(([view, point]) => S.project(view, point)),
sorted: S.depthSort([{id: "a", depth: 5}, {id: "b", depth: 50}, {id: "c", depth: -20}, {id: "d", depth: 50}]).map((item) => item.id),
axis: {
time: [low, (low + now) / 2, now, low - 100, now + 100].map((ts) => S.depthOf("time", {ts, level: 0}, [low, now])),
still: S.depthOf("time", {ts: now, level: 0}, [now, now]),
spawn: [0, 1, 2, 4, 9].map((level) => S.depthOf("spawn", {ts: now, level}, [low, now])),
none: [S.depthOf("none", {ts: low, level: 3}, [low, now]), S.depthOf("none", {ts: now, level: 0}, [low, now])],
},
parents: S.notBeforeParent([
{id: "c", kind: "chair", ts: 200}, {id: "s", kind: "session", parent: "c", ts: 100}, {id: "w", kind: "twig", parent: "v", ts: 120},
{id: "t", kind: "twig", parent: "s", ts: 50}, {id: "u", kind: "twig", parent: "t"}, {id: "v", kind: "twig", parent: "s", ts: 150},
{id: "x", kind: "subagent", parent: "gone", ts: 30}, {id: "y", kind: "twig", parent: "z", ts: 5}, {id: "z", kind: "twig", parent: "y", ts: 7},
], new Set(["twig", "subagent"])).map((node) => [node.id, node.ts]),
same: [S.scene(input), S.scene(JSON.parse(JSON.stringify(input)))].every((other) => JSON.stringify(other) === JSON.stringify(built)),
lines: Object.fromEntries(built.lines.map((entry) => [entry.sub || entry.node, {kind: entry.kind, level: entry.level, alive: entry.alive, stamps: entry.points.map((point) => point.ts), first: entry.points[0], name: entry.timeline[0].name}])),
labels: built.labels.filter((label) => built.lines[label.line].node === "s5").map((label) => [label.at.ts, label.text]),
spawns: Object.fromEntries(["root", "leaf"].map((sub) => [sub, spawnOf(built, sub)])),
plane: built.plane, guides: built.guides, total: built.total, dropped: built.dropped, arcs: built.arcs.length,
alarms: built.arcs.filter((arc) => arc.tone === "alarm").length,
spawned: {
points: spawn.lines.map((entry) => entry.points.length), leaf: spawn.lines.find((entry) => entry.sub === "leaf").points[0].z, session: spawn.lines.find((entry) => entry.node === "s1").points[0].z,
arc: spawnOf(spawn, "leaf"), plane: spawn.plane, messages: spawn.arcs.filter((arc) => arc.kind !== "spawn").length,
},
capped: {kept: capped.kept.map((arc) => arc.ts), dropped: capped.dropped, small: S.capArcs(synthetic.slice(0, 100), 1500).kept.length},
frame: {build: median(builds), paint: median(paints), stats, calls: calls.count / 30},
picks: [hit(tail.x, tail.y), hit(0.25 * a.x + 0.5 * b.x + 0.25 * c.x, 0.25 * a.y + 0.5 * b.y + 0.25 * c.y), hit(3, 3)],
names: [5, 10, 15, 20, 99].map((ts) => S.nameAt([{name: "A", since: 10}, {name: "B", since: 20}], ts)),
tree: S.tree([{id: "x", depth: 1, since: 10, until: 50, started: 10}, {id: "y", depth: 1, since: 20, until: 30, started: 20}, {id: "z", depth: 2, since: 40, until: null, started: 40}, {id: "w", depth: 2, since: 25, until: null, started: 25}]).parents,
clamped: [S.clampView("graph", {axis: "time", yaw: 190, pitch: 100, zoom: 9, pan_x: -5000, pan_y: 1}), S.clampView("sky", {azimuth: -10, altitude: 99, fov: 5})],
tweened: S.tween("graph", {axis: "time", yaw: 170, pitch: 0, zoom: 1, pan_x: 0, pan_y: 0}, {axis: "spawn", yaw: -170, pitch: 10, zoom: 2, pan_x: 0, pan_y: 0}, 0.5),
tidy: S.tidy({axis: "time", yaw: 12.3456, zoom: 0.999}),
sky: {
south: [[30, 135], [0, 90], [0, 270], [45, 180]].map(([alt, az]) => S.place(sight, alt, az)),
horizon: S.horizon(sight, 8).map((point) => point.y),
east: S.place(S.lens({azimuth: 90, altitude: 0, fov: 180}, 1600, 900), 0, 90),
up: S.place(S.lens({azimuth: 180, altitude: 30, fov: 180}, 1600, 900), 30, 180),
narrow: S.place(S.lens({azimuth: 180, altitude: 0, fov: 90}, 1600, 900), 0, 225),
},
compass: [0, 22.4, 22.6, 180, 359, -90, 404].map(S.compass),
}));
"""
class TestSpaceModule(unittest.TestCase):
@classmethod
def setUpClass(cls):
command = ["node", "-e", SPACE_PROBE, str(SPACE), str(SPACE_NOW), str(SPACE_LOW)]
done = subprocess.run(command, capture_output=True, text=True, timeout=120, check=True)
cls.data = json.loads(done.stdout)
def assert_close(self, actual: dict, expected: dict) -> None:
for key, value in expected.items():
with self.subTest(key=key):
self.assertAlmostEqual(actual[key], value, places=6)
def test_the_defaults_limits_and_axes_match_the_server_validation(self):
self.assertEqual(self.data["defaults"], settings.SETTINGS["space"])
self.assertEqual(self.data["limits"], {view: {key: list(pair) for key, pair in limits.items()} for view, limits in settings.SPACE_LIMITS.items()})
self.assertEqual(tuple(self.data["axes"]), settings.SPACE_AXES)
def test_rotation_matrices_are_orthonormal(self):
self.assertEqual(len(self.data["matrices"]), 6)
for matrix in self.data["matrices"]:
rows = [matrix[0:3], matrix[3:6], matrix[6:9]]
with self.subTest(matrix=matrix):
for first, second in itertools.product(range(3), repeat=2):
self.assertAlmostEqual(sum(rows[first][k] * rows[second][k] for k in range(3)), float(first == second), places=12)
self.assertAlmostEqual(sum(rows[k][first] * rows[k][second] for k in range(3)), float(first == second), places=12)
(a, b, c), (d, e, f), (g, h, i) = rows
self.assertAlmostEqual(a * (e * i - f * h) - b * (d * i - f * g) + c * (d * h - e * g), 1.0, places=12)
def test_known_points_project_to_the_expected_screen_coordinates(self):
expected = [
{"x": 430, "y": 430, "depth": 0, "size": 1},
{"x": 530, "y": 430, "depth": 0, "size": 1},
{"x": 510, "y": 470, "depth": 400, "size": 0.8},
{"x": 430, "y": 430, "depth": -100, "size": 1600 / 1500},
{"x": 430, "y": 430, "depth": 100, "size": 1600 / 1700},
{"x": 1080, "y": 390, "depth": 0, "size": 2},
]
projected = self.data["projected"]
for actual, wanted in zip(projected[:-1], expected, strict=True):
self.assert_close(actual, wanted)
self.assertIsNone(projected[-1])
def test_depth_sort_draws_far_before_near_and_keeps_ties_in_order(self):
self.assertEqual(self.data["sorted"], ["b", "d", "a", "c"])
def test_the_axis_maps_time_spawn_depth_or_nothing(self):
axis = self.data["axis"]
self.assertEqual(axis["time"], [300, 0, -300, 300, -300])
self.assertEqual(axis["still"], -300)
self.assertEqual(axis["spawn"], [-300, -150, 0, 300, 300])
self.assertEqual(axis["none"], [0, 0])
def test_a_subagent_never_lies_before_the_agent_that_spawned_it(self):
self.assertEqual(self.data["parents"], [["c", 200], ["s", 100], ["w", 150], ["t", 100], ["u", 100], ["v", 150], ["x", 30], ["y", 7], ["z", 7]])
def test_world_lines_are_deterministic_and_follow_the_layout_of_their_time(self):
self.assertTrue(self.data["same"])
lines = self.data["lines"]
self.assertLessEqual({"Operator", "name:Maintainer", "subagent x @ s1", *(f"s{n}" for n in range(30))}, set(lines))
root, leaf, late = lines["root"], lines["leaf"], lines["late"]
self.assertEqual((root["kind"], root["level"], root["alive"], root["stamps"]), ("twig", 1, False, [SPACE_LOW + 3600, SPACE_LOW + 7200]))
self.assertEqual((leaf["level"], leaf["alive"], leaf["stamps"][0], leaf["stamps"][-1]), (2, True, SPACE_LOW + 4000, SPACE_NOW))
self.assertEqual((late["stamps"], late["name"]), ([SPACE_LOW + 80000, SPACE_NOW], "Subagent"))
self.assertEqual((lines["s2"]["stamps"], lines["s2"]["alive"]), ([SPACE_LOW + 600, SPACE_LOW + 1200], False))
self.assertEqual(lines["s5"]["stamps"], [SPACE_LOW + 7200 * step for step in range(13)])
self.assertEqual((lines["subagent x @ s1"]["level"], lines["subagent x @ s1"]["kind"]), (1, "subagent"))
angle, radius = -math.pi / 2 + 0.425, 229.5
born = self.data["spawns"]["root"]
self.assert_close(born["from"], {"x": radius * math.cos(angle), "y": radius * math.sin(angle), "z": 275, "ts": SPACE_LOW + 3600})
for sub in ("root", "leaf"):
with self.subTest(sub=sub):
self.assertEqual(self.data["spawns"][sub]["to"], lines[sub]["first"])
self.assertEqual(self.data["spawns"][sub]["from"]["z"], lines[sub]["first"]["z"])
self.assertEqual(self.data["labels"], [[SPACE_NOW, "Renamed 5"], [SPACE_LOW + 43200, "Renamed 5"], [SPACE_LOW, "Agent 5"]])
self.assertEqual(self.data["plane"], {"z": -300, "ts": SPACE_NOW})
self.assertEqual([(guide["z"], guide["ts"]) for guide in self.data["guides"]], [(300, SPACE_LOW), (-300, SPACE_NOW)])
spawned = self.data["spawned"]
self.assertEqual(set(spawned["points"]), {1})
self.assertEqual((spawned["session"], spawned["leaf"], spawned["plane"]), (-300, 0, None))
self.assertEqual((spawned["arc"]["from"]["z"], spawned["arc"]["to"]["z"]), (-150, 0))
self.assertLessEqual(spawned["messages"], self.data["cap"])
def test_arcs_are_capped_by_urgency_then_recency(self):
self.assertEqual((self.data["arcs"], self.data["dropped"]), (self.data["cap"], self.data["total"] - self.data["cap"]))
self.assertGreater(self.data["total"], self.data["cap"])
capped = self.data["capped"]
kept = capped["kept"]
self.assertEqual((len(kept), capped["dropped"], capped["small"]), (2000, 3000, 100))
self.assertEqual(kept, sorted(kept))
tones = ["direct", "broadcast", "alarm", "security", "grant", "spawn"]
by_tone = {tone: [ts for ts in kept if tones[ts % 6] == tone] for tone in tones}
self.assertEqual({tone: len(found) for tone, found in by_tone.items()}, {"direct": 0, "broadcast": 0, "alarm": 833, "security": 334, "grant": 0, "spawn": 833})
self.assertEqual(by_tone["security"], [ts for ts in range(5000) if ts % 6 == 3][-334:])
def test_a_frame_of_5000_events_draws_capped_batched_arcs_inside_the_frame_budget(self):
frame = self.data["frame"]
self.assertLessEqual(frame["stats"]["arcs"], self.data["cap"])
self.assertLess(frame["stats"]["batches"], 400)
self.assertLess(frame["paint"], 1000 / 60)
def test_picking_finds_the_world_line_the_arc_or_nothing(self):
self.assertEqual(self.data["picks"], ["line:s0", "arc:message", None])
def test_names_trees_and_views_follow_their_rules(self):
self.assertEqual(self.data["names"], ["A", "A", "A", "B", "B"])
self.assertEqual(self.data["tree"], {"x": "", "y": "", "w": "y", "z": "x"})
self.assertEqual(self.data["clamped"], [{"axis": "time", "yaw": -170, "pitch": 90, "zoom": 4, "pan_x": -2000, "pan_y": 1}, {"azimuth": 350, "altitude": 85, "fov": 20}])
self.assert_close(self.data["tweened"], {"pitch": 5, "zoom": 1.5, "pan_x": 0, "pan_y": 0})
self.assertAlmostEqual(abs(self.data["tweened"]["yaw"]), 180, places=6)
self.assertEqual((self.data["tweened"]["axis"], self.data["tidy"]), ("spawn", {"axis": "time", "yaw": 12.35, "zoom": 1}))
def test_the_sky_faces_south_by_default_and_turns_with_the_view(self):
sky = self.data["sky"]
width, height = 1600, 900
horizon = 0.86 * height
today = [{"x": width / 2 + ((az - 180) / 180) * width, "y": horizon - (alt / 90) * horizon * 0.94} for alt, az in ((30, 135), (0, 90), (0, 270), (45, 180))]
for actual, wanted in zip(sky["south"], today, strict=True):
self.assert_close(actual, wanted)
for y in sky["horizon"]:
self.assertAlmostEqual(y, horizon, places=6)
for name, wanted in (("east", {"x": 800, "y": horizon}), ("up", {"x": 800, "y": horizon}), ("narrow", {"x": width, "y": horizon})):
with self.subTest(name=name):
self.assert_close(sky[name], wanted)
self.assertEqual(self.data["compass"], [0, 0, 1, 4, 0, 6, 1])
if __name__ == "__main__":
unittest.main()