fuli-context 0.8.1 → 0.8.3

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 (79) hide show
  1. package/README.md +11 -7
  2. package/README.zh-CN.md +8 -7
  3. package/dist/web/assets/{AboutPage-fBq5VFoQ.js → AboutPage-HLhChD_Q.js} +1 -1
  4. package/dist/web/assets/{AgentHand-CMZk5GjY.js → AgentHand-BaLSbn08.js} +1 -1
  5. package/dist/web/assets/{AgentName-DRZHAIB2.js → AgentName-0PIU7MOX.js} +1 -1
  6. package/dist/web/assets/{AgentProfilePage-BPQEfMhB.js → AgentProfilePage-9cSsZHnk.js} +1 -1
  7. package/dist/web/assets/{AgentsDirectoryPage-CKZfrcuD.js → AgentsDirectoryPage-7G02Xm6C.js} +1 -1
  8. package/dist/web/assets/{ConnectionsPage-COb56qnk.js → ConnectionsPage-D0wPYbwd.js} +1 -1
  9. package/dist/web/assets/{EmployeeRecruitDialog-BLTpjCcK.js → EmployeeRecruitDialog-BrPH3e50.js} +1 -1
  10. package/dist/web/assets/{EmployeeWorkbenchPage-HUd-gne2.js → EmployeeWorkbenchPage-u-jw38PW.js} +1 -1
  11. package/dist/web/assets/{GrowthLoading-Bu_kH-I8.js → GrowthLoading-DFOy-mEl.js} +1 -1
  12. package/dist/web/assets/{KnowledgeEditDialog-BTwXXRCD.js → KnowledgeEditDialog-DBsjlkDr.js} +1 -1
  13. package/dist/web/assets/{KnowledgeInspector-Ct5hl2--.js → KnowledgeInspector-C79hbuOo.js} +1 -1
  14. package/dist/web/assets/{KnowledgeOrganizerPage-DMC8Hh43.js → KnowledgeOrganizerPage-Cz1BnAtz.js} +1 -1
  15. package/dist/web/assets/KnowledgePage-JyfFFUrg.js +1 -0
  16. package/dist/web/assets/{KnowledgeWorkspace-DmHIc0jb.js → KnowledgeWorkspace-B2vj12QG.js} +1 -1
  17. package/dist/web/assets/{NotFoundPage-C0yOR9T9.js → NotFoundPage-D340EQJP.js} +1 -1
  18. package/dist/web/assets/{OverviewPage-BreX_kb4.js → OverviewPage-BKiYudrC.js} +1 -1
  19. package/dist/web/assets/{PersonalProfilePage-CX1a6_E3.js → PersonalProfilePage-BIGTcem2.js} +1 -1
  20. package/dist/web/assets/PersonalProjectsPage-CzjDCIi0.js +1 -0
  21. package/dist/web/assets/{ProjectAgentsPage-CA04h1Uf.js → ProjectAgentsPage-C5F2WgwJ.js} +4 -4
  22. package/dist/web/assets/ProjectAgentsPage-C89ZEDgi.css +1 -0
  23. package/dist/web/assets/{ProjectScopePicker-DVHfXX1f.js → ProjectScopePicker-8hrgvmg-.js} +1 -1
  24. package/dist/web/assets/{PublicProjectsPage-CtctKml1.js → PublicProjectsPage-b5zx0dFS.js} +1 -1
  25. package/dist/web/assets/{ReviewPage-DUqaK-do.js → ReviewPage-BLdWYDmj.js} +1 -1
  26. package/dist/web/assets/{SearchableSelect-BZtyYmZN.js → SearchableSelect-vaoSDq1g.js} +1 -1
  27. package/dist/web/assets/{SettingsPage-CB4ymLr8.js → SettingsPage-Dxjrkq1g.js} +1 -1
  28. package/dist/web/assets/{SharedJefaReportPage-CbFtrDCY.js → SharedJefaReportPage-YG2xwQQy.js} +1 -1
  29. package/dist/web/assets/{WritingTastePage-B7lSzs2z.js → WritingTastePage-CPPduPb1.js} +1 -1
  30. package/dist/web/assets/{agent-profile.css_vue_type_style_index_0_src_true_lang-C22UdfU0.js → agent-profile.css_vue_type_style_index_0_src_true_lang-LTJ1ptv9.js} +1 -1
  31. package/dist/web/assets/{catalog-DU4Kfd-N.js → catalog-B9tlJAp9.js} +1 -1
  32. package/dist/web/assets/{client-CEHkJJ-5.js → client-InXscj8J.js} +1 -1
  33. package/dist/web/assets/{console--CZxc3PW.js → console-hUns2aD-.js} +1 -1
  34. package/dist/web/assets/{conversation-launcher-settings-Co1RlTFH.js → conversation-launcher-settings-DEtJ6pZg.js} +1 -1
  35. package/dist/web/assets/{i18n-BtOOoyJd.js → i18n-75woNt31.js} +2 -2
  36. package/dist/web/assets/{identity-CsowvtIm.js → identity-Wt2Xio_B.js} +1 -1
  37. package/dist/web/assets/index-pleRuLpw.js +2 -0
  38. package/dist/web/assets/{model-CGpllrds.js → model-C1GwBvd9.js} +1 -1
  39. package/dist/web/assets/{personal-profile-graph-BNTG4PSY.js → personal-profile-graph-BdV_3I1u.js} +1 -1
  40. package/dist/web/assets/{shared-report-BFUJqqQe.js → shared-report-tRQV1iJl.js} +1 -1
  41. package/dist/web/assets/{useApi-CROJJdhE-raNlHRDl.js → useApi-CROJJdhE-QfNm4ADP.js} +1 -1
  42. package/dist/web/assets/{useModalDialog-CroGk2Cs.js → useModalDialog-BopF-KHN.js} +1 -1
  43. package/dist/web/assets/{vue-router-JFYBLXB1.js → vue-router-COCNAJJn.js} +1 -1
  44. package/dist/web/index.html +10 -10
  45. package/docs/agent-conversations-and-collaboration.md +18 -1
  46. package/docs/agent-interface-architecture.md +17 -0
  47. package/docs/employee-agents.md +14 -1
  48. package/docs/project-agent-memory.md +6 -1
  49. package/graph-provider/fuli_graph/knowledge_search.py +8 -16
  50. package/graph-provider/fuli_graph/project_agent_routes.py +5 -1
  51. package/graph-provider/fuli_graph/store_project_agent_context.py +18 -1
  52. package/graph-provider/fuli_graph/store_project_agent_task_staffing.py +86 -11
  53. package/graph-provider/pyproject.toml +1 -1
  54. package/npm-shrinkwrap.json +15 -15
  55. package/package.json +4 -4
  56. package/skills/capturing-session-knowledge/SKILL.md +20 -2
  57. package/skills/operating-fuli/SKILL.md +8 -0
  58. package/src/agent-tools/employee-definitions.js +6 -5
  59. package/src/agent-tools/graph-definitions.js +2 -2
  60. package/src/agents/identity-receipt.js +29 -0
  61. package/src/conversations/task-context-view.js +3 -1
  62. package/src/employees/service.js +17 -3
  63. package/src/employees/task-entry.js +5 -1
  64. package/src/graphiti/agent-conversations.js +15 -1
  65. package/src/graphiti/agent-knowledge-workflows.js +1 -1
  66. package/src/graphiti/federated-application.js +6 -40
  67. package/src/graphiti/project-agent-mention.js +35 -0
  68. package/src/graphiti/project-agent-task-entry.js +22 -3
  69. package/src/graphiti/project-agent-workflows.js +13 -2
  70. package/src/graphiti/project-path-context.js +17 -10
  71. package/src/graphiti/task-entry-preferences.js +50 -0
  72. package/src/mcp/create-mcp-server.js +6 -1
  73. package/src/mcp/employee-tool-catalog.js +22 -0
  74. package/src/mcp/tool-result.js +3 -1
  75. package/dist/web/assets/KnowledgePage-uXW1Eovz.js +0 -1
  76. package/dist/web/assets/PersonalProjectsPage-Br1S8ETM.js +0 -1
  77. package/dist/web/assets/ProjectAgentsPage-CZWWV4Kw.css +0 -1
  78. package/dist/web/assets/index-MKmHNBtc.js +0 -2
  79. package/src/mcp/task-context-registry.js +0 -165
@@ -8,6 +8,16 @@ from .project_agent_access import authorize_project_agent
8
8
  from .project_agent_models import ProjectAgentProfile
9
9
  from .project_agent_task_models import ProjectAgentParallelPlan
10
10
  from .store_project_agents import SYSTEM_HR_AGENT_ID
11
+ from .system_hr_identity import LEGACY_HR_AGENT_ID
12
+
13
+
14
+ IMPLICIT_PEER_AGENT_IDS = frozenset({
15
+ 'employee.jefa',
16
+ SYSTEM_HR_AGENT_ID.casefold(),
17
+ LEGACY_HR_AGENT_ID.casefold(),
18
+ })
19
+ IMPLICIT_PEER_CAPABILITIES = frozenset({'fuli.employee:jefa', 'fuli.employee:bole'})
20
+ IMPLICIT_PEER_AGENT_TYPES = frozenset({'hr'})
11
21
 
12
22
 
13
23
  class StoreProjectAgentTaskStaffing:
@@ -79,10 +89,14 @@ class StoreProjectAgentTaskStaffing:
79
89
  eligible = [row for row in rows if required.issubset({
80
90
  item.casefold() for item in row.get('capabilities', [])
81
91
  })]
82
- candidates, history = await self._rank_agent_candidates(eligible, request)
92
+ implicit_eligible = [
93
+ row for row in eligible
94
+ if self._implicit_owner_allowed(row, request)
95
+ ]
96
+ candidates, history = await self._rank_agent_candidates(implicit_eligible, request)
83
97
  # Continuity may break a tie between qualified specialists; it must
84
98
  # never override a capability requirement or a stronger work-kind fit.
85
- continuity_pool = eligible
99
+ continuity_pool = implicit_eligible
86
100
  if candidates:
87
101
  best_work_match = normalized_work_kind in {
88
102
  item.casefold() for item in candidates[0].get('work_kinds', [])
@@ -100,7 +114,10 @@ class StoreProjectAgentTaskStaffing:
100
114
  )
101
115
  if continuity:
102
116
  selected = continuity[0]
103
- remaining = [row for row in candidates or eligible if row['agent_id'] != selected['agent_id']]
117
+ remaining = [
118
+ row for row in candidates or implicit_eligible
119
+ if row['agent_id'] != selected['agent_id']
120
+ ]
104
121
  return [selected], [selected, *remaining], 'project_continuity', [
105
122
  self._project_history_match_basis(
106
123
  project_history.get(selected['agent_id']),
@@ -129,20 +146,20 @@ class StoreProjectAgentTaskStaffing:
129
146
  'matching Agent is unavailable to source client: '
130
147
  f'{request.source_application or "other"}',
131
148
  ]
132
- if len(eligible) == 1:
133
- return [eligible[0]], eligible, 'sole_active_assignment', [
149
+ if len(eligible) == 1 and len(implicit_eligible) == 1:
150
+ return [implicit_eligible[0]], implicit_eligible, 'sole_active_assignment', [
134
151
  'the project has one active Agent assignment',
135
- *self._automatic_selection_basis(eligible[0], request),
152
+ *self._automatic_selection_basis(implicit_eligible[0], request),
136
153
  ]
137
- if eligible and normalized_work_kind == 'project_context' and not required:
138
- eligible.sort(key=lambda row: (
154
+ if implicit_eligible and normalized_work_kind == 'project_context' and not required:
155
+ implicit_eligible.sort(key=lambda row: (
139
156
  self._metric_int(row.get('active_task_count')),
140
157
  -int(self._metric_int(row.get('memory_revision')) > 0),
141
158
  row['assigned_at'], row['agent_id'],
142
159
  ))
143
- return eligible[:1], eligible, 'project_default', [
144
- f'{len(eligible)} eligible project roles; working-memory continuity and stable tie-break',
145
- *self._automatic_selection_basis(eligible[0], request),
160
+ return implicit_eligible[:1], implicit_eligible, 'project_default', [
161
+ f'{len(implicit_eligible)} eligible project roles; working-memory continuity and stable tie-break',
162
+ *self._automatic_selection_basis(implicit_eligible[0], request),
146
163
  ]
147
164
  return [], rows, 'no_match', ['no active assignment matched exactly']
148
165
  selected = candidates[0]
@@ -166,6 +183,64 @@ class StoreProjectAgentTaskStaffing:
166
183
  if item not in match_basis)
167
184
  return [selected], candidates, reason, match_basis
168
185
 
186
+ @classmethod
187
+ def _implicit_owner_allowed(cls, row, request):
188
+ """Keep management peers out of unrelated automatic ownership paths."""
189
+
190
+ if not cls._is_implicit_peer(row):
191
+ return True
192
+ profile = row.get('profile')
193
+ work_kinds = {
194
+ str(item).casefold()
195
+ for item in row.get('work_kinds', [])
196
+ }
197
+ work_kinds.update(
198
+ str(item).casefold()
199
+ for item in (cls._profile_value(profile, 'work_kinds', []) or [])
200
+ )
201
+ capabilities = {
202
+ str(item).casefold()
203
+ for item in row.get('capabilities', [])
204
+ }
205
+ capabilities.update(
206
+ str(item).casefold()
207
+ for item in (cls._profile_value(profile, 'capabilities', []) or [])
208
+ )
209
+ required = {
210
+ str(item).casefold()
211
+ for item in request.required_capabilities
212
+ }
213
+ return request.work_kind.casefold() in work_kinds or (
214
+ bool(required) and required.issubset(capabilities)
215
+ )
216
+
217
+ @classmethod
218
+ def _is_implicit_peer(cls, row):
219
+ agent_id = str(row.get('agent_id') or '').casefold()
220
+ profile = row.get('profile')
221
+ agent_type = str(cls._profile_value(profile, 'agent_type', '')).casefold()
222
+ capabilities = {
223
+ str(item).casefold()
224
+ for item in (cls._profile_value(profile, 'capabilities', []) or [])
225
+ }
226
+ capabilities.update(
227
+ str(item).casefold()
228
+ for item in (row.get('capabilities', []) or [])
229
+ )
230
+ return (
231
+ agent_id in IMPLICIT_PEER_AGENT_IDS
232
+ or agent_type in IMPLICIT_PEER_AGENT_TYPES
233
+ or bool(capabilities & IMPLICIT_PEER_CAPABILITIES)
234
+ )
235
+
236
+ @staticmethod
237
+ def _profile_value(profile, field, default):
238
+ if profile is None:
239
+ return default
240
+ if isinstance(profile, dict):
241
+ return profile.get(field, default)
242
+ return getattr(profile, field, default)
243
+
169
244
  def _automatic_selection_basis(self, selected, request):
170
245
  work_kind = request.work_kind.casefold()
171
246
  if work_kind == 'project_context':
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "fuli-graph-provider"
3
- version = "0.8.1"
3
+ version = "0.8.3"
4
4
  description = "Graphiti-backed personal and shared project provider for Fuli."
5
5
  requires-python = ">=3.12"
6
6
  dependencies = [
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "fuli-context",
3
- "version": "0.8.1",
3
+ "version": "0.8.3",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "fuli-context",
9
- "version": "0.8.1",
9
+ "version": "0.8.3",
10
10
  "license": "Apache-2.0",
11
11
  "dependencies": {
12
12
  "@hono/node-server": "2.0.12",
@@ -22,7 +22,7 @@
22
22
  "@types/node": "^24.13.3",
23
23
  "@vitejs/plugin-vue": "^6.0.8",
24
24
  "@vue/test-utils": "^2.4.11",
25
- "brace-expansion": "5.0.9",
25
+ "brace-expansion": "5.0.12",
26
26
  "d3": "^7.9.0",
27
27
  "jsdom": "^29.1.1",
28
28
  "linkedom": "^0.18.12",
@@ -1867,9 +1867,9 @@
1867
1867
  "license": "ISC"
1868
1868
  },
1869
1869
  "node_modules/brace-expansion": {
1870
- "version": "5.0.9",
1871
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz",
1872
- "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==",
1870
+ "version": "5.0.12",
1871
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz",
1872
+ "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==",
1873
1873
  "dev": true,
1874
1874
  "license": "MIT",
1875
1875
  "dependencies": {
@@ -2949,9 +2949,9 @@
2949
2949
  "license": "MIT"
2950
2950
  },
2951
2951
  "node_modules/fast-uri": {
2952
- "version": "3.1.7",
2953
- "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz",
2954
- "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==",
2952
+ "version": "3.1.8",
2953
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz",
2954
+ "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==",
2955
2955
  "funding": [
2956
2956
  {
2957
2957
  "type": "github",
@@ -3285,9 +3285,9 @@
3285
3285
  }
3286
3286
  },
3287
3287
  "node_modules/ip-address": {
3288
- "version": "10.3.1",
3289
- "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.3.1.tgz",
3290
- "integrity": "sha512-1e9d3kb97NHJTIJDZW9rKqW2h6+dFa50Dy0fpPSMQp2ADje5gvKsXmdiK6dwY5t76TaTt5+P5N1Y/LoToIxP6g==",
3288
+ "version": "10.7.2",
3289
+ "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.2.tgz",
3290
+ "integrity": "sha512-7H/2gFSIitxc0hG3nOI1glS8QLo/EHBFFLk8vEUjXY/xu0AdL8jZ9U1IzO2PUm0d2D/ofQcAifb0g6OBkt8U7w==",
3291
3291
  "license": "MIT",
3292
3292
  "engines": {
3293
3293
  "node": ">= 12"
@@ -5077,9 +5077,9 @@
5077
5077
  "license": "ISC"
5078
5078
  },
5079
5079
  "node_modules/undici": {
5080
- "version": "7.29.0",
5081
- "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz",
5082
- "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==",
5080
+ "version": "7.30.0",
5081
+ "resolved": "https://registry.npmjs.org/undici/-/undici-7.30.0.tgz",
5082
+ "integrity": "sha512-dkrQXeHSaoamnItlYbmzG0wFYrM0ZwDxCIg0A7aKjTyyhh9svRzCNFEzV+Vm05/yehjCzjDZ31KXfGEjYSztDQ==",
5083
5083
  "dev": true,
5084
5084
  "license": "MIT",
5085
5085
  "engines": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fuli-context",
3
- "version": "0.8.1",
3
+ "version": "0.8.3",
4
4
  "description": "Local-first collaboration relationship graph for AI agents.",
5
5
  "type": "module",
6
6
  "files": [
@@ -65,7 +65,7 @@
65
65
  "type": "git",
66
66
  "url": "git+https://github.com/JevonsCode/fuli.git"
67
67
  },
68
- "homepage": "https://github.com/JevonsCode/fuli#readme",
68
+ "homepage": "https://jevonscode.github.io/fuli/",
69
69
  "bugs": {
70
70
  "url": "https://github.com/JevonsCode/fuli/issues"
71
71
  },
@@ -83,7 +83,7 @@
83
83
  "@types/node": "^24.13.3",
84
84
  "@vitejs/plugin-vue": "^6.0.8",
85
85
  "@vue/test-utils": "^2.4.11",
86
- "brace-expansion": "5.0.9",
86
+ "brace-expansion": "5.0.12",
87
87
  "d3": "^7.9.0",
88
88
  "jsdom": "^29.1.1",
89
89
  "linkedom": "^0.18.12",
@@ -102,7 +102,7 @@
102
102
  "@hono/node-server": "2.0.12"
103
103
  },
104
104
  "brace-expansion": "$brace-expansion",
105
- "fast-uri": "3.1.7",
105
+ "fast-uri": "3.1.8",
106
106
  "qs": "6.16.0"
107
107
  }
108
108
  }
@@ -9,6 +9,13 @@ Accumulate reusable knowledge without interrupting the user's normal work. Keep
9
9
 
10
10
  ## Establish Task Context
11
11
 
12
+ For a coordinated worker sharing its parent's host task, use the isolated context and lifecycle
13
+ policy supplied by the coordinator. An inherited task token belongs to the parent: do not call
14
+ `begin_task_context`, `checkpoint_task_knowledge`, or write the parent's Agent memory. Return
15
+ your result, evidence and proposed work summary to the coordinator for integration. Worker
16
+ activity uses `record_project_agent_task_activity`. A separately created host task/session has
17
+ its own lifecycle and follows the normal entry and checkpoint rules below.
18
+
12
19
  At the start of every user task, first use the lifecycle context supplied by the host when one
13
20
  exists. A Claude Code `UserPromptSubmit` hook calls `begin_task_context`, which already loads
14
21
  collaboration preferences and resolves the exact local personal project from the current working
@@ -230,9 +237,11 @@ project history. Explicit user-selected Agents and locked policies remain higher
230
237
  Treat the returned durable Agent identity and a host worker as different things. A durable Agent
231
238
  keeps its project assignment, preferences, task history, and Agent-scoped graph memory while idle;
232
239
  the host creates an execution process only when needed. Start only entries in `worker_plan`, give
233
- each worker only its returned isolated `context`, role, and workstream boundary, and never expose
240
+ each worker only its returned isolated `context` (including `lifecycle`), role, and workstream boundary, and never expose
234
241
  another Agent's private context. Fuli is the control plane and does not itself prove that a native
235
242
  worker started.
243
+ Do not forward the parent's task token. The coordinator alone checkpoints the shared host task
244
+ after integrating worker results; a worker's completion is not that task's completion.
236
245
 
237
246
  The host adapter must keep an adaptive-runtime graph lease for the whole worker run, and an
238
247
  executor lease when Fuli manages that executor. Call `acquire_runtime_lease` before starting
@@ -243,6 +252,14 @@ including concrete worker identity. Call `record_project_agent_executor_actual`
243
252
  executor/model use. If the host cannot start a worker, report the task as blocked or failed and do
244
253
  not fabricate an execution-summary row. The Provider's `executionSummary` remains authoritative.
245
254
 
255
+ `workerStatus` requires `workerId` and the participating `agentId`. Structured actual executor,
256
+ provider and model form one report and must match authorized preflight evidence. When that
257
+ evidence is missing or inconsistent, omit all three; preserve the observed `workerRuntime`,
258
+ terminal status and mismatch in the summary. Do not guess a provider or rewrite preflight to
259
+ make history fit. Valid terminal activity automatically creates its routing outcome. Additional
260
+ user/test outcome evidence needs a matching recorded execution, the task's exact work kind and
261
+ model strategy, and real references; a CLI session ID alone does not establish that execution.
262
+
246
263
  Keep per-worker completion separate from the global task transition. While parallel work is still
247
264
  being integrated, report each worker's terminal result in `workerStatus` and keep the task status
248
265
  `running`; this preserves one terminal execution-summary row per actual worker. Only after every
@@ -388,7 +405,8 @@ If `capture_session_knowledge` or automatic project creation returns `capture_di
388
405
  user has disabled automatic capture. Do not retry, do not create a fallback record elsewhere,
389
406
  and do not describe the skipped batch as stored. Existing knowledge may still be queried.
390
407
 
391
- Before sending the final response, perform one checkpoint. For a hook-managed task, call
408
+ Before sending the final response for a task you own, perform one checkpoint. A coordinated
409
+ worker returns its result to the owner instead of checkpointing an inherited token. For a hook-managed task, call
392
410
  `checkpoint_task_knowledge` exactly once with either `capture_candidates` and the bounded durable
393
411
  batch, or `retain_nothing`. For a prompt-only Agent, either flush the durable batch directly or
394
412
  determine that the turn produced no reusable knowledge. Do not finish a reusable project decision
@@ -39,6 +39,10 @@ For read-only questions, use the narrowest list/get/search tool and return sourc
39
39
 
40
40
  When the user addresses HR or Bole to ask about organization or find someone, resolve the existing dedicated HR identity and read `list_project_agents` plus `get_project_agent_coordination_policy` for the exact personal space and relevant project. Use recorded roles, assignments and team membership; do not invent an organization or recruit a replacement HR. Match names, responsibilities and capabilities, but never guess between ambiguous or duplicate names. Link each resolved name to its personal page at `/agents/{encodedPersonalSpaceId}/{encodedAgentId}`. Answering in the HR role does not mean a worker has started; report actual execution separately.
41
41
 
42
+ ## Before starting collaborators
43
+
44
+ Read each returned worker's `context.agent.profile.allowedClients` and `executorPolicy`, together with the task's effective executor policy. Intersect the restrictions before starting either a native or external worker; the lead's selected executor does not authorize every collaborator. A locked allow-list applies even when the current host has an idle worker. If the intended platform is outside any effective lock, select an eligible Agent/executor or report the constraint before launch. Never relax the lock, relabel the worker, or invent model evidence to satisfy reporting. FULI context recovery and a ready worker plan are not execution authorization by themselves.
45
+
42
46
  ## Handling conflicts and failures
43
47
 
44
48
  - On revision or timestamp conflict, re-read, explain what changed, and recompute the intended patch. Do not blindly retry stale input.
@@ -49,3 +53,7 @@ When the user addresses HR or Bole to ask about organization or find someone, re
49
53
  ## Completion response
50
54
 
51
55
  State what changed, the exact project or item scope, the verification result, and any remaining human-only action. Do not claim success from a configured capability or submitted request alone.
56
+
57
+ Include the task's named durable owner and profile link from `agent_receipt` or the successful checkpoint's `conversation.receipt`. Include Jefa or other peers only with an actual `collaboration_receipt`, naming the tool work they performed; board tools do not imply a separate model worker ran. Keep this receipt brief and distinguish work completion, memory checkpoint and visible-transcript coverage. Do not report saved memory when capture failed or is disabled.
58
+
59
+ For cross-client continuation, copy the supplied continuation prompt into the same project's connected client. A leading `@Name` matches exact directory names; `@{agentId}` is the stable form (the generated prompt percent-encodes unusual IDs). Duplicate names require an exact ID in the current request; an ambiguous explicit mention must not fall back to the prior session owner or memory. This is FULI task-entry matching, not registration in a client's native @ menu. Obtain a fresh task token before `resume_agent_conversation`; never copy an old token between clients. If no qualified owner exists, route staffing through HR before implementation; Jefa remains a management peer. Preserve confirmed taste/personality/preferences and Agent character/expectations in delegated context.
@@ -3,6 +3,7 @@ import { booleanSchema, objectSchema, stringSchema } from './schema.js';
3
3
  const id = { ...stringSchema(), minLength: 1, maxLength: 256 };
4
4
  const projectPath = { ...stringSchema(), minLength: 1, maxLength: 4096 };
5
5
  const target = { projectPath, templateId: id };
6
+ const workbenchTarget = { ...target, personalProjectId: id };
6
7
 
7
8
  export const EMPLOYEE_TOOL_DEFINITIONS = [
8
9
  {
@@ -29,15 +30,15 @@ export const EMPLOYEE_TOOL_DEFINITIONS = [
29
30
  },
30
31
  {
31
32
  name: 'list_employee_tools', title: 'READ · Employee workbench tools',
32
- description: 'Discover the exact input schemas and permissions for a recruited employee in the current project. Read these schemas before calling a tool; a workbench has to be installed first.',
33
- inputSchema: objectSchema(target, ['projectPath', 'templateId'])
33
+ description: 'Discover complete input schemas and permitted tools for a recruited employee. Supply projectPath, or a previously resolved personalProjectId without projectPath when directory resolution is unavailable. If both are supplied they must resolve to the same project. Existing project and employee authorization always apply. Use includeSchemas=false for a compact name list, then toolName to read one exact schema. Oversized contracts fail explicitly, never return partial schemas. Read the schema before calling; a workbench has to be installed first.',
34
+ inputSchema: objectSchema({ ...workbenchTarget, toolName: id, includeSchemas: booleanSchema() }, ['templateId'])
34
35
  },
35
36
  {
36
37
  name: 'call_employee_tool', title: 'WRITE · Call an employee workbench tool',
37
- description: 'Call a discovered employee tool inside the exact assigned project. May read or change data according to the tool schema. Preserve optimistic concurrency and idempotency keys. Does not grant cross-project access, execute an external model, or confirm task completion for the human.',
38
+ description: 'Call a discovered employee tool inside the exact assigned project. Supply projectPath or a previously resolved personalProjectId; omit projectPath if directory resolution is unavailable. If both are supplied they must resolve to the same project. May read or change data according to the tool schema. Preserve optimistic concurrency and idempotency keys. Does not grant cross-project access, execute an external model, or confirm task completion for the human.',
38
39
  inputSchema: objectSchema({
39
- ...target, tool: id,
40
+ ...workbenchTarget, tool: id,
40
41
  arguments: { type: 'object', additionalProperties: true }
41
- }, ['projectPath', 'templateId', 'tool', 'arguments'])
42
+ }, ['templateId', 'tool', 'arguments'])
42
43
  }
43
44
  ];
@@ -806,7 +806,7 @@ export const GRAPH_TOOL_DEFINITIONS = [
806
806
  {
807
807
  name: 'record_project_agent_task_activity',
808
808
  title: 'WRITE · Record task activity',
809
- description: 'Record one explicit task event, including real terminal activity, actual executor/model/client attribution, and an optional source-labelled cumulative token snapshot for the concrete worker. Never estimate missing usage. Terminal activity is the only source for per-Agent activity summaries.',
809
+ description: 'Record real task/worker activity and optional source-labelled cumulative token usage; never estimate. workerStatus requires workerId and agentId. Report actualExecutorId, actualModelProvider and actualModel together only with matching authorized preflight evidence. Otherwise omit all three, report observed workerRuntime and explain the mismatch in summary. A terminal activity with valid actual execution automatically records its routing outcome.',
810
810
  inputSchema: projectAgentTaskActivityInput
811
811
  },
812
812
  {
@@ -1016,7 +1016,7 @@ export const GRAPH_TOOL_DEFINITIONS = [
1016
1016
  {
1017
1017
  name: 'record_project_agent_task_outcome',
1018
1018
  title: 'WRITE · Record explicit routing outcome evidence',
1019
- description: 'Record only explicit acceptance/satisfaction evidence such as rework_requested, repeated_negative_feedback, explicit_praise, test/acceptance pass or fail, or an explicit rating. Natural-language text is never used to infer satisfaction.',
1019
+ description: 'Record explicit user/test outcome evidence against a matching recorded execution. Use the task workKind and effective modelStrategy plus real referenceIds; a CLI session ID alone is not a recorded run. Terminal routing outcomes are already derived from valid task activity; do not submit them again. Never infer satisfaction or fabricate execution evidence to clear a validation error.',
1020
1020
  inputSchema: projectAgentTaskOutcomeInput
1021
1021
  },
1022
1022
  {
@@ -0,0 +1,29 @@
1
+ import { sourceConsoleUrl } from '../graphiti/source-marker.js';
2
+
3
+ export function agentProfileUrl(application, agentId) {
4
+ return `${application.consoleUrl ?? sourceConsoleUrl(null)}/agents/${encodeURIComponent(application.config.personal.spaceId)}/${encodeURIComponent(agentId)}`;
5
+ }
6
+
7
+ export function agentContinuation(projectId, agentId, conversationId = null) {
8
+ return { personal_project_id: projectId, agent_id: agentId,
9
+ ...(conversationId ? { conversation_id: conversationId } : {}),
10
+ prompt: `@{${encodeURIComponent(agentId)}} 请在 FULI 项目 ${JSON.stringify(projectId)} 对应的工作目录继续工作。`
11
+ + (conversationId ? `先用当前任务的 taskContextToken 调用 resume_agent_conversation,conversationId=${JSON.stringify(conversationId)},再按需读取上下文。` : '先恢复这个 Agent 的项目记忆和最近会话。'),
12
+ guidance: 'Use a connected client and the same authorized project/Agent. Obtain a fresh task context; never reuse the previous client’s token. This is a FULI prompt, not a native client @ picker registration.' };
13
+ }
14
+
15
+ export function taskAgentReceipt(application, { projectId, agentId, name = null,
16
+ sourceApplication = null, workStatus = null, conversationId = null, persistence = null }) {
17
+ return { owner: { agent_id: agentId, name,
18
+ profile_url: agentProfileUrl(application, agentId), source_application: sourceApplication },
19
+ personal_project_id: projectId, work_status: workStatus, persistence,
20
+ continuation: agentContinuation(projectId, agentId, conversationId),
21
+ guidance: 'Finish with the named owner and profile link, truthful work/memory status and continuation. Add only actually used collaboration_receipt participants (for example Jefa); tools and configured peers are not independent model workers.' };
22
+ }
23
+
24
+ export function employeeCollaborationReceipt(application, context, { tool, permission, sourceApplication }) {
25
+ return { agent_id: context.agentId, name: context.name, role: context.role,
26
+ profile_url: agentProfileUrl(application, context.agentId),
27
+ personal_project_id: context.project.id, tool, permission,
28
+ status: 'tool_completed', source_application: sourceApplication ?? 'other', worker_started: false };
29
+ }
@@ -5,7 +5,9 @@ export function compactTaskContext(value) {
5
5
  const result = { ...value };
6
6
  if (Array.isArray(value.effective_preferences)) result.effective_preferences = value.effective_preferences.map(item => ({
7
7
  instruction: item.instruction, preference_key: item.preference_key,
8
- preference_scope: item.preference_scope, confirmation_status: item.confirmation_status
8
+ preference_scope: item.preference_scope, confirmation_status: item.confirmation_status,
9
+ profile_aspect: item.profile_aspect,
10
+ ...(item.attributes?.tasteDomain ? { attributes: { tasteDomain: item.attributes.tasteDomain } } : {})
9
11
  }));
10
12
  const continuity = value.project_agent_context;
11
13
  if (continuity?.memory) {
@@ -4,6 +4,7 @@ import { createEmployeeRecruitment } from './recruitment.js';
4
4
  import { join } from 'node:path';
5
5
  import { EmployeeManagementStore } from './management-policy.js';
6
6
  import { employeeTaskEntry } from './task-entry.js';
7
+ import { employeeCollaborationReceipt } from '../agents/identity-receipt.js';
7
8
 
8
9
  export function createEmployeeService({ app, runtimeConfigPath, registry: configuredRegistry, managementStore: configuredManagementStore }) {
9
10
  const registry = configuredRegistry ?? createEmployeePackageRegistry(employeeDirectories(runtimeConfigPath));
@@ -39,7 +40,14 @@ export function createEmployeeService({ app, runtimeConfigPath, registry: config
39
40
  async function describeTools(input) {
40
41
  const context = await workspace(input);
41
42
  const runtime = await registry.runtime(context.templateId);
42
- return { ...context, tools: await runtime.describeTools() };
43
+ const { manifest } = registry.get(context.templateId);
44
+ let tools = (await runtime.describeTools()).filter(tool => manifest.permissions.includes(tool.permission));
45
+ if (input.toolName) {
46
+ tools = tools.filter(tool => tool.name === input.toolName);
47
+ if (!tools.length) throw new EmployeeError('Employee tool is not available or permitted', 403, 'tool_not_permitted');
48
+ }
49
+ if (input.includeSchemas === false) tools = tools.map(({ inputSchema, ...tool }) => tool);
50
+ return { ...context, tools };
43
51
  }
44
52
 
45
53
  async function callTool(input) {
@@ -58,7 +66,13 @@ export function createEmployeeService({ app, runtimeConfigPath, registry: config
58
66
  sourceApplication: input.sourceApplication ?? 'other', id: input.sourceSessionId,
59
67
  verified: input.sourceSessionVerified === true,
60
68
  } : null;
61
- try { return await runtime.callTool(input.tool, input.arguments ?? {}, { ...context, session }); }
69
+ try {
70
+ const result = await runtime.callTool(input.tool, input.arguments ?? {}, { ...context, session });
71
+ if (!result || typeof result !== 'object' || Array.isArray(result)) return result;
72
+ return { ...result, collaboration_receipt: employeeCollaborationReceipt(app, context, {
73
+ tool: input.tool, permission: tool.permission, sourceApplication: input.sourceApplication
74
+ }) };
75
+ }
62
76
  catch (error) {
63
77
  if (Number.isInteger(error?.status) && error.status >= 400 && error.status < 500) {
64
78
  throw new EmployeeError(error.message, error.status, error.code ?? 'tool_error');
@@ -72,7 +86,7 @@ export function createEmployeeService({ app, runtimeConfigPath, registry: config
72
86
  recruit: async (input) => recruitment.recruit(await resolveInput(input)),
73
87
  workspace, describeTools, callTool,
74
88
  authorize: recruitment.authorize,
75
- taskEntry: (input) => employeeTaskEntry({ registry, recruitment, workspace }, input)
89
+ taskEntry: (input) => employeeTaskEntry({ app, registry, recruitment, workspace }, input)
76
90
  .catch(() => ({ status: 'unavailable', managers: [], worker_started: false })),
77
91
  async handleHttp(request, response, input) {
78
92
  const context = await workspace(input);
@@ -1,6 +1,7 @@
1
1
  // Management is a task-entry capability, not a replacement for the specialist
2
2
  // role/executor chosen by FULI. Never persist the user's prompt or start a model.
3
- export async function employeeTaskEntry({ registry, recruitment, workspace }, input) {
3
+ import { employeeCollaborationReceipt } from '../agents/identity-receipt.js';
4
+ export async function employeeTaskEntry({ app, registry, recruitment, workspace }, input) {
4
5
  if (!input.personalProjectId) return { status: 'project_unresolved', managers: [], worker_started: false };
5
6
  const managers = [];
6
7
  let unavailable = false;
@@ -18,6 +19,9 @@ export async function employeeTaskEntry({ registry, recruitment, workspace }, in
18
19
  const board = await runtime.callTool(manifest.taskEntry.boardTool, { limit: 8 }, resolved);
19
20
  managers.push({
20
21
  template_id: manifest.id, agent_id: context.agentId, name: manifest.name,
22
+ collaboration_receipt: employeeCollaborationReceipt(app, resolved, {
23
+ tool: manifest.taskEntry.boardTool, permission: 'board.read', sourceApplication: input.sourceApplication
24
+ }),
21
25
  personal_project_id: context.project.id, scope_mode: context.management.mode,
22
26
  title_mode: context.management.titleMode, title_style: context.management.titleStyle,
23
27
  board: { version: board.version, total: board.total, truncated: board.truncated,
@@ -1,4 +1,5 @@
1
1
  import { detectSensitiveContent } from '../security/sensitive-content.js';
2
+ import { agentContinuation, taskAgentReceipt } from '../agents/identity-receipt.js';
2
3
 
3
4
  export function conversationScope(application, input) {
4
5
  return { personal_space_id: application.config.personal.spaceId,
@@ -46,16 +47,29 @@ export async function checkpointConversation(application, task, workLog) {
46
47
  ...conversationTask(application, task), summary: safeConversationContent(workLog.summary), status: workLog.status,
47
48
  events: [{ event_id: `${task.token}:summary`, role: 'summary', kind: 'checkpoint', content: safeConversationContent(workLog.summary) }]
48
49
  });
50
+ if (result.status !== 'saved') return result;
51
+ let name = null;
52
+ try {
53
+ const agent = await application.personal.getProjectAgent(application.config.personal.spaceId, task.personalProjectId, task.projectAgentId);
54
+ name = agent?.profile?.display_name || agent?.profile?.name || null;
55
+ } catch { /* Metadata availability does not undo a successful save. */ }
49
56
  return { ...result, agent_id: task.projectAgentId, work_status: workLog.status,
57
+ receipt: taskAgentReceipt(application, { projectId: task.personalProjectId,
58
+ agentId: task.projectAgentId, name, sourceApplication: task.sourceApplication,
59
+ workStatus: workLog.status, conversationId: result.conversation_id,
60
+ persistence: result.status }),
50
61
  coverage: 'task_summary; visible transcript capture is confirmed separately by the host hook',
51
62
  guidance: 'Include the Agent name, truthful work status and conversation ID in the final receipt. In another client select this Agent, then call resume_agent_conversation with its current taskContextToken and this conversation ID.' };
52
63
  } catch { return unavailable(); }
53
64
  }
54
65
 
55
66
  export async function queryConversations(application, input) {
56
- return application.personal.conversation('query', { ...conversationScope(application, input),
67
+ const result = await application.personal.conversation('query', { ...conversationScope(application, input),
57
68
  mode: input.mode ?? 'list', ...(input.conversationId ? { conversation_id: input.conversationId } : {}),
58
69
  after: input.after ?? 0, limit: input.limit ?? (input.mode === 'events' ? 3 : 10) });
70
+ if (!Array.isArray(result.conversations)) return result;
71
+ return { ...result, conversations: result.conversations.map(conversation => ({ ...conversation,
72
+ continuation_prompt: agentContinuation(input.personalProjectId, input.agentId, conversation.id).prompt })) };
59
73
  }
60
74
  export async function resumeConversation(application, input) {
61
75
  const task = await application.taskContextRegistry.context(input.taskContextToken, input.sourceApplication);
@@ -69,7 +69,7 @@ export async function beginTaskContext(application, {
69
69
  profile_capture: 'Evaluate durable artifact taste (including writing voice, wording, structure and formatting), stable collaboration personality, and decision preferences separately. Do not default every preference to judgment_preference. Use tasteDomain: writing for writing evidence; retain exact session/turn/time provenance. Agent-inferred personality remains pending. Report actual missing evidence rather than promising automatic progress from use alone.',
70
70
  human_attention: 'When the selected Project Agent needs a human answer, decision, review or permission, use request_agent_attention with the exact current space/project/Agent IDs, a stable idempotency key and a specific requestedAction. Never raise a hand for ordinary running, queueing or automatic retries. Read list_agent_attention with status=resolved for replies; cancel obsolete requests with cancel_agent_attention. A response does not grant permission or accept task completion.',
71
71
  retrieval: 'Inspect task_knowledge_recall before asking for a stable project fact or method again. On a miss, use search_current_project_knowledge with focused action, artifact, target-system, or identifier queries; never use the full conversational request as the only query.',
72
- checkpoint: 'Before finishing, call checkpoint_task_knowledge with capture_candidates or retain_nothing. For an assigned employee include workLog with a concise result summary and truthful status (completed, incomplete, failed, no_change, or reported), even when there is no knowledge to capture. When durable role context changed, also include agentMemory with the loaded revision and bounded merged notes. Do not overwrite from truncated context or store raw transcripts.'
72
+ checkpoint: 'The task owner checkpoints once after integration. Coordinated workers return results and evidence to the coordinator; they must not begin or checkpoint an inherited parent context. Before finishing the owned task, call checkpoint_task_knowledge with capture_candidates or retain_nothing. For an assigned employee include workLog with a concise result summary and truthful status (completed, incomplete, failed, no_change, or reported), even when there is no knowledge to capture. When durable role context changed, also include agentMemory with the loaded revision and bounded merged notes. Do not overwrite from truncated context or store raw transcripts.'
73
73
  }
74
74
  };
75
75
  }
@@ -76,11 +76,8 @@ import {
76
76
  import {
77
77
  relatedProjectGuidance
78
78
  } from './related-project-suggestions.js';
79
- import {
80
- getCollaborationPreferences as getCollaborationPreferencesWorkflow
81
- } from './collaboration-preference-workflow.js';
82
79
  import { buildUserTasteSkill } from './user-taste-skill.js';
83
- import { resolveTaskEntryAgent, loadProjectAgentContinuity } from './project-agent-task-entry.js';
80
+ import { taskEntryPreferences } from './task-entry-preferences.js';
84
81
  import { getWritingTasteProfile as getWritingTasteProfileWorkflow } from './writing-taste-profile-workflow.js';
85
82
  import {
86
83
  groupSubscriptions,
@@ -256,42 +253,11 @@ export class FederatedGraphApplication extends ProjectAgentControlPlaneApplicati
256
253
  agentToolName = 'get_collaboration_preferences'
257
254
  } = {}) {
258
255
  const projectResolution = await this.#resolvePreferenceProject({ personalProjectId, projectPath });
259
- const selection = projectPath === null && !personalProjectId && !projectAgentId
260
- ? null
261
- : await resolveTaskEntryAgent(
262
- this, projectResolution, { projectAgentId, taskPrompt, sourceApplication,
263
- sessionId: agentToolName === 'get_collaboration_preferences'
264
- ? sessionId ?? sourceSessionId : turnId ? sessionId : null,
265
- turnId: agentToolName === 'begin_task_context' ? turnId : null,
266
- workKind, requiredCapabilities, agentInvocation, agentToolName }
267
- );
268
- // A rejected explicit role must not leak its private preferences via fallback.
269
- const selectedAgentId = selection ? selection.agent?.agentId ?? null : projectAgentId;
270
- const preferences = await getCollaborationPreferencesWorkflow(
271
- this,
272
- projectResolution,
273
- {
274
- projectAgentId: selectedAgentId,
275
- taskPrompt,
276
- limit,
277
- agentInvocation,
278
- agentToolName
279
- },
280
- (items, toolName) => this.#recordAgentViews(items, toolName)
281
- );
282
- const management = this.employees && agentInvocation &&
283
- ['begin_task_context', 'get_collaboration_preferences'].includes(agentToolName)
284
- ? await this.employees.taskEntry({ personalProjectId: projectResolution.personalProjectId,
285
- sourceApplication, sourceSessionId, sessionId })
286
- : null;
287
- const managedPreferences = management ? { ...preferences, project_management_context: management } : preferences;
288
- if (!selection) return managedPreferences;
289
- const context = selection.agent ? await loadProjectAgentContinuity(this, {
290
- projectId: projectResolution.personalProjectId, agent: selection.agent,
291
- sourceApplication, taskPrompt, selectionReason: selection.reason,
292
- matchBasis: selection.match_basis
293
- }) : selection;
294
- return { ...managedPreferences, project_agent_context: context };
256
+ return taskEntryPreferences(this, projectResolution, {
257
+ personalProjectId, projectAgentId, projectPath, taskPrompt, sourceApplication,
258
+ sourceSessionId, sessionId, turnId, workKind, requiredCapabilities,
259
+ limit, agentInvocation, agentToolName
260
+ }, (items, toolName) => this.#recordAgentViews(items, toolName));
295
261
  }
296
262
 
297
263
  async getUserTasteSkill({