watchmyagents 0.8.2 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -6
- package/package.json +7 -3
- package/scripts/agents.js +218 -0
- package/scripts/fetch-anthropic.js +97 -32
- package/scripts/service.js +7 -5
- package/scripts/shield.js +130 -97
- package/src/sources/anthropic-managed.js +18 -0
- package/src/typology-weights.json +88 -0
- package/src/typology.js +398 -0
package/scripts/shield.js
CHANGED
|
@@ -33,7 +33,7 @@ import {
|
|
|
33
33
|
getAgentConfig, detectAlwaysAsk,
|
|
34
34
|
} from '../src/shield/enforce.js';
|
|
35
35
|
import { DecisionLogger } from '../src/shield/decisions.js';
|
|
36
|
-
import { listSessions } from '../src/sources/anthropic-managed.js';
|
|
36
|
+
import { listSessions, listAgents } from '../src/sources/anthropic-managed.js';
|
|
37
37
|
import { FortressPolicySource, postDecision } from '../src/shield/sources/fortress.js';
|
|
38
38
|
import { resolveFortressBase } from '../src/fortress/url.js';
|
|
39
39
|
import { isValidAgentId, isValidSessionId } from '../src/validate.js';
|
|
@@ -57,6 +57,14 @@ function info(msg) { process.stdout.write(`[shield] ${msg}\n`); }
|
|
|
57
57
|
function warn(msg) { process.stderr.write(`[shield] ⚠️ ${msg}\n`); }
|
|
58
58
|
function sinfo(sid, msg) { process.stdout.write(`[shield/${sid.slice(0, 12)}] ${msg}\n`); }
|
|
59
59
|
function swarn(sid, msg) { process.stderr.write(`[shield/${sid.slice(0, 12)}] ⚠️ ${msg}\n`); }
|
|
60
|
+
const sleep = (ms, signal) => new Promise((res) => {
|
|
61
|
+
const t = setTimeout(res, ms);
|
|
62
|
+
if (signal) signal.addEventListener('abort', () => { clearTimeout(t); res(); }, { once: true });
|
|
63
|
+
});
|
|
64
|
+
function parseWindowMs(v, fallback) {
|
|
65
|
+
const m = v && String(v).match(/^(\d+)\s*([smhd])$/);
|
|
66
|
+
return m ? parseInt(m[1], 10) * { s: 1000, m: 60_000, h: 3_600_000, d: 86_400_000 }[m[2]] : fallback;
|
|
67
|
+
}
|
|
60
68
|
|
|
61
69
|
const CACHEABLE_TOOL_TYPES = new Set([
|
|
62
70
|
'agent.tool_use', 'agent.mcp_tool_use', 'agent.custom_tool_use',
|
|
@@ -319,6 +327,10 @@ async function runSessionWorker({ sessionId, ctx }) {
|
|
|
319
327
|
// ────────────────────────────────────────────────────────────────────────
|
|
320
328
|
async function runAgentWide(ctx) {
|
|
321
329
|
const { apiKey, agentId, signal } = ctx;
|
|
330
|
+
// Discovery window for sessions we haven't attached yet (default 7d). Already-
|
|
331
|
+
// attached workers stream until the session terminates regardless of age, so a
|
|
332
|
+
// long-running session never loses enforcement once attached.
|
|
333
|
+
const discoveryWindowMs = ctx.discoveryWindowMs || 7 * 24 * 3600_000;
|
|
322
334
|
const workers = new Map(); // sessionId → AbortController (active workers)
|
|
323
335
|
const cooldown = new Map(); // sessionId → unix-ms timestamp when re-attach is allowed
|
|
324
336
|
|
|
@@ -332,9 +344,7 @@ async function runAgentWide(ctx) {
|
|
|
332
344
|
async function discoverAndAttach() {
|
|
333
345
|
let sessions;
|
|
334
346
|
try {
|
|
335
|
-
|
|
336
|
-
// is probably stale; the user can extend the window if needed).
|
|
337
|
-
const since = new Date(Date.now() - 24 * 3600_000);
|
|
347
|
+
const since = new Date(Date.now() - discoveryWindowMs);
|
|
338
348
|
sessions = await listSessions(apiKey, { agentId, since });
|
|
339
349
|
} catch (e) {
|
|
340
350
|
warn(`listSessions failed: ${e.message}`);
|
|
@@ -423,10 +433,16 @@ async function main() {
|
|
|
423
433
|
explicitUrl: args['fortress-url'],
|
|
424
434
|
});
|
|
425
435
|
const logDir = resolve(args['log-dir'] || './watchmyagents-logs');
|
|
436
|
+
const allAgents = !!args['all-agents'];
|
|
437
|
+
const discoveryWindowMs = parseWindowMs(args['discovery-since'], 7 * 24 * 3600_000);
|
|
426
438
|
|
|
427
439
|
if (!apiKey) die('error: --api-key or ANTHROPIC_API_KEY required');
|
|
428
|
-
if (!agentId) die('error: --agent-id required');
|
|
429
|
-
if (
|
|
440
|
+
if (!allAgents && !agentId) die('error: --agent-id required (or --all-agents for fleet mode)');
|
|
441
|
+
if (allAgents && singleSessionId) die('error: --all-agents is incompatible with --session-id');
|
|
442
|
+
if (allAgents && policiesSource !== 'fortress') {
|
|
443
|
+
die('error: --all-agents requires --policies-source fortress (per-agent policies).');
|
|
444
|
+
}
|
|
445
|
+
if (agentId && !isValidAgentId(agentId)) {
|
|
430
446
|
die(`error: --agent-id has invalid format (expected "agent_" + alphanumeric, got "${agentId}")`);
|
|
431
447
|
}
|
|
432
448
|
// --session-id ends up in the Anthropic SSE URL path (src/shield/stream.js).
|
|
@@ -435,120 +451,137 @@ async function main() {
|
|
|
435
451
|
die(`error: --session-id has invalid format (expected "sesn_" + alphanumeric, got "${singleSessionId}")`);
|
|
436
452
|
}
|
|
437
453
|
|
|
438
|
-
//
|
|
439
|
-
|
|
440
|
-
let
|
|
441
|
-
|
|
454
|
+
// Validate the policy source config once (shared across the fleet). For local
|
|
455
|
+
// mode the ruleset is loaded once and shared by every agent.
|
|
456
|
+
let sharedLocalRuleset = null;
|
|
442
457
|
if (policiesSource === 'fortress') {
|
|
443
458
|
if (!wmaApiKey) die('error: --policies-source fortress requires --wma-api-key or WMA_API_KEY env');
|
|
444
459
|
if (!fortressBase) die('error: --policies-source fortress requires --fortress-base-url or WMA_FORTRESS_BASE_URL env');
|
|
445
460
|
if (!/^wma_[a-f0-9]{32}$/i.test(wmaApiKey)) warn(`WMA_API_KEY format looks unusual (expected wma_<32hex>).`);
|
|
446
|
-
|
|
447
|
-
fortressPolicies = new FortressPolicySource({
|
|
448
|
-
apiKey: wmaApiKey,
|
|
449
|
-
base: fortressBase,
|
|
450
|
-
anthropicAgentId: agentId,
|
|
451
|
-
refreshIntervalMs: 5 * 60_000,
|
|
452
|
-
onError: (e) => warn(`policy refresh failed (keeping cached): ${e.message}`),
|
|
453
|
-
onRefresh: ({ policies, fetched_at, initial }) => {
|
|
454
|
-
info(`policies ${initial ? 'loaded' : 'refreshed'} from Fortress — ${policies.length} active (fetched_at: ${fetched_at})`);
|
|
455
|
-
},
|
|
456
|
-
});
|
|
457
|
-
try {
|
|
458
|
-
await fortressPolicies.start();
|
|
459
|
-
} catch (e) {
|
|
460
|
-
die(`error fetching policies from Fortress: ${e.message}\n` +
|
|
461
|
-
` Check WMA_FORTRESS_BASE_URL and WMA_API_KEY.`);
|
|
462
|
-
}
|
|
463
|
-
ruleset = fortressPolicies.current();
|
|
464
461
|
} else if (policiesSource === 'local') {
|
|
465
462
|
if (!policyPath) die('error: --policies-source local requires --policy <path-to-policies.json>');
|
|
466
|
-
try {
|
|
467
|
-
|
|
468
|
-
} catch (e) {
|
|
469
|
-
die(`error loading policies: ${e.message}`);
|
|
470
|
-
}
|
|
463
|
+
try { sharedLocalRuleset = await loadPolicies(resolve(policyPath)); }
|
|
464
|
+
catch (e) { die(`error loading policies: ${e.message}`); }
|
|
471
465
|
} else {
|
|
472
466
|
die('error: --policy <path> OR --policies-source fortress required');
|
|
473
467
|
}
|
|
474
468
|
|
|
475
|
-
|
|
476
|
-
let
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
? `Fortress (${fortressBase})`
|
|
486
|
-
: policyPath;
|
|
487
|
-
info(`armed — ${ruleset.policies.length} policies loaded from ${sourceLabel}`);
|
|
488
|
-
info(`default action when no rule matches: ${ruleset.default.action}`);
|
|
489
|
-
info(`agent: ${agentId}${agentMeta?.name ? ` "${agentMeta.name}"` : ''}`);
|
|
490
|
-
info(`enforcement mode: ${mode}`);
|
|
491
|
-
if (mode === 'interrupt') {
|
|
492
|
-
warn('DEGRADED mode — Shield will interrupt AFTER a violating tool runs.');
|
|
493
|
-
warn(`For pre-execution blocking, run: wma-shield --setup-guide --agent-id ${agentId}`);
|
|
469
|
+
// Resolve the agent list: whole fleet (--all-agents) or a single agent.
|
|
470
|
+
let agentIds;
|
|
471
|
+
if (allAgents) {
|
|
472
|
+
info('discovering agents (fleet mode)…');
|
|
473
|
+
const all = await listAgents(apiKey).catch((e) => die(`failed to list agents: ${e.message}`));
|
|
474
|
+
agentIds = all.map((a) => a.id).filter((id) => id && isValidAgentId(id));
|
|
475
|
+
if (agentIds.length === 0) die('error: no agents found under this API key');
|
|
476
|
+
info(`fleet: ${agentIds.length} agent(s)`);
|
|
477
|
+
} else {
|
|
478
|
+
agentIds = [agentId];
|
|
494
479
|
}
|
|
480
|
+
const fleet = agentIds.length > 1;
|
|
495
481
|
|
|
496
|
-
//
|
|
497
|
-
|
|
498
|
-
const
|
|
499
|
-
const
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
return loggers.get(sessionId);
|
|
482
|
+
// Shared infra: one shutdown signal, one fortress-source registry, one pusher.
|
|
483
|
+
const ac = new AbortController();
|
|
484
|
+
const fortressSources = [];
|
|
485
|
+
const shutdown = (sig) => {
|
|
486
|
+
info(`${sig} received, shutting down…`);
|
|
487
|
+
for (const fp of fortressSources) fp.stop();
|
|
488
|
+
ac.abort();
|
|
504
489
|
};
|
|
490
|
+
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
491
|
+
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
505
492
|
|
|
506
|
-
// Optional Fortress decision pusher
|
|
507
|
-
//
|
|
508
|
-
// and-forget extra channel if both are set.
|
|
493
|
+
// Optional Fortress decision pusher (each ctx carries its own agent id, so a
|
|
494
|
+
// single shared pusher tags decisions with the right agent).
|
|
509
495
|
const canPushToFortress = !!(wmaApiKey && fortressBase);
|
|
510
496
|
const pushDecisionToFortress = canPushToFortress
|
|
511
497
|
? async (decisionData) => {
|
|
512
|
-
try {
|
|
513
|
-
|
|
514
|
-
} catch (e) {
|
|
515
|
-
warn(`Fortress decision push failed: ${e.message}`);
|
|
516
|
-
}
|
|
498
|
+
try { await postDecision({ apiKey: wmaApiKey, base: fortressBase, decision: decisionData }); }
|
|
499
|
+
catch (e) { warn(`Fortress decision push failed: ${e.message}`); }
|
|
517
500
|
}
|
|
518
501
|
: null;
|
|
519
502
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
503
|
+
// Per-agent SETUP (separate from the long-running phase so we can COUNT how
|
|
504
|
+
// many actually armed). In fleet mode a per-agent startup failure is skipped
|
|
505
|
+
// (warn) instead of killing the fleet. Returns the agent's ctx, or null if skipped.
|
|
506
|
+
async function setupAgent(aid) {
|
|
507
|
+
const tag = fleet ? `[${aid.slice(0, 16)}…] ` : '';
|
|
508
|
+
let fortressPolicies = null;
|
|
509
|
+
let ruleset = sharedLocalRuleset;
|
|
510
|
+
if (policiesSource === 'fortress') {
|
|
511
|
+
fortressPolicies = new FortressPolicySource({
|
|
512
|
+
apiKey: wmaApiKey, base: fortressBase, anthropicAgentId: aid, refreshIntervalMs: 5 * 60_000,
|
|
513
|
+
onError: (e) => warn(`${tag}policy refresh failed (keeping cached): ${e.message}`),
|
|
514
|
+
onRefresh: ({ policies, fetched_at, initial }) => info(`${tag}policies ${initial ? 'loaded' : 'refreshed'} from Fortress — ${policies.length} active (fetched_at: ${fetched_at})`),
|
|
515
|
+
});
|
|
516
|
+
try { await fortressPolicies.start(); }
|
|
517
|
+
catch (e) {
|
|
518
|
+
if (fleet) { warn(`${tag}skipped — policy fetch failed: ${e.message}`); return null; }
|
|
519
|
+
die(`error fetching policies from Fortress: ${e.message}\n Check WMA_FORTRESS_BASE_URL and WMA_API_KEY.`);
|
|
520
|
+
}
|
|
521
|
+
fortressSources.push(fortressPolicies);
|
|
522
|
+
ruleset = fortressPolicies.current();
|
|
523
|
+
}
|
|
531
524
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
apiKey,
|
|
535
|
-
|
|
536
|
-
get ruleset() {
|
|
537
|
-
return fortressPolicies ? fortressPolicies.current() : ruleset;
|
|
538
|
-
},
|
|
539
|
-
mode,
|
|
540
|
-
decisions,
|
|
541
|
-
pushDecisionToFortress,
|
|
542
|
-
signalsSalt,
|
|
543
|
-
signal: ac.signal,
|
|
544
|
-
};
|
|
525
|
+
let mode = 'interrupt';
|
|
526
|
+
let agentMeta = null;
|
|
527
|
+
try { agentMeta = await getAgentConfig(apiKey, aid); if (detectAlwaysAsk(agentMeta)) mode = 'tool_confirmation'; }
|
|
528
|
+
catch (e) { warn(`${tag}could not fetch agent config (${e.message}). Defaulting to interrupt mode.`); }
|
|
545
529
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
530
|
+
info(`${tag}armed — ${ruleset.policies.length} policies · default ${ruleset.default.action} · mode ${mode}${agentMeta?.name ? ` · "${agentMeta.name}"` : ''}`);
|
|
531
|
+
if (mode === 'interrupt' && !fleet) {
|
|
532
|
+
warn('DEGRADED mode — Shield will interrupt AFTER a violating tool runs.');
|
|
533
|
+
warn(`For pre-execution blocking, run: wma-shield --setup-guide --agent-id ${aid}`);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
const loggers = new Map();
|
|
537
|
+
const decisions = (sessionId) => {
|
|
538
|
+
if (!loggers.has(sessionId)) loggers.set(sessionId, new DecisionLogger({ logDir, agentId: aid, sessionId }));
|
|
539
|
+
return loggers.get(sessionId);
|
|
540
|
+
};
|
|
541
|
+
return {
|
|
542
|
+
apiKey, agentId: aid,
|
|
543
|
+
get ruleset() { return fortressPolicies ? fortressPolicies.current() : ruleset; },
|
|
544
|
+
mode, decisions, pushDecisionToFortress, signalsSalt, signal: ac.signal, discoveryWindowMs,
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
if (!fleet) {
|
|
549
|
+
// Single agent: arm + run (blocks until SIGINT/SIGTERM). die() on failure
|
|
550
|
+
// already fires inside setupAgent for the non-fleet path.
|
|
551
|
+
const ctx = await setupAgent(agentIds[0]);
|
|
552
|
+
await (singleSessionId ? runSessionWorker({ sessionId: singleSessionId, ctx }) : runAgentWide(ctx));
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// Fleet: arm all discovered agents, then RECONCILE periodically so an agent
|
|
557
|
+
// created after startup gets armed + protected without a restart. A per-agent
|
|
558
|
+
// arm failure is skipped and retried on the next reconcile.
|
|
559
|
+
const armed = new Set();
|
|
560
|
+
const running = [];
|
|
561
|
+
const armNew = async (ids) => {
|
|
562
|
+
for (const aid of ids) {
|
|
563
|
+
if (armed.has(aid)) continue;
|
|
564
|
+
const ctx = await setupAgent(aid);
|
|
565
|
+
if (!ctx) continue; // skipped (policy fetch failed) → retry next reconcile
|
|
566
|
+
armed.add(aid);
|
|
567
|
+
running.push(runAgentWide(ctx)); // fire; blocks on the shared signal until shutdown
|
|
568
|
+
info(`fleet: armed ${aid.slice(0, 16)}…`);
|
|
569
|
+
}
|
|
570
|
+
};
|
|
571
|
+
await armNew(agentIds);
|
|
572
|
+
if (armed.size === 0) {
|
|
573
|
+
die(`error: no agents could be armed (${agentIds.length} discovered; all policy fetches failed). Check WMA_API_KEY / WMA_FORTRESS_BASE_URL.`);
|
|
574
|
+
}
|
|
575
|
+
info(`fleet: ${armed.size}/${agentIds.length} agent(s) armed; reconciling every 60s for new agents.`);
|
|
576
|
+
while (!ac.signal.aborted) {
|
|
577
|
+
await sleep(60_000, ac.signal);
|
|
578
|
+
if (ac.signal.aborted) break;
|
|
579
|
+
let all;
|
|
580
|
+
try { all = await listAgents(apiKey); }
|
|
581
|
+
catch (e) { warn(`fleet reconcile failed (keeping current): ${e.message}`); continue; }
|
|
582
|
+
await armNew(all.map((a) => a.id).filter((id) => id && isValidAgentId(id)));
|
|
551
583
|
}
|
|
584
|
+
await Promise.all(running);
|
|
552
585
|
}
|
|
553
586
|
|
|
554
587
|
main().catch(e => {
|
|
@@ -77,6 +77,24 @@ export async function getAgent(apiKey, agentId) {
|
|
|
77
77
|
return getWithRetry(apiKey, `/v1/agents/${agentId}`);
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
// List every Managed Agent under the API key (paginated). Used for fleet mode
|
|
81
|
+
// (watch/shield/service --all-agents) and agent discovery.
|
|
82
|
+
export async function listAgents(apiKey, { limit = 100 } = {}) {
|
|
83
|
+
const agents = [];
|
|
84
|
+
let after = null;
|
|
85
|
+
while (true) {
|
|
86
|
+
const qs = new URLSearchParams({ limit: String(limit) });
|
|
87
|
+
if (after) qs.set('after_id', after);
|
|
88
|
+
const data = await getWithRetry(apiKey, `/v1/agents?${qs}`);
|
|
89
|
+
const page = data.data || [];
|
|
90
|
+
for (const a of page) agents.push(a);
|
|
91
|
+
if (!data.has_more || page.length === 0) break;
|
|
92
|
+
after = page[page.length - 1]?.id;
|
|
93
|
+
if (!after) break;
|
|
94
|
+
}
|
|
95
|
+
return agents;
|
|
96
|
+
}
|
|
97
|
+
|
|
80
98
|
export async function listSessions(apiKey, { agentId, since, limit = 100 } = {}) {
|
|
81
99
|
const sessions = [];
|
|
82
100
|
let after = null;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": "WatchMyAgents — typology classifier weights + thresholds (Guardian Core, agent-typology-classification.spec.md §3/§4/§5). INVARIANT: weights and thresholds live HERE, never hardcoded in typology.js ('poids de signature en config, pas en dur'). Calibrate on labelled real traffic. Modèle C: all inputs are anonymized behavioural fractions/flags only.",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"updated_at": "2026-05-29T00:00:00Z",
|
|
5
|
+
|
|
6
|
+
"thresholds": {
|
|
7
|
+
"$comment": "§4 'Seuils par défaut (à calibrer)' + §5 downgrade asymmetry.",
|
|
8
|
+
"n_events_min": 50,
|
|
9
|
+
"confidence_min": 0.70,
|
|
10
|
+
"margin_min": 0.15,
|
|
11
|
+
"stable_windows": 3,
|
|
12
|
+
"downgrade_confidence_min": 0.85,
|
|
13
|
+
"downgrade_windows": 5,
|
|
14
|
+
"untrusted_modifier_min": 0.1,
|
|
15
|
+
"sensitive_modifier_min": 0.0,
|
|
16
|
+
"payment_overlay_min": 0.0,
|
|
17
|
+
"autonomy_modifier_min": 0.5,
|
|
18
|
+
"$comment_tie": "§8 conservative tie-break: when |score(top1)-score(top2)| <= tie_epsilon (a near/exact tie between two REAL types with real signal), select the STRICTER of the two rather than falling to the more-permissive generic — 'dans le doute, on reste sur le plus protecteur'. Set to 0 for exact-tie only.",
|
|
19
|
+
"tie_epsilon": 0.0
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
"confidence_sigmoid": {
|
|
23
|
+
"$comment": "§4 confidence = sigmoid(a·top1.score + b·margin + c·log(n_events)). All three coefficients live in config; a naive impl that only used top1.score would be wrong.",
|
|
24
|
+
"a": 4.0,
|
|
25
|
+
"b": 6.0,
|
|
26
|
+
"c": 0.6,
|
|
27
|
+
"bias": -3.5
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
"strictness_rank": {
|
|
31
|
+
"$comment": "§5 restriction ranking — derived from each template's baseline_policies enforcement severity (isolate>block>require_approval>throttle>monitor>warn). Higher rank = STRICTER. Drives re-classification asymmetry: to a stricter rank = normal threshold; to a looser rank = downgrade gate (conf>=0.85 AND 5 windows). NOT alphabetical.",
|
|
32
|
+
"devops_infra": 10,
|
|
33
|
+
"transactional_financial": 9,
|
|
34
|
+
"workflow_backoffice": 8,
|
|
35
|
+
"coding": 7,
|
|
36
|
+
"orchestrator": 6,
|
|
37
|
+
"browser_web": 5,
|
|
38
|
+
"personal_assistant": 4,
|
|
39
|
+
"data_rag": 3,
|
|
40
|
+
"generic": 2,
|
|
41
|
+
"customer_facing": 1
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
"features": {
|
|
45
|
+
"$comment": "Canonical anonymized feature keys (Modèle C). Fractions f_* in [0,1]; flag_* in {0,1}; aux_* in [0,1]. Order is informational only — scoring is key-addressed.",
|
|
46
|
+
"fractions": ["f_code", "f_browser", "f_database", "f_http", "f_email", "f_payment", "f_secret", "f_search", "f_memory", "f_handoff", "f_user_msg", "f_file"],
|
|
47
|
+
"flags": ["flag_deploy", "flag_internal_sys", "flag_on_behalf"],
|
|
48
|
+
"aux": ["aux_autonomy", "aux_untrusted", "aux_sensitive"]
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
"weights": {
|
|
52
|
+
"$comment": "w[type][feature] — signature weights (§3). Positive = signal for the type; negative = signal against. flag_* are the REQUIRED discriminators for the 3 inseparable pairs (coding/devops, data_rag/workflow, personal_assistant/workflow). 'generic' has no positive weights (pure fallback).",
|
|
53
|
+
|
|
54
|
+
"coding": {
|
|
55
|
+
"f_code": 1.0, "f_file": 0.5, "f_search": 0.3, "f_secret": 0.1,
|
|
56
|
+
"flag_deploy": -0.9
|
|
57
|
+
},
|
|
58
|
+
"devops_infra": {
|
|
59
|
+
"f_code": 0.7, "f_secret": 0.6, "f_file": 0.2,
|
|
60
|
+
"flag_deploy": 1.2
|
|
61
|
+
},
|
|
62
|
+
"data_rag": {
|
|
63
|
+
"f_database": 0.8, "f_search": 0.35, "f_memory": 0.7, "aux_untrusted": 0.2,
|
|
64
|
+
"flag_internal_sys": -0.7
|
|
65
|
+
},
|
|
66
|
+
"customer_facing": {
|
|
67
|
+
"f_user_msg": 1.0, "f_handoff": 0.3, "f_email": 0.2
|
|
68
|
+
},
|
|
69
|
+
"browser_web": {
|
|
70
|
+
"f_browser": 1.0, "f_http": 0.6, "f_search": 0.7
|
|
71
|
+
},
|
|
72
|
+
"orchestrator": {
|
|
73
|
+
"f_handoff": 1.2, "f_code": -0.2, "f_browser": -0.2, "f_database": -0.2
|
|
74
|
+
},
|
|
75
|
+
"workflow_backoffice": {
|
|
76
|
+
"f_database": 0.6, "f_http": 0.5, "f_file": 0.2,
|
|
77
|
+
"flag_internal_sys": 0.9, "flag_on_behalf": -0.6
|
|
78
|
+
},
|
|
79
|
+
"personal_assistant": {
|
|
80
|
+
"f_email": 0.8, "f_file": 0.4, "f_user_msg": 0.3,
|
|
81
|
+
"flag_on_behalf": 1.0
|
|
82
|
+
},
|
|
83
|
+
"transactional_financial": {
|
|
84
|
+
"f_payment": 1.5
|
|
85
|
+
},
|
|
86
|
+
"generic": {}
|
|
87
|
+
}
|
|
88
|
+
}
|