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
@@ -578,11 +578,25 @@ async def _upsert_reference(
578
578
  space['id'], 'knowledge-reference', item_kind, item_id, project_id
579
579
  )
580
580
  changed_at = datetime.now(timezone.utc)
581
+ item_match = (
582
+ 'MATCH (item:Entity {uuid: $item_id, group_id: $group_id})'
583
+ if item_kind == 'entity'
584
+ else (
585
+ 'MATCH ()-[item:RELATES_TO '
586
+ '{uuid: $item_id, group_id: $group_id}]->()'
587
+ )
588
+ )
581
589
  records, _, _ = await store.runtime.driver.execute_query(
582
590
  '''
583
591
  MATCH (space:FuliSpace {id: $space_id, kind: 'personal'})
584
592
  MATCH (space)-[:CONTAINS_PROJECT]->
585
593
  (target:FuliPersonalProject {project_id: $project_id})
594
+ '''
595
+ + item_match
596
+ + '''
597
+ SET item.fuli_ai_review_lock_version =
598
+ coalesce(item.fuli_ai_review_lock_version, 0) + 1
599
+ WITH space, target
586
600
  MERGE (reference:FuliKnowledgeProjectReference {
587
601
  space_id: $space_id,
588
602
  item_kind: $item_kind,
@@ -604,6 +618,7 @@ async def _upsert_reference(
604
618
  space_id=space['id'],
605
619
  item_kind=item_kind,
606
620
  item_id=item_id,
621
+ group_id=space['group_id'],
607
622
  project_id=project_id,
608
623
  reference_id=reference_id,
609
624
  source_project_id=source_project_id,
@@ -635,6 +650,31 @@ async def _upsert_conflict(
635
650
  records, _, _ = await store.runtime.driver.execute_query(
636
651
  '''
637
652
  MATCH (space:FuliSpace {id: $space_id, kind: 'personal'})
653
+ MATCH (item:Entity {uuid: $item_id, group_id: $group_id})
654
+ MATCH (target_item:Entity {
655
+ uuid: $target_item_id,
656
+ group_id: $group_id
657
+ })
658
+ WITH space,
659
+ CASE
660
+ WHEN item.uuid = target_item.uuid
661
+ THEN [{kind: 'entity', item: item}]
662
+ WHEN item.uuid < target_item.uuid
663
+ THEN [
664
+ {kind: 'entity', item: item},
665
+ {kind: 'entity', item: target_item}
666
+ ]
667
+ ELSE [
668
+ {kind: 'entity', item: target_item},
669
+ {kind: 'entity', item: item}
670
+ ]
671
+ END AS lock_items
672
+ UNWIND lock_items AS lock_item
673
+ WITH space, lock_item
674
+ ORDER BY lock_item.kind + ':' + lock_item.item.uuid
675
+ SET lock_item.item.fuli_ai_review_lock_version =
676
+ coalesce(lock_item.item.fuli_ai_review_lock_version, 0) + 1
677
+ WITH space, collect(lock_item) AS _locked_items
638
678
  MERGE (conflict:FuliKnowledgeConflict {id: $conflict_id})
639
679
  ON CREATE SET conflict.created_at = $changed_at
640
680
  SET conflict.space_id = $space_id,
@@ -651,6 +691,7 @@ async def _upsert_conflict(
651
691
  RETURN conflict
652
692
  ''',
653
693
  space_id=space['id'],
694
+ group_id=space['group_id'],
654
695
  conflict_id=conflict_id,
655
696
  item_id=item_id,
656
697
  target_item_id=target_item_id,
@@ -331,6 +331,13 @@ class GraphitiRuntime:
331
331
  'FOR (n:FuliProjectAgentExecutorOutcomeReset) REQUIRE n.id IS UNIQUE',
332
332
  'CREATE CONSTRAINT fuli_project_agent_executor_observation_id IF NOT EXISTS '
333
333
  'FOR (n:FuliProjectAgentExecutorObservation) REQUIRE n.id IS UNIQUE',
334
+ 'CREATE CONSTRAINT fuli_remote_origin_key_space IF NOT EXISTS '
335
+ 'FOR (n:FuliRemoteOriginKey) REQUIRE n.space_id IS UNIQUE',
336
+ 'CREATE CONSTRAINT fuli_remote_agent_origin_message IF NOT EXISTS '
337
+ 'FOR (n:FuliRemoteAgentOrigin) REQUIRE (n.space_id, n.message_id) IS UNIQUE',
338
+ 'CREATE CONSTRAINT fuli_remote_delegation_attempt IF NOT EXISTS '
339
+ 'FOR (n:FuliRemoteDelegationAttempt) '
340
+ 'REQUIRE (n.space_id, n.origin_node, n.origin_message_id, n.origin_attempt_id) IS UNIQUE',
334
341
  ):
335
342
  await self.driver.execute_query(query)
336
343
  for query in (
@@ -656,6 +663,10 @@ class GraphitiRuntime:
656
663
  OR item.fuli_usage_generation IS NULL
657
664
  OR item.fuli_inheritance_mode IS NULL
658
665
  OR item.fuli_inherited_project_ids IS NULL
666
+ WITH item
667
+ ORDER BY 'entity:' + item.uuid
668
+ SET item.fuli_ai_review_lock_version =
669
+ coalesce(item.fuli_ai_review_lock_version, 0) + 1
659
670
  SET item.fuli_utility_score =
660
671
  coalesce(item.fuli_utility_score, 0.0),
661
672
  item.fuli_confidence_score = coalesce(
@@ -688,6 +699,10 @@ class GraphitiRuntime:
688
699
  OR item.fuli_usage_generation IS NULL
689
700
  OR item.fuli_inheritance_mode IS NULL
690
701
  OR item.fuli_inherited_project_ids IS NULL
702
+ WITH item
703
+ ORDER BY 'relationship:' + item.uuid
704
+ SET item.fuli_ai_review_lock_version =
705
+ coalesce(item.fuli_ai_review_lock_version, 0) + 1
691
706
  SET item.fuli_utility_score =
692
707
  coalesce(item.fuli_utility_score, 0.0),
693
708
  item.fuli_confidence_score = coalesce(
@@ -43,7 +43,7 @@ class StoreAgentVerification:
43
43
  task = await self._quality_task(actor, request)
44
44
  rows, _, _ = await self.runtime.driver.execute_query(
45
45
  'MATCH (t:FuliProjectAgentTask {task_id:$task, personal_space_id:$space})-[:HAS_VERIFICATION]->(v:FuliAgentVerification) '
46
- 'RETURN v.record_json AS record_json ORDER BY v.sequence', task=request.task_id, space=request.personal_space_id)
46
+ 'RETURN v.record_json AS record_json, v.run_id AS run_id ORDER BY v.sequence', task=request.task_id, space=request.personal_space_id)
47
47
  models = await self._quality_models(actor, request, task)
48
48
  result = evaluate_quality_policy(complexity=task.get('complexity') or 'standard',
49
49
  current_strategy=json.loads(task.get('effective_model_strategy_json') or '{"mode":"adaptive"}'),
@@ -53,7 +53,12 @@ class StoreAgentVerification:
53
53
  if selected:
54
54
  selected['executor_id'] = next(model['executor_id'] for model in models
55
55
  if all(model.get(key) == selected[key] for key in ('provider', 'model', 'capability_tier', 'cost_rank')))
56
- return {**result, 'evidence_authority': 'host_reported_test_evidence',
56
+ verified = next((row for row in rows if json.loads(row['record_json']).get('attempt_id') == result['verified_attempt_id']), None)
57
+ attempt = json.loads(verified['record_json']) if verified else None
58
+ if attempt:
59
+ attempt = {key: attempt.get(key) for key in ('attempt_id', 'artifact_revision', 'outcome', 'evidence_refs', 'verifier_id', 'executor_id', 'provider', 'model')}
60
+ attempt['run_id'] = verified.get('run_id')
61
+ return {**result, 'verified_attempt': attempt, 'evidence_authority': 'host_reported_test_evidence',
57
62
  'execution_started': False, 'guidance': 'Only complete the task after passed for the current artifact revision. Blocked means no authorized preflighted model proves the required capability tier. Model selection does not prove execution.'}
58
63
 
59
64
  async def record_agent_verification(self, actor, request):
@@ -92,7 +97,8 @@ class StoreAgentVerification:
92
97
  raise HTTPException(409, 'An execution run cannot count as two verification attempts')
93
98
  identifier = stable_uuid('verification', request.personal_space_id, request.task_id, request.attempt_id)
94
99
  record = dict(attempt_id=request.attempt_id, outcome=request.outcome, artifact_revision=request.artifact_revision,
95
- evidence_refs=request.evidence_refs, capability_tier=observed['capability_tier'], verifier_id=context['project_agent_id'])
100
+ evidence_refs=request.evidence_refs, capability_tier=observed['capability_tier'], verifier_id=context['project_agent_id'],
101
+ executor_id=request.executor_id, provider=request.provider, model=request.model)
96
102
  fingerprint = store._payload_hash(request)
97
103
  rows, _, _ = await store.runtime.driver.execute_query(
98
104
  'MATCH (t:FuliProjectAgentTask {task_id:$task, personal_space_id:$space}) '
@@ -106,6 +112,7 @@ class StoreAgentVerification:
106
112
  result = await store.query_agent_quality(actor, request)
107
113
  await store.runtime.driver.execute_query(
108
114
  'MATCH (t:FuliProjectAgentTask {task_id:$task, personal_space_id:$space}) '
109
- 'SET t.quality_gate=$status, t.verified_artifact_revision=$revision',
110
- task=request.task_id, space=request.personal_space_id, status=result['status'], revision=request.artifact_revision)
115
+ 'SET t.quality_gate=$status, t.verified_artifact_revision=$revision, t.verified_attempt_id=$attempt',
116
+ task=request.task_id, space=request.personal_space_id, status=result['status'], revision=request.artifact_revision,
117
+ attempt=result.get('verified_attempt_id'))
111
118
  return result
@@ -325,6 +325,8 @@ class StoreKnowledge:
325
325
  await self.runtime.driver.execute_query(
326
326
  '''
327
327
  UNWIND $entities AS row
328
+ WITH row
329
+ ORDER BY 'entity:' + row.uuid
328
330
  MERGE (entity:Entity {uuid: row.uuid})
329
331
  ON CREATE SET entity.group_id = $group_id,
330
332
  entity.name = row.name,
@@ -365,6 +367,8 @@ class StoreKnowledge:
365
367
  entity.fuli_preference_project_id = row.preference_project_id,
366
368
  entity.fuli_preference_agent_id = row.preference_agent_id,
367
369
  entity.fuli_attributes_json = row.attributes_json
370
+ SET entity.fuli_ai_review_lock_version =
371
+ coalesce(entity.fuli_ai_review_lock_version, 0) + 1
368
372
  WITH count(entity) AS entity_count
369
373
  CREATE (episode:Episodic {
370
374
  uuid: $episode_id,
@@ -392,12 +396,95 @@ class StoreKnowledge:
392
396
  })
393
397
  WITH episode
394
398
  UNWIND $entities AS row
399
+ WITH episode, row
400
+ ORDER BY 'entity:' + row.uuid
395
401
  MATCH (entity:Entity {uuid: row.uuid})
396
402
  MERGE (episode)-[mention:MENTIONS {uuid: row.mention_uuid}]->(entity)
397
403
  SET mention.group_id = $group_id,
398
404
  mention.created_at = $created_at
399
405
  WITH episode
406
+ CALL {
407
+ WITH episode
408
+ OPTIONAL MATCH ()-[old:RELATES_TO {group_id: $group_id}]->()
409
+ WITH collect(old) AS candidate_olds
410
+ UNWIND candidate_olds AS old
411
+ WITH old
412
+ WHERE old IS NOT NULL
413
+ AND old.invalid_at IS NULL
414
+ AND NOT (old.uuid IN $relationship_ids)
415
+ AND (
416
+ EXISTS {
417
+ MATCH (:FuliSpace {id: $space_id})-
418
+ [:HAS_KNOWLEDGE_ASSIGNMENT]->
419
+ (assignment:FuliKnowledgeAssignment {
420
+ item_kind: 'relationship',
421
+ item_id: old.uuid
422
+ })
423
+ WHERE assignment.project_id = $personal_project_id
424
+ }
425
+ OR (
426
+ NOT EXISTS {
427
+ MATCH (:FuliSpace {id: $space_id})-
428
+ [:HAS_KNOWLEDGE_ASSIGNMENT]->
429
+ (:FuliKnowledgeAssignment {
430
+ item_kind: 'relationship',
431
+ item_id: old.uuid
432
+ })
433
+ }
434
+ AND EXISTS {
435
+ MATCH (old_episode:Episodic {group_id: $group_id})
436
+ WHERE old_episode.uuid IN coalesce(old.episodes, [])
437
+ AND (
438
+ old_episode.fuli_personal_project_id =
439
+ $personal_project_id
440
+ OR (
441
+ old_episode.fuli_personal_project_id IS NULL
442
+ AND $personal_project_id IS NULL
443
+ )
444
+ )
445
+ }
446
+ )
447
+ )
448
+ AND EXISTS {
449
+ MATCH (agent_episode:Episodic {group_id: $group_id})
450
+ WHERE agent_episode.uuid IN coalesce(old.episodes, [])
451
+ AND (
452
+ agent_episode.fuli_project_agent_id = $project_agent_id
453
+ OR (
454
+ agent_episode.fuli_project_agent_id IS NULL
455
+ AND $project_agent_id IS NULL
456
+ )
457
+ )
458
+ }
459
+ AND any(
460
+ replacement IN $superseded_relationships
461
+ WHERE replacement.key = old.fuli_key
462
+ )
463
+ WITH collect(old) AS superseded_edges
464
+ OPTIONAL MATCH ()-[existing:RELATES_TO {
465
+ group_id: $group_id
466
+ }]->()
467
+ WITH superseded_edges, collect(existing) AS candidate_existing
468
+ RETURN superseded_edges,
469
+ [edge IN candidate_existing
470
+ WHERE edge IS NOT NULL
471
+ AND edge.uuid IN $relationship_ids]
472
+ AS existing_edges
473
+ }
474
+ CALL {
475
+ WITH superseded_edges, existing_edges
476
+ UNWIND superseded_edges + existing_edges AS lock_edge
477
+ WITH lock_edge
478
+ WHERE lock_edge IS NOT NULL
479
+ ORDER BY 'relationship:' + lock_edge.uuid
480
+ SET lock_edge.fuli_ai_review_lock_version =
481
+ coalesce(lock_edge.fuli_ai_review_lock_version, 0) + 1
482
+ RETURN count(*) AS _locked_relationship_count
483
+ }
484
+ WITH episode, superseded_edges
400
485
  UNWIND $relationships AS row
486
+ WITH episode, superseded_edges, row
487
+ ORDER BY 'relationship:' + row.uuid
401
488
  MATCH (source:Entity {uuid: row.source_uuid})
402
489
  MATCH (target:Entity {uuid: row.target_uuid})
403
490
  MERGE (source)-[edge:RELATES_TO {uuid: row.uuid}]->(target)
@@ -435,73 +522,26 @@ class StoreKnowledge:
435
522
  edge.fuli_workflow_confirmation_authority =
436
523
  row.workflow_confirmation_authority,
437
524
  edge.fuli_workflow_session_authority =
438
- row.workflow_session_authority
525
+ row.workflow_session_authority,
526
+ edge.fuli_ai_review_lock_version = 1
439
527
  SET edge.episodes =
440
528
  CASE WHEN $episode_id IN coalesce(edge.episodes, [])
441
529
  THEN edge.episodes
442
530
  ELSE coalesce(edge.episodes, []) + $episode_id END
443
- WITH count(edge) AS edge_count
444
- OPTIONAL MATCH ()-[old:RELATES_TO {group_id: $group_id}]->()
445
- WHERE old.invalid_at IS NULL
446
- AND NOT (old.uuid IN $relationship_ids)
447
- AND (
448
- EXISTS {
449
- MATCH (:FuliSpace {id: $space_id})-
450
- [:HAS_KNOWLEDGE_ASSIGNMENT]->
451
- (assignment:FuliKnowledgeAssignment {
452
- item_kind: 'relationship',
453
- item_id: old.uuid
454
- })
455
- WHERE assignment.project_id = $personal_project_id
456
- }
457
- OR (
458
- NOT EXISTS {
459
- MATCH (:FuliSpace {id: $space_id})-
460
- [:HAS_KNOWLEDGE_ASSIGNMENT]->
461
- (:FuliKnowledgeAssignment {
462
- item_kind: 'relationship',
463
- item_id: old.uuid
464
- })
465
- }
466
- AND EXISTS {
467
- MATCH (old_episode:Episodic {group_id: $group_id})
468
- WHERE old_episode.uuid IN coalesce(old.episodes, [])
469
- AND (
470
- old_episode.fuli_personal_project_id =
471
- $personal_project_id
472
- OR (
473
- old_episode.fuli_personal_project_id IS NULL
474
- AND $personal_project_id IS NULL
475
- )
476
- )
477
- }
478
- )
479
- )
480
- AND EXISTS {
481
- MATCH (agent_episode:Episodic {group_id: $group_id})
482
- WHERE agent_episode.uuid IN coalesce(old.episodes, [])
483
- AND (
484
- agent_episode.fuli_project_agent_id = $project_agent_id
485
- OR (
486
- agent_episode.fuli_project_agent_id IS NULL
487
- AND $project_agent_id IS NULL
488
- )
489
- )
490
- }
491
- AND any(
531
+ WITH episode, superseded_edges, count(edge) AS edge_count
532
+ CALL {
533
+ WITH superseded_edges
534
+ UNWIND superseded_edges AS old
535
+ WITH old, head([
492
536
  replacement IN $superseded_relationships
493
- WHERE replacement.key = old.fuli_key
494
- )
495
- WITH edge_count, old, head([
496
- replacement IN $superseded_relationships
497
- WHERE replacement.key = old.fuli_key | replacement
498
- ]) AS replacement
499
- FOREACH (_ IN CASE WHEN old IS NULL THEN [] ELSE [1] END |
537
+ WHERE replacement.key = old.fuli_key | replacement
538
+ ]) AS replacement
500
539
  SET old.invalid_at = $reference_time,
501
540
  old.expired_at = $created_at,
502
541
  old.fuli_replaced_by_item_id = replacement.replacement_id,
503
542
  old.fuli_replaced_by_item_kind = 'relationship'
504
- )
543
+ RETURN count(*) AS _superseded_relationship_count
544
+ }
505
545
  RETURN edge_count
506
546
  ''',
507
547
  entities=[
@@ -24,7 +24,7 @@ class StoreProjectAgentAttention:
24
24
  self, actor, space, request.personal_project_id, agent_id,
25
25
  require_active=True,
26
26
  allow_unassigned=agent_id in {
27
- 'employee.bole', 'employee.jefa', 'fuli-project-coordinator',
27
+ 'employee.bole', 'employee.jefa', 'employee.tonborg', 'fuli-project-coordinator',
28
28
  },
29
29
  )
30
30
  if request.task_id:
@@ -5,9 +5,10 @@ from .project_agent_context_models import ProjectAgentContextResolution
5
5
  from .project_agent_task_models import ProjectAgentTaskSubmit
6
6
  from .task_context_temporary_owner import temporary_task_owner
7
7
  from .project_agent_delegations import StoreAgentDelegations
8
+ from .project_agent_remote_origins import StoreRemoteAgentOrigins
8
9
 
9
10
 
10
- class StoreProjectAgentContext(StoreAgentDelegations):
11
+ class StoreProjectAgentContext(StoreAgentDelegations, StoreRemoteAgentOrigins):
11
12
  async def resolve_project_agent_context(self, actor, request):
12
13
  self._require_personal()
13
14
  space = await self.authorize(actor, request.personal_space_id, 'reader')
@@ -68,7 +69,7 @@ class StoreProjectAgentContext(StoreAgentDelegations):
68
69
  )
69
70
  if selected and not self._implicit_owner_allowed(selected[0], fallback_selection):
70
71
  selected, reason, basis = [], 'no_match', [
71
- 'management or HR peer cannot own unrelated project context implicitly',
72
+ 'management or AR peer cannot own unrelated project context implicitly',
72
73
  ]
73
74
  if selected:
74
75
  reason = 'project_context_fallback'
@@ -113,7 +113,7 @@ class StoreProjectAgentCoordinationPolicy:
113
113
  if (profile.status != 'active' or not active_assignment
114
114
  or profile.agent_type != 'durable'
115
115
  or any(cap.startswith('fuli.employee:') for cap in profile.capabilities)):
116
- raise HTTPException(status_code=422, detail='team roles require active durable project assignments; HR and project managers remain peers')
116
+ raise HTTPException(status_code=422, detail='team roles require active durable project assignments; AR and project managers remain peers')
117
117
  updated_at = now_utc()
118
118
  written, _, _ = await self.runtime.driver.execute_query(
119
119
  '''
@@ -41,6 +41,19 @@ class StoreProjectAgentTaskActivity:
41
41
  space,
42
42
  request.personal_project_id,
43
43
  )
44
+ if request.judgment_task_context_token:
45
+ # Match the task-context lifecycle lock order: session, then task.
46
+ # A new host turn cannot supersede the checked token before commit.
47
+ contexts, _, _ = await self.runtime.driver.execute_query(
48
+ '''
49
+ MATCH (:FuliSpace {id: $space_id, kind: 'personal'})-
50
+ [:HAS_TASK_CONTEXT_SESSION]->(session:FuliTaskContextSession)-
51
+ [:HAS_CONTEXT]->(context:FuliTaskContext {token: $token})
52
+ SET session.write_serial = coalesce(session.write_serial, 0) + 1
53
+ RETURN context.token AS token
54
+ ''', space_id=request.personal_space_id, token=request.judgment_task_context_token)
55
+ if not contexts:
56
+ raise HTTPException(403, 'Judgment acceptance context is unavailable')
44
57
  # Acquire the task's write lock before reading its revision, transition
45
58
  # state or idempotency events. A transaction alone permits two concurrent
46
59
  # readers to validate the same revision before either obtains a lock.
@@ -96,6 +109,19 @@ class StoreProjectAgentTaskActivity:
96
109
  if (not request.artifact_revision or raw_task.get('quality_gate') != 'passed'
97
110
  or raw_task.get('verified_artifact_revision') != request.artifact_revision):
98
111
  raise HTTPException(409, 'Verify this exact artifact revision before completing the task')
112
+ if request.expected_verification_attempt_id is not None:
113
+ if (request.status != 'completed' or raw_task.get('quality_gate') != 'passed'
114
+ or raw_task.get('verified_attempt_id') != request.expected_verification_attempt_id
115
+ or raw_task.get('verified_artifact_revision') != request.artifact_revision):
116
+ raise HTTPException(409, 'Verification evidence changed before task acceptance')
117
+ if not request.judgment_task_context_token or not request.source_application:
118
+ raise HTTPException(403, 'Judgment acceptance requires a current lead context')
119
+ context = await self.get_task_context(actor, request.personal_space_id,
120
+ request.judgment_task_context_token, request.source_application)
121
+ if (context.get('personal_project_id') != request.personal_project_id
122
+ or context.get('project_agent_id') != raw_task.get('lead_agent_id')
123
+ or request.agent_id != raw_task.get('lead_agent_id')):
124
+ raise HTTPException(403, 'Judgment acceptance requires the current accountable lead')
99
125
  self._validate_task_transition(
100
126
  raw_task.get('status'),
101
127
  request.status,
@@ -618,7 +618,7 @@ class StoreProjectAgentTaskReads:
618
618
  if must_disclose:
619
619
  notice = ' '.join(
620
620
  (
621
- f'HR {item.hr_agent_id} recruited '
621
+ f'AR {item.hr_agent_id} recruited '
622
622
  f'{item.proposed_profile.name} as a '
623
623
  f'{item.position_kind} {item.participant_role} Agent for '
624
624
  f'{item.proposed_profile.responsibility}. '
@@ -33,7 +33,7 @@ RECRUITMENT_CLAIM_TTL = timedelta(minutes=2)
33
33
 
34
34
 
35
35
  class StoreProjectAgentTaskRecruitment:
36
- """Own the audited HR lifecycle independently from task routing mechanics."""
36
+ """Own the audited AR lifecycle independently from task routing mechanics."""
37
37
 
38
38
  async def get_project_agent_recruitment_policy(
39
39
  self,
@@ -202,7 +202,7 @@ class StoreProjectAgentTaskRecruitment:
202
202
  if not raw.get('hr_agent_id'):
203
203
  raise HTTPException(
204
204
  status_code=409,
205
- detail='recruitment cannot proceed without an active HR Agent',
205
+ detail='recruitment cannot proceed without an active AR Agent',
206
206
  )
207
207
  raw['_guarded_task_id'] = raw['task_id']
208
208
  selected = await self._provision_recruitment(actor, raw)
@@ -321,7 +321,7 @@ class StoreProjectAgentTaskRecruitment:
321
321
  model_strategy_source=model_source,
322
322
  outcome=outcome,
323
323
  reason=recruitment['reason_code'],
324
- match_basis=['HR recruitment approved and Agent assigned'],
324
+ match_basis=['AR recruitment approved and Agent assigned'],
325
325
  candidate_agent_ids=[routing_selected['agent_id']],
326
326
  created_at=now,
327
327
  **self._decision_executor_fields(executor_decision),
@@ -333,7 +333,7 @@ class StoreProjectAgentTaskRecruitment:
333
333
  )
334
334
  explanation = self._routing_explanation(
335
335
  recruitment['reason_code'],
336
- ['HR recruitment approved and Agent assigned'],
336
+ ['AR recruitment approved and Agent assigned'],
337
337
  executor_decision,
338
338
  )
339
339
  event_id = stable_uuid(
@@ -445,7 +445,7 @@ class StoreProjectAgentTaskRecruitment:
445
445
  decision_id=decision_id,
446
446
  decision_json=decision.model_dump_json(),
447
447
  event_id=event_id,
448
- event_summary=f'HR recruitment approved: {approval_reason}',
448
+ event_summary=f'AR recruitment approved: {approval_reason}',
449
449
  expected_task_revision=expected_task_revision,
450
450
  updated_at=now,
451
451
  )
@@ -13,10 +13,11 @@ from .system_hr_identity import LEGACY_HR_AGENT_ID
13
13
 
14
14
  IMPLICIT_PEER_AGENT_IDS = frozenset({
15
15
  'employee.jefa',
16
+ 'employee.tonborg',
16
17
  SYSTEM_HR_AGENT_ID.casefold(),
17
18
  LEGACY_HR_AGENT_ID.casefold(),
18
19
  })
19
- IMPLICIT_PEER_CAPABILITIES = frozenset({'fuli.employee:jefa', 'fuli.employee:bole'})
20
+ IMPLICIT_PEER_CAPABILITIES = frozenset({'fuli.employee:jefa', 'fuli.employee:bole', 'fuli.employee:tonborg'})
20
21
  IMPLICIT_PEER_AGENT_TYPES = frozenset({'hr'})
21
22
 
22
23
 
@@ -83,7 +84,7 @@ class StoreProjectAgentTaskStaffing:
83
84
  return [lead], [lead, *members], 'project_team_lead', [
84
85
  'project team lead is the stable conversation owner',
85
86
  f'{len(members)} active team member(s) available for explicit workstreams',
86
- 'HR and project manager remain peer roles',
87
+ 'AR and project manager remain peer roles',
87
88
  ]
88
89
  normalized_work_kind = request.work_kind.casefold()
89
90
  required = {item.casefold() for item in request.required_capabilities}
@@ -885,7 +886,7 @@ class StoreProjectAgentTaskStaffing:
885
886
  status_code=422,
886
887
  detail=(
887
888
  'parallel work requires at least two active Agents; '
888
- 'automatic recruitment needs an active HR Agent, otherwise '
889
+ 'automatic recruitment needs an active AR Agent, otherwise '
889
890
  'recruit another qualified collaborator or assign enough '
890
891
  'existing Agents before enabling parallel work'
891
892
  ),
@@ -272,7 +272,7 @@ class StoreProjectAgentTasks(
272
272
  *match_basis,
273
273
  'parallel automatic recruitment: filled '
274
274
  f'{recruited_count} missing participant slot(s)',
275
- 'HR recruitment recorded before Agent creation',
275
+ 'AR recruitment recorded before Agent creation',
276
276
  ]
277
277
  if recruited_lead:
278
278
  model_strategy, model_source = self._effective_model_strategy(
@@ -322,7 +322,7 @@ class StoreProjectAgentTasks(
322
322
  else 'no_match'
323
323
  )
324
324
  match_basis = [
325
- 'HR recruitment recorded before Agent creation',
325
+ 'AR recruitment recorded before Agent creation',
326
326
  *(
327
327
  [
328
328
  'parallel auto-staffing: added '
@@ -68,11 +68,11 @@ class StoreProjectAgents:
68
68
  ) -> ProjectAgentRecord:
69
69
  self._require_personal()
70
70
  if request.agent_id == LEGACY_HR_AGENT_ID:
71
- raise HTTPException(409, 'This HR identity has moved to employee.bole')
71
+ raise HTTPException(409, 'This AR identity has moved to employee.bole')
72
72
  if request.profile.agent_type == 'temporary' and not recruitment_id:
73
73
  raise HTTPException(
74
74
  status_code=422,
75
- detail='temporary Agents must be created by an audited HR recruitment',
75
+ detail='temporary Agents must be created by an audited AR recruitment',
76
76
  )
77
77
  if (
78
78
  request.profile.agent_type == 'coordinator'
@@ -88,7 +88,7 @@ class StoreProjectAgents:
88
88
  ):
89
89
  raise HTTPException(
90
90
  status_code=422,
91
- detail='only the system-managed Bole identity may use HR type',
91
+ detail='only the system-managed Bole identity may use AR type',
92
92
  )
93
93
  if (
94
94
  request.agent_id == SYSTEM_COORDINATOR_AGENT_ID
@@ -104,7 +104,7 @@ class StoreProjectAgents:
104
104
  ):
105
105
  raise HTTPException(
106
106
  status_code=422,
107
- detail='the system HR identity cannot change Agent type',
107
+ detail='the system AR identity cannot change Agent type',
108
108
  )
109
109
  if (
110
110
  request.agent_id in (SYSTEM_HR_AGENT_ID, SYSTEM_COORDINATOR_AGENT_ID)
@@ -112,7 +112,7 @@ class StoreProjectAgents:
112
112
  ):
113
113
  raise HTTPException(
114
114
  status_code=422,
115
- detail='the project manager and HR must remain active',
115
+ detail='the project manager and AR must remain active',
116
116
  )
117
117
  space = await self.authorize(actor, request.personal_space_id, 'maintainer')
118
118
  if request.personal_project_id:
@@ -402,7 +402,7 @@ class StoreProjectAgents:
402
402
  if agent_id == SYSTEM_HR_AGENT_ID:
403
403
  raise HTTPException(
404
404
  status_code=422,
405
- detail='the system HR cannot be archived',
405
+ detail='the system AR cannot be archived',
406
406
  )
407
407
  await self.authorize(actor, personal_space_id, 'maintainer')
408
408
  if not reason.strip():