feat: let the coordinator see the relationship graph
The graph has been built for months and fed nothing but a dashboard. Nothing in the autonomy pipeline has ever read an edge: not the coordinator, not calibration, not execution. Its only downstream consumer, trade_signals, last produced anything in April. It was roughly 70% of the llm bill and informed no prediction, decision or order. Relationships are the one piece of context a per-event coordinator genuinely cannot derive from its own articles, because "this company supplies that one" is knowledge about companies rather than about this event. So the coordinator now receives the relationships of the companies the event is about, and is told to name the relationship in causal_channel when it reasons through one. The cutoff filter is the part that matters. first_seen_at on a relationship is derived from article dates rather than processing time, so a historical proposal only sees what the world had actually revealed by its own cutoff. Without that this feature would quietly reintroduce the lookahead the evidence check exists to prevent, and it is pinned by a test rather than left to review. Relationships are explicitly background rather than evidence: predictions still have to cite the article ids the story came from, and an instrument the articles give no reason to care about is still not a prediction. strategy_version moves to autonomy-2, because a prompt change this material changes what a prediction means and the two populations should be comparable later rather than silently blended. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WnNxwxfXSbeNtjvtz5gayb
This commit is contained in:
@@ -12,6 +12,8 @@ const { calculateOutcome, addTradingDays } = require('../src/autonomy/outcomes')
|
||||
const { yahooSymbol } = require('../workers/outcomeAutonomyWorker');
|
||||
const { createOrderIntent } = require('../src/autonomy/orderIntents');
|
||||
const { enqueueCoordinatorEvent, reconcileArchiveBatch, reconcileLiveBatch, isTransientCoordinatorFailure } = require('../workers/autonomyWorker');
|
||||
const { buildGraphContext } = require('../src/autonomy/graphContext');
|
||||
const { buildPrompt } = require('../workers/coordinatorWorker');
|
||||
const { scheduleNext } = require('../workers/replayWorker');
|
||||
const { refreshHistoricalCalibration, createDecisions, ensureCalibrationColumns } = require('../workers/calibrationWorker');
|
||||
|
||||
@@ -393,3 +395,46 @@ test('lookahead still rejects the whole proposal, not just one prediction', () =
|
||||
}, { informationCutoff: '2020-01-01T00:00:00Z' }), /missing evidence/);
|
||||
assert.equal(intelligence.prepare('SELECT COUNT(*) AS n FROM autonomy_predictions').get().n, 0);
|
||||
});
|
||||
|
||||
test('graph context never shows a relationship the world had not revealed yet', () => {
|
||||
const db = new Database(':memory:');
|
||||
initAutonomySchema(db);
|
||||
db.exec(`
|
||||
CREATE TABLE tracked_companies (id INTEGER PRIMARY KEY, name TEXT, ticker TEXT, aliases TEXT);
|
||||
CREATE TABLE event_knowledge (id INTEGER PRIMARY KEY, event_id INTEGER, company_id INTEGER, type TEXT, data TEXT, event_date TEXT);
|
||||
CREATE TABLE company_relationships (id INTEGER PRIMARY KEY, from_company_id INTEGER, relationship_type TEXT,
|
||||
to_entity TEXT, to_company_id INTEGER, confidence TEXT, confirmation_count INTEGER, first_seen_at DATETIME,
|
||||
last_seen_at DATETIME, supporting_event_ids TEXT);
|
||||
`);
|
||||
db.prepare("INSERT INTO tracked_companies VALUES (1,'Nvidia','NVDA','[]')").run();
|
||||
db.prepare("INSERT INTO event_knowledge (event_id, company_id, type) VALUES (55, 1, 'x')").run();
|
||||
const edge = db.prepare(`INSERT INTO company_relationships
|
||||
(from_company_id, relationship_type, to_entity, confidence, confirmation_count, first_seen_at)
|
||||
VALUES (1, ?, ?, 'high', ?, ?)`);
|
||||
edge.run('supplier', 'Taiwan Semiconductor', 9, '2025-01-01T00:00:00Z');
|
||||
edge.run('customer', 'Future Corp', 4, '2026-01-01T00:00:00Z');
|
||||
|
||||
// a proposal dated mid-2025 may only see what existed by then
|
||||
const past = buildGraphContext(db, 55, '2025-06-01T00:00:00Z');
|
||||
assert.match(past, /Taiwan Semiconductor/);
|
||||
assert.equal(/Future Corp/.test(past), false);
|
||||
|
||||
// later, both are legitimately visible
|
||||
const now = buildGraphContext(db, 55, '2026-06-01T00:00:00Z');
|
||||
assert.match(now, /Taiwan Semiconductor/);
|
||||
assert.match(now, /Future Corp/);
|
||||
|
||||
// an event we know nothing about contributes nothing rather than a stray header
|
||||
assert.equal(buildGraphContext(db, 999, '2026-06-01T00:00:00Z'), '');
|
||||
assert.equal(buildGraphContext(db, 55, null), '');
|
||||
});
|
||||
|
||||
test('the coordinator prompt carries graph context only when there is some', () => {
|
||||
const withCtx = buildPrompt({ id: 1, title: 't' }, [{ id: 9, title: 'a', content: 'c' }],
|
||||
'Known company relationships, as they stood at the information cutoff:\n\nNvidia (NVDA):\n - supplier: TSMC');
|
||||
assert.match(withCtx, /Known company relationships/);
|
||||
assert.match(withCtx, /supplier: TSMC/);
|
||||
|
||||
const without = buildPrompt({ id: 1, title: 't' }, [{ id: 9, title: 'a', content: 'c' }]);
|
||||
assert.equal(/Known company relationships/.test(without), false);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user