Skip to content

Commit 408bf7f

Browse files
Claude/resume repos migration 9 o2 u1 (#30)
<!-- SPDX-License-Identifier: PMPL-1.0-or-later --> ## Summary <!-- Briefly describe what this PR does and why. Link to related issues with "Closes #N". --> ## Changes <!-- List the key changes introduced by this PR. --> - ## RSR Quality Checklist <!-- Check all that apply. PRs that fail required checks will not be merged. --> ### Required - [ ] Tests pass (`just test` or equivalent) - [ ] Code is formatted (`just fmt` or equivalent) - [ ] Linter is clean (no new warnings or errors) - [ ] No banned language patterns (no TypeScript, no npm/bun, no Go/Python) - [ ] No `unsafe` blocks without `// SAFETY:` comments - [ ] No banned functions (`believe_me`, `unsafeCoerce`, `Obj.magic`, `Admitted`, `sorry`) - [ ] SPDX license headers present on all new/modified source files - [ ] No secrets, credentials, or `.env` files included ### As Applicable - [ ] `.machine_readable/STATE.a2ml` updated (if project state changed) - [ ] `.machine_readable/ECOSYSTEM.a2ml` updated (if integrations changed) - [ ] `.machine_readable/META.a2ml` updated (if architectural decisions changed) - [ ] Documentation updated for user-facing changes - [ ] `TOPOLOGY.md` updated (if architecture changed) - [ ] `CHANGELOG` or release notes updated - [ ] New dependencies reviewed for license compatibility (PMPL-1.0-or-later / MPL-2.0) - [ ] ABI/FFI changes validated (`src/abi/` and `ffi/zig/` consistent) ## Testing <!-- Describe how you tested these changes. --> ## Screenshots <!-- If applicable, add screenshots or terminal output demonstrating the change. --> --------- Signed-off-by: Jonathan D.A. Jewell <6759885+hyperpolymath@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com>
1 parent c44b572 commit 408bf7f

4 files changed

Lines changed: 227 additions & 3 deletions

File tree

cartridges/local-coord-mcp/adapter/local_coord_adapter.zig

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -818,6 +818,86 @@ fn dispatch(tool: []const u8, body: []const u8, resp: []u8, allocator: std.mem.A
818818
return .{ .status = 200, .body = resp[0..stream.pos] };
819819
}
820820

821+
if (std.mem.eql(u8, tool, "coord_health")) {
822+
const parsed = std.json.parseFromSlice(std.json.Value, allocator, body, .{}) catch return .{ .status = 400, .body = errJson(resp, "invalid json") };
823+
defer parsed.deinit();
824+
const token_val = parsed.value.object.get("token") orelse return .{ .status = 400, .body = errJson(resp, "missing token") };
825+
var token: [16]u8 = undefined;
826+
if (!parseToken(token_val.string, &token)) return .{ .status = 400, .body = errJson(resp, "invalid token hex") };
827+
828+
// Gather per-peer counts by scanning the 16 slots directly.
829+
var peer_active: u8 = 0;
830+
var by_kind = [_]u8{ 0, 0, 0, 0, 0, 0 }; // claude/gemini/copilot/custom/openai/mistral
831+
var by_role = [_]u8{ 0, 0, 0 }; // master / journeyman / apprentice
832+
var i: c_int = 0;
833+
while (i < 16) : (i += 1) {
834+
const k = ffi.coord_read_peer_kind(i);
835+
if (k < 0) continue;
836+
peer_active += 1;
837+
if (k >= 0 and k < 6) by_kind[@intCast(k)] += 1;
838+
const r = ffi.coord_read_peer_role(i);
839+
if (r >= 0 and r < 3) by_role[@intCast(r)] += 1;
840+
}
841+
842+
// Per-kind reject window counts + cooldown flags.
843+
var reject_counts = [_]c_int{ 0, 0, 0, 0, 0, 0 };
844+
var cooldown_flags = [_]c_int{ 0, 0, 0, 0, 0, 0 };
845+
var kind_valid = [_]bool{ false, false, false, false, false, false };
846+
var ki: c_int = 0;
847+
while (ki < 6) : (ki += 1) {
848+
const rc = ffi.coord_count_rejects_recent(&token, 16, ki);
849+
if (rc < 0) {
850+
// First bad-token return short-circuits to 401 — otherwise we'd
851+
// silently emit {success:true, ...0s}.
852+
if (ki == 0 and rc == -1) return .{ .status = 401, .body = errJson(resp, "unauthenticated") };
853+
continue;
854+
}
855+
reject_counts[@intCast(ki)] = rc;
856+
kind_valid[@intCast(ki)] = true;
857+
const cd = ffi.coord_kind_in_cooldown(&token, 16, ki);
858+
if (cd >= 0) cooldown_flags[@intCast(ki)] = cd;
859+
}
860+
861+
const quar = ffi.coord_count_quarantine(&token, 16);
862+
const clm = ffi.coord_count_claims(&token, 16);
863+
const trk = ffi.coord_count_track(&token, 16);
864+
865+
var stream = std.io.fixedBufferStream(resp);
866+
const w = stream.writer();
867+
std.fmt.format(w,
868+
"{{\"success\":true,\"peers\":{{\"active\":{d},\"max\":16," ++
869+
"\"by_kind\":{{\"claude\":{d},\"gemini\":{d},\"copilot\":{d},\"custom\":{d},\"openai\":{d},\"mistral\":{d}}}," ++
870+
"\"by_role\":{{\"master\":{d},\"journeyman\":{d},\"apprentice\":{d}}}}}," ++
871+
"\"quarantine\":{{\"pending\":{d},\"max\":32}}," ++
872+
"\"claims\":{{\"active\":{d},\"max\":64}}," ++
873+
"\"track\":{{\"entries\":{d},\"max\":512}}," ++
874+
"\"rejects\":{{\"window_ms\":600000,\"cooldown_ms\":30000," ++
875+
"\"recent_by_kind\":{{\"claude\":{d},\"gemini\":{d},\"copilot\":{d},\"custom\":{d},\"openai\":{d},\"mistral\":{d}}}," ++
876+
"\"in_cooldown\":[",
877+
.{
878+
peer_active,
879+
by_kind[0], by_kind[1], by_kind[2], by_kind[3], by_kind[4], by_kind[5],
880+
by_role[0], by_role[1], by_role[2],
881+
quar, clm, trk,
882+
reject_counts[0], reject_counts[1], reject_counts[2], reject_counts[3], reject_counts[4], reject_counts[5],
883+
},
884+
) catch return .{ .status = 500, .body = errJson(resp, "buffer overflow") };
885+
886+
var wrote_cd: bool = false;
887+
ki = 0;
888+
while (ki < 6) : (ki += 1) {
889+
if (cooldown_flags[@intCast(ki)] == 1) {
890+
if (wrote_cd) w.writeAll(",") catch return .{ .status = 500, .body = errJson(resp, "buffer overflow") };
891+
wrote_cd = true;
892+
w.writeAll("\"") catch return .{ .status = 500, .body = errJson(resp, "buffer overflow") };
893+
w.writeAll(kindName(ki)) catch return .{ .status = 500, .body = errJson(resp, "buffer overflow") };
894+
w.writeAll("\"") catch return .{ .status = 500, .body = errJson(resp, "buffer overflow") };
895+
}
896+
}
897+
w.writeAll("]}}") catch return .{ .status = 500, .body = errJson(resp, "buffer overflow") };
898+
return .{ .status = 200, .body = resp[0..stream.pos] };
899+
}
900+
821901
return .{ .status = 404, .body = errJson(resp, "not implemented") };
822902
}
823903

cartridges/local-coord-mcp/cartridge.json

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -573,7 +573,23 @@
573573
"type": "object"
574574
},
575575
"name": "coord_get_peer_capabilities"
576+
},
577+
{
578+
"description": "Read-only snapshot of coord-mcp state: active peer count (with per-kind / per-role breakdown), pending quarantine depth, active claims, track-record fill, and recent reject counts with cooldown flags. Lets other tooling monitor coord health without walking every export individually.",
579+
"inputSchema": {
580+
"properties": {
581+
"token": {
582+
"description": "Session token from coord_register — any active peer may poll.",
583+
"type": "string"
584+
}
585+
},
586+
"required": [
587+
"token"
588+
],
589+
"type": "object"
590+
},
591+
"name": "coord_health"
576592
}
577593
],
578-
"version": "0.7.0"
594+
"version": "0.8.0"
579595
}

cartridges/local-coord-mcp/cartridge.ncl

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
spdx = "PMPL-1.0-or-later",
1212
copyright = "Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>",
1313
name = "local-coord-mcp",
14-
version = "0.7.0",
14+
version = "0.8.0",
1515
description = "Localhost multi-instance coordination — peer discovery, message passing, and task claiming for parallel AI sessions on the same machine",
1616
domain = "ai",
1717
tier = "Ayo",
@@ -295,5 +295,16 @@
295295
required = ["token", "peer_id"],
296296
},
297297
},
298+
{
299+
name = "coord_health",
300+
description = "Read-only snapshot of coord-mcp state: active peer count (with per-kind / per-role breakdown), pending quarantine depth, active claims, track-record fill, and recent reject counts with cooldown flags. Lets other tooling monitor coord health without walking every export individually.",
301+
inputSchema = {
302+
type = "object",
303+
properties = {
304+
token = { type = "string", description = "Session token from coord_register — any active peer may poll." },
305+
},
306+
required = ["token"],
307+
},
308+
},
298309
],
299310
}

cartridges/local-coord-mcp/ffi/local_coord_ffi.zig

Lines changed: 118 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -840,6 +840,89 @@ pub export fn coord_read_peer_provers(peer_idx: c_int, out: [*]u8, out_cap: c_in
840840
return @intCast(plen);
841841
}
842842

843+
// ═══════════════════════════════════════════════════════════════════════
844+
// Health metrics — read-only aggregates over live registries. Exposed so
845+
// the adapter can render `coord_health` without knowing the registry
846+
// layout. All functions take the caller's token and return -1 on auth
847+
// failure so individual peers can poll without a master gate but rogue
848+
// local processes can't.
849+
// ═══════════════════════════════════════════════════════════════════════
850+
851+
fn validateToken(token_ptr: [*]const u8, token_len: c_int) bool {
852+
return findPeerByToken(token_ptr, @intCast(token_len)) != null;
853+
}
854+
855+
/// Count active quarantine entries. Returns count, -1 on bad token.
856+
pub export fn coord_count_quarantine(token_ptr: [*]const u8, token_len: c_int) c_int {
857+
mutex.lock();
858+
defer mutex.unlock();
859+
if (!validateToken(token_ptr, token_len)) return -1;
860+
var n: c_int = 0;
861+
for (&quarantine) |*q| {
862+
if (q.active) n += 1;
863+
}
864+
return n;
865+
}
866+
867+
/// Count active claims. Returns count, -1 on bad token.
868+
pub export fn coord_count_claims(token_ptr: [*]const u8, token_len: c_int) c_int {
869+
mutex.lock();
870+
defer mutex.unlock();
871+
if (!validateToken(token_ptr, token_len)) return -1;
872+
var n: c_int = 0;
873+
for (&claims) |*c| {
874+
if (c.active) n += 1;
875+
}
876+
return n;
877+
}
878+
879+
/// Count track-record entries in the ring (saturates at MAX_TRACK).
880+
/// Returns count, -1 on bad token.
881+
pub export fn coord_count_track(token_ptr: [*]const u8, token_len: c_int) c_int {
882+
mutex.lock();
883+
defer mutex.unlock();
884+
if (!validateToken(token_ptr, token_len)) return -1;
885+
return @intCast(track_count);
886+
}
887+
888+
/// Count rejections in the current window for the given client_kind.
889+
/// Returns count, -1 on bad token, -2 on bad kind.
890+
pub export fn coord_count_rejects_recent(
891+
token_ptr: [*]const u8,
892+
token_len: c_int,
893+
kind: c_int,
894+
) c_int {
895+
mutex.lock();
896+
defer mutex.unlock();
897+
if (!validateToken(token_ptr, token_len)) return -1;
898+
if (kind < 0 or kind >= KIND_COUNT) return -2;
899+
const k: usize = @intCast(kind);
900+
const now_ms: u64 = @intCast(std.time.milliTimestamp());
901+
var n: c_int = 0;
902+
for (reject_ring[k]) |ts| {
903+
if (ts == 0) continue;
904+
if (now_ms > ts and (now_ms - ts) > REJECT_WINDOW_MS) continue;
905+
n += 1;
906+
}
907+
return n;
908+
}
909+
910+
/// Returns 1 if the given client_kind is currently in reject-cooldown,
911+
/// 0 otherwise, -1 on bad token, -2 on bad kind.
912+
pub export fn coord_kind_in_cooldown(
913+
token_ptr: [*]const u8,
914+
token_len: c_int,
915+
kind: c_int,
916+
) c_int {
917+
mutex.lock();
918+
defer mutex.unlock();
919+
if (!validateToken(token_ptr, token_len)) return -1;
920+
if (kind < 0 or kind >= KIND_COUNT) return -2;
921+
const ck: ClientKind = @enumFromInt(kind);
922+
const now_ms: u64 = @intCast(std.time.milliTimestamp());
923+
return if (isInCooldown(ck, now_ms)) 1 else 0;
924+
}
925+
843926
/// Deregister a peer. Releases any claims it holds.
844927
pub export fn coord_deregister(token_ptr: [*]const u8, token_len: c_int) c_int {
845928
mutex.lock();
@@ -2016,7 +2099,7 @@ pub export fn boj_cartridge_name() [*:0]const u8 {
20162099
}
20172100

20182101
pub export fn boj_cartridge_version() [*:0]const u8 {
2019-
return "0.7.0";
2102+
return "0.8.0";
20202103
}
20212104

20222105
// ═══════════════════════════════════════════════════════════════════════
@@ -2391,6 +2474,40 @@ test "set and read peer capabilities (Task #34)" {
23912474
coord_reset();
23922475
}
23932476

2477+
test "coord_health counts basics" {
2478+
coord_reset();
2479+
var tok1: [TOKEN_LEN]u8 = undefined;
2480+
var tok2: [TOKEN_LEN]u8 = undefined;
2481+
var tok3: [TOKEN_LEN]u8 = undefined;
2482+
var suf: [4]u8 = undefined;
2483+
2484+
_ = coord_register(0, -1, &tok1, &suf); // claude → journeyman
2485+
_ = coord_register(1, -1, &tok2, &suf); // gemini → apprentice
2486+
_ = coord_register(4, -1, &tok3, &suf); // openai → apprentice
2487+
2488+
// Claim from tok1 contributes to active claim count.
2489+
const t = "health-test-task";
2490+
try std.testing.expectEqual(@as(c_int, 0), coord_claim_task(&tok1, TOKEN_LEN, t.ptr, @intCast(t.len)));
2491+
2492+
// Bad token returns -1.
2493+
var bad: [TOKEN_LEN]u8 = [_]u8{0} ** TOKEN_LEN;
2494+
try std.testing.expectEqual(@as(c_int, -1), coord_count_claims(&bad, TOKEN_LEN));
2495+
try std.testing.expectEqual(@as(c_int, -1), coord_count_quarantine(&bad, TOKEN_LEN));
2496+
try std.testing.expectEqual(@as(c_int, -1), coord_count_track(&bad, TOKEN_LEN));
2497+
2498+
// With a valid token, counts are positive / zero as expected.
2499+
try std.testing.expectEqual(@as(c_int, 1), coord_count_claims(&tok1, TOKEN_LEN));
2500+
try std.testing.expectEqual(@as(c_int, 0), coord_count_quarantine(&tok1, TOKEN_LEN));
2501+
try std.testing.expectEqual(@as(c_int, 0), coord_count_track(&tok1, TOKEN_LEN));
2502+
2503+
// Recent-rejects for an unseen kind (mistral=5) is 0; kind out of range is -2.
2504+
try std.testing.expectEqual(@as(c_int, 0), coord_count_rejects_recent(&tok1, TOKEN_LEN, 5));
2505+
try std.testing.expectEqual(@as(c_int, -2), coord_count_rejects_recent(&tok1, TOKEN_LEN, 99));
2506+
try std.testing.expectEqual(@as(c_int, 0), coord_kind_in_cooldown(&tok1, TOKEN_LEN, 0));
2507+
2508+
coord_reset();
2509+
}
2510+
23942511
test "register rejects master role_hint" {
23952512
coord_reset();
23962513
var tok: [TOKEN_LEN]u8 = undefined;

0 commit comments

Comments
 (0)