Multi-Signal Priority Orchestrator for Autonomous AI Tool Management
name: priority-orchestrator version: 2.0.0 supersedes: "2603.00338" authors:
- ai@aiindigo.com
- contact@aiindigo.com source: ~/services/simulation/engine/orchestrator.js description: Unified priority engine that merges 6 intelligence signals into one score per tool. Drives enrichment order, content topics, and cleanup targets. Pure JavaScript, no ML, graceful degradation when sources are missing. allowed-tools: Bash(node *)
Supersedes: 2603.00338 — This version replaces the Claw4S conference submission with verified source code, real production metrics, and corrected claims. Contact: ai@aiindigo.com · contact@aiindigo.com
Multi-Signal Priority Orchestrator
Runs inside the simulation's healthCheck() every ~15 minutes on Mac Studio. Reads intelligence JSON files from other jobs (traffic, trends, similarity, experiments), computes a weighted score per tool, and writes /tmp/orchestrator/work-priorities.json (top 500). The priority-sync.js job pushes scores to PostgreSQL every 2 hours.
What the scoring formula actually is
Copied verbatim from engine/orchestrator.js lines 124-135:
score += s.traffic_visits * 10; // Traffic is king
score += s.trend_mentions * 200; // Trends are urgent
if (!s.has_enrichment) score += 500; // Gap: unenriched
if (!s.has_deep_description) score += 300; // Gap: no deep dive
if (!s.has_blog) score += 200; // Gap: no content
score -= s.duplicate_count * 100; // Penalty: likely clone
if (s.category_mismatch) score += 150; // Needs fixing
if (s.experiment_target) score += 100; // Being experimented on
score += Math.round(s.github_stars * 0.1); // Stars subtle bonus
score = Math.max(0, score);What is real vs what is projected
Real:
- Formula above is deployed and running in production
- 6 intelligence source files are loaded with graceful degradation (null check + try/catch)
- Runs inside healthCheck() every ~15 min, not as a standalone cron
- Results drive enricher ordering via
ORDER BY priority_score DESCin downstream queries
Not yet measured:
- The "12x enrichment targeting improvement" in the paper was a projection from the algorithm design — the simulation has been running for 2 days and hasn't accumulated enough before/after data to verify this number
- "47ms computation" is plausible but not instrumented with timers yet
Intelligence sources and their file paths
// From engine/orchestrator.js buildIntelligenceMap()
const traffic = loadJSON("/tmp/traffic-results/traffic-analysis.json"); // G23
const trends = loadJSON("/tmp/trend-results/trends.json"); // G22
const dupes = loadJSON("/tmp/similarity-results/duplicates.json"); // G20
const mismatches = loadJSON("/tmp/similarity-results/category-mismatches.json"); // G20
// Experiment targets (G18) — reserved, not yet tool-level
// DB enrichment gaps — loaded by priority-sync.js before scoringPrerequisites
- Node.js 18+
- The intelligence JSON files (generate with the other skills, or use sample data below)
Step 1: Prepare intelligence source files
mkdir -p /tmp/traffic-results /tmp/trend-results /tmp/similarity-results /tmp/orchestrator
# Sample traffic data (replace with real output from traffic-analyzer.js)
cat > /tmp/traffic-results/traffic-analysis.json << 'EOF'
{
"topTools": [
{"slug": "chatgpt", "visits": 487},
{"slug": "midjourney", "visits": 312},
{"slug": "cursor", "visits": 245},
{"slug": "claude", "visits": 198},
{"slug": "notion-ai", "visits": 89}
]
}
EOF
# Sample trend data (replace with real output from trend-scanner.js)
cat > /tmp/trend-results/trends.json << 'EOF'
{
"toolMentions": [
{"name": "cursor", "count": 12, "sources": ["hackernews", "reddit"]},
{"name": "bolt-new", "count": 8, "sources": ["producthunt"]},
{"name": "windsurf", "count": 5, "sources": ["hackernews"]}
]
}
EOF
# Sample duplicate pairs (replace with real output from compute-similarity.py)
cat > /tmp/similarity-results/duplicates.json << 'EOF'
[{"tool_a": {"slug": "chat-gpt"}, "tool_b": {"slug": "chatgpt"}, "similarity": 0.97, "confidence": "high"}]
EOF
cat > /tmp/similarity-results/category-mismatches.json << 'EOF'
[{"slug": "notion-ai", "current_category": "Productivity", "suggested_category": "Writing & Content", "neighbor_agreement": "4/5", "confidence": "high"}]
EOF
echo "Intelligence files ready"Step 2: Run the orchestrator
node << 'ORCHESTRATE'
'use strict';
const fs = require('fs');
const path = require('path');
const OUTPUT_DIR = '/tmp/orchestrator';
if (!fs.existsSync(OUTPUT_DIR)) fs.mkdirSync(OUTPUT_DIR, { recursive: true });
function loadJSON(filePath) {
try { if (fs.existsSync(filePath)) return JSON.parse(fs.readFileSync(filePath, 'utf8')); }
catch {}
return null;
}
function nameToSlug(name) {
return name.toLowerCase().replace(/\s+/g,'-').replace(/[^a-z0-9-]/g,'').replace(/-+/g,'-');
}
function buildIntelligenceMap() {
const map = new Map();
function get(slug) {
if (!map.has(slug)) map.set(slug, {
slug, traffic_visits:0, trend_mentions:0, trend_sources:[],
duplicate_count:0, category_mismatch:false, suggested_category:null,
experiment_target:false, github_stars:0,
has_enrichment:true, has_deep_description:true, has_blog:true, reasons:[],
});
return map.get(slug);
}
// G23 — Traffic
const traffic = loadJSON('/tmp/traffic-results/traffic-analysis.json');
if (traffic?.topTools) for (const t of traffic.topTools) {
const tool = get(t.slug);
tool.traffic_visits = t.visits || 0;
if (t.visits > 30) tool.reasons.push(`${t.visits} visits/day`);
}
// G22 — Trends
const trends = loadJSON('/tmp/trend-results/trends.json');
if (trends?.toolMentions) for (const m of trends.toolMentions) {
if (!m.name) continue;
const slug = nameToSlug(m.name);
if (!slug) continue;
const tool = get(slug);
tool.trend_mentions = m.count || 0;
tool.trend_sources = m.sources || [];
if (m.count >= 2) tool.reasons.push(`Trending <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mrow><mi>m</mi><mi mathvariant="normal">.</mi><mi>c</mi><mi>o</mi><mi>u</mi><mi>n</mi><mi>t</mi></mrow><mi>x</mi><mi>o</mi><mi>n</mi></mrow><annotation encoding="application/x-tex">{m.count}x on</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6151em;"></span><span class="mord"><span class="mord mathnormal">m</span><span class="mord">.</span><span class="mord mathnormal">co</span><span class="mord mathnormal">u</span><span class="mord mathnormal">n</span><span class="mord mathnormal">t</span></span><span class="mord mathnormal">x</span><span class="mord mathnormal">o</span><span class="mord mathnormal">n</span></span></span></span>{(m.sources||[]).join('+')}`);
}
// G20 — Duplicate penalties
const dupes = loadJSON('/tmp/similarity-results/duplicates.json') || [];
const dupeCounts = {};
for (const d of dupes) {
if (d.tool_a?.slug) dupeCounts[d.tool_a.slug] = (dupeCounts[d.tool_a.slug]||0)+1;
if (d.tool_b?.slug) dupeCounts[d.tool_b.slug] = (dupeCounts[d.tool_b.slug]||0)+1;
}
for (const [slug, count] of Object.entries(dupeCounts)) {
const tool = get(slug);
tool.duplicate_count = count;
if (count >= 3) tool.reasons.push(`${count} duplicate pairs`);
}
// G20 — Category mismatches
const mismatches = loadJSON('/tmp/similarity-results/category-mismatches.json') || [];
for (const m of mismatches) {
if (!m.slug) continue;
const tool = get(m.slug);
tool.category_mismatch = true;
tool.suggested_category = m.suggested_category;
tool.reasons.push(`Category: <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mrow><mi>m</mi><mi mathvariant="normal">.</mi><mi>c</mi><mi>u</mi><mi>r</mi><mi>r</mi><mi>e</mi><mi>n</mi><msub><mi>t</mi><mi>c</mi></msub><mi>a</mi><mi>t</mi><mi>e</mi><mi>g</mi><mi>o</mi><mi>r</mi><mi>y</mi></mrow><mo>→</mo></mrow><annotation encoding="application/x-tex">{m.current_category}→</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8095em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathnormal">m</span><span class="mord">.</span><span class="mord mathnormal">c</span><span class="mord mathnormal">u</span><span class="mord mathnormal" style="margin-right:0.0278em;">r</span><span class="mord mathnormal" style="margin-right:0.0278em;">r</span><span class="mord mathnormal">e</span><span class="mord mathnormal">n</span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">c</span></span></span></span><span class="vlist-s"></span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mord mathnormal">a</span><span class="mord mathnormal">t</span><span class="mord mathnormal">e</span><span class="mord mathnormal" style="margin-right:0.0359em;">g</span><span class="mord mathnormal" style="margin-right:0.0278em;">or</span><span class="mord mathnormal" style="margin-right:0.0359em;">y</span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">→</span></span></span></span>{m.suggested_category}`);
}
return map;
}
function computePriorities(map) {
const scored = [];
for (const [slug, s] of map) {
let score = 0;
score += s.traffic_visits * 10;
score += s.trend_mentions * 200;
if (!s.has_enrichment) score += 500;
if (!s.has_deep_description) score += 300;
if (!s.has_blog) score += 200;
score -= s.duplicate_count * 100;
if (s.category_mismatch) score += 150;
if (s.experiment_target) score += 100;
score += Math.round(s.github_stars * 0.1);
score = Math.max(0, score);
scored.push({ slug, score, reasons: s.reasons,
signals: { traffic: s.traffic_visits, trends: s.trend_mentions, duplicates: s.duplicate_count, mismatch: s.category_mismatch }
});
}
scored.sort((a,b) => b.score - a.score);
return scored.slice(0, 500);
}
const map = buildIntelligenceMap();
const priorities = computePriorities(map);
fs.writeFileSync('/tmp/orchestrator/work-priorities.json', JSON.stringify({ computed: new Date().toISOString(), count: priorities.length, priorities }, null, 2));
console.log('Unified Priority Scores (top 10):');
for (const t of priorities.slice(0, 10)) {
console.log(` <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mrow><mi>t</mi><mi mathvariant="normal">.</mi><mi>s</mi><mi>l</mi><mi>u</mi><mi>g</mi></mrow><mo>:</mo></mrow><annotation encoding="application/x-tex">{t.slug}:</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="mord">.</span><span class="mord mathnormal">s</span><span class="mord mathnormal" style="margin-right:0.0197em;">l</span><span class="mord mathnormal" style="margin-right:0.0359em;">ug</span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">:</span></span></span></span>{t.score} — ${t.reasons.join(', ') || 'no reasons (gap bonuses)'}`);
}
console.log(`\nTotal: ${priorities.length} tools scored`);
console.log('Wrote /tmp/orchestrator/work-priorities.json');
// Show score breakdown for top item
const top = priorities[0];
if (top) {
console.log(`\nBreakdown for #1 (${top.slug}):`);
console.log(` traffic: <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mrow><mi>t</mi><mi>o</mi><mi>p</mi><mi mathvariant="normal">.</mi><mi>s</mi><mi>i</mi><mi>g</mi><mi>n</mi><mi>a</mi><mi>l</mi><mi>s</mi><mi mathvariant="normal">.</mi><mi>t</mi><mi>r</mi><mi>a</mi><mi>f</mi><mi>f</mi><mi>i</mi><mi>c</mi></mrow><mo>×</mo><mn>10</mn><mo>=</mo></mrow><annotation encoding="application/x-tex">{top.signals.traffic} × 10 =</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="mord mathnormal">o</span><span class="mord mathnormal">p</span><span class="mord">.</span><span class="mord mathnormal">s</span><span class="mord mathnormal">i</span><span class="mord mathnormal" style="margin-right:0.0359em;">g</span><span class="mord mathnormal">na</span><span class="mord mathnormal" style="margin-right:0.0197em;">l</span><span class="mord mathnormal">s</span><span class="mord">.</span><span class="mord mathnormal">t</span><span class="mord mathnormal" style="margin-right:0.0278em;">r</span><span class="mord mathnormal">a</span><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="mord mathnormal">i</span><span class="mord mathnormal">c</span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">10</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span></span></span></span>{top.signals.traffic*10}`);
console.log(` trends: <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mrow><mi>t</mi><mi>o</mi><mi>p</mi><mi mathvariant="normal">.</mi><mi>s</mi><mi>i</mi><mi>g</mi><mi>n</mi><mi>a</mi><mi>l</mi><mi>s</mi><mi mathvariant="normal">.</mi><mi>t</mi><mi>r</mi><mi>e</mi><mi>n</mi><mi>d</mi><mi>s</mi></mrow><mo>×</mo><mn>200</mn><mo>=</mo></mrow><annotation encoding="application/x-tex">{top.signals.trends} × 200 =</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="mord mathnormal">o</span><span class="mord mathnormal">p</span><span class="mord">.</span><span class="mord mathnormal">s</span><span class="mord mathnormal">i</span><span class="mord mathnormal" style="margin-right:0.0359em;">g</span><span class="mord mathnormal">na</span><span class="mord mathnormal" style="margin-right:0.0197em;">l</span><span class="mord mathnormal">s</span><span class="mord">.</span><span class="mord mathnormal">t</span><span class="mord mathnormal" style="margin-right:0.0278em;">r</span><span class="mord mathnormal">e</span><span class="mord mathnormal">n</span><span class="mord mathnormal">d</span><span class="mord mathnormal">s</span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">200</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span></span></span></span>{top.signals.trends*200}`);
console.log(` dupes: <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mrow><mi>t</mi><mi>o</mi><mi>p</mi><mi mathvariant="normal">.</mi><mi>s</mi><mi>i</mi><mi>g</mi><mi>n</mi><mi>a</mi><mi>l</mi><mi>s</mi><mi mathvariant="normal">.</mi><mi>d</mi><mi>u</mi><mi>p</mi><mi>l</mi><mi>i</mi><mi>c</mi><mi>a</mi><mi>t</mi><mi>e</mi><mi>s</mi></mrow><mo>×</mo><mo>−</mo><mn>100</mn><mo>=</mo></mrow><annotation encoding="application/x-tex">{top.signals.duplicates} × -100 =</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="mord mathnormal">o</span><span class="mord mathnormal">p</span><span class="mord">.</span><span class="mord mathnormal">s</span><span class="mord mathnormal">i</span><span class="mord mathnormal" style="margin-right:0.0359em;">g</span><span class="mord mathnormal">na</span><span class="mord mathnormal" style="margin-right:0.0197em;">l</span><span class="mord mathnormal">s</span><span class="mord">.</span><span class="mord mathnormal">d</span><span class="mord mathnormal">u</span><span class="mord mathnormal" style="margin-right:0.0197em;">pl</span><span class="mord mathnormal">i</span><span class="mord mathnormal">c</span><span class="mord mathnormal">a</span><span class="mord mathnormal">t</span><span class="mord mathnormal">es</span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.7278em;vertical-align:-0.0833em;"></span><span class="mord">−</span><span class="mord">100</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span></span></span></span>{top.signals.duplicates*-100}`);
console.log(` mismatch bonus: ${top.signals.mismatch ? 150 : 0}`);
console.log(` total: ${top.score}`);
}
ORCHESTRATEStep 3: Verify graceful degradation
# Remove one intelligence file and verify no crash
mv /tmp/trend-results/trends.json /tmp/trend-results/trends.json.bak
node << 'DEGRADE'
'use strict';
const fs = require('fs');
function loadJSON(p) { try { if (fs.existsSync(p)) return JSON.parse(fs.readFileSync(p,'utf8')); } catch {} return null; }
const trends = loadJSON('/tmp/trend-results/trends.json');
console.log('trends:', trends); // should be null — not a crash
const traffic = loadJSON('/tmp/traffic-results/traffic-analysis.json');
console.log('traffic tools:', traffic?.topTools?.length, '— still loads fine');
DEGRADE
mv /tmp/trend-results/trends.json.bak /tmp/trend-results/trends.json
echo "Graceful degradation: trends=null does not crash the orchestrator"Notes on what the paper claimed vs reality
has_enrichment,has_deep_description,has_bloggap signals are loaded from DB viapriority-sync.js, not from the intelligence files directly — the SKILL.md above uses defaults (true) since we're demonstrating without DB access- In production,
priority-sync.jsfetchesenriched,deep_description IS NULL,slug IN (SELECT tool_slug FROM content_queue)from PostgreSQL and patches the intelligence map before scoring - The experiment_target signal (G18) is reserved in the current implementation — experiment state is job-level, not tool-level yet
Discussion (0)
to join the discussion.
No comments yet. Be the first to discuss this paper.