whoami: a bounded, edge-first peer list in compact --json (output.compact) (#1225)

With `output.compact` on, `rig whoami --json` lists at most `output.whoami_peers` peers (a new setting, default 8, env `OPENRIG_OUTPUT_WHOAMI_PEERS`), so the identity call every seat makes at boot stays small as a rig grows. Selection is edge-first: peers with an edge to or from the seat, then peers in the seat's pod, then the rest, each in the daemon's roster order; `edges` stays complete. When the bound cuts the list, `peersNote` says how many are shown out of how many, the order, the bound and the exact command that lists them all (with the same `--node-id` or `--session`), and an additive `peersOmitted` gives the count. A rig within the bound passes through unchanged plus `peersOmitted: 0`. Error answers print exactly as before. The new `--all-peers` prints today's compact output byte for byte and is forwarded on the SSH `--host` path; the remote's note names its own command. With the switch off (the default), every form is byte-identical to before, and `--full` and plain text are unchanged. The setting is registered like `output.compact` (CLI store, daemon twin, help, TUI label), and the `output.compact` help line now names queue writes, `queue show`, `queue transitions` and whoami peers. The startup guidance, the identity hint, two openrig-core skills, the capability map and three shipped team cultures add one conditional sentence naming `--all-peers`, true with the switch on or off. The daemon's `/api/whoami` is unchanged. Refs slice 15 P5.
This commit is contained in:
Mike Schwarz authored and GitHub committed 2026-10-13 07:48:06 -07:00
1 parent 8add46c40c
commit 904fa99bc5
18 files changed
+413 -18

No files matched your search

+2 -1
View File
@@ -101,7 +101,8 @@ Keys:
watchdog_keep_per_job, batch_size
launch.non_interruptive accept harness first-launch warnings at full bypass for new rigs (default off)
terminal.status_bar show the inner tmux status bar on launch (default off)
output.compact short default output for queue writes; --full restores the whole row (default off)
output.compact short default output for queue writes, queue show, queue transitions and whoami peers; --full restores today's output (default off)
output.whoami_peers with output.compact on, how many peers rig whoami --json lists, edge peers first; --all-peers lists all (default 8)
provider.usage_windows Codex and Claude usage windows in rig provider status (Codex read on request; Claude from its seats' status lines) (default off)
Precedence: CLI flag > environment variable > config file > default`)
+78 -6
View File
@@ -9,6 +9,9 @@ import type { StatusDeps } from "./status.js";
import { loadHostRegistry, resolveHost, hostDisplayTarget, resolveRemoteBearer, bearerAuthHeaders, classifyHttpFailedStep, classifyHttpError, type HttpHostEntry } from "../host-registry.js";
import { runCrossHostCommand, type RunCrossHostCommandOpts } from "../cross-host-executor.js";
import { emitCrossHostError, emitCrossHostFailure } from "../cross-host-cli-helpers.js";
import { ConfigStore } from "../config-store.js";
import { compactOutputEnabled } from "../queue-receipt.js";
import { shellQuote } from "../cross-host-executor.js";
interface WhoamiCliOptions {
nodeId?: string;
@@ -19,6 +22,7 @@ interface WhoamiCliOptions {
json?: boolean;
full?: boolean;
verbose?: boolean;
allPeers?: boolean;
}
export interface WhoamiDeps extends StatusDeps {
@@ -75,9 +79,9 @@ interface WhoamiResult {
* cannot silently re-bloat the every-boot path). Carries exactly the fields the
* boot + compaction-restore recovery contract treats as ground truth.
*/
function projectCompactWhoami(data: Record<string, unknown>): Record<string, unknown> {
function projectCompactWhoami(data: Record<string, unknown>, bounded?: BoundedPeers): Record<string, unknown> {
const id = (data["identity"] ?? {}) as Record<string, unknown>;
const peers = Array.isArray(data["peers"]) ? (data["peers"] as unknown[]) : [];
const peers = bounded ? bounded.peers : Array.isArray(data["peers"]) ? (data["peers"] as unknown[]) : [];
const transcript = (data["transcript"] ?? {}) as Record<string, unknown>;
return {
resolvedBy: data["resolvedBy"],
@@ -96,13 +100,63 @@ function projectCompactWhoami(data: Record<string, unknown>): Record<string, unk
return { logicalId: peer["logicalId"], sessionName: peer["sessionName"], runtime: peer["runtime"] };
}),
// KEEP: openrig-user SKILL.md documents peersNote as a required recovery field.
peersNote: data["peersNote"],
peersNote: bounded ? bounded.note : data["peersNote"],
...(bounded ? { peersOmitted: bounded.omitted } : {}),
// edges already carry only kind + to/from {logicalId, sessionName}.
edges: data["edges"],
transcript: { path: transcript["path"], tailCommand: transcript["tailCommand"] },
};
}
export interface BoundedPeers {
peers: unknown[];
omitted: number;
/** The daemon's own note when every peer fits; otherwise what was left out and how to list it. */
note: unknown;
}
/**
* Slice 15 (OPR.0.7.0.15) P5 — with `output.compact` on, compact `--json` lists at most `bound` peers so the
* every-boot call stays small as a rig grows. Edge peers (either direction) come first, then peers in the
* seat's own pod, then the rest, each in the daemon's roster order. Nothing is silently missing: the note
* says how many were left out and the exact command that lists them all, and `edges` stays complete. When
* every peer fits, the roster and the daemon's note pass through unchanged, so a small rig pays only for
* `peersOmitted: 0`.
*/
export function boundPeers(data: Record<string, unknown>, bound: number, allPeersCommand: string): BoundedPeers {
const peers = Array.isArray(data["peers"]) ? (data["peers"] as Array<Record<string, unknown> | null>) : [];
if (peers.length <= bound) return { peers, omitted: 0, note: data["peersNote"] };
const id = (data["identity"] ?? {}) as Record<string, unknown>;
const edges = (data["edges"] ?? {}) as { outgoing?: WhoamiEdge[]; incoming?: WhoamiEdge[] };
const linked = new Set<unknown>([
...(Array.isArray(edges.outgoing) ? edges.outgoing : []).map((edge) => edge?.to?.logicalId),
...(Array.isArray(edges.incoming) ? edges.incoming : []).map((edge) => edge?.from?.logicalId),
]);
const ownPod = id["podId"] ?? null;
const tier = (peer: Record<string, unknown> | null): number => {
if (linked.has(peer?.["logicalId"])) return 0;
if (ownPod !== null && peer?.["podId"] === ownPod) return 1;
return 2;
};
const ordered = [0, 1, 2].flatMap((t) => peers.filter((peer) => tier(peer) === t));
const shown = ordered.slice(0, bound);
const omitted = peers.length - shown.length;
const note = `peers = ${shown.length} of this rig's ${peers.length} (roster excluding self; edge peers first, then your pod, then roster order; `
+ `bound output.whoami_peers=${bound}); ${omitted} not shown; all: ${allPeersCommand}; `
+ "edges = directional relationships (always complete); use `rig ps --nodes` for node inventory including self + live state";
return { peers: shown, omitted, note };
}
/** The bound from `output.whoami_peers`, by the config store's own resolution; the default if unreadable. */
function whoamiPeersBound(): number {
try {
const value = new ConfigStore().get("output.whoami_peers");
return typeof value === "number" && Number.isInteger(value) && value >= 1 ? value : 8;
} catch {
return 8;
}
}
type TmuxExecFn = (cmd: string) => string;
const defaultTmuxExec: TmuxExecFn = (cmd: string) => execSync(cmd, { encoding: "utf-8" }).trim();
@@ -228,13 +282,18 @@ export function whoamiCommand(depsOverride?: WhoamiDeps): Command {
.option("--json", "JSON output for agents (compact identity-recovery projection by default)")
.option("--full", "Show the complete whoami payload (contextUsage, commands, runtimeContext, workspace, all sub-fields)")
.option("--verbose", "Alias for --full")
.option("--all-peers", "With output.compact on, list every peer in --json (today's compact output)")
.addHelpText("after", `
By default rig whoami is COMPACT: identity (rig/pod/member/session/runtime),
peers (with sessionName for 'rig send'), edges, and the transcript path — the
boot + compaction-restore recovery essentials. Use --full / --verbose for the
complete payload (contextUsage, command examples, runtime token detail,
workspace block). The compact form omits the Context line; use 'rig context' or
'rig whoami --full' for usage.`)
'rig whoami --full' for usage.
With output.compact on, --json lists at most output.whoami_peers peers (default
8), edge peers first, then your pod; peersNote and peersOmitted say how many were
left out. --all-peers lists every one (today's compact output).`)
.action(async (opts: WhoamiCliOptions) => {
// OPR.0.4.6.MH1 FR-2: selected-host routing — explicit --host wins;
// else the persisted selection feeds the SHIPPED --host path; no
@@ -292,8 +351,12 @@ workspace block). The compact form omits the Context line; use 'rig context' or
// Preserve the complete payload and original error; the routing hint is additive.
console.log(JSON.stringify(hint ? { ...res.data, hint } : res.data, null, 2));
} else {
// Compact default: the identity-recovery ALLOWLIST projection.
console.log(JSON.stringify(projectCompactWhoami(res.data)));
// Compact default: the identity-recovery ALLOWLIST projection; with output.compact on,
// a bounded peer list unless --all-peers asks for every one.
const bounded = !opts.allPeers && compactOutputEnabled()
? boundPeers(res.data, whoamiPeersBound(), allPeersCommand(opts))
: undefined;
console.log(JSON.stringify(projectCompactWhoami(res.data, bounded)));
}
if (res.status >= 400) process.exitCode = 1;
return;
@@ -421,6 +484,7 @@ async function runCrossHostWhoami(
if (opts.nodeId !== undefined) argv.push("--node-id", opts.nodeId);
if (opts.session !== undefined) argv.push("--session", opts.session);
if (opts.json) argv.push("--json");
if (opts.allPeers) argv.push("--all-peers");
const result = await runner(host, argv);
@@ -593,3 +657,11 @@ async function runFanOutWhoami(opts: WhoamiCliOptions, deps: WhoamiDeps): Promis
if (hasFailure) process.exitCode = 3;
}
/** The exact command that lists every peer, for the same identity this call resolved. */
function allPeersCommand(opts: WhoamiCliOptions): string {
const parts = ["rig", "whoami", "--json", "--all-peers"];
if (opts.nodeId) parts.push("--node-id", shellQuote(opts.nodeId));
else if (opts.session) parts.push("--session", shellQuote(opts.session));
return parts.join(" ");
}
+13
View File
@@ -168,6 +168,8 @@ export interface RiggedConfig {
// settings-store twin keeps the two registries in lockstep.
output: {
compact: boolean;
/** With compact on, how many peers `rig whoami --json` lists (edge peers first). */
whoamiPeers: number;
};
// Slice 01 (OPR.0.7.0.1) — Codex and Claude usage windows in the provider view (default
// off). Read by the daemon; the CLI key is the lockstep twin.
@@ -326,6 +328,7 @@ const DEFAULTS = {
// Slice 15 — compact command-output defaults OFF until the switch is on.
output: {
compact: false,
whoamiPeers: 8,
},
// Slice 01 — Codex usage windows OFF until the switch is on.
provider: {
@@ -446,6 +449,7 @@ export const VALID_KEYS = [
"terminal.status_bar",
// Slice 15 — compact command-output defaults; lockstep with the daemon twin.
"output.compact",
"output.whoami_peers",
// Slice 01 — Codex and Claude usage windows in the provider view; lockstep with the daemon twin.
"provider.usage_windows",
// OPR.0.4.6.FS-1 W2 — queue-retention maintenance knobs; CLI-settable twin,
@@ -545,6 +549,7 @@ export const ENV_MAP: Record<ValidKey, { primary: string; legacy?: string }> = {
// OPR.0.4.6.02 S1 — net-new key; OPENRIG_* primary only (no RIGGED_* legacy).
"terminal.status_bar": { primary: "OPENRIG_TERMINAL_STATUS_BAR" },
"output.compact": { primary: "OPENRIG_OUTPUT_COMPACT" },
"output.whoami_peers": { primary: "OPENRIG_OUTPUT_WHOAMI_PEERS" },
"provider.usage_windows": { primary: "OPENRIG_PROVIDER_USAGE_WINDOWS" },
// OPR.0.4.6.FS-1 W2 — retention knobs; net-new keys, OPENRIG_* primary only.
"retention.enabled": { primary: "OPENRIG_RETENTION_ENABLED" },
@@ -626,6 +631,7 @@ const KEY_TO_PATH: Record<ValidKey, string[]> = {
"snapshots.periodic.retention_keep": ["snapshots", "periodic", "retentionKeep"],
"terminal.status_bar": ["terminal", "statusBar"],
"output.compact": ["output", "compact"],
"output.whoami_peers": ["output", "whoamiPeers"],
"provider.usage_windows": ["provider", "usageWindows"],
"retention.enabled": ["retention", "enabled"],
"retention.transitions_days": ["retention", "transitionsDays"],
@@ -922,6 +928,12 @@ const KEY_CONSTRAINTS: Partial<Record<ValidKey, (raw: string, coerced: string |
// OPR.0.4.6.FS-1 W2 — retention numeric bounds. Lockstep with the daemon
// settings-store KEY_CONSTRAINTS twin (same messages/bounds). retention.enabled
// is a boolean (coerceValue enforces true/false) — no constraint entry.
// Slice 15 P5 — the whoami peer bound; lockstep with the daemon twin.
"output.whoami_peers": (raw, coerced) => {
if (!/^-?\d+$/.test((raw ?? "").trim()) || typeof coerced !== "number" || !Number.isInteger(coerced) || coerced < 1) {
throw new Error(`Invalid value for output.whoami_peers: must be an integer >= 1, got "${raw}"`);
}
},
"retention.transitions_days": (raw, coerced) => {
if (!/^-?\d+$/.test((raw ?? "").trim()) || typeof coerced !== "number" || !Number.isInteger(coerced) || coerced < 1) {
throw new Error(`Invalid value for retention.transitions_days: must be an integer >= 1, got "${raw}"`);
@@ -1158,6 +1170,7 @@ export class ConfigStore {
},
output: {
compact: v("output.compact") as boolean,
whoamiPeers: v("output.whoami_peers") as number,
},
provider: {
usageWindows: v("provider.usage_windows") as boolean,
@@ -173,6 +173,8 @@ describe("ConfigStore — extended namespaces (User Settings v0)", () => {
"terminal.status_bar",
// Slice 15 (OPR.0.7.0.15) — compact command-output defaults switch.
"output.compact",
// Slice 15 P5 — the whoami peer bound under output.compact.
"output.whoami_peers",
// Slice 01 (OPR.0.7.0.1) — Codex usage windows switch.
"provider.usage_windows",
// OPR.0.4.6.FS-1 W2 — queue-retention maintenance knobs.
@@ -0,0 +1,277 @@
import { describe, it, expect, vi, beforeAll, afterAll, beforeEach, afterEach } from "vitest";
import http from "node:http";
import fs from "node:fs";
import path from "node:path";
import os from "node:os";
import { ConfigStore } from "../src/config-store.js";
import { Command } from "commander";
import { whoamiCommand, boundPeers } from "../src/commands/whoami.js";
import { DaemonClient } from "../src/client.js";
import { STATE_FILE, type LifecycleDeps, type DaemonState } from "../src/daemon-lifecycle.js";
import type { StatusDeps } from "../src/commands/status.js";
/**
* Slice 15 (OPR.0.7.0.15) P5 — `rig whoami --json` lists a bounded, edge-first set of peers when
* `output.compact` is on. Switch off (the default) and `--all-peers` print today's compact output.
*/
function lifecycle(port: number): LifecycleDeps {
return {
spawn: vi.fn(() => ({ pid: 1, unref: vi.fn() }) as never), fetch: vi.fn(async () => ({ ok: true })), kill: vi.fn(() => true),
readFile: vi.fn((p: string) => (p === STATE_FILE
? JSON.stringify({ pid: 123, port, db: "test.sqlite", startedAt: "2026-04-03T00:00:00Z" } as DaemonState) : null)),
writeFile: vi.fn(), removeFile: vi.fn(), exists: vi.fn((p: string) => p === STATE_FILE), mkdirp: vi.fn(),
openForAppend: vi.fn(() => 3), isProcessAlive: vi.fn(() => true),
};
}
const DAEMON_NOTE = "peers = this rig's roster excluding self (no edge filter); edges = directional relationships; use `rig ps --nodes` for node inventory including self + live state";
const peer = (logicalId: string, podId: string) => ({
logicalId, sessionName: `${logicalId.replace(".", "-")}@big`, runtime: "claude-code", podId, podNamespace: podId, memberId: logicalId.split(".")[1],
});
/** A 23-peer roster in daemon (created_at) order. The edge peers sit late, so edge-first ordering is visible. */
const BIG_PEERS = [
...Array.from({ length: 10 }, (_, i) => peer(`ops.o${i}`, "ops")),
peer("dev.a", "dev"), peer("dev.b", "dev"),
...Array.from({ length: 9 }, (_, i) => peer(`misc.m${i}`, "misc")),
peer("review.r1", "review"), peer("orch.lead", "orch"),
];
function response(peers: ReturnType<typeof peer>[], edges = {
outgoing: [{ kind: "delegates_to", to: { logicalId: "orch.lead", sessionName: "orch-lead@big" } }],
incoming: [{ kind: "can_observe", from: { logicalId: "review.r1", sessionName: "review-r1@big" } }],
}) {
return {
resolvedBy: "node_id",
identity: {
rigId: "rig-1", rigName: "big", nodeId: "node-1", logicalId: "dev.impl", attachmentType: "tmux", podId: "dev",
podNamespace: "dev", podLabel: null, memberId: "impl", memberLabel: null, sessionName: "dev-impl@big", runtime: "claude-code",
cwd: "/tmp", agentRef: null, profile: null, resolvedSpecName: null, resolvedSpecVersion: null,
},
peers,
peersNote: DAEMON_NOTE,
edges,
transcript: { enabled: true, path: "/tmp/t.log", tailCommand: "rig transcript dev-impl@big --tail 100", grepCommand: null },
commands: { sendExamples: [], captureExamples: [] },
};
}
const SMALL_PEERS = [peer("dev.a", "dev"), peer("orch.lead", "orch"), peer("review.r1", "review")];
/** Main's compact projection (`projectCompactWhoami` at 7510731d), restated so "unchanged" is checked against a fixed shape. */
function todaysCompact(data: ReturnType<typeof response>): string {
return JSON.stringify({
resolvedBy: data.resolvedBy,
identity: {
rigName: data.identity.rigName, nodeId: data.identity.nodeId, logicalId: data.identity.logicalId, podId: data.identity.podId,
podNamespace: data.identity.podNamespace, memberId: data.identity.memberId, sessionName: data.identity.sessionName,
runtime: data.identity.runtime,
},
peers: data.peers.map((p) => ({ logicalId: p.logicalId, sessionName: p.sessionName, runtime: p.runtime })),
peersNote: data.peersNote,
edges: data.edges,
transcript: { path: data.transcript.path, tailCommand: data.transcript.tailCommand },
});
}
describe("rig whoami --json: bounded peers (output.compact, output.whoami_peers)", () => {
let server: http.Server;
let port: number;
let served: ReturnType<typeof response> | Record<string, unknown>;
let status = 200;
beforeAll(async () => {
server = http.createServer((req, res) => {
if ((req.url ?? "").includes("/api/whoami")) {
res.writeHead(status, { "Content-Type": "application/json" });
res.end(JSON.stringify(served));
} else {
res.writeHead(404, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: "not found" }));
}
});
await new Promise<void>((resolve) => { server.listen(0, resolve); });
port = (server.address() as { port: number }).port;
});
afterAll(() => { server.close(); });
beforeEach(() => {
served = response(BIG_PEERS);
status = 200;
vi.stubEnv("OPENRIG_NODE_ID", "");
vi.stubEnv("OPENRIG_SESSION_NAME", "");
vi.stubEnv("TMUX_PANE", "");
});
afterEach(() => { vi.unstubAllEnvs(); });
async function run(...args: string[]): Promise<string> {
const deps: StatusDeps = { lifecycleDeps: lifecycle(port), clientFactory: (url) => new DaemonClient(url) };
const prog = new Command();
prog.exitOverride();
prog.addCommand(whoamiCommand(deps));
const logs: string[] = [];
const spy = vi.spyOn(console, "log").mockImplementation((...a: unknown[]) => { logs.push(a.join(" ")); });
try {
await prog.parseAsync(["node", "rig", "whoami", "--node-id", "node-1", ...args]);
} finally {
spy.mockRestore();
}
return logs.join("\n");
}
it("switch off (default): today's compact output byte for byte, on a large and a small rig", async () => {
expect(await run("--json")).toBe(todaysCompact(served as ReturnType<typeof response>));
served = response(SMALL_PEERS);
expect(await run("--json")).toBe(todaysCompact(served as ReturnType<typeof response>));
});
it("switch off: --all-peers changes nothing", async () => {
expect(await run("--json", "--all-peers")).toBe(todaysCompact(served as ReturnType<typeof response>));
});
it("switch on, large rig: 8 peers, edge peers (both directions) first, then the seat's pod, then roster order", async () => {
vi.stubEnv("OPENRIG_OUTPUT_COMPACT", "1");
const out = JSON.parse(await run("--json"));
expect(out.peers.map((p: { logicalId: string }) => p.logicalId)).toEqual([
"review.r1", "orch.lead", "dev.a", "dev.b", "ops.o0", "ops.o1", "ops.o2", "ops.o3",
]);
expect(out.peers[0]).toEqual({ logicalId: "review.r1", sessionName: "review-r1@big", runtime: "claude-code" });
expect(out.peersOmitted).toBe(15);
expect(out.peersNote).toBe(
"peers = 8 of this rig's 23 (roster excluding self; edge peers first, then your pod, then roster order; "
+ "bound output.whoami_peers=8); 15 not shown; all: rig whoami --json --all-peers --node-id 'node-1'; "
+ "edges = directional relationships (always complete); use `rig ps --nodes` for node inventory including self + live state",
);
// Identity, edges and transcript stay at today's paths, complete.
const today = JSON.parse(todaysCompact(served as ReturnType<typeof response>));
expect(out.identity).toEqual(today.identity);
expect(out.edges).toEqual(today.edges);
expect(out.transcript).toEqual(today.transcript);
expect(Object.keys(out)).toEqual(["resolvedBy", "identity", "peers", "peersNote", "peersOmitted", "edges", "transcript"]);
});
it("switch on, small rig: today's peers and note verbatim, plus only peersOmitted: 0", async () => {
vi.stubEnv("OPENRIG_OUTPUT_COMPACT", "1");
served = response(SMALL_PEERS);
const out = await run("--json");
const today = JSON.parse(todaysCompact(served as ReturnType<typeof response>));
expect(JSON.parse(out)).toEqual({ ...today, peersOmitted: 0 });
expect(Object.keys(JSON.parse(out))).toEqual(["resolvedBy", "identity", "peers", "peersNote", "peersOmitted", "edges", "transcript"]);
expect(Buffer.byteLength(out) - Buffer.byteLength(todaysCompact(served as ReturnType<typeof response>))).toBe('"peersOmitted":0,'.length);
});
it("a rig of exactly the bound is shown whole, unchanged", async () => {
vi.stubEnv("OPENRIG_OUTPUT_COMPACT", "1");
served = response(BIG_PEERS.slice(0, 8));
const out = JSON.parse(await run("--json"));
expect(out.peers).toEqual(JSON.parse(todaysCompact(served as ReturnType<typeof response>)).peers);
expect(out.peersNote).toBe(DAEMON_NOTE);
expect(out.peersOmitted).toBe(0);
});
it("switch on: an error answer prints exactly as main does (no bounding, no rewritten note, no peersOmitted)", async () => {
vi.stubEnv("OPENRIG_OUTPUT_COMPACT", "1");
for (const code of [404, 409, 500]) {
status = code;
served = { error: "Session not found", peers: BIG_PEERS, peersNote: DAEMON_NOTE };
const out = await run("--json");
// Main's error path: the whole answer, pretty-printed; a 404 also gets its additive routing hint.
if (code === 404) expect(JSON.parse(out)).toEqual({ ...served, hint: expect.any(String) });
else expect(out).toBe(JSON.stringify(served, null, 2));
expect(out).not.toContain("peersOmitted");
}
});
it("the setting changes the bound", async () => {
vi.stubEnv("OPENRIG_OUTPUT_COMPACT", "1");
vi.stubEnv("OPENRIG_OUTPUT_WHOAMI_PEERS", "3");
let out = JSON.parse(await run("--json"));
expect(out.peers.map((p: { logicalId: string }) => p.logicalId)).toEqual(["review.r1", "orch.lead", "dev.a"]);
expect(out.peersOmitted).toBe(20);
expect(out.peersNote).toContain("bound output.whoami_peers=3); 20 not shown");
vi.stubEnv("OPENRIG_OUTPUT_WHOAMI_PEERS", "30");
out = JSON.parse(await run("--json"));
expect(out.peers).toEqual(JSON.parse(todaysCompact(served as ReturnType<typeof response>)).peers);
expect(out.peersNote).toBe(DAEMON_NOTE);
expect(out.peersOmitted).toBe(0);
});
it("switch on: --all-peers prints today's compact output byte for byte", async () => {
vi.stubEnv("OPENRIG_OUTPUT_COMPACT", "1");
expect(await run("--json", "--all-peers")).toBe(todaysCompact(served as ReturnType<typeof response>));
});
it("switch on: --full keeps today's whole payload", async () => {
vi.stubEnv("OPENRIG_OUTPUT_COMPACT", "1");
expect(await run("--json", "--full")).toBe(JSON.stringify(served, null, 2));
});
it("switch on: plain text still lists every peer (out of P5)", async () => {
vi.stubEnv("OPENRIG_OUTPUT_COMPACT", "1");
const out = await run();
for (const p of BIG_PEERS) expect(out).toContain(p.logicalId);
});
it("the full-list command names the identity the call used: --session, quoted", () => {
const bounded = boundPeers(response(BIG_PEERS), 2, "rig whoami --json --all-peers --session 'dev-impl@big'");
expect(bounded.note).toContain("all: rig whoami --json --all-peers --session 'dev-impl@big';");
});
it("edge peers past the bound are cut like any other, and edges still name them", () => {
const bounded = boundPeers(response(BIG_PEERS), 1, "rig whoami --json --all-peers");
expect(bounded.peers.map((p) => (p as { logicalId: string }).logicalId)).toEqual(["review.r1"]);
expect(bounded.omitted).toBe(22);
});
it("is deterministic and keeps roster order inside each tier", () => {
const a = boundPeers(response(BIG_PEERS), 8, "x");
const b = boundPeers(response(BIG_PEERS), 8, "x");
expect(a).toEqual(b);
const noEdges = boundPeers(response(BIG_PEERS, { outgoing: [], incoming: [] }), 4, "x");
expect(noEdges.peers.map((p) => (p as { logicalId: string }).logicalId)).toEqual(["dev.a", "dev.b", "ops.o0", "ops.o1"]);
});
it("the bound set in the config file applies, as `rig config get` reports it", async () => {
// Its own scratch home, so the test never depends on the suite's (or the caller's) OPENRIG_HOME.
const home = fs.mkdtempSync(path.join(os.tmpdir(), "whoami-peers-home-"));
vi.stubEnv("OPENRIG_HOME", home);
try {
expect(new ConfigStore().configPath).toBe(path.join(home, "config.json"));
new ConfigStore().set("output.compact", "true");
new ConfigStore().set("output.whoami_peers", "5");
expect(new ConfigStore().resolveWithSource("output.whoami_peers")).toMatchObject({ value: 5, source: "file" });
const out = JSON.parse(await run("--json"));
expect(out.peers).toHaveLength(5);
expect(out.peersOmitted).toBe(18);
} finally {
// OPENRIG_HOME is restored by afterEach's vi.unstubAllEnvs(); remove only this test's own home.
fs.rmSync(home, { recursive: true, force: true });
}
});
});
describe("output.whoami_peers: registered like output.compact", () => {
let home: string;
beforeEach(() => { vi.unstubAllEnvs(); home = fs.mkdtempSync(path.join(os.tmpdir(), "whoami-peers-config-")); });
afterEach(() => { vi.restoreAllMocks(); vi.unstubAllEnvs(); fs.rmSync(home, { recursive: true, force: true }); });
it("defaults to 8 and accepts only an integer >= 1", () => {
const store = new ConfigStore(path.join(home, "config.json"));
expect(store.resolveWithSource("output.whoami_peers")).toMatchObject({ value: 8, source: "default" });
store.set("output.whoami_peers", "12");
expect(store.get("output.whoami_peers")).toBe(12);
for (const raw of ["0", "-3", "2.5", "8junk", "many"]) {
expect(() => store.set("output.whoami_peers", raw)).toThrow(/output\.whoami_peers/);
}
});
it("an invalid OPENRIG_OUTPUT_WHOAMI_PEERS is rejected with the store's warning and falls back", () => {
const stderr: string[] = [];
vi.spyOn(process.stderr, "write").mockImplementation(((s: string) => { stderr.push(String(s)); return true; }) as typeof process.stderr.write);
vi.stubEnv("OPENRIG_OUTPUT_WHOAMI_PEERS", "0");
expect(new ConfigStore(path.join(home, "config.json")).get("output.whoami_peers")).toBe(8);
expect(stderr.join("")).toContain("output.whoami_peers");
});
});
@@ -12,7 +12,8 @@ orientation, and it cannot tell you what your rig is for or what you are suppose
rig whoami --json
```
Returns your rig, pod, member, peers, edges, and transcript path. **Treat it as ground truth.**
Returns your rig, pod, member, peers, edges, and transcript path. If `peersNote` says some peers
are not shown, `rig whoami --json --all-peers` lists them all. **Treat it as ground truth.**
A startup overlay can be stale; this one is small precisely so it has less room to be wrong.
Run it again after any compaction, restart or restore — **before** concluding anything about
@@ -117,7 +117,9 @@ read only the pieces a task needs.
The session-name format `{pod}-{member}@{rig}` is your address. When you
run `rig whoami --json`, you get back your full topology context: rig name,
pod, member, peers, edges, transcript path.
pod, member, peers, edges, transcript path. On a large rig the peer list can be
a bounded, edge-first selection: `peersNote` then says how many are not shown,
and `rig whoami --json --all-peers` lists them all.
---
@@ -232,7 +234,7 @@ OpenRig has a few load-bearing cultural principles. Internalize these:
If you're booting into a new seat in an OpenRig rig:
1. **`rig whoami --json`** — recover identity. Know your rig, pod, member,
peers.
peers (`--all-peers` when `peersNote` says some are not shown).
2. **Read your role guidance** — typically delivered via startup files.
`guidance/role.md` for your specific seat.
3. **Read the rig's `CULTURE.md`** if it has one — the team operating
@@ -500,11 +500,12 @@ rig ps --active # opt-in active-state filter (does NOT change the al
```bash
rig whoami # compact: identity + peers names + edges + transcript path
rig whoami --json # compact JSON
rig whoami --json --all-peers # compact JSON with every peer, when output.compact bounds the list
rig whoami --full # complete payload (v0.3.4 default shape)
rig whoami --verbose # alias of --full
```
The first command every agent runs on boot AND every compaction-restore. The compact default keeps identity-recovery essentials (`identity`, `peers` names + sessionNames, `edges` directional `kind` + `to.sessionName`, `transcriptPath`). `--full` adds `contextUsage`, `commands`, `peersNote`, `runtimeContext`. The compact-default is an ALLOWLIST projection — future payload fields default to `--full` and cannot silently re-bloat the every-boot path.
The first command every agent runs on boot AND every compaction-restore. The compact default keeps identity-recovery essentials (`identity`, `peers` names + sessionNames, `peersNote`, `edges` directional `kind` + `to.sessionName`, the transcript path). `--full` adds `contextUsage`, `commands`, `runtimeContext` and `workspace`. With `output.compact` on, compact JSON lists at most `output.whoami_peers` peers (default 8): edge peers first, then your pod, then roster order. `peersNote` and `peersOmitted` then say how many were left out, and `--all-peers` lists every one; `edges` stays complete. The compact-default is an ALLOWLIST projection — future payload fields default to `--full` and cannot silently re-bloat the every-boot path.
### `rig queue list` — active-frontier + docker/kubectl grammar
@@ -24,7 +24,8 @@ model IDs).
one command away. Your memory of the fleet is a claim about the past.
- **`rig whoami`** — who you are, who your peers are, and the exact string you use to reach each of
them. Ground truth over anything your startup overlay says.
them. Ground truth over anything your startup overlay says. On a large rig the JSON peer list can be
bounded; `peersNote` says how many it left out, and `--all-peers` lists them all.
- **`rig ps`** — what rigs exist. **`rig ps --nodes -A`** — the state of every seat everywhere,
*including the broken ones*. **The bare form is the question you usually want:** `--active`
filters to running and **hides detached, exited and attention states** — which are exactly the
@@ -40,7 +40,7 @@ Do not stall silently. Do not pretend you have permissions you don't.
## After startup
Every agent should run `rig whoami --json` immediately after launch or compaction to recover identity, peers, and edges.
Every agent should run `rig whoami --json` immediately after launch or compaction to recover identity, peers, and edges. If `peersNote` says some peers are not shown, `rig whoami --json --all-peers` lists them all.
## Culture
@@ -46,7 +46,7 @@ If blocked on another agent:
## After startup or compaction
Run `rig whoami --json` immediately. This tells you who you are, who your peers are, and how to reach them.
Run `rig whoami --json` immediately. This tells you who you are, who your peers are, and how to reach them. If `peersNote` says some peers are not shown, `rig whoami --json --all-peers` lists them all.
## Culture
@@ -46,7 +46,7 @@ If blocked on another agent:
## After startup or compaction
Run `rig whoami --json` immediately. This tells you who you are, who your peers are, and how to reach them.
Run `rig whoami --json` immediately. This tells you who you are, who your peers are, and how to reach them. If `peersNote` says some peers are not shown, `rig whoami --json --all-peers` lists them all.
## Culture
@@ -2644,6 +2644,7 @@ export class PodRigInstantiator {
"This is your startup identity hint. For durable identity recovery after compaction, run:",
" rig whoami --json",
"That command returns your full topology context: rig, pod, peers, edges, and transcript path.",
"If its peersNote says some peers are not shown, rig whoami --json --all-peers lists them all.",
].filter((line): line is string => Boolean(line));
return {
@@ -184,6 +184,7 @@ export const SETTINGS_VALID_KEYS = [
"terminal.status_bar",
// Slice 15 (OPR.0.7.0.15) — compact command-output defaults; CLI-read, twin of the CLI key.
"output.compact",
"output.whoami_peers",
// Slice 01 (OPR.0.7.0.1) — Codex and Claude usage windows in the provider view; daemon-read, twin of the CLI key.
"provider.usage_windows",
// OPR.0.4.6.FS-1 W2 — queue-retention maintenance knobs (arch D3; closed-set,
@@ -290,6 +291,7 @@ const ENV_MAP: Record<SettingsValidKey, { primary: string; legacy?: string }> =
// OPR.0.4.6.02 S1 — net-new key; OPENRIG_* primary only (no RIGGED_* legacy).
"terminal.status_bar": { primary: "OPENRIG_TERMINAL_STATUS_BAR" },
"output.compact": { primary: "OPENRIG_OUTPUT_COMPACT" },
"output.whoami_peers": { primary: "OPENRIG_OUTPUT_WHOAMI_PEERS" },
"provider.usage_windows": { primary: "OPENRIG_PROVIDER_USAGE_WINDOWS" },
// OPR.0.4.6.FS-1 W2 — retention knobs; net-new keys, OPENRIG_* primary only.
"retention.enabled": { primary: "OPENRIG_RETENTION_ENABLED" },
@@ -370,6 +372,7 @@ const KEY_TO_PATH: Record<SettingsValidKey, string[]> = {
"snapshots.periodic.retention_keep": ["snapshots", "periodic", "retentionKeep"],
"terminal.status_bar": ["terminal", "statusBar"],
"output.compact": ["output", "compact"],
"output.whoami_peers": ["output", "whoamiPeers"],
"provider.usage_windows": ["provider", "usageWindows"],
"retention.enabled": ["retention", "enabled"],
"retention.transitions_days": ["retention", "transitionsDays"],
@@ -645,6 +648,7 @@ function getDefaultValue(key: SettingsValidKey, workspaceRoot: string): string |
// is redundant chrome). Operator flip is future-launches-only.
case "terminal.status_bar": return false;
case "output.compact": return false;
case "output.whoami_peers": return 8;
case "provider.usage_windows": return false;
// OPR.0.4.6.FS-1 W2 — retention defaults (arch D3 safe values: archive
// terminal+aged transitions >30d; prune watchdog_history >14d keep-50/job;
@@ -816,6 +820,12 @@ const KEY_CONSTRAINTS: Partial<Record<SettingsValidKey, (raw: string, coerced: s
// cli/src/config-store.ts KEY_CONSTRAINTS twin so the daemon HTTP write
// surface rejects exactly what the CLI rejects. `retention.enabled` is a
// boolean (coerceValue enforces true/false) — no constraint entry needed.
// Slice 15 P5 — the whoami peer bound; lockstep with the CLI twin.
"output.whoami_peers": (raw, coerced) => {
if (!/^-?\d+$/.test((raw ?? "").trim()) || typeof coerced !== "number" || !Number.isInteger(coerced) || coerced < 1) {
throw new Error(`Invalid value for output.whoami_peers: must be an integer >= 1, got "${raw}"`);
}
},
"retention.transitions_days": (raw, coerced) => {
if (!/^-?\d+$/.test((raw ?? "").trim()) || typeof coerced !== "number" || !Number.isInteger(coerced) || coerced < 1) {
throw new Error(`Invalid value for retention.transitions_days: must be an integer >= 1, got "${raw}"`);
@@ -111,7 +111,8 @@ describe("config routes (User Settings v0)", () => {
// + 1 queue.wake_human_rung (#811; default "always").
// + 1 output.compact (slice 15 compact command-output switch; default off).
// + 1 provider.usage_windows (slice 01 Codex usage windows; default off).
expect(Object.keys(body.settings).length).toBe(75);
// + 1 output.whoami_peers (slice 15 P5 whoami peer bound; default 8).
expect(Object.keys(body.settings).length).toBe(76);
expect(body.settings["launch.non_interruptive"]).toMatchObject({ value: false, source: "default" });
expect(body.settings["runtime.readiness_timeout_seconds"]).toMatchObject({ value: 30, source: "default" });
expect(body.settings["ui.enabled"]).toMatchObject({ value: false, source: "default" });
@@ -155,6 +155,8 @@ describe("SettingsStore (User Settings v0)", () => {
"terminal.status_bar",
// Slice 15 (OPR.0.7.0.15) — compact command-output defaults switch.
"output.compact",
// Slice 15 P5 — the whoami peer bound under output.compact.
"output.whoami_peers",
// Slice 01 (OPR.0.7.0.1) — Codex usage windows switch.
"provider.usage_windows",
// OPR.0.4.6.FS-1 W2 — queue-retention maintenance knobs.
@@ -201,6 +203,16 @@ describe("SettingsStore (User Settings v0)", () => {
expect(store.resolveOne("agents.advisor_session")).toMatchObject({ value: "advisor-lead@kernel", source: "default" });
});
it("defaults output.whoami_peers to 8 and accepts only an integer >= 1 (slice 15 P5)", () => {
const store = new SettingsStore(configPath);
expect(store.resolveOne("output.whoami_peers")).toMatchObject({ value: 8, source: "default" });
store.set("output.whoami_peers", "12");
expect(store.resolveOne("output.whoami_peers")).toMatchObject({ value: 12, source: "file" });
for (const raw of ["0", "-3", "2.5", "8junk", "many"]) {
expect(() => store.set("output.whoami_peers", raw)).toThrow(/output\.whoami_peers/);
}
});
it("resolves mutable 95/99 context-pressure thresholds and rejects invalid order", () => {
const store = new SettingsStore(configPath);
expect(store.resolveContextPressurePolicy()).toEqual({ warningPercent: 95, criticalPercent: 99 });
+1
View File
@@ -66,6 +66,7 @@ const LABELS: Record<string, string> = {
"ui.preview.default_lines": "Preview lines", "context.system_world": "System world",
"onboarding.default_pack.enabled": "Default onboarding pack",
"output.compact": "Compact command output",
"output.whoami_peers": "Peers listed by whoami (compact)",
"provider.usage_windows": "Codex and Claude usage windows",
"ui.enabled": "Web UI",
"health.context_pressure.warning_percent": "Context warning threshold",
+2 -2
View File
@@ -65,7 +65,7 @@
"claude-compaction-restore/templates/compact-instruction.md": "682c778464517b831902ddc2678c055545899b4751a1d32391ce965d5eb7061d",
"claude-compaction-restore/templates/post-compact-restore-instruction.md": "10a5a7e5e052f3aa000a4209f386dc4eb90a3c2bba449ca685f124714f59e47a",
"delegating-work/SKILL.md": "54c061d1b28435266952d431765b40f4170581279decfffd3b10839848cd65e4",
"forming-an-openrig-mental-model/SKILL.md": "58476b06e677c740e3986838488653b5be2010bff562f3038e6a7e339e4457f9",
"forming-an-openrig-mental-model/SKILL.md": "5dce5a8c2cbe4928a0c2bff7f7701f7b54fd0ecc4d9650dd1287e8ec1960df60",
"loading-addressable-markdown/SKILL.md": "104dc6a02ae6e2d0eff519387c6839cb09803945b9ee35ec3498af8feaaa8d1e",
"loading-addressable-markdown/scripts/resolve-markdown.mjs": "61a6d52c6c94fb1878361d19bdfaeb4524062fc4c483f557ab2f140e516d15f9",
"messaging-the-human/SKILL.md": "4804d32fe66db050722caf2b43f8b868ca65b3dfcf2f2830f8f4859726f6f1b0",
@@ -82,7 +82,7 @@
"openrig-operating-model/templates/SOP.md": "1eaf0daf4f9bbf0715e6a5aaf0f29f030af5fe8b8a10232488ee97c507afa6dc",
"openrig-operating-model/templates/SPEC.md": "f126536daa59cea3f1cfb0898d83c7cdf99f6392f6f155ea64fe55187dd0f5f1",
"openrig-skills/SKILL.md": "ed2de7fe238dcfeda0e108e3cd8c447a6c9f2d16869bbdbac404e0a1621cd6f6",
"openrig-user/SKILL.md": "5f6ac4f04470ce33b0ab588dac1b118be4dc0e74a644632ff6f522348484b340",
"openrig-user/SKILL.md": "f5acb06f98c225c6be1212572ea266af9a73a949f86249d684a3767d41edc0be",
"orienting-to-an-inherited-seat/CHANGELOG.md": "4d0970e75e5c15b00a48d6a7150376d646257777264fd1b9a191165a8d5a6e84",
"orienting-to-an-inherited-seat/SKILL.md": "65300ce0e299cce0148a8bacd51b0475ac1b59cd77a51a9bb2db9d43bda6e208",
"queue-handoff/SKILL.md": "d4d7e3e8772886b3771daffd60523b5f79040309b892c8be1afd47d1f7287f7b",