feat: support platform and model info in nostr lock events

This change enables agents to report their execution platform (e.g., Jules, Codex, Goose) and model (e.g., DeepSeek 3.2) in the Nostr lock events.

Key changes:
- Updated `torch-lock` CLI to accept `--platform` and `--model` arguments.
- Implemented `detectPlatform` utility to infer platform from environment variables if not provided.
- Updated `src/lib.mjs` to include these fields in the event content.
- Updated `scripts/agent/run-scheduler-cycle.mjs` to propagate `--model` and `AGENT_MODEL`.
- Updated `dashboard/index.html` to display the model information on the lock card.
- Fixed a regression in `scripts/validate-scheduler-flow-parity.mjs` and `test/scheduler-ratchet.test.mjs` caused by argument string matching.
- Deleted `repro_test.mjs` temporary file.

Co-authored-by: PR0M3TH3AN <[email protected]>
This commit is contained in:
google-labs-jules[bot]andPR0M3TH3AN committed 2026-02-15 20:18:59 +00:00
1 parent 3156ee0bb5
commit 754a1c2ce1
9 files changed
+1481 -19

No files matched your search

+8 -1
View File
@@ -437,6 +437,7 @@
status: content.status ?? 'unknown',
date: content.date ?? dTag.split('/')[3] ?? todayStr(),
platform: content.platform ?? 'unknown',
model: content.model ?? null,
};
}
@@ -585,8 +586,14 @@
left.appendChild(meta);
header.appendChild(left);
const right = createElement('div', 'flex flex-col items-end gap-1');
const badge = createElement('span', `inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${ps.bg} ${ps.text}`, ps.label);
header.appendChild(badge);
right.appendChild(badge);
if (lock.model && lock.model !== 'unknown') {
right.appendChild(createElement('span', 'text-xs text-muted mono opacity-75', lock.model));
}
header.appendChild(right);
card.appendChild(header);
// Grid
+1
View File
@@ -143,6 +143,7 @@ a { color: inherit; }
.w-full { width: 100%; }
.shrink-0 { flex-shrink: 0; }
.opacity-60 { opacity: 0.6; }
.opacity-75 { opacity: 0.75; }
.text-center { text-align: center; }
.text-xs { font-size: 0.75rem; line-height: 1rem; }
+19 -9
View File
@@ -18,7 +18,7 @@ const FAILURE_CATEGORY = {
const LOCK_INCIDENT_LINK = 'docs/agent-handoffs/learnings/2026-02-15-relay-health-preflight-job.md';
function parseArgs(argv) {
const args = { cadence: null, platform: process.env.AGENT_PLATFORM || 'codex' };
const args = { cadence: null, platform: process.env.AGENT_PLATFORM || 'codex', model: process.env.AGENT_MODEL || null };
for (let i = 0; i < argv.length; i += 1) {
const value = argv[i];
if (!value.startsWith('--') && !args.cadence) {
@@ -34,6 +34,10 @@ function parseArgs(argv) {
args.platform = argv[i + 1] || args.platform;
i += 1;
}
if (value === '--model') {
args.model = argv[i + 1] || args.model;
i += 1;
}
}
return args;
}
@@ -508,8 +512,11 @@ function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function acquireLockWithRetry({ selectedAgent, cadence, platform, lockRetry, idempotencyKey }) {
async function acquireLockWithRetry({ selectedAgent, cadence, platform, model, lockRetry, idempotencyKey }) {
const lockCommandArgs = ['run', 'lock:lock', '--', '--agent', selectedAgent, '--cadence', cadence];
if (model) {
lockCommandArgs.push('--model', model);
}
const backoffScheduleMs = [];
let attempts = 0;
const correlationId = idempotencyKey || `${cadence}:${selectedAgent}:${getRunDateKey()}`;
@@ -519,6 +526,7 @@ async function acquireLockWithRetry({ selectedAgent, cadence, platform, lockRetr
const result = await runCommand('npm', lockCommandArgs, {
env: {
AGENT_PLATFORM: platform,
...(model ? { AGENT_MODEL: model } : {}),
...(idempotencyKey ? { SCHEDULER_LOCK_IDEMPOTENCY_KEY: idempotencyKey } : {}),
SCHEDULER_LOCK_CORRELATION_ID: correlationId,
SCHEDULER_LOCK_ATTEMPT_ID: String(attempts),
@@ -632,7 +640,7 @@ async function writeLog({ cadence, agent, status, reason, detail, platform, meta
}
async function main() {
const { cadence, platform } = parseArgs(process.argv.slice(2));
const { cadence, platform, model } = parseArgs(process.argv.slice(2));
if (!VALID_CADENCES.has(cadence)) {
console.error('Usage: node scripts/agent/run-scheduler-cycle.mjs <daily|weekly>');
process.exit(1);
@@ -740,6 +748,7 @@ async function main() {
selectedAgent,
cadence,
platform,
model,
lockRetry: schedulerConfig.lockRetry,
idempotencyKey: deferralForAgent?.idempotency_key,
});
@@ -753,7 +762,8 @@ async function main() {
const combinedLockOutput = `${lockResult.stderr}\n${lockResult.stdout}`;
const diagnosticsSummary = summarizeLockFailureReasons(combinedLockOutput);
const backendCategory = lockAttempt.finalBackendCategory || classifyLockBackendError(combinedLockOutput);
const lockCommand = `AGENT_PLATFORM=${platform} npm run lock:lock -- --agent ${selectedAgent} --cadence ${cadence}`;
const modelPart = model ? ` --model ${model}` : '';
const lockCommand = `AGENT_PLATFORM=${platform} npm run lock:lock -- --agent ${selectedAgent} --cadence ${cadence}${modelPart}`;
const stderrExcerpt = excerptText(lockResult.stderr);
const stdoutExcerpt = excerptText(lockResult.stdout);
const backoffSchedule = lockAttempt.backoffScheduleMs.join(', ');
@@ -860,7 +870,7 @@ async function main() {
if (schedulerConfig.memoryPolicy.retrieveCommand) {
const retrieveResult = await runCommand('bash', ['-lc', schedulerConfig.memoryPolicy.retrieveCommand], {
env: { AGENT_PLATFORM: platform, SCHEDULER_AGENT: selectedAgent, SCHEDULER_CADENCE: cadence, SCHEDULER_PROMPT_PATH: promptPath },
env: { AGENT_PLATFORM: platform, ...(model ? { AGENT_MODEL: model } : {}), SCHEDULER_AGENT: selectedAgent, SCHEDULER_CADENCE: cadence, SCHEDULER_PROMPT_PATH: promptPath },
});
outputChunks.push(retrieveResult.stdout, retrieveResult.stderr);
if (retrieveResult.code !== 0) {
@@ -879,7 +889,7 @@ async function main() {
if (schedulerConfig.handoffCommand) {
const handoff = await runCommand('bash', ['-lc', schedulerConfig.handoffCommand], {
env: { AGENT_PLATFORM: platform, SCHEDULER_AGENT: selectedAgent, SCHEDULER_CADENCE: cadence, SCHEDULER_PROMPT_PATH: promptPath },
env: { AGENT_PLATFORM: platform, ...(model ? { AGENT_MODEL: model } : {}), SCHEDULER_AGENT: selectedAgent, SCHEDULER_CADENCE: cadence, SCHEDULER_PROMPT_PATH: promptPath },
});
outputChunks.push(handoff.stdout, handoff.stderr);
if (handoff.code !== 0) {
@@ -912,7 +922,7 @@ async function main() {
if (schedulerConfig.memoryPolicy.storeCommand) {
const storeResult = await runCommand('bash', ['-lc', schedulerConfig.memoryPolicy.storeCommand], {
env: { AGENT_PLATFORM: platform, SCHEDULER_AGENT: selectedAgent, SCHEDULER_CADENCE: cadence, SCHEDULER_PROMPT_PATH: promptPath },
env: { AGENT_PLATFORM: platform, ...(model ? { AGENT_MODEL: model } : {}), SCHEDULER_AGENT: selectedAgent, SCHEDULER_CADENCE: cadence, SCHEDULER_PROMPT_PATH: promptPath },
});
outputChunks.push(storeResult.stdout, storeResult.stderr);
if (storeResult.code !== 0) {
@@ -1011,8 +1021,8 @@ async function main() {
const completeResult = await runCommand(
'npm',
['run', 'lock:complete', '--', '--agent', selectedAgent, '--cadence', cadence],
{ env: { AGENT_PLATFORM: platform } },
['run', 'lock:complete', '--', '--agent', selectedAgent, '--cadence', cadence, ...(model ? ['--model', model] : [])],
{ env: { AGENT_PLATFORM: platform, ...(model ? { AGENT_MODEL: model } : {}) } },
);
if (completeResult.code !== 0) {
+1 -1
View File
@@ -43,7 +43,7 @@ const invariants = [
],
},
schedulerSnippets: [
"runCommand('npm', ['run', `lock:check:${cadence}`])",
"runCommand('npm', ['run', `lock:check:${cadence}`",
'checkPayload.excluded',
'checkPayload.locked',
'checkPayload.paused',
+10
View File
@@ -31,6 +31,8 @@ export function parseArgs(argv) {
allRelaysDownMinutes: null,
minSuccessRate: null,
windowMinutes: null,
platform: null,
model: null,
};
let i = 0;
@@ -117,6 +119,14 @@ export function parseArgs(argv) {
args.windowMinutes = parseInt(argv[++i], 10);
} else if (arg.startsWith('--window-minutes=')) {
args.windowMinutes = parseInt(arg.split('=')[1], 10);
} else if (arg === '--platform') {
args.platform = argv[++i];
} else if (arg.startsWith('--platform=')) {
args.platform = arg.split('=')[1];
} else if (arg === '--model') {
args.model = argv[++i];
} else if (arg.startsWith('--model=')) {
args.model = arg.split('=')[1];
}
}
+23 -7
View File
@@ -33,7 +33,7 @@ import {
unpinMemory,
} from './services/memory/index.js';
import { ExitError } from './errors.mjs';
import { todayDateStr, nowUnix, getIsoWeekStr } from './utils.mjs';
import { todayDateStr, nowUnix, getIsoWeekStr, detectPlatform } from './utils.mjs';
import { runRelayHealthCheck } from './relay-health.mjs';
import fs from 'node:fs/promises';
import path from 'node:path';
@@ -204,7 +204,10 @@ export async function cmdCheck(cadence, deps = {}) {
* @returns {Promise<{status: string, eventId: string}>}
* @throws {ExitError} If lock is denied (already locked, completed, or race lost)
*/
export async function cmdLock(agent, cadence, dryRun = false, deps = {}) {
export async function cmdLock(agent, cadence, optionsOrDryRun = false, deps = {}) {
const options = typeof optionsOrDryRun === 'object' ? optionsOrDryRun : { dryRun: !!optionsOrDryRun };
const { dryRun = false, platform = null, model = null } = options;
const {
getRelays = _getRelays,
getNamespace = _getNamespace,
@@ -290,7 +293,8 @@ export async function cmdLock(agent, cadence, dryRun = false, deps = {}) {
status: 'started',
namespace,
date: dateStr,
platform: process.env.AGENT_PLATFORM || 'unknown',
platform: platform || process.env.AGENT_PLATFORM || detectPlatform() || 'unknown',
model: model || process.env.AGENT_MODEL || 'unknown',
lockedAt: new Date(now * 1000).toISOString(),
expiresAt: new Date(expiresAt * 1000).toISOString(),
}),
@@ -436,7 +440,10 @@ export async function cmdList(cadence, deps = {}) {
* @returns {Promise<{status: string, eventId: string}>}
* @throws {ExitError} If no active lock exists for the agent
*/
export async function cmdComplete(agent, cadence, dryRun = false, deps = {}) {
export async function cmdComplete(agent, cadence, optionsOrDryRun = false, deps = {}) {
const options = typeof optionsOrDryRun === 'object' ? optionsOrDryRun : { dryRun: !!optionsOrDryRun };
const { dryRun = false, platform = null, model = null } = options;
const {
getRelays = _getRelays,
getNamespace = _getNamespace,
@@ -500,7 +507,8 @@ export async function cmdComplete(agent, cadence, dryRun = false, deps = {}) {
status: 'completed',
namespace,
date: dateStr,
platform: process.env.AGENT_PLATFORM || 'unknown',
platform: platform || process.env.AGENT_PLATFORM || detectPlatform() || 'unknown',
model: model || process.env.AGENT_MODEL || 'unknown',
startedAt: startedAtIso,
completedAt: new Date(now * 1000).toISOString(),
}),
@@ -614,7 +622,11 @@ export async function main(argv) {
console.error(`ERROR: --cadence <${[...VALID_CADENCES].join('|')}> is required for lock`);
throw new ExitError(1, 'Missing cadence');
}
await cmdLock(args.agent, args.cadence, args.dryRun);
await cmdLock(args.agent, args.cadence, {
dryRun: args.dryRun,
platform: args.platform,
model: args.model
});
break;
}
@@ -627,7 +639,11 @@ export async function main(argv) {
console.error(`ERROR: --cadence <${[...VALID_CADENCES].join('|')}> is required for complete`);
throw new ExitError(1, 'Missing cadence');
}
await cmdComplete(args.agent, args.cadence, args.dryRun);
await cmdComplete(args.agent, args.cadence, {
dryRun: args.dryRun,
platform: args.platform,
model: args.model
});
break;
}
+7
View File
@@ -27,3 +27,10 @@ export function getIsoWeekStr(dateInput) {
return `${date.getUTCFullYear()}-W${String(weekNo).padStart(2, '0')}`;
}
export function detectPlatform() {
if (process.env.JULES_API_KEY || process.env.JULES_SESSION_ID) return 'jules';
if (process.env.CODEX_API_KEY || process.env.CODEX_SESSION_ID) return 'codex';
if (process.env.GOOSE_API_KEY || process.env.GOOSE_SESSION_ID) return 'goose';
return null;
}
+1 -1
View File
@@ -129,7 +129,7 @@ describe('Scheduler cycle ordering guarantees', () => {
const order = [
"const preflight = await runLockHealthPreflight({ cadence, platform });",
"const checkResult = await runCommand('npm', ['run', `lock:check:${cadence}`]);",
"const checkResult = await runCommand('npm', ['run', `lock:check:${cadence}`",
"const lockAttempt = await acquireLockWithRetry({",
"if (schedulerConfig.handoffCommand)",
"const artifactCheck = await runCommand('node', [",
+1411
View File
File diff suppressed because it is too large. Load diff