@archsight/aios 1.0.1 → 1.1.0

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +49 -5
  2. package/LICENSE +184 -21
  3. package/README.md +43 -17
  4. package/agents/README.md +2 -1
  5. package/agents/euclid/constraints.md +2 -1
  6. package/agents/euclid/responsibilities.md +1 -1
  7. package/agents/euclid/role.md +1 -1
  8. package/agents/euclid/system-prompt.md +5 -2
  9. package/agents/euclid/workflow.md +3 -3
  10. package/bin/archsight-aios.mjs +436 -1
  11. package/docs/quickstart.md +2 -1
  12. package/governance/README.md +3 -0
  13. package/governance/arbitration-protocol.md +153 -0
  14. package/package.json +3 -3
  15. package/runtime/README.md +7 -0
  16. package/runtime/agent-routing.md +41 -17
  17. package/runtime/archsight-aios.manifest.json +58 -36
  18. package/runtime/capability-adapters.json +27 -0
  19. package/runtime/capability-registry.json +458 -0
  20. package/runtime/capability-registry.schema.json +135 -0
  21. package/runtime/skill-routing.md +17 -13
  22. package/skills/README.md +12 -9
  23. package/skills/aios-arch/SKILL.md +62 -24
  24. package/skills/aios-ceo/SKILL.md +11 -8
  25. package/skills/aios-exec/SKILL.md +11 -8
  26. package/skills/aios-knowledge/SKILL.md +12 -9
  27. package/skills/aios-plan/SKILL.md +38 -28
  28. package/skills/aios-review/SKILL.md +12 -9
  29. package/skills/aios-runtime/SKILL.md +14 -11
  30. package/skills/aios-structural/SKILL.md +67 -0
  31. package/skills/aios-structural/agents/openai.yaml +4 -0
  32. package/templates/project-ai/.ai/ARCHSIGHT_AIOS_RULES.md +13 -10
  33. package/templates/project-ai/.ai/agent-routing.md +17 -12
  34. package/templates/project-ai/.ai/skills.md +14 -11
  35. package/templates/project-ai/.ai/workflows.md +6 -5
  36. package/workflows/README.md +2 -0
  37. package/workflows/architecture-review.md +44 -22
  38. package/workflows/feature-development.md +25 -19
  39. package/workflows/rag-pipeline.md +9 -5
@@ -3,6 +3,7 @@
3
3
  import fs from "node:fs/promises";
4
4
  import path from "node:path";
5
5
  import os from "node:os";
6
+ import { spawn } from "node:child_process";
6
7
  import { fileURLToPath } from "node:url";
7
8
 
8
9
  const __filename = fileURLToPath(import.meta.url);
@@ -43,6 +44,7 @@ const skillAliases = {
43
44
  "aios-bim-domain-modeling",
44
45
  "archsight-bim-domain-modeling"
45
46
  ],
47
+ "aios-structural": ["aios-structural-review", "archsight-structural-review"],
46
48
  "aios-runtime": [
47
49
  "aios-runtime-design",
48
50
  "archsight-runtime-design",
@@ -62,6 +64,7 @@ function usage() {
62
64
  " archsight-aios doctor",
63
65
  " archsight-aios init [--cwd <path>] [--mode <auto|full|linked|ai-only>] [--profile <name>]",
64
66
  " archsight-aios validate [--cwd <path>] [--profile <name>] [--temp]",
67
+ " archsight-aios capability:call --capability <id> --agent <id> --skill <id> --input <json-file>",
65
68
  " archsight-aios hermes:validate",
66
69
  " archsight-aios hermes:sync-dry-run",
67
70
  " archsight-aios hermes:detect-drift",
@@ -72,6 +75,7 @@ function usage() {
72
75
  " doctor Check repository assets and user-level installation.",
73
76
  " init Add AI rules and .ai governance files to a project.",
74
77
  " validate Validate the project AI template output.",
78
+ " capability:call Authorize and call a registered local Capability adapter.",
75
79
  " hermes:* Validate or dry-run Hermes runtime prompt sync.",
76
80
  "",
77
81
  "Examples:",
@@ -94,7 +98,15 @@ function parseArgs(argv) {
94
98
  profile: undefined,
95
99
  cwd: process.cwd(),
96
100
  help,
97
- temp: false
101
+ temp: false,
102
+ capability: undefined,
103
+ agent: undefined,
104
+ skill: undefined,
105
+ input: undefined,
106
+ mcpCwd: undefined,
107
+ mcpCommand: undefined,
108
+ mcpArgs: [],
109
+ timeoutMs: undefined
98
110
  };
99
111
 
100
112
  for (let i = 0; i < rest.length; i += 1) {
@@ -111,6 +123,22 @@ function parseArgs(argv) {
111
123
  options.profile = rest[++i];
112
124
  } else if (arg === "--temp") {
113
125
  options.temp = true;
126
+ } else if (arg === "--capability") {
127
+ options.capability = rest[++i];
128
+ } else if (arg === "--agent") {
129
+ options.agent = rest[++i];
130
+ } else if (arg === "--skill") {
131
+ options.skill = rest[++i];
132
+ } else if (arg === "--input") {
133
+ options.input = path.resolve(rest[++i]);
134
+ } else if (arg === "--mcp-cwd") {
135
+ options.mcpCwd = path.resolve(rest[++i]);
136
+ } else if (arg === "--mcp-command") {
137
+ options.mcpCommand = rest[++i];
138
+ } else if (arg === "--mcp-arg") {
139
+ options.mcpArgs.push(rest[++i]);
140
+ } else if (arg === "--timeout-ms") {
141
+ options.timeoutMs = Number(rest[++i]);
114
142
  } else if (arg === "--help" || arg === "-h") {
115
143
  options.help = true;
116
144
  } else {
@@ -213,6 +241,373 @@ async function readJson(filePath) {
213
241
  return JSON.parse(raw);
214
242
  }
215
243
 
244
+ async function readCapabilityRegistry() {
245
+ const manifest = await readManifest();
246
+ const registryPath = path.join(repoRoot, manifest.capabilityRegistry.registryPath);
247
+ return readJson(registryPath);
248
+ }
249
+
250
+ async function readCapabilityAdapters() {
251
+ const manifest = await readManifest();
252
+ const adapterPath = manifest.capabilityRegistry?.adapterPath;
253
+ if (!adapterPath) {
254
+ return { schema: 1, adapters: [] };
255
+ }
256
+ return readJson(path.join(repoRoot, adapterPath));
257
+ }
258
+
259
+ function jsonTypeMatches(schemaType, value) {
260
+ if (schemaType === "object") {
261
+ return value !== null && typeof value === "object" && !Array.isArray(value);
262
+ }
263
+ if (schemaType === "array") {
264
+ return Array.isArray(value);
265
+ }
266
+ if (schemaType === "integer") {
267
+ return Number.isInteger(value);
268
+ }
269
+ if (schemaType === "number") {
270
+ return typeof value === "number" && Number.isFinite(value);
271
+ }
272
+ if (schemaType === "string") {
273
+ return typeof value === "string";
274
+ }
275
+ if (schemaType === "boolean") {
276
+ return typeof value === "boolean";
277
+ }
278
+ return true;
279
+ }
280
+
281
+ function valueAtPath(value, fieldPath) {
282
+ return fieldPath.split(".").reduce((current, key) => {
283
+ if (current === null || typeof current !== "object") {
284
+ return undefined;
285
+ }
286
+ return current[key];
287
+ }, value);
288
+ }
289
+
290
+ function validateJsonSchemaSubset(schema, value, label = "$", errors = []) {
291
+ if (!schema || typeof schema !== "object") {
292
+ return errors;
293
+ }
294
+
295
+ if (schema.type && !jsonTypeMatches(schema.type, value)) {
296
+ errors.push(`${label} expected ${schema.type}`);
297
+ return errors;
298
+ }
299
+
300
+ if (schema.enum && !schema.enum.includes(value)) {
301
+ errors.push(`${label} expected one of ${schema.enum.join(", ")}`);
302
+ }
303
+
304
+ if (typeof schema.minimum === "number" && typeof value === "number" && value < schema.minimum) {
305
+ errors.push(`${label} must be >= ${schema.minimum}`);
306
+ }
307
+
308
+ if (schema.type === "object" && value !== null && typeof value === "object" && !Array.isArray(value)) {
309
+ for (const requiredField of schema.required ?? []) {
310
+ if (!Object.hasOwn(value, requiredField)) {
311
+ errors.push(`${label}.${requiredField} is required`);
312
+ }
313
+ }
314
+
315
+ for (const [property, propertySchema] of Object.entries(schema.properties ?? {})) {
316
+ if (Object.hasOwn(value, property)) {
317
+ validateJsonSchemaSubset(propertySchema, value[property], `${label}.${property}`, errors);
318
+ }
319
+ }
320
+ }
321
+
322
+ if (schema.type === "array" && Array.isArray(value) && schema.items) {
323
+ value.forEach((item, index) => validateJsonSchemaSubset(schema.items, item, `${label}[${index}]`, errors));
324
+ }
325
+
326
+ return errors;
327
+ }
328
+
329
+ function findCapability(registry, capabilityId) {
330
+ return (registry.capabilities ?? []).find((capability) => capability.id === capabilityId);
331
+ }
332
+
333
+ function findCapabilityAdapter(adapters, capabilityId) {
334
+ return (adapters.adapters ?? []).find((adapter) => (adapter.capabilityIds ?? []).includes(capabilityId));
335
+ }
336
+
337
+ function authorizeCapability(capability, options) {
338
+ if (!options.agent) {
339
+ throw new Error("--agent is required for Capability calls");
340
+ }
341
+ if (!options.skill) {
342
+ throw new Error("--skill is required for Capability calls");
343
+ }
344
+ if (!capability.ownerAgents.includes(options.agent)) {
345
+ throw new Error(`Capability denied: agent ${options.agent} cannot call ${capability.id}`);
346
+ }
347
+ if ((capability.allowedSkills ?? []).length > 0 && !capability.allowedSkills.includes(options.skill)) {
348
+ throw new Error(`Capability denied: skill ${options.skill} cannot call ${capability.id}`);
349
+ }
350
+ }
351
+
352
+ function normalizeExpectedValue(rawValue) {
353
+ const trimmed = rawValue.trim();
354
+ if (trimmed === "true") {
355
+ return true;
356
+ }
357
+ if (trimmed === "false") {
358
+ return false;
359
+ }
360
+ if (/^-?\d+(\.\d+)?$/.test(trimmed)) {
361
+ return Number(trimmed);
362
+ }
363
+ return trimmed.replace(/^["']|["']$/g, "");
364
+ }
365
+
366
+ function evaluateRuleCondition(condition, result) {
367
+ const match = condition.match(/^([A-Za-z0-9_.]+)\s*==\s*(.+)$/);
368
+ if (!match) {
369
+ return false;
370
+ }
371
+ const actual = valueAtPath(result, match[1]);
372
+ const expected = normalizeExpectedValue(match[2]);
373
+ return actual === expected;
374
+ }
375
+
376
+ function evaluateCapabilityDecision(capability, result, validationErrors) {
377
+ const missingEvidence = (capability.evidenceContract?.requiredFields ?? [])
378
+ .filter((field) => valueAtPath(result, field) === undefined);
379
+ const matchedRules = (capability.blockingRules ?? [])
380
+ .filter((rule) => evaluateRuleCondition(rule.when, result));
381
+
382
+ if (validationErrors.length > 0 || missingEvidence.length > 0) {
383
+ return {
384
+ action: "hold",
385
+ severity: "P1",
386
+ matchedRules,
387
+ missingEvidence,
388
+ validationErrors
389
+ };
390
+ }
391
+
392
+ if (matchedRules.length === 0) {
393
+ return {
394
+ action: "proceed",
395
+ severity: "none",
396
+ matchedRules,
397
+ missingEvidence,
398
+ validationErrors
399
+ };
400
+ }
401
+
402
+ const actionRank = { block: 4, human_escalation: 3, hold: 2, revise: 1 };
403
+ const severityRank = { P0: 3, P1: 2, P2: 1 };
404
+ const sorted = [...matchedRules].sort((left, right) => {
405
+ const severityDiff = (severityRank[right.severity] ?? 0) - (severityRank[left.severity] ?? 0);
406
+ if (severityDiff !== 0) {
407
+ return severityDiff;
408
+ }
409
+ return (actionRank[right.action] ?? 0) - (actionRank[left.action] ?? 0);
410
+ });
411
+
412
+ return {
413
+ action: sorted[0].action,
414
+ severity: sorted[0].severity,
415
+ matchedRules,
416
+ missingEvidence,
417
+ validationErrors
418
+ };
419
+ }
420
+
421
+ function defaultAdapterCwd(adapter, options) {
422
+ if (options.mcpCwd) {
423
+ return options.mcpCwd;
424
+ }
425
+ if (adapter.cwdEnv && process.env[adapter.cwdEnv]) {
426
+ return path.resolve(process.env[adapter.cwdEnv]);
427
+ }
428
+ if (adapter.defaultSiblingDir) {
429
+ return path.resolve(repoRoot, "..", adapter.defaultSiblingDir);
430
+ }
431
+ return repoRoot;
432
+ }
433
+
434
+ function resolveCapabilityAdapter(adapter, capabilityId, options) {
435
+ return {
436
+ command: options.mcpCommand || adapter.command,
437
+ args: options.mcpArgs.length > 0 ? options.mcpArgs : adapter.args ?? [],
438
+ cwd: defaultAdapterCwd(adapter, options),
439
+ toolName: adapter.toolNameMap?.[capabilityId] ?? capabilityId.split(".").at(-1),
440
+ timeoutMs: Number(options.timeoutMs || adapter.timeoutMs || 30000)
441
+ };
442
+ }
443
+
444
+ function callMcpStdio({ command, args, cwd, toolName, input, timeoutMs }) {
445
+ return new Promise((resolve, reject) => {
446
+ const child = spawn(command, args, {
447
+ cwd,
448
+ stdio: ["pipe", "pipe", "pipe"],
449
+ windowsHide: true
450
+ });
451
+ let stdout = "";
452
+ let stderr = "";
453
+ let settled = false;
454
+
455
+ function settle(callback, value) {
456
+ if (settled) {
457
+ return;
458
+ }
459
+ settled = true;
460
+ clearTimeout(timer);
461
+ callback(value);
462
+ }
463
+
464
+ const timer = setTimeout(() => {
465
+ child.kill();
466
+ settle(reject, new Error(`MCP call timed out after ${timeoutMs}ms`));
467
+ }, timeoutMs);
468
+
469
+ child.stdout.on("data", (chunk) => {
470
+ stdout += chunk.toString("utf8");
471
+ });
472
+ child.stderr.on("data", (chunk) => {
473
+ stderr += chunk.toString("utf8");
474
+ });
475
+ child.on("error", (error) => {
476
+ settle(reject, error);
477
+ });
478
+ child.on("close", (exitCode) => {
479
+ if (exitCode !== 0) {
480
+ settle(reject, new Error(`MCP server exited with ${exitCode}: ${stderr.trim()}`));
481
+ return;
482
+ }
483
+
484
+ try {
485
+ const responses = stdout
486
+ .split(/\r?\n/)
487
+ .filter((line) => line.trim().length > 0)
488
+ .map((line) => JSON.parse(line));
489
+ const callResponse = responses.find((response) => response.id === 2);
490
+ if (!callResponse) {
491
+ throw new Error("MCP tools/call response was not returned");
492
+ }
493
+ if (callResponse.error) {
494
+ throw new Error(`MCP tools/call failed: ${callResponse.error.message}`);
495
+ }
496
+ settle(resolve, {
497
+ initialize: responses.find((response) => response.id === 1)?.result,
498
+ call: callResponse.result,
499
+ stderr
500
+ });
501
+ } catch (error) {
502
+ settle(reject, new Error(`${error.message}. stdout: ${stdout.trim()}`));
503
+ }
504
+ });
505
+
506
+ const initialize = {
507
+ jsonrpc: "2.0",
508
+ id: 1,
509
+ method: "initialize",
510
+ params: {
511
+ protocolVersion: "2025-06-18",
512
+ capabilities: {},
513
+ clientInfo: { name: "archsight-aios", version: "1.0.1" }
514
+ }
515
+ };
516
+ const callTool = {
517
+ jsonrpc: "2.0",
518
+ id: 2,
519
+ method: "tools/call",
520
+ params: {
521
+ name: toolName,
522
+ arguments: input
523
+ }
524
+ };
525
+ child.stdin.write(`${JSON.stringify(initialize)}\n`);
526
+ child.stdin.write(`${JSON.stringify(callTool)}\n`);
527
+ child.stdin.end();
528
+ });
529
+ }
530
+
531
+ async function capabilityCall(options) {
532
+ if (!options.capability) {
533
+ throw new Error("--capability is required");
534
+ }
535
+ if (!options.input) {
536
+ throw new Error("--input is required");
537
+ }
538
+
539
+ const registry = await readCapabilityRegistry();
540
+ const capability = findCapability(registry, options.capability);
541
+ if (!capability) {
542
+ throw new Error(`Unknown Capability: ${options.capability}`);
543
+ }
544
+ authorizeCapability(capability, options);
545
+
546
+ const adapters = await readCapabilityAdapters();
547
+ const adapter = findCapabilityAdapter(adapters, capability.id);
548
+ if (!adapter) {
549
+ throw new Error(`No local adapter registered for Capability: ${capability.id}`);
550
+ }
551
+
552
+ const input = await readJson(options.input);
553
+ const inputErrors = validateJsonSchemaSubset(capability.inputSchema, input, "$.input");
554
+ if (inputErrors.length > 0) {
555
+ throw new Error(`Capability input validation failed: ${inputErrors.join("; ")}`);
556
+ }
557
+
558
+ const resolvedAdapter = resolveCapabilityAdapter(adapter, capability.id, options);
559
+ if (!resolvedAdapter.command) {
560
+ throw new Error(`Capability adapter ${adapter.id} does not define a command`);
561
+ }
562
+ if (!(await exists(resolvedAdapter.cwd))) {
563
+ throw new Error(`MCP adapter cwd not found: ${resolvedAdapter.cwd}`);
564
+ }
565
+
566
+ const mcp = await callMcpStdio({
567
+ command: resolvedAdapter.command,
568
+ args: resolvedAdapter.args,
569
+ cwd: resolvedAdapter.cwd,
570
+ toolName: resolvedAdapter.toolName,
571
+ input,
572
+ timeoutMs: resolvedAdapter.timeoutMs
573
+ });
574
+ const structuredContent = mcp.call?.structuredContent;
575
+ if (!structuredContent || typeof structuredContent !== "object") {
576
+ throw new Error("MCP tool result did not include structuredContent");
577
+ }
578
+
579
+ const outputErrors = validateJsonSchemaSubset(capability.outputSchema, structuredContent, "$.toolResult");
580
+ const decision = evaluateCapabilityDecision(capability, structuredContent, outputErrors);
581
+ const envelope = {
582
+ schema: 1,
583
+ capabilityId: capability.id,
584
+ agent: options.agent,
585
+ skill: options.skill,
586
+ authorized: true,
587
+ inputValidated: true,
588
+ adapter: {
589
+ id: adapter.id,
590
+ transport: adapter.transport,
591
+ cwd: resolvedAdapter.cwd,
592
+ command: resolvedAdapter.command,
593
+ args: resolvedAdapter.args,
594
+ toolName: resolvedAdapter.toolName
595
+ },
596
+ toolResult: structuredContent,
597
+ decision,
598
+ evidence: {
599
+ level: capability.authorityLevel,
600
+ source: "mcp.structuredContent",
601
+ serverInfo: mcp.initialize?.serverInfo
602
+ }
603
+ };
604
+
605
+ console.log(JSON.stringify(envelope, null, 2));
606
+ if (["block", "hold", "human_escalation"].includes(decision.action)) {
607
+ process.exitCode = 2;
608
+ }
609
+ }
610
+
216
611
  function routeAgentName(manifest, agentId) {
217
612
  return manifest.agents.find((agent) => agent.id === agentId)?.displayName ?? agentId;
218
613
  }
@@ -624,6 +1019,44 @@ async function doctor() {
624
1019
  await check("hermes sync record template", path.join(repoRoot, manifest.hermes.syncRecordTemplatePath));
625
1020
  }
626
1021
 
1022
+ if (manifest.capabilityRegistry) {
1023
+ const registryPath = path.join(repoRoot, manifest.capabilityRegistry.registryPath);
1024
+ await check("capability registry schema", path.join(repoRoot, manifest.capabilityRegistry.schemaPath));
1025
+ await check("capability registry", registryPath);
1026
+ if (manifest.capabilityRegistry.adapterPath) {
1027
+ await check("capability adapters", path.join(repoRoot, manifest.capabilityRegistry.adapterPath));
1028
+ }
1029
+
1030
+ const registry = await readJson(registryPath);
1031
+ checkCondition("capability registry schema version", registry.schema === 1, "schema === 1");
1032
+ checkCondition("capability registry has capabilities", registry.capabilities?.length > 0, "capabilities.length > 0");
1033
+
1034
+ const capabilityIds = new Set();
1035
+ for (const capability of registry.capabilities ?? []) {
1036
+ checkCondition(`capability id unique ${capability.id}`, !capabilityIds.has(capability.id), capability.id);
1037
+ capabilityIds.add(capability.id);
1038
+ checkCondition(`capability authority ${capability.id}`, ["L1", "L2", "L3"].includes(capability.authorityLevel), capability.authorityLevel);
1039
+ checkCondition(`capability has blocking rules ${capability.id}`, capability.blockingRules?.length > 0, "blockingRules.length > 0");
1040
+ for (const agentId of capability.ownerAgents ?? []) {
1041
+ checkCondition(`capability owner exists ${capability.id}/${agentId}`, agentIds.has(agentId), agentId);
1042
+ }
1043
+ for (const skillId of capability.allowedSkills ?? []) {
1044
+ checkCondition(`capability skill exists ${capability.id}/${skillId}`, skillIds.has(skillId), skillId);
1045
+ }
1046
+ }
1047
+
1048
+ if (manifest.capabilityRegistry.adapterPath) {
1049
+ const adapters = await readJson(path.join(repoRoot, manifest.capabilityRegistry.adapterPath));
1050
+ checkCondition("capability adapters schema version", adapters.schema === 1, "schema === 1");
1051
+ for (const adapter of adapters.adapters ?? []) {
1052
+ checkCondition(`capability adapter transport ${adapter.id}`, adapter.transport === "stdio-mcp", adapter.transport);
1053
+ for (const capabilityId of adapter.capabilityIds ?? []) {
1054
+ checkCondition(`capability adapter target exists ${adapter.id}/${capabilityId}`, capabilityIds.has(capabilityId), capabilityId);
1055
+ }
1056
+ }
1057
+ }
1058
+ }
1059
+
627
1060
  await check("gemini support assets", geminiRoot);
628
1061
  await check("gemini support skills", path.join(geminiRoot, "skills"));
629
1062
  await check("gemini support workflows", path.join(geminiRoot, "workflows"));
@@ -942,6 +1375,8 @@ async function main() {
942
1375
  await initProject(options);
943
1376
  } else if (options.command === "validate") {
944
1377
  await validateProjectTemplate(options);
1378
+ } else if (options.command === "capability:call") {
1379
+ await capabilityCall(options);
945
1380
  } else if (options.command === "hermes:validate") {
946
1381
  await validateHermesRegistry();
947
1382
  } else if (options.command === "hermes:sync-dry-run") {
@@ -23,7 +23,8 @@ npx @archsight/aios doctor
23
23
  Windows PowerShell 示例:
24
24
 
25
25
  ```powershell
26
- cd C:\Work\YourProject
26
+ $projectPath = "<你的业务项目绝对路径>"
27
+ cd $projectPath
27
28
  npx @archsight/aios init
28
29
  ```
29
30
 
@@ -8,8 +8,11 @@
8
8
  - [AI Review Policy](ai-review-policy.md)
9
9
  - [Security Policy](security-policy.md)
10
10
  - [Agent Boundary Policy](agent-boundary.md)
11
+ - [Capability-Backed Arbitration Protocol](arbitration-protocol.md)
11
12
  - [Delivery Policy](delivery-policy.md)
12
13
  - [Context Policy](context-policy.md)
13
14
  - [Memory Policy](memory-policy.md)
14
15
 
15
16
  治理目标是防止 agent 乱调用、prompt 泄露、上下文爆炸、AI 瞎改代码、权限失控和未经评审的自动交付。
17
+
18
+ 多 Agent 产生逻辑冲突时,优先按 `arbitration-protocol.md` 的证据等级处理:确定性工具、项目事实和结构化知识优先于 Agent 自然语言判断;涉及生产授权、法规合规最终结论、结构安全结论和商业范围取舍时升级给人类负责人。
@@ -0,0 +1,153 @@
1
+ # Capability-Backed Arbitration Protocol
2
+
3
+ 状态:治理基线草案
4
+ 适用范围:ArchSight AIOS 多 Agent 冲突仲裁、工具证据裁决和人工升级
5
+
6
+ ---
7
+
8
+ ## 一、目标
9
+
10
+ 本协议把 AIOS 的多 Agent 协作从“角色意见协商”升级为“证据驱动仲裁”。
11
+
12
+ 核心原则:
13
+
14
+ > Agent 可以提出 Claim,但不能只凭自然语言推理裁决事实。事实裁决必须回到项目证据、结构化知识、确定性工具或人工授权。
15
+
16
+ 适用场景:
17
+
18
+ - Atlas、Mason、Vitruvius、Argus、Daedalus、Euclid 等 Agent 对方案产生逻辑冲突。
19
+ - 架构建议、工程计划、规范语义、结构计算、安全审查或 Runtime 权限之间存在互相阻断。
20
+ - AIOS 需要决定继续执行、回滚、重新评审、收缩范围或升级给人类负责人。
21
+
22
+ ---
23
+
24
+ ## 二、证据优先级
25
+
26
+ 仲裁时按以下优先级裁决。低层级证据不能推翻高层级证据,除非高层级证据本身被标记为无效、过期或不适用。
27
+
28
+ | 等级 | 证据 | 说明 |
29
+ | --- | --- | --- |
30
+ | L0 | 人类硬约束 | 预算、交付窗口、生产授权、法律责任、商业取舍、签章和最终发布许可。 |
31
+ | L1 | 确定性工具返回值 | 测试、构建、lint、schema 校验、安全扫描、结构求解器、规范检查 API。 |
32
+ | L2 | 项目事实 | 当前代码、配置、接口契约、数据库迁移、部署脚本、CI、ADR、运行日志。 |
33
+ | L3 | 结构化知识库 | GraphRAG、规范条文、版本关系、地区 / 专业适用条件、来源页码和质量状态。 |
34
+ | L4 | 专项 Agent 判断 | Atlas 的架构判断、Mason 的交付判断、Vitruvius 的领域判断、Euclid 的建模判断。 |
35
+ | L5 | LLM 自然语言推理 | 只能作为假设、解释或建议,不能单独作为阻断或放行依据。 |
36
+
37
+ 工具结果优先,但不得盲信工具结果。L1/L3 证据必须带有输入、版本、适用条件和执行状态;缺失时只能进入 `Need verify`。
38
+
39
+ ---
40
+
41
+ ## 三、Claim 契约
42
+
43
+ Agent 之间发生冲突时,不能只输出“我不同意”。每个参与方必须把意见转成 Claim:
44
+
45
+ ```text
46
+ Claim:
47
+ id:
48
+ owner_agent:
49
+ type: architecture | delivery | domain_semantics | structural | security | runtime | business
50
+ statement:
51
+ evidence_level: L0 | L1 | L2 | L3 | L4 | L5
52
+ evidence:
53
+ assumptions:
54
+ need_verify:
55
+ blocking: true | false
56
+ severity: P0 | P1 | P2
57
+ requested_action: proceed | revise | reduce | stop | human_escalation
58
+ ```
59
+
60
+ Claim 必须明确区分事实、判断、假设和待验证项。没有证据的 Claim 默认不具备阻断权。
61
+
62
+ ---
63
+
64
+ ## 四、冲突分类与主裁决依据
65
+
66
+ | 冲突类型 | 主裁决依据 | 默认处理 |
67
+ | --- | --- | --- |
68
+ | 架构冲突 | Atlas + L2 项目事实 + L1 验证工具 | 回到 `architecture-review`。 |
69
+ | 交付冲突 | Mason + CI / 测试 / 依赖图 | 收缩范围、重排计划或回到任务拆解。 |
70
+ | 行业语义冲突 | Vitruvius + 规范工具 + 结构化知识库 | 不满足适用条件时阻断执行。 |
71
+ | 结构计算冲突 | Euclid + Solver / 数值校验 + 单位和边界条件 | 缺少输入或工具失败时不得输出确定结论。 |
72
+ | 安全 / 权限冲突 | Argus + 安全扫描 + 权限策略 | 默认阻断,除非人工明确批准。 |
73
+ | Runtime 冲突 | Daedalus + Capability 权限 + 上下文策略 | 降权、隔离或重新设计工具调用边界。 |
74
+ | 商业 / 范围冲突 | Janus / CEO + L0 人类目标 | 人工裁决或明确阶段性取舍。 |
75
+
76
+ ---
77
+
78
+ ## 五、状态机
79
+
80
+ ```text
81
+ proposed
82
+ -> evidence_required
83
+ -> tool_check
84
+ -> accepted
85
+ -> planned
86
+ -> executed
87
+ -> reviewed
88
+ -> completed
89
+ ```
90
+
91
+ 异常路径:
92
+
93
+ ```text
94
+ tool_check -> rejected -> revise
95
+ tool_check -> need_verify -> hold
96
+ reviewed -> rejected -> revise
97
+ any -> human_escalation
98
+ ```
99
+
100
+ 阻断规则:
101
+
102
+ - L1 工具返回 `fail` 且适用条件有效时,必须阻断后续执行或放行。
103
+ - L3 规范 / 知识证据缺少版本、来源或适用条件时,不能自动判定合规。
104
+ - L4 Agent 判断只能触发复核、重评或工具调用,不能独立推翻 L1/L2/L3。
105
+ - L0 人类授权可以覆盖执行路线,但不能把未验证事实改写成已验证事实。
106
+
107
+ ---
108
+
109
+ ## 六、人工升级条件
110
+
111
+ 以下情况必须升级给人类负责人或项目指定审批人:
112
+
113
+ - 核心技术栈替换。
114
+ - 生产数据模型迁移。
115
+ - Runtime 权限扩大。
116
+ - 多 Agent 自动执行权限放开。
117
+ - 自动发布到生产。
118
+ - 法规合规最终判定、结构安全结论或工程签章。
119
+ - 商业范围、预算、交付窗口和停损信号的最终取舍。
120
+
121
+ 人工裁决也必须记录证据和约束,不能只记录“老板同意”。
122
+
123
+ ---
124
+
125
+ ## 七、Decision Ledger
126
+
127
+ 每次仲裁必须沉淀为可复核记录。最小字段:
128
+
129
+ ```text
130
+ Decision:
131
+ id:
132
+ date:
133
+ conflict:
134
+ claims:
135
+ evidence:
136
+ tool_results:
137
+ decision: proceed | revise | reduce | stop | escalate
138
+ rejected:
139
+ owner:
140
+ follow_up:
141
+ ```
142
+
143
+ Decision Ledger 可以写入 ADR、memory、PR 描述、issue 或项目 `.ai/` 目录,具体位置由项目接入规则决定。
144
+
145
+ ---
146
+
147
+ ## 八、落地要求
148
+
149
+ - Workflow 输出必须包含 `Claim / Evidence / Tool Result / Decision`。
150
+ - Skill 需要声明可用 Capability、权限边界和证据契约。
151
+ - Runtime Adapter 只负责调用工具和回传证据,不替代 Agent 判断。
152
+ - 工具调用失败时必须暴露失败原因、输入摘要和可恢复路径。
153
+ - 没有 Capability 实现时,必须标为 `declared-interface` 或 `Need verify`,不得伪造工具结果。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@archsight/aios",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "面向建筑 AI 研发的规则、Skill、Workflow 与多 Agent 项目接入工具包。",
5
5
  "type": "module",
6
6
  "homepage": "https://github.com/ArchSightLabs/archsight-aios#readme",
@@ -65,5 +65,5 @@
65
65
  "CLAUDE.md",
66
66
  "GEMINI.md"
67
67
  ],
68
- "license": "MIT"
69
- }
68
+ "license": "Apache-2.0"
69
+ }