fuli-context 0.11.1 → 0.12.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.
Files changed (213) hide show
  1. package/README.md +6 -5
  2. package/README.zh-CN.md +5 -4
  3. package/dist/web/assets/AboutPage-Dized8Uz.js +1 -0
  4. package/dist/web/assets/AgentConversations-Ccy41mEA.js +2 -0
  5. package/dist/web/assets/AgentHand-UzVhbKUC.js +1 -0
  6. package/dist/web/assets/AgentName-DJpvkub2.js +1 -0
  7. package/dist/web/assets/AgentProfilePage-CFViQ8kj.js +1 -0
  8. package/dist/web/assets/AgentsDirectoryPage-CBFTg_IE.js +1 -0
  9. package/dist/web/assets/AgentsDirectoryPage-Cv-vxw2C.css +1 -0
  10. package/dist/web/assets/ConnectionsPage-uXcabab_.js +1 -0
  11. package/dist/web/assets/EmployeeRecruitDialog-D8Hvhc-M.js +1 -0
  12. package/dist/web/assets/EmployeeWorkbenchPage-BQes4L1f.css +1 -0
  13. package/dist/web/assets/EmployeeWorkbenchPage-TBl_HNj6.js +3 -0
  14. package/dist/web/assets/KnowledgeEditDialog-HmiqLC-Y.js +1 -0
  15. package/dist/web/assets/KnowledgeInspector-Cj2yK4Qh.js +1 -0
  16. package/dist/web/assets/KnowledgeOrganizerPage-DT3BROZm.js +1 -0
  17. package/dist/web/assets/KnowledgeOrganizerPage-ccgscrPd.css +1 -0
  18. package/dist/web/assets/KnowledgePage-C4J4VueL.js +1 -0
  19. package/dist/web/assets/KnowledgeWorkspace-DA_dqVRG.js +4 -0
  20. package/dist/web/assets/NotFoundPage-x_Yd59mm.js +1 -0
  21. package/dist/web/assets/OverviewPage-3eZjQ26v.js +1 -0
  22. package/dist/web/assets/OverviewPage-8LBN8Shi.css +1 -0
  23. package/dist/web/assets/PersonalProfilePage-CDQk096L.js +1 -0
  24. package/dist/web/assets/PersonalProjectsPage-CJxp4Wyz.js +1 -0
  25. package/dist/web/assets/ProjectAgentFirstTask-BkRJ0zwO.js +1 -0
  26. package/dist/web/assets/ProjectAgentsPage-BuNNqSEy.js +12 -0
  27. package/dist/web/assets/ProjectScopePicker-CiDxM2Yx.js +1 -0
  28. package/dist/web/assets/PublicProjectsPage-ByOT559r.js +1 -0
  29. package/dist/web/assets/ReviewPage-C0Vdr9uq.js +1 -0
  30. package/dist/web/assets/RoundtablesPage-Drou-B81.css +1 -0
  31. package/dist/web/assets/RoundtablesPage-L3ShEWS3.js +1 -0
  32. package/dist/web/assets/SettingsPage-BE8pWbI6.css +1 -0
  33. package/dist/web/assets/SettingsPage-McYuinGz.js +1 -0
  34. package/dist/web/assets/SharedJefaReportPage-Bg6lIk3E.js +1 -0
  35. package/dist/web/assets/UiButton-x8hiHNRM.js +1 -0
  36. package/dist/web/assets/UiDialog-mjletK0y.js +1 -0
  37. package/dist/web/assets/UiDisclosure-_IxeiexW.js +1 -0
  38. package/dist/web/assets/UiSegmented-WFfz9Evl.js +1 -0
  39. package/dist/web/assets/UiSelect-K29jnV-1.js +1 -0
  40. package/dist/web/assets/WritingTastePage-DCULB-d2.js +1 -0
  41. package/dist/web/assets/agent-pins-DS4mNsNt.js +1 -0
  42. package/dist/web/assets/agent-profile-Bh3MJGvb.css +1 -0
  43. package/dist/web/assets/agent-work-summary-C-yW7x7A.js +1 -0
  44. package/dist/web/assets/{attention-store-D-qIFRIb.js → attention-store-CA5y8j1R.js} +1 -1
  45. package/dist/web/assets/catalog-D0VEfOAV.js +1 -0
  46. package/dist/web/assets/client-DnwnUBQV.js +1 -0
  47. package/dist/web/assets/console-Cw7TzOSC.js +1 -0
  48. package/dist/web/assets/conversation-launcher-settings-Yc13viwr.js +1 -0
  49. package/dist/web/assets/{i18n-b14l2pQA.js → i18n-oJFlt8hW.js} +3 -3
  50. package/dist/web/assets/{identity-BWbPmZZf.js → identity-DSGS4CZc.js} +1 -1
  51. package/dist/web/assets/index-BfHCcUL2.css +1 -0
  52. package/dist/web/assets/index-DO10_fLc.js +5 -0
  53. package/dist/web/assets/judgment-types-DR931cZ7.js +1 -0
  54. package/dist/web/assets/model-BJ-UF4cs.js +1 -0
  55. package/dist/web/assets/{personal-profile-graph-BPTtMJTZ.js → personal-profile-graph-SOK0BR0w.js} +1 -1
  56. package/dist/web/assets/project-team-api-C_0qJk_B.js +1 -0
  57. package/dist/web/assets/shared-report-CiX64ZiD.js +1 -0
  58. package/dist/web/assets/{agent-work-summary-LeW-Al_S.js → task-evidence-DTiPCs7r.js} +1 -1
  59. package/dist/web/assets/useAgentRoster-B84mzZIg.js +1 -0
  60. package/dist/web/assets/{useApi-CROJJdhE-CEQAEuug.js → useApi-CROJJdhE-7gt0QyQ5.js} +1 -1
  61. package/dist/web/assets/vue-router-CCvPGaLD.js +1 -0
  62. package/dist/web/index.html +17 -12
  63. package/dist/web/licenses/ant-design-icons.txt +22 -0
  64. package/docs/agent-conversations-and-collaboration.md +3 -3
  65. package/docs/agent-roundtable.md +33 -0
  66. package/docs/client-connections.md +86 -0
  67. package/docs/employee-agents.md +3 -3
  68. package/docs/judgment-and-autonomy.md +33 -0
  69. package/docs/project-agent-memory.md +6 -6
  70. package/graph-provider/fuli_graph/ai_review.py +154 -0
  71. package/graph-provider/fuli_graph/common_knowledge.py +12 -0
  72. package/graph-provider/fuli_graph/default_project_lead.py +1 -1
  73. package/graph-provider/fuli_graph/graph_models.py +14 -0
  74. package/graph-provider/fuli_graph/graph_query.py +217 -0
  75. package/graph-provider/fuli_graph/knowledge_audit.py +6 -0
  76. package/graph-provider/fuli_graph/knowledge_batch_confirmation.py +6 -0
  77. package/graph-provider/fuli_graph/knowledge_management.py +23 -0
  78. package/graph-provider/fuli_graph/knowledge_review.py +383 -4
  79. package/graph-provider/fuli_graph/knowledge_review_models.py +61 -2
  80. package/graph-provider/fuli_graph/personal_global_preferences.py +5 -1
  81. package/graph-provider/fuli_graph/preference_conflicts.py +91 -1
  82. package/graph-provider/fuli_graph/project_agent_delegations.py +70 -2
  83. package/graph-provider/fuli_graph/project_agent_memory_routes.py +18 -1
  84. package/graph-provider/fuli_graph/project_agent_remote_origins.py +154 -0
  85. package/graph-provider/fuli_graph/project_agent_task_models.py +3 -1
  86. package/graph-provider/fuli_graph/project_knowledge.py +41 -0
  87. package/graph-provider/fuli_graph/runtime.py +15 -0
  88. package/graph-provider/fuli_graph/store_agent_verification.py +12 -5
  89. package/graph-provider/fuli_graph/store_knowledge.py +98 -58
  90. package/graph-provider/fuli_graph/store_project_agent_attention.py +1 -1
  91. package/graph-provider/fuli_graph/store_project_agent_context.py +3 -2
  92. package/graph-provider/fuli_graph/store_project_agent_coordination_policy.py +1 -1
  93. package/graph-provider/fuli_graph/store_project_agent_task_activity.py +26 -0
  94. package/graph-provider/fuli_graph/store_project_agent_task_reads.py +1 -1
  95. package/graph-provider/fuli_graph/store_project_agent_task_recruitment.py +5 -5
  96. package/graph-provider/fuli_graph/store_project_agent_task_staffing.py +4 -3
  97. package/graph-provider/fuli_graph/store_project_agent_tasks.py +2 -2
  98. package/graph-provider/fuli_graph/store_project_agents.py +6 -6
  99. package/graph-provider/fuli_graph/system_agents.py +31 -2
  100. package/graph-provider/fuli_graph/system_hr_identity.py +21 -7
  101. package/graph-provider/pyproject.toml +2 -1
  102. package/npm-shrinkwrap.json +280 -5
  103. package/package.json +7 -2
  104. package/skills/operating-fuli/SKILL.md +3 -3
  105. package/src/agent-peer/coordinator-http.js +117 -0
  106. package/src/agent-peer/coordinator-service.js +258 -0
  107. package/src/agent-peer/coordinator-store.js +95 -0
  108. package/src/agent-peer/device-identity.js +70 -0
  109. package/src/agent-peer/http.js +47 -0
  110. package/src/agent-peer/node-store.js +127 -0
  111. package/src/agent-peer/origin-proof.js +56 -0
  112. package/src/agent-peer/peer-port.js +140 -0
  113. package/src/agent-peer/peer-runtime.js +298 -0
  114. package/src/agent-peer/peer-transport.js +78 -0
  115. package/src/agent-peer/protocol.js +161 -0
  116. package/src/agent-peer/runner.js +251 -0
  117. package/src/agent-roundtable/http.js +17 -3
  118. package/src/agent-roundtable/service.js +272 -43
  119. package/src/agent-roundtable/tools.js +15 -1
  120. package/src/agent-roundtable/wake.js +12 -3
  121. package/src/agent-tools/graph-definitions.js +16 -5
  122. package/src/agent-tools/graph-handlers.js +8 -0
  123. package/src/agent-tools/interface-catalog.js +16 -1
  124. package/src/agent-tools/judgment-definitions.js +12 -0
  125. package/src/agent-tools/project-agent-definitions.js +1 -1
  126. package/src/agents/claude-code/wake.js +7 -0
  127. package/src/agents/codex/wake.js +7 -0
  128. package/src/agents/identity-receipt.js +1 -1
  129. package/src/cli/command-registry.js +3 -0
  130. package/src/cli/connect-command.js +28 -0
  131. package/src/cli/mcp-command.js +99 -0
  132. package/src/cli/peer-command.js +100 -0
  133. package/src/cli/setup-agent-selection.js +2 -2
  134. package/src/cli/setup-command.js +7 -2
  135. package/src/cli.js +16 -0
  136. package/src/employees/catalog/bole.json +2 -2
  137. package/src/employees/catalog/tonborg.json +20 -0
  138. package/src/employees/manifest.js +2 -2
  139. package/src/employees/recruitment.js +20 -1
  140. package/src/graphiti/agent-collaboration.js +8 -2
  141. package/src/graphiti/federated-application.js +9 -1
  142. package/src/graphiti/knowledge-review.js +7 -3
  143. package/src/graphiti/project-agent-control-plane.js +7 -0
  144. package/src/graphiti/project-agent-mapping.js +2 -0
  145. package/src/graphiti/project-agent-task-entry.js +4 -4
  146. package/src/graphiti/project-agent-team-context.js +16 -18
  147. package/src/graphiti/project-team-view.js +82 -0
  148. package/src/graphiti/provider-client.js +9 -0
  149. package/src/graphiti/task-entry-preferences.js +6 -1
  150. package/src/http/api-router.js +12 -1
  151. package/src/http/graph-api-router.js +7 -0
  152. package/src/judgment/acceptance.js +91 -0
  153. package/src/judgment/engine.js +168 -0
  154. package/src/judgment/http.js +24 -0
  155. package/src/judgment/model.js +24 -0
  156. package/src/judgment/routing.js +96 -0
  157. package/src/judgment/scheduler.js +25 -0
  158. package/src/judgment/service.js +66 -0
  159. package/src/judgment/store.js +123 -0
  160. package/src/mcp/session-id.js +1 -0
  161. package/src/mcp/tool-annotations.js +8 -5
  162. package/src/mcp-server.js +2 -100
  163. package/src/server.js +22 -3
  164. package/src/setup/client-connection.js +16 -0
  165. package/src/system/system-service.js +5 -0
  166. package/dist/web/assets/AboutPage-cW9hDghN.js +0 -1
  167. package/dist/web/assets/AgentConversations-CGznTWuV.js +0 -2
  168. package/dist/web/assets/AgentHand-DkyF4vgk.js +0 -1
  169. package/dist/web/assets/AgentName-C1mAVr-v.js +0 -1
  170. package/dist/web/assets/AgentProfilePage-pKUR0rfj.js +0 -1
  171. package/dist/web/assets/AgentsDirectoryPage-CUlbT2hD.js +0 -1
  172. package/dist/web/assets/AgentsDirectoryPage-yRWktiin.css +0 -1
  173. package/dist/web/assets/ConnectionsPage-CuDHIJXu.js +0 -1
  174. package/dist/web/assets/EmployeeRecruitDialog-D9QfGipd.js +0 -1
  175. package/dist/web/assets/EmployeeWorkbenchPage-D-c_Jm2z.css +0 -1
  176. package/dist/web/assets/EmployeeWorkbenchPage-DAtvGney.js +0 -3
  177. package/dist/web/assets/KnowledgeEditDialog-a-5rasG0.js +0 -1
  178. package/dist/web/assets/KnowledgeInspector-BGsqjNY2.js +0 -1
  179. package/dist/web/assets/KnowledgeOrganizerPage-BndgUsHS.js +0 -1
  180. package/dist/web/assets/KnowledgeOrganizerPage-DeL7Jp34.css +0 -1
  181. package/dist/web/assets/KnowledgePage-CQweO0YJ.js +0 -1
  182. package/dist/web/assets/KnowledgeWorkspace-BtUjKTLb.js +0 -4
  183. package/dist/web/assets/NotFoundPage-BmQTTqQn.js +0 -1
  184. package/dist/web/assets/OverviewPage-By-fUzSG.css +0 -1
  185. package/dist/web/assets/OverviewPage-C73HyTsM.js +0 -1
  186. package/dist/web/assets/PersonalProfilePage-BMuOrppx.js +0 -1
  187. package/dist/web/assets/PersonalProjectsPage-Cq9_kFvs.js +0 -1
  188. package/dist/web/assets/ProjectAgentFirstTask-CaHvOjkp.js +0 -1
  189. package/dist/web/assets/ProjectAgentsPage-WVcNdzVi.js +0 -12
  190. package/dist/web/assets/ProjectScopePicker-zAgiaZKL.js +0 -1
  191. package/dist/web/assets/PublicProjectsPage-DL093f65.js +0 -1
  192. package/dist/web/assets/ReviewPage-D_NIhPx8.js +0 -1
  193. package/dist/web/assets/RoundtablesPage-Cb0R4GaM.css +0 -1
  194. package/dist/web/assets/RoundtablesPage-CdVVyZE-.js +0 -1
  195. package/dist/web/assets/SettingsPage-BmYkLul4.css +0 -1
  196. package/dist/web/assets/SettingsPage-CbLaau8Q.js +0 -1
  197. package/dist/web/assets/SharedJefaReportPage-aygd6wvb.js +0 -1
  198. package/dist/web/assets/UiDialog-Q7fsJUoO.js +0 -1
  199. package/dist/web/assets/UiDisclosure-BS-muK5j.js +0 -1
  200. package/dist/web/assets/UiSegmented-CT28bxqw.js +0 -1
  201. package/dist/web/assets/UiSelect-DcGpPQQo.js +0 -1
  202. package/dist/web/assets/WritingTastePage-CYlwAFqV.js +0 -1
  203. package/dist/web/assets/agent-profile-BZj3ezWe.css +0 -1
  204. package/dist/web/assets/catalog-UDv854JN.js +0 -1
  205. package/dist/web/assets/client-DLS5FZBI.js +0 -1
  206. package/dist/web/assets/console-CqzwSo7a.js +0 -1
  207. package/dist/web/assets/conversation-launcher-settings-DRoIz42V.js +0 -1
  208. package/dist/web/assets/index-D1OHIz4c.js +0 -5
  209. package/dist/web/assets/index-kPB0Cf8Q.css +0 -1
  210. package/dist/web/assets/model-DupZy8Qb.js +0 -1
  211. package/dist/web/assets/shared-report-BYnLhmCZ.js +0 -1
  212. package/dist/web/assets/useAgentRoster-ep7SpKv1.js +0 -1
  213. package/dist/web/assets/vue-router-B6ZjK-Br.js +0 -1
@@ -4,6 +4,7 @@ from .graph_projection import (
4
4
  management_projection,
5
5
  personal_project_projection,
6
6
  )
7
+ from .ai_review import effective_ai_review
7
8
  from .graph_models import GraphEdge, GraphNode, GraphResult
8
9
  from .models import (
9
10
  ConfirmationBasis,
@@ -82,6 +83,7 @@ async def read_graph(
82
83
  assignments = await _read_assignments(store, space_id, item_ids)
83
84
  references = await read_project_references(store, space_id, item_ids)
84
85
  conflicts = await read_knowledge_conflicts(store, space_id, item_ids)
86
+ preference_conflicts = await _read_preference_conflicts(store, space_id, item_ids)
85
87
  audits = await read_knowledge_audits(store, space_id, item_ids)
86
88
  knowledge_nodes = [
87
89
  _graph_node(
@@ -92,6 +94,9 @@ async def read_graph(
92
94
  references,
93
95
  conflicts,
94
96
  audits,
97
+ preference_conflicts=preference_conflicts,
98
+ review_scope='project' if project else 'all',
99
+ personal_project_id=personal_project_id,
95
100
  )
96
101
  for record in node_records
97
102
  ]
@@ -104,6 +109,9 @@ async def read_graph(
104
109
  references,
105
110
  conflicts,
106
111
  audits,
112
+ preference_conflicts=preference_conflicts,
113
+ review_scope='project' if project else 'all',
114
+ personal_project_id=personal_project_id,
107
115
  )
108
116
  for record in edge_records
109
117
  ]
@@ -229,6 +237,13 @@ def _node_query(project_scoped: bool, paginated: bool = False) -> str:
229
237
  node.fuli_last_human_changed_at AS last_human_changed_at,
230
238
  node.fuli_last_agent_viewed_at AS last_agent_viewed_at,
231
239
  node.fuli_last_agent_reviewed_at AS last_agent_reviewed_at,
240
+ node.fuli_ai_review_evidence_token AS ai_review_evidence_token,
241
+ node.fuli_ai_assessment_json AS ai_assessment_json,
242
+ node.fuli_ai_review_scope AS ai_review_scope,
243
+ node.fuli_ai_review_project_id AS ai_review_project_id,
244
+ node.fuli_ai_reviewed_at AS ai_reviewed_at,
245
+ node.fuli_ai_review_id AS ai_review_id,
246
+ node.fuli_ai_review_decision_id AS ai_review_decision_id,
232
247
  coalesce(node.fuli_utility_score, 0.0) AS utility_score,
233
248
  coalesce(node.fuli_confidence_score, 0.5) AS confidence_score,
234
249
  coalesce(node.fuli_qualified_use_count, 0)
@@ -345,6 +360,13 @@ def _edge_query(project_scoped: bool, paginated: bool = False) -> str:
345
360
  edge.fuli_last_human_changed_at AS last_human_changed_at,
346
361
  edge.fuli_last_agent_viewed_at AS last_agent_viewed_at,
347
362
  edge.fuli_last_agent_reviewed_at AS last_agent_reviewed_at,
363
+ edge.fuli_ai_review_evidence_token AS ai_review_evidence_token,
364
+ edge.fuli_ai_assessment_json AS ai_assessment_json,
365
+ edge.fuli_ai_review_scope AS ai_review_scope,
366
+ edge.fuli_ai_review_project_id AS ai_review_project_id,
367
+ edge.fuli_ai_reviewed_at AS ai_reviewed_at,
368
+ edge.fuli_ai_review_id AS ai_review_id,
369
+ edge.fuli_ai_review_decision_id AS ai_review_decision_id,
348
370
  coalesce(edge.fuli_utility_score, 0.0) AS utility_score,
349
371
  coalesce(edge.fuli_confidence_score, 0.5) AS confidence_score,
350
372
  coalesce(edge.fuli_qualified_use_count, 0)
@@ -462,6 +484,31 @@ async def _read_assignments(store, space_id: str, item_ids: list[str]):
462
484
  return result
463
485
 
464
486
 
487
+ async def _read_preference_conflicts(store, space_id: str, item_ids: list[str]):
488
+ if not item_ids:
489
+ return {}
490
+ records, _, _ = await store.runtime.driver.execute_query(
491
+ '''
492
+ MATCH (:FuliSpace {id: $space_id, kind: 'personal'})-
493
+ [:HAS_PREFERENCE_CONFLICT]->
494
+ (conflict:FuliPreferenceConflict {status: 'ai_pending'})
495
+ WHERE conflict.left_item_id IN $item_ids
496
+ OR conflict.right_item_id IN $item_ids
497
+ RETURN conflict
498
+ ''',
499
+ space_id=space_id,
500
+ item_ids=item_ids,
501
+ routing_='r',
502
+ )
503
+ result = {}
504
+ for record in records:
505
+ value = dict(record['conflict'])
506
+ for item_id in (value.get('left_item_id'), value.get('right_item_id')):
507
+ if item_id in item_ids:
508
+ result.setdefault(item_id, []).append(value)
509
+ return result
510
+
511
+
465
512
  def _graph_node(
466
513
  record,
467
514
  evidence,
@@ -470,12 +517,27 @@ def _graph_node(
470
517
  references,
471
518
  conflicts,
472
519
  audits=None,
520
+ *,
521
+ preference_conflicts=None,
522
+ review_scope: str = 'all',
523
+ personal_project_id: str | None = None,
473
524
  ) -> GraphNode:
474
525
  episode_ids = record.get('episodes') or []
475
526
  status, basis, confidence, confirmation_explicit = _confirmation_projection(
476
527
  record
477
528
  )
478
529
  attributes = json_object(record.get('attributes_json'))
530
+ ai_assessment = _effective_ai_projection(
531
+ record,
532
+ item_kind='entity',
533
+ evidence=evidence,
534
+ assignments=assignments,
535
+ references=references,
536
+ conflicts=conflicts,
537
+ preference_conflicts=preference_conflicts or {},
538
+ review_scope=review_scope,
539
+ personal_project_id=personal_project_id,
540
+ )
479
541
  return GraphNode(
480
542
  id=record['id'],
481
543
  name=record['name'],
@@ -516,6 +578,14 @@ def _graph_node(
516
578
  last_agent_reviewed_at=_native_datetime(
517
579
  record.get('last_agent_reviewed_at')
518
580
  ),
581
+ ai_review_evidence_token=record.get('ai_review_evidence_token'),
582
+ ai_assessment=ai_assessment,
583
+ ai_review_scope=record.get('ai_review_scope'),
584
+ ai_review_project_id=record.get('ai_review_project_id'),
585
+ ai_reviewed_at=_native_datetime(record.get('ai_reviewed_at'))
586
+ if ai_assessment is not None else None,
587
+ ai_review_id=record.get('ai_review_id'),
588
+ ai_review_decision_id=record.get('ai_review_decision_id'),
519
589
  utility_score=float(record.get('utility_score') or 0),
520
590
  confidence_score=confidence,
521
591
  qualified_use_count=int(record.get('qualified_use_count') or 0),
@@ -550,12 +620,27 @@ def _graph_edge(
550
620
  references,
551
621
  conflicts,
552
622
  audits=None,
623
+ *,
624
+ preference_conflicts=None,
625
+ review_scope: str = 'all',
626
+ personal_project_id: str | None = None,
553
627
  ) -> GraphEdge:
554
628
  episode_ids = record.get('episodes') or []
555
629
  status, basis, confidence, confirmation_explicit = _confirmation_projection(
556
630
  record
557
631
  )
558
632
  attributes = json_object(record.get('attributes_json'))
633
+ ai_assessment = _effective_ai_projection(
634
+ record,
635
+ item_kind='relationship',
636
+ evidence=evidence,
637
+ assignments=assignments,
638
+ references=references,
639
+ conflicts=conflicts,
640
+ preference_conflicts=preference_conflicts or {},
641
+ review_scope=review_scope,
642
+ personal_project_id=personal_project_id,
643
+ )
559
644
  return GraphEdge(
560
645
  id=record['id'],
561
646
  source=record['source'],
@@ -598,6 +683,14 @@ def _graph_edge(
598
683
  last_agent_reviewed_at=_native_datetime(
599
684
  record.get('last_agent_reviewed_at')
600
685
  ),
686
+ ai_review_evidence_token=record.get('ai_review_evidence_token'),
687
+ ai_assessment=ai_assessment,
688
+ ai_review_scope=record.get('ai_review_scope'),
689
+ ai_review_project_id=record.get('ai_review_project_id'),
690
+ ai_reviewed_at=_native_datetime(record.get('ai_reviewed_at'))
691
+ if ai_assessment is not None else None,
692
+ ai_review_id=record.get('ai_review_id'),
693
+ ai_review_decision_id=record.get('ai_review_decision_id'),
601
694
  utility_score=float(record.get('utility_score') or 0),
602
695
  confidence_score=confidence,
603
696
  qualified_use_count=int(record.get('qualified_use_count') or 0),
@@ -658,3 +751,127 @@ def _confirmation_projection(record) -> tuple[
658
751
  and basis is not None
659
752
  )
660
753
  return status, basis, confidence, explicit
754
+
755
+
756
+ def _field_value(value, name: str, default=None):
757
+ if isinstance(value, dict):
758
+ return value.get(name, default)
759
+ return getattr(value, name, default)
760
+
761
+
762
+ def _effective_ai_projection(
763
+ record,
764
+ *,
765
+ item_kind: str,
766
+ evidence,
767
+ assignments,
768
+ references,
769
+ conflicts,
770
+ preference_conflicts,
771
+ review_scope: str,
772
+ personal_project_id: str | None,
773
+ ) -> dict | None:
774
+ # The display scope may be broader than the scope used to produce the
775
+ # assessment. Validate against the persisted review scope so a
776
+ # project-bound token remains effective in an authorized all-space view.
777
+ stored_review_scope = record.get('ai_review_scope')
778
+ stored_review_project_id = record.get('ai_review_project_id')
779
+ validation_scope = stored_review_scope or review_scope
780
+ validation_project_id = (
781
+ stored_review_project_id
782
+ if stored_review_project_id is not None
783
+ else personal_project_id
784
+ )
785
+ if (
786
+ stored_review_project_id is not None
787
+ and review_scope in {'project', 'preferences_project'}
788
+ and personal_project_id != stored_review_project_id
789
+ ):
790
+ return None
791
+ project_scoped_review = validation_scope in {'project', 'preferences_project'}
792
+ item_id = record['id']
793
+ project_ids = {
794
+ str(project_id)
795
+ for project_id in (record.get('project_ids') or [])
796
+ if project_id
797
+ }
798
+ preference_project_id = record.get('preference_project_id')
799
+ if preference_project_id:
800
+ project_ids.add(str(preference_project_id))
801
+ for assignment in assignments.get(item_id, []):
802
+ project_id = _field_value(assignment, 'project_id')
803
+ if project_id:
804
+ project_ids.add(str(project_id))
805
+ for reference in references.get(item_id, []):
806
+ project_id = _field_value(reference, 'project_id')
807
+ if project_id and _field_value(reference, 'status') == 'active':
808
+ project_ids.add(str(project_id))
809
+ for episode_id in record.get('episodes') or []:
810
+ project_id = _field_value(evidence.get(episode_id), 'personal_project_id')
811
+ if project_id:
812
+ project_ids.add(str(project_id))
813
+
814
+ scoped_conflict = False
815
+ for conflict in conflicts.get(item_id, []):
816
+ if _field_value(conflict, 'status') != 'pending':
817
+ continue
818
+ if not project_scoped_review or validation_project_id is None:
819
+ scoped_conflict = True
820
+ break
821
+ target_project_id = _field_value(conflict, 'target_project_id')
822
+ source_project_id = _field_value(conflict, 'source_project_id')
823
+ if validation_project_id in {target_project_id, source_project_id}:
824
+ scoped_conflict = True
825
+ break
826
+ if not scoped_conflict:
827
+ for conflict in preference_conflicts.get(item_id, []):
828
+ if _field_value(conflict, 'status') != 'ai_pending':
829
+ continue
830
+ if not project_scoped_review or validation_project_id is None:
831
+ scoped_conflict = True
832
+ break
833
+ if _field_value(conflict, 'preference_project_id') == validation_project_id:
834
+ scoped_conflict = True
835
+ break
836
+
837
+ item = {
838
+ 'item_kind': item_kind,
839
+ 'item_id': item_id,
840
+ 'title': (
841
+ record.get('name') or item_id
842
+ if item_kind == 'entity'
843
+ else record.get('type') or 'RELATES_TO'
844
+ ),
845
+ 'content': (
846
+ record.get('summary') or ''
847
+ if item_kind == 'entity'
848
+ else record.get('fact') or ''
849
+ ),
850
+ 'valid_at': record.get('valid_at'),
851
+ 'invalid_at': record.get('invalid_at'),
852
+ 'confirmation_status': record.get('confirmation_status'),
853
+ 'confirmation_basis_json': record.get('confirmation_basis_json'),
854
+ 'profile_aspect': record.get('profile_aspect'),
855
+ 'preference_scope': record.get('preference_scope'),
856
+ 'preference_project_id': preference_project_id,
857
+ 'preference_agent_id': record.get('preference_agent_id'),
858
+ 'inheritance_mode': record.get('inheritance_mode') or 'local_only',
859
+ 'current_quadrant': record.get('current_quadrant') or 'known_known',
860
+ 'utility_score': record.get('utility_score'),
861
+ 'confidence_score': record.get('confidence_score'),
862
+ 'qualified_use_count': record.get('qualified_use_count'),
863
+ 'distinct_task_count': record.get('distinct_task_count'),
864
+ 'negative_evidence_count': record.get('negative_evidence_count'),
865
+ 'requires_attention': record.get('requires_attention') is True,
866
+ 'human_change_version': record.get('human_change_version') or 0,
867
+ 'attributes_json': record.get('attributes_json'),
868
+ 'project_ids': sorted(project_ids),
869
+ }
870
+ return effective_ai_review(
871
+ item,
872
+ stored_token=record.get('ai_review_evidence_token'),
873
+ stored_assessment=record.get('ai_assessment_json'),
874
+ scope=validation_scope,
875
+ personal_project_id=validation_project_id,
876
+ has_conflict=scoped_conflict,
877
+ )
@@ -630,6 +630,8 @@ def _human_change_query(item_kind: str) -> str:
630
630
  return f'''
631
631
  MATCH (space:FuliSpace {{id: $space_id, kind: 'personal'}})
632
632
  {_item_match(item_kind)}
633
+ SET item.fuli_ai_review_lock_version =
634
+ coalesce(item.fuli_ai_review_lock_version, 0) + 1
633
635
  SET item.fuli_human_edited = true,
634
636
  item.fuli_human_change_version =
635
637
  coalesce(item.fuli_human_change_version, 0) + 1,
@@ -797,6 +799,8 @@ def _knowledge_feedback_query(item_kind: str) -> str:
797
799
  MATCH (space:FuliSpace {{id: $space_id, kind: 'personal'}})
798
800
  {_item_match(item_kind)}
799
801
  WHERE coalesce(item.fuli_usage_generation, 1) = $usage_generation
802
+ SET item.fuli_ai_review_lock_version =
803
+ coalesce(item.fuli_ai_review_lock_version, 0) + 1
800
804
  MERGE (audit:FuliKnowledgeAudit {{id: $audit_id}})
801
805
  ON CREATE SET audit.space_id = $space_id,
802
806
  audit.item_id = $item_id,
@@ -850,6 +854,8 @@ def _usage_score_update_query(item_kind: str) -> str:
850
854
  return f'''
851
855
  {_item_match(item_kind)}
852
856
  WHERE coalesce(item.fuli_usage_generation, 1) = $usage_generation
857
+ SET item.fuli_ai_review_lock_version =
858
+ coalesce(item.fuli_ai_review_lock_version, 0) + 1
853
859
  SET item.fuli_utility_score = $utility_score,
854
860
  item.fuli_confidence_score = $confidence_score,
855
861
  item.fuli_qualified_use_count = $qualified_use_count,
@@ -201,6 +201,9 @@ CALL {
201
201
  WITH space, valid_entities
202
202
  UNWIND valid_entities AS valid
203
203
  WITH space, valid.item AS item, valid.row AS row
204
+ ORDER BY 'entity:' + row.item_id
205
+ SET item.fuli_ai_review_lock_version =
206
+ coalesce(item.fuli_ai_review_lock_version, 0) + 1
204
207
  SET item.fuli_epistemic_status = 'confirmed',
205
208
  item.fuli_confirmation_status = 'confirmed',
206
209
  item.fuli_confirmation_basis_json = row.confirmation_basis_json
@@ -223,6 +226,9 @@ CALL {
223
226
  WITH space, valid_relationships
224
227
  UNWIND valid_relationships AS valid
225
228
  WITH space, valid.item AS item, valid.row AS row
229
+ ORDER BY 'relationship:' + row.item_id
230
+ SET item.fuli_ai_review_lock_version =
231
+ coalesce(item.fuli_ai_review_lock_version, 0) + 1
226
232
  SET item.fuli_epistemic_status = 'confirmed',
227
233
  item.fuli_confirmation_status = 'confirmed',
228
234
  item.fuli_confirmation_basis_json = row.confirmation_basis_json
@@ -211,11 +211,25 @@ async def reassign_knowledge_item(
211
211
  assignment_id = existing.id if existing else _stable_uuid(
212
212
  space['id'], 'knowledge-assignment', request.item_kind, item_id
213
213
  )
214
+ item_match = (
215
+ 'MATCH (item:Entity {uuid: $item_id, group_id: $group_id})'
216
+ if request.item_kind == 'entity'
217
+ else (
218
+ 'MATCH ()-[item:RELATES_TO '
219
+ '{uuid: $item_id, group_id: $group_id}]->()'
220
+ )
221
+ )
214
222
  records, _, _ = await store.runtime.driver.execute_query(
215
223
  '''
216
224
  MATCH (space:FuliSpace {id: $space_id, kind: 'personal'})
217
225
  MATCH (space)-[:CONTAINS_PROJECT]->
218
226
  (target:FuliPersonalProject {project_id: $target_project_id})
227
+ '''
228
+ + item_match
229
+ + '''
230
+ SET item.fuli_ai_review_lock_version =
231
+ coalesce(item.fuli_ai_review_lock_version, 0) + 1
232
+ WITH space, target
219
233
  MERGE (assignment:FuliKnowledgeAssignment {
220
234
  space_id: $space_id,
221
235
  item_kind: $item_kind,
@@ -236,6 +250,7 @@ async def reassign_knowledge_item(
236
250
  RETURN assignment
237
251
  ''',
238
252
  space_id=space['id'],
253
+ group_id=space['group_id'],
239
254
  target_project_id=request.target_project_id,
240
255
  item_kind=request.item_kind,
241
256
  item_id=item_id,
@@ -712,6 +727,8 @@ async def _update_item(store, space, item_id, item_kind, value, embedding):
712
727
  if item_kind == 'entity':
713
728
  query = '''
714
729
  MATCH (item:Entity {uuid: $item_id, group_id: $group_id})
730
+ SET item.fuli_ai_review_lock_version =
731
+ coalesce(item.fuli_ai_review_lock_version, 0) + 1
715
732
  SET item.name = $name,
716
733
  item.summary = $summary,
717
734
  item.fuli_invalid_at = $invalid_at,
@@ -744,6 +761,8 @@ async def _update_item(store, space, item_id, item_kind, value, embedding):
744
761
  else:
745
762
  query = '''
746
763
  MATCH ()-[item:RELATES_TO {uuid: $item_id, group_id: $group_id}]->()
764
+ SET item.fuli_ai_review_lock_version =
765
+ coalesce(item.fuli_ai_review_lock_version, 0) + 1
747
766
  SET item.fact = $fact,
748
767
  item.invalid_at = $invalid_at,
749
768
  item.fuli_origin_quadrant = $origin_quadrant,
@@ -844,12 +863,16 @@ async def _update_preference_scope(
844
863
  if item_kind == 'entity':
845
864
  query = '''
846
865
  MATCH (item:Entity {uuid: $item_id, group_id: $group_id})
866
+ SET item.fuli_ai_review_lock_version =
867
+ coalesce(item.fuli_ai_review_lock_version, 0) + 1
847
868
  SET item.fuli_preference_scope = $scope,
848
869
  item.fuli_preference_project_id = $project_id
849
870
  '''
850
871
  else:
851
872
  query = '''
852
873
  MATCH ()-[item:RELATES_TO {uuid: $item_id, group_id: $group_id}]->()
874
+ SET item.fuli_ai_review_lock_version =
875
+ coalesce(item.fuli_ai_review_lock_version, 0) + 1
853
876
  SET item.fuli_preference_scope = $scope,
854
877
  item.fuli_preference_project_id = $project_id
855
878
  '''