mirror of
https://github.com/PR0M3TH3AN/TORCH.git
synced 2026-11-03 03:11:57 +00:00
Harden specialist backlog loop
This commit is contained in:
1 parent
a900459d87
commit
770cdde4a7
10 files changed
+406
-28
No files matched your search
+25
-8
@@ -9,6 +9,7 @@ import { forgeStatus } from '../forge/service.mjs';
|
||||
import { classifyRecoverability } from '../canonical/local.mjs';
|
||||
import { createClaudeAdapter } from '../adapters/claude.mjs';
|
||||
import { createCodexAdapter } from '../adapters/codex.mjs';
|
||||
import { assessBacklogHealth, backlogObservedCommitDistance } from '../backlog/service.mjs';
|
||||
|
||||
function parseJsonYaml(path) {
|
||||
return JSON.parse(readFileSync(path, 'utf8'));
|
||||
@@ -19,6 +20,7 @@ export function diagnoseProject({ repository, env = process.env, now = () => new
|
||||
const configPath = join(trackedRoot, 'torch.yaml');
|
||||
const manifestPath = join(trackedRoot, 'install-manifest.json');
|
||||
const findings = [];
|
||||
let identityRows = [];
|
||||
|
||||
if (!existsSync(configPath)) {
|
||||
return { status: 'not-installed', healthy: false, findings: [{ severity: 'error', code: 'CONFIG_MISSING', path: configPath }] };
|
||||
@@ -78,10 +80,11 @@ export function diagnoseProject({ repository, env = process.env, now = () => new
|
||||
LEFT JOIN message_acks a ON a.message_id = m.id AND a.area_id = i.area_id
|
||||
WHERE a.message_id IS NULL
|
||||
`).get().count;
|
||||
const present = database.prepare(`
|
||||
identityRows = database.prepare(`
|
||||
SELECT area_id, state, runtime, runtime_session_id, heartbeat_at
|
||||
FROM identities WHERE state != 'offline' ORDER BY area_id
|
||||
FROM identities ORDER BY area_id
|
||||
`).all();
|
||||
const present = identityRows.filter((identity) => identity.state !== 'offline');
|
||||
if (unacknowledged > 0) {
|
||||
findings.push({ severity: 'warning', code: 'MESSAGE_BACKLOG', unacknowledged });
|
||||
}
|
||||
@@ -167,19 +170,33 @@ export function diagnoseProject({ repository, env = process.env, now = () => new
|
||||
try {
|
||||
const task = parseJsonYaml(join(backlogRoot, name));
|
||||
if (task.schema !== 'torch.dev/backlog-item/v1alpha1' || !task.id || !task.state) throw new Error('invalid task schema');
|
||||
if (!['completed', 'cancelled'].includes(task.state)) {
|
||||
tasks.push({ id: task.id, state: task.state, owner: task.owner, revision: task.revision });
|
||||
}
|
||||
tasks.push(task);
|
||||
} catch (error) {
|
||||
findings.push({
|
||||
severity: 'error', code: 'BACKLOG_TASK_INVALID', path: join(backlogRoot, name), message: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (tasks.length) findings.push({
|
||||
severity: tasks.some((task) => task.state === 'blocked') ? 'warning' : 'info',
|
||||
code: 'BACKLOG_ACTIVITY', tasks,
|
||||
const activeTasks = tasks.filter((task) => !['completed', 'cancelled'].includes(task.state));
|
||||
if (activeTasks.length) findings.push({
|
||||
severity: activeTasks.some((task) => task.state === 'blocked') ? 'warning' : 'info',
|
||||
code: 'BACKLOG_ACTIVITY', tasks: activeTasks.map((task) => ({
|
||||
id: task.id, state: task.state, owner: task.owner, revision: task.revision,
|
||||
})),
|
||||
});
|
||||
try {
|
||||
const roster = parseJsonYaml(join(trackedRoot, 'roster.yaml'));
|
||||
const presenceByArea = new Map(identityRows.map((identity) => [identity.area_id, identity]));
|
||||
const agents = (roster.areas ?? []).map((area) => ({
|
||||
...area, areaId: area.id, ...(presenceByArea.get(area.id) ?? { state: 'offline' }),
|
||||
}));
|
||||
findings.push(...assessBacklogHealth({
|
||||
tasks, agents, now: now(),
|
||||
commitDistance: (observedAt) => backlogObservedCommitDistance(repository.root, observedAt),
|
||||
}).findings);
|
||||
} catch (error) {
|
||||
findings.push({ severity: 'error', code: 'BACKLOG_HEALTH_INVALID', message: error.message });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ function initialFiles({ repository, projectId, createdAt, proposal, runtimes })
|
||||
const managerPrompt = `${promptContent(
|
||||
proposal.session_manager?.prompt_source,
|
||||
'# TORCH Session Manager\n\nRoute owner requests, establish ownership and priority, and keep routine coordination inside the fleet.\n',
|
||||
).trimEnd()}\n\n## Fleet evolution\n\nAt startup, after backlog intake, and when repeated handoffs or cross-domain work appear, call \`torch_assess_fleet_evolution\`. Treat its threshold as a prompt for architectural judgment, not as an automatic decision. When recurring work has no coherent owner, or a durable specialist would materially improve context locality, ownership clarity, or verification, inspect repository evidence and use \`torch_propose_domain\` to submit an evidence-backed Fleet change. When a specialist no longer earns its coordination cost, use \`torch_propose_domain_retirement\`; retirement preserves its branch and refuses active or unrecoverable work. Use \`torch_list_fleet_changes\` to follow change state. Never create, retire, approve, activate, or start a persistent identity yourself; owner approval, CLI activation, and quota-consuming runtime start are separate. Recommend a merge or split for owner review when boundaries should change but do not silently rewrite ownership.\n`;
|
||||
).trimEnd()}\n\n## Backlog loop\n\nKeep one durable Fleet backlog. Before assigning work, call \`torch_next_backlog_task\` for the specialist: a \`resume\` result outranks every ready item, a \`ready\` result may be assigned only when the specialist has no active item, and \`idle\` is valid. After completion, repeat the same lookup. Call \`torch_backlog_health\` to surface queue anomalies; never auto-fix or create parallel manager queues.\n\n## Fleet evolution\n\nAt startup, after backlog intake, and when repeated handoffs or cross-domain work appear, call \`torch_assess_fleet_evolution\`. Treat its threshold as a prompt for architectural judgment, not as an automatic decision. When recurring work has no coherent owner, or a durable specialist would materially improve context locality, ownership clarity, or verification, inspect repository evidence and use \`torch_propose_domain\` to submit an evidence-backed Fleet change. When a specialist no longer earns its coordination cost, use \`torch_propose_domain_retirement\`; retirement preserves its branch and refuses active or unrecoverable work. Use \`torch_list_fleet_changes\` to follow change state. Never create, retire, approve, activate, or start a persistent identity yourself; owner approval, CLI activation, and quota-consuming runtime start are separate. Recommend a merge or split for owner review when boundaries should change but do not silently rewrite ownership.\n`;
|
||||
const files = new Map([
|
||||
['torch.yaml', jsonYaml(config)],
|
||||
['roster.yaml', jsonYaml(roster)],
|
||||
|
||||
@@ -52,11 +52,14 @@ export function renderDomainPrompt(domain) {
|
||||
'',
|
||||
'## Initial backlog',
|
||||
'',
|
||||
'- No task is implied by session creation. Read the tracked backlog and accept only an explicit assignment.',
|
||||
'- No task is implied by session creation. Call `torch_next_backlog_task` for this area before choosing work.',
|
||||
'- A `resume` result is the only current assignment. Continue it before considering any `ready` item.',
|
||||
'- A `ready` result is a candidate, not an assignment; request Session Manager assignment before implementation.',
|
||||
'- An `idle` result is valid. Do not invent filler work.',
|
||||
'',
|
||||
'## First move',
|
||||
'',
|
||||
'Query live identity and ownership, read the assigned backlog item and inbox, then report current repository evidence to the Session Manager.',
|
||||
'Query live identity and ownership, call `torch_next_backlog_task`, read the inbox, then report current repository evidence to the Session Manager.',
|
||||
'',
|
||||
].join('\n');
|
||||
}
|
||||
Reference in new issue
Block a user