fuli-context 0.7.4-beta.3 → 0.7.6

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 (130) hide show
  1. package/README.md +77 -6
  2. package/README.zh-CN.md +69 -6
  3. package/compose.graphiti.yml +6 -6
  4. package/dist/web/assets/AboutPage-sLV9WChi.js +1 -0
  5. package/dist/web/assets/ConnectionsPage-BWn4ywGJ.js +1 -0
  6. package/dist/web/assets/KnowledgeEditDialog-CZ5ziPrD.js +1 -0
  7. package/dist/web/assets/KnowledgeInspector-CRQWOaos.js +1 -0
  8. package/dist/web/assets/KnowledgeOrganizerPage-Dfj3vIpU.js +1 -0
  9. package/dist/web/assets/KnowledgePage-BRYC9S5Y.js +1 -0
  10. package/dist/web/assets/KnowledgeWorkspace-ChEB5Bwk.js +4 -0
  11. package/dist/web/assets/OverviewPage-Cd66M0RT.js +1 -0
  12. package/dist/web/assets/PersonalProfilePage-RCFHL0Wz.js +1 -0
  13. package/dist/web/assets/PersonalProjectsPage-DEh8XWby.js +1 -0
  14. package/dist/web/assets/ProjectAgentsPage-Crh7PrXa.css +1 -0
  15. package/dist/web/assets/ProjectAgentsPage-DPnaH-TR.js +11 -0
  16. package/dist/web/assets/PublicProjectsPage-mstPEjHe.js +1 -0
  17. package/dist/web/assets/ReviewPage-6Y1SmfZy.js +1 -0
  18. package/dist/web/assets/SearchableSelect-D3HM8YF8.js +1 -0
  19. package/dist/web/assets/SettingsPage--LdTMUm7.js +1 -0
  20. package/dist/web/assets/SettingsPage-CwZp2ka4.css +1 -0
  21. package/dist/web/assets/WritingTastePage-CIpKswvs.js +1 -0
  22. package/dist/web/assets/{console-D_2zC4lm.js → console-BT1P2Db4.js} +1 -1
  23. package/dist/web/assets/{conversation-launcher-settings-DQmMH2j7.js → conversation-launcher-settings-DrJEotdf.js} +1 -1
  24. package/dist/web/assets/i18n-js6gfc7j.js +9 -0
  25. package/dist/web/assets/{identity-DmGcY3Y6.js → identity-CfDfanpa.js} +1 -1
  26. package/dist/web/assets/index-BnH5mcZx.css +1 -0
  27. package/dist/web/assets/index-GqJf1xqg.js +2 -0
  28. package/dist/web/assets/{model-CVoOdRsL.js → model-DJkLAJsg.js} +1 -1
  29. package/dist/web/assets/{useApi-CROJJdhE-CBNg4CdH.js → useApi-CROJJdhE-BdSti7Y2.js} +1 -1
  30. package/dist/web/assets/vue-router-Kk67EaCP.js +1 -0
  31. package/dist/web/index.html +6 -6
  32. package/graph-provider/fuli_graph/app.py +8 -0
  33. package/graph-provider/fuli_graph/collaboration_context.py +69 -6
  34. package/graph-provider/fuli_graph/knowledge_search.py +84 -5
  35. package/graph-provider/fuli_graph/model_validation.py +130 -0
  36. package/graph-provider/fuli_graph/models.py +24 -5
  37. package/graph-provider/fuli_graph/project_agent_access.py +54 -0
  38. package/graph-provider/fuli_graph/project_agent_coordination_models.py +22 -0
  39. package/graph-provider/fuli_graph/project_agent_executor_models.py +635 -0
  40. package/graph-provider/fuli_graph/project_agent_models.py +278 -0
  41. package/graph-provider/fuli_graph/project_agent_routes.py +626 -0
  42. package/graph-provider/fuli_graph/project_agent_task_models.py +545 -0
  43. package/graph-provider/fuli_graph/runtime.py +259 -0
  44. package/graph-provider/fuli_graph/search_projection.py +3 -0
  45. package/graph-provider/fuli_graph/store.py +6 -0
  46. package/graph-provider/fuli_graph/store_knowledge.py +62 -9
  47. package/graph-provider/fuli_graph/store_project_agent_coordination_policy.py +122 -0
  48. package/graph-provider/fuli_graph/store_project_agent_executor_learning.py +756 -0
  49. package/graph-provider/fuli_graph/store_project_agent_executor_routing.py +601 -0
  50. package/graph-provider/fuli_graph/store_project_agent_executors.py +1087 -0
  51. package/graph-provider/fuli_graph/store_project_agent_task_reads.py +458 -0
  52. package/graph-provider/fuli_graph/store_project_agent_task_recruitment.py +705 -0
  53. package/graph-provider/fuli_graph/store_project_agent_task_staffing.py +586 -0
  54. package/graph-provider/fuli_graph/store_project_agent_tasks.py +1151 -0
  55. package/graph-provider/fuli_graph/store_project_agents.py +965 -0
  56. package/graph-provider/pyproject.toml +2 -1
  57. package/npm-shrinkwrap.json +2 -2
  58. package/package.json +1 -1
  59. package/skills/capturing-session-knowledge/SKILL.md +39 -0
  60. package/src/adaptive-runtime/executor-pool.js +127 -0
  61. package/src/adaptive-runtime/graph-services.js +166 -0
  62. package/src/adaptive-runtime/lease-client.js +335 -0
  63. package/src/adaptive-runtime/runtime-broker.js +318 -0
  64. package/src/adaptive-runtime/settings.js +111 -0
  65. package/src/adaptive-runtime/state.js +74 -0
  66. package/src/agent-tools/graph-definitions.js +419 -0
  67. package/src/agent-tools/graph-handlers.js +65 -0
  68. package/src/agent-tools/project-agent-definitions.js +307 -0
  69. package/src/backup/graph-backup.js +289 -0
  70. package/src/backup/identity-reconciliation.js +208 -0
  71. package/src/backup/runtime-adapters.js +269 -0
  72. package/src/cli/command-registry.js +6 -2
  73. package/src/cli/graph-data-command.js +112 -0
  74. package/src/cli/local-runtime-command.js +16 -2
  75. package/src/cli/setup-command.js +19 -3
  76. package/src/cli/update-command.js +4 -0
  77. package/src/cli.js +6 -0
  78. package/src/graphiti/collaboration-preference-projection.js +29 -1
  79. package/src/graphiti/collaboration-preference-workflow.js +121 -0
  80. package/src/graphiti/federated-application.js +53 -90
  81. package/src/graphiti/project-agent-control-plane.js +275 -0
  82. package/src/graphiti/project-agent-mapping.js +1024 -0
  83. package/src/graphiti/project-agent-workflows.js +576 -0
  84. package/src/graphiti/provider-client.js +282 -1
  85. package/src/http/api-router.js +63 -9
  86. package/src/http/graph-api-router.js +290 -0
  87. package/src/local-runtime/lifecycle.js +46 -10
  88. package/src/mcp/context-resources.js +15 -6
  89. package/src/mcp/create-mcp-server.js +51 -16
  90. package/src/mcp/instructions.js +7 -5
  91. package/src/mcp/runtime.js +17 -5
  92. package/src/mcp/session-id.js +38 -1
  93. package/src/mcp/tool-annotations.js +71 -0
  94. package/src/mcp-server.js +23 -6
  95. package/src/native-runtime/runtime.js +717 -0
  96. package/src/server.js +6 -2
  97. package/src/setup/agent-installation-status.js +7 -1
  98. package/src/setup/agents.js +3 -1
  99. package/src/setup/claude-code-config.js +3 -1
  100. package/src/setup/codex-bootstrap.js +11 -10
  101. package/src/setup/codex-config.js +7 -1
  102. package/src/setup/cursor-config.js +3 -1
  103. package/src/setup/graph-runtime.js +158 -13
  104. package/src/setup/neo4j-memory-profile.js +43 -0
  105. package/src/setup/options.js +38 -2
  106. package/src/setup/paths.js +14 -0
  107. package/src/setup/setup.js +37 -1
  108. package/src/system/resource-monitor.js +181 -16
  109. package/src/system/runtime-settings.js +10 -1
  110. package/src/system/system-service.js +27 -2
  111. package/dist/web/assets/AboutPage-Dpf5fRWv.js +0 -1
  112. package/dist/web/assets/ConnectionsPage-D21aUDCP.js +0 -1
  113. package/dist/web/assets/KnowledgeEditDialog-u7lpfN3j.js +0 -1
  114. package/dist/web/assets/KnowledgeInspector-k4xnHtXd.js +0 -1
  115. package/dist/web/assets/KnowledgeOrganizerPage-Bdj7Rn84.js +0 -1
  116. package/dist/web/assets/KnowledgePage-UHVAbC1W.js +0 -1
  117. package/dist/web/assets/KnowledgeWorkspace-Lq3d-MeC.js +0 -4
  118. package/dist/web/assets/OverviewPage-CycAgA1V.js +0 -1
  119. package/dist/web/assets/PersonalProfilePage-U9oIV5FT.js +0 -1
  120. package/dist/web/assets/PersonalProjectsPage-C5RpXKcA.js +0 -1
  121. package/dist/web/assets/PublicProjectsPage-DiWxm35E.js +0 -1
  122. package/dist/web/assets/ReviewPage-jxRfGSJK.js +0 -1
  123. package/dist/web/assets/SearchableSelect-B3-5IPup.js +0 -1
  124. package/dist/web/assets/SettingsPage-BqfGDOho.js +0 -1
  125. package/dist/web/assets/SettingsPage-CAfz4qiV.css +0 -1
  126. package/dist/web/assets/WritingTastePage-BJxhMqJR.js +0 -1
  127. package/dist/web/assets/i18n-CwqXDI1C.js +0 -5
  128. package/dist/web/assets/index-IUGLt4lG.js +0 -2
  129. package/dist/web/assets/index-X2j29j8i.css +0 -1
  130. package/dist/web/assets/vue-router-CWSsUUio.js +0 -1
@@ -0,0 +1,545 @@
1
+ from datetime import date, datetime
2
+ from typing import Literal
3
+
4
+ from pydantic import AliasChoices, Field, field_validator, model_validator
5
+
6
+ from .model_validation import reject_credentials, validate_emoji_sequence
7
+ from .models import SourceApplication, StrictModel
8
+ from .project_agent_models import (
9
+ ProjectAgentExecutorPolicy,
10
+ ProjectAgentModelStrategy,
11
+ ProjectAgentProfile,
12
+ ProjectAgentRecord,
13
+ )
14
+
15
+
16
+ ProjectAgentTaskDuration = Literal['ongoing', 'one_off']
17
+ ProjectAgentStaffingIntent = Literal[
18
+ 'reuse_preferred',
19
+ 'new_durable',
20
+ 'temporary',
21
+ 'unassigned',
22
+ ]
23
+ ProjectAgentTaskStatus = Literal[
24
+ 'awaiting_recruitment',
25
+ 'queued',
26
+ 'running',
27
+ 'paused',
28
+ 'failed',
29
+ 'awaiting_review',
30
+ 'blocked',
31
+ 'completed',
32
+ 'cancelled',
33
+ ]
34
+ ProjectAgentParticipantRole = Literal['lead', 'collaborator']
35
+ ProjectAgentRoutingOutcome = Literal[
36
+ 'assigned_existing',
37
+ 'recruited',
38
+ 'unassigned',
39
+ 'recruitment_required',
40
+ 'blocked',
41
+ ]
42
+ ProjectAgentRoutingReason = Literal[
43
+ 'explicit_agent',
44
+ 'project_continuity',
45
+ 'sole_active_assignment',
46
+ 'manual_agent_selection',
47
+ 'exact_work_kind',
48
+ 'exact_capability',
49
+ 'no_match',
50
+ 'agent_unavailable',
51
+ 'explicit_new_agent',
52
+ 'explicit_temporary_agent',
53
+ 'explicit_unassigned',
54
+ 'hr_unavailable',
55
+ ]
56
+ ProjectAgentPositionKind = Literal['durable', 'temporary']
57
+ ProjectAgentRecruitmentStatus = Literal[
58
+ 'awaiting_confirmation',
59
+ 'requested',
60
+ 'fulfilled',
61
+ 'cancelled',
62
+ 'blocked',
63
+ 'no_hr',
64
+ ]
65
+ ProjectAgentRecruitmentConfirmationMode = Literal[
66
+ 'automatic',
67
+ 'require_confirmation',
68
+ ]
69
+ ProjectAgentTaskComplexity = Literal['simple', 'standard', 'complex']
70
+ ProjectAgentOptimizationPriority = Literal[
71
+ 'quality_and_acceptance',
72
+ 'token_and_cost',
73
+ 'time',
74
+ ]
75
+
76
+
77
+ def _unique_text(value: list[str], label: str) -> list[str]:
78
+ normalized = [item.strip() for item in value]
79
+ if any(not item or len(item) > 512 for item in normalized):
80
+ raise ValueError(f'{label} must contain 1 to 512 characters')
81
+ if len({item.casefold() for item in normalized}) != len(normalized):
82
+ raise ValueError(f'{label} must be unique')
83
+ return normalized
84
+
85
+
86
+ class ProjectAgentParallelPlan(StrictModel):
87
+ enabled: bool = False
88
+ independent_verification: bool = False
89
+ conflict_free_scopes: bool = False
90
+ reason: str | None = Field(default=None, min_length=1, max_length=2048)
91
+ workstream_boundaries: list[str] = Field(default_factory=list, max_length=16)
92
+
93
+ @field_validator('workstream_boundaries')
94
+ @classmethod
95
+ def validate_boundaries(cls, value: list[str]) -> list[str]:
96
+ return _unique_text(value, 'parallel workstream boundaries')
97
+
98
+ @model_validator(mode='after')
99
+ def validate_parallel_plan(self):
100
+ if self.enabled and not (
101
+ self.independent_verification
102
+ and self.conflict_free_scopes
103
+ and self.reason
104
+ and len(self.workstream_boundaries) >= 2
105
+ ):
106
+ raise ValueError(
107
+ 'parallel work requires independent verification, conflict-free '
108
+ 'scopes, a reason, and at least two workstream boundaries'
109
+ )
110
+ if not self.enabled and self.workstream_boundaries:
111
+ raise ValueError('disabled parallel plan cannot contain workstreams')
112
+ return self
113
+
114
+
115
+ class ProjectAgentTaskSubmit(StrictModel):
116
+ personal_space_id: str = Field(min_length=1, max_length=128)
117
+ personal_project_id: str = Field(min_length=1, max_length=128)
118
+ idempotency_key: str = Field(min_length=8, max_length=256)
119
+ title: str = Field(min_length=1, max_length=160)
120
+ objective: str = Field(min_length=1, max_length=4096)
121
+ work_kind: str = Field(min_length=1, max_length=128)
122
+ required_capabilities: list[str] = Field(default_factory=list, max_length=16)
123
+ duration: ProjectAgentTaskDuration = 'ongoing'
124
+ staffing_intent: ProjectAgentStaffingIntent = 'reuse_preferred'
125
+ lead_agent_id: str | None = Field(default=None, min_length=1, max_length=128)
126
+ collaborator_agent_ids: list[str] = Field(default_factory=list, max_length=16)
127
+ coordinator_agent_id: str | None = Field(default=None, min_length=1, max_length=128)
128
+ complexity_hint: ProjectAgentTaskComplexity | None = None
129
+ parallel_plan: ProjectAgentParallelPlan = Field(
130
+ default_factory=ProjectAgentParallelPlan
131
+ )
132
+ model_strategy_override: ProjectAgentModelStrategy | None = None
133
+ executor_policy_override: ProjectAgentExecutorPolicy | None = None
134
+ # Compatibility spelling used by the task store's executor hook.
135
+ executor_override: ProjectAgentExecutorPolicy | None = None
136
+ source_application: SourceApplication | None = None
137
+ source_session_id: str | None = Field(default=None, min_length=1, max_length=256)
138
+ routing_reason: str = Field(min_length=1, max_length=2048)
139
+ recruitment_profile: ProjectAgentProfile | None = None
140
+
141
+ @field_validator('required_capabilities')
142
+ @classmethod
143
+ def validate_capabilities(cls, value: list[str]) -> list[str]:
144
+ return _unique_text(value, 'required capabilities')
145
+
146
+ @field_validator('collaborator_agent_ids')
147
+ @classmethod
148
+ def validate_collaborators(cls, value: list[str]) -> list[str]:
149
+ return _unique_text(value, 'collaborator Agent IDs')
150
+
151
+ @model_validator(mode='after')
152
+ def validate_staffing(self):
153
+ if self.lead_agent_id and self.staffing_intent not in {
154
+ 'reuse_preferred',
155
+ }:
156
+ raise ValueError(
157
+ 'explicit lead Agent requires reuse_preferred staffing intent'
158
+ )
159
+ if self.lead_agent_id in self.collaborator_agent_ids:
160
+ raise ValueError('lead Agent cannot also be a collaborator')
161
+ # The coordinator may fill missing collaborators from qualified durable
162
+ # assignments; the final participant minimum is checked after routing.
163
+ if self.staffing_intent == 'temporary' and self.duration != 'one_off':
164
+ raise ValueError('temporary staffing requires a one-off task')
165
+ if (
166
+ self.recruitment_profile
167
+ and self.recruitment_profile.agent_type != 'durable'
168
+ ):
169
+ raise ValueError('recruitment profile agent_type must be durable input')
170
+ if self.executor_policy_override and self.executor_override:
171
+ raise ValueError(
172
+ 'executor_policy_override and executor_override are mutually exclusive'
173
+ )
174
+ reject_credentials(self, 'project Agent task')
175
+ return self
176
+
177
+
178
+ class ProjectAgentTaskParticipantRecord(StrictModel):
179
+ agent_id: str
180
+ role: ProjectAgentParticipantRole
181
+ status: ProjectAgentTaskStatus
182
+ assignment_summary: str | None = None
183
+ joined_at: datetime
184
+ updated_at: datetime
185
+ ended_at: datetime | None = None
186
+
187
+
188
+ class ProjectAgentTaskExecutionSummary(StrictModel):
189
+ """Auditable execution evidence projected onto a real task participant.
190
+
191
+ The participant identity is durable Agent data; the optional worker fields
192
+ identify a concrete subagent run when a client reports one. None means the
193
+ provider did not observe that fact and must not infer it from routing
194
+ configuration.
195
+ """
196
+
197
+ agent_id: str
198
+ agent_name: str | None = None
199
+ occupation_emoji: str | None = None
200
+ participant_role: ProjectAgentParticipantRole
201
+ executor: str | None = None
202
+ executor_id: str | None = None
203
+ source_application: SourceApplication | None = None
204
+ actual_model_provider: str | None = None
205
+ actual_model: str | None = None
206
+ work_summary: str | None = None
207
+ status: ProjectAgentTaskStatus
208
+ worker_id: str | None = None
209
+ worker_label: str | None = None
210
+ worker_occupation_emoji: str | None = None
211
+
212
+ @field_validator('occupation_emoji', 'worker_occupation_emoji', mode='before')
213
+ @classmethod
214
+ def validate_emojis(cls, value):
215
+ return validate_emoji_sequence(value, 'occupation emoji')
216
+
217
+
218
+ class ProjectAgentTaskEventRecord(StrictModel):
219
+ event_id: str
220
+ task_id: str
221
+ agent_id: str | None = None
222
+ status: ProjectAgentTaskStatus
223
+ actor_kind: Literal['agent', 'human', 'system', 'hr']
224
+ summary: str
225
+ source_application: SourceApplication | None = None
226
+ source_session_id: str | None = None
227
+ actual_model_provider: str | None = None
228
+ actual_model: str | None = None
229
+ actual_executor_id: str | None = None
230
+ executor_rule_id: str | None = None
231
+ matched_executor_rule_id: str | None = None
232
+ executor_selection_reason: str | None = None
233
+ executor_fallback_reason: str | None = None
234
+ executor_blocked_reason: str | None = None
235
+ worker_id: str | None = Field(
236
+ default=None,
237
+ validation_alias=AliasChoices('worker_id', 'workerId'),
238
+ )
239
+ worker_label: str | None = Field(
240
+ default=None,
241
+ validation_alias=AliasChoices('worker_label', 'workerLabel'),
242
+ )
243
+ worker_occupation_emoji: str | None = Field(
244
+ default=None,
245
+ validation_alias=AliasChoices(
246
+ 'worker_occupation_emoji',
247
+ 'workerOccupationEmoji',
248
+ ),
249
+ )
250
+ worker_status: ProjectAgentTaskStatus | None = Field(
251
+ default=None,
252
+ validation_alias=AliasChoices('worker_status', 'workerStatus'),
253
+ )
254
+ created_at: datetime
255
+
256
+ @field_validator('worker_occupation_emoji', mode='before')
257
+ @classmethod
258
+ def validate_worker_occupation_emoji(cls, value):
259
+ return validate_emoji_sequence(value, 'worker occupation emoji')
260
+
261
+
262
+ class ProjectAgentRoutingDecisionRecord(StrictModel):
263
+ decision_id: str
264
+ task_id: str
265
+ coordinator_agent_id: str
266
+ complexity: ProjectAgentTaskComplexity
267
+ complexity_basis: list[str] = Field(default_factory=list)
268
+ selected_model_strategy: ProjectAgentModelStrategy | None = None
269
+ model_strategy_source: Literal['agent', 'assignment', 'task', 'coordinator']
270
+ outcome: ProjectAgentRoutingOutcome
271
+ reason: ProjectAgentRoutingReason
272
+ match_basis: list[str] = Field(default_factory=list)
273
+ candidate_agent_ids: list[str] = Field(default_factory=list)
274
+ optimization_priority: list[ProjectAgentOptimizationPriority] = Field(
275
+ default_factory=lambda: [
276
+ 'quality_and_acceptance',
277
+ 'token_and_cost',
278
+ 'time',
279
+ ]
280
+ )
281
+ parallel_plan: ProjectAgentParallelPlan = Field(
282
+ default_factory=ProjectAgentParallelPlan
283
+ )
284
+ selected_executor_id: str | None = None
285
+ executor_rule_id: str | None = None
286
+ matched_executor_rule_id: str | None = None
287
+ executor_selection_reason: str | None = None
288
+ executor_fallback_outcome: str | None = None
289
+ executor_blocked_reason: str | None = None
290
+ executor_decision: dict[str, object] | None = None
291
+ executor_policy: ProjectAgentExecutorPolicy = Field(
292
+ default_factory=ProjectAgentExecutorPolicy
293
+ )
294
+ created_at: datetime
295
+
296
+
297
+ class ProjectAgentTaskRecord(StrictModel):
298
+ task_id: str
299
+ personal_space_id: str
300
+ personal_project_id: str
301
+ title: str
302
+ objective: str
303
+ work_kind: str
304
+ required_capabilities: list[str]
305
+ duration: ProjectAgentTaskDuration
306
+ staffing_intent: ProjectAgentStaffingIntent
307
+ status: ProjectAgentTaskStatus
308
+ revision: int = Field(ge=0)
309
+ routing_outcome: ProjectAgentRoutingOutcome
310
+ routing_reason: ProjectAgentRoutingReason
311
+ routing_explanation: str
312
+ match_basis: list[str] = Field(default_factory=list)
313
+ coordinator_agent_id: str
314
+ complexity: ProjectAgentTaskComplexity
315
+ complexity_basis: list[str] = Field(default_factory=list)
316
+ routing_decision: ProjectAgentRoutingDecisionRecord
317
+ lead_agent_id: str | None = None
318
+ participants: list[ProjectAgentTaskParticipantRecord] = Field(default_factory=list)
319
+ execution_summary: list[ProjectAgentTaskExecutionSummary] = Field(
320
+ default_factory=list,
321
+ validation_alias=AliasChoices('execution_summary', 'executionSummary'),
322
+ )
323
+ effective_model_strategy: ProjectAgentModelStrategy | None = None
324
+ model_strategy_source: Literal[
325
+ 'agent',
326
+ 'assignment',
327
+ 'task',
328
+ 'coordinator',
329
+ ] = 'coordinator'
330
+ executor_policy: ProjectAgentExecutorPolicy = Field(
331
+ default_factory=ProjectAgentExecutorPolicy
332
+ )
333
+ selected_executor_id: str | None = None
334
+ executor_rule_id: str | None = None
335
+ actual_run_id: str | None = None
336
+ actual_executor_id: str | None = None
337
+ actual_model_provider: str | None = None
338
+ actual_model: str | None = None
339
+ matched_executor_rule_id: str | None = None
340
+ executor_selection_reason: str | None = None
341
+ executor_fallback_outcome: str | None = None
342
+ executor_fallback_reason: str | None = None
343
+ executor_blocked_reason: str | None = None
344
+ executor_decision: dict[str, object] | None = None
345
+ hr_agent_id: str | None = None
346
+ recruitment_id: str | None = None
347
+ source_application: SourceApplication | None = None
348
+ source_session_id: str | None = None
349
+ result_summary: str | None = None
350
+ failure_reason: str | None = None
351
+ created_at: datetime
352
+ updated_at: datetime
353
+ completed_at: datetime | None = None
354
+ events: list[ProjectAgentTaskEventRecord] = Field(default_factory=list)
355
+
356
+
357
+ class ProjectAgentTaskActivityCreate(StrictModel):
358
+ personal_space_id: str = Field(min_length=1, max_length=128)
359
+ personal_project_id: str = Field(min_length=1, max_length=128)
360
+ task_id: str = Field(min_length=1, max_length=128)
361
+ idempotency_key: str = Field(min_length=8, max_length=256)
362
+ expected_revision: int | None = Field(default=None, ge=0)
363
+ status: ProjectAgentTaskStatus
364
+ summary: str = Field(min_length=1, max_length=4096)
365
+ agent_id: str | None = Field(default=None, min_length=1, max_length=128)
366
+ actor_kind: Literal['agent', 'human'] = 'agent'
367
+ source_application: SourceApplication | None = None
368
+ source_session_id: str | None = Field(default=None, min_length=1, max_length=256)
369
+ actual_model_provider: str | None = Field(default=None, min_length=1, max_length=128)
370
+ actual_model: str | None = Field(default=None, min_length=1, max_length=256)
371
+ actual_executor_id: str | None = Field(default=None, min_length=1, max_length=128)
372
+ matched_executor_rule_id: str | None = Field(
373
+ default=None,
374
+ min_length=1,
375
+ max_length=128,
376
+ )
377
+ executor_selection_reason: str | None = Field(
378
+ default=None,
379
+ min_length=1,
380
+ max_length=2048,
381
+ )
382
+ executor_fallback_reason: str | None = Field(
383
+ default=None,
384
+ min_length=1,
385
+ max_length=2048,
386
+ )
387
+ executor_blocked_reason: str | None = Field(
388
+ default=None,
389
+ min_length=1,
390
+ max_length=2048,
391
+ )
392
+ worker_id: str | None = Field(
393
+ default=None,
394
+ min_length=1,
395
+ max_length=128,
396
+ validation_alias=AliasChoices('worker_id', 'workerId'),
397
+ )
398
+ worker_label: str | None = Field(
399
+ default=None,
400
+ min_length=1,
401
+ max_length=160,
402
+ validation_alias=AliasChoices('worker_label', 'workerLabel'),
403
+ )
404
+ worker_occupation_emoji: str | None = Field(
405
+ default=None,
406
+ min_length=1,
407
+ max_length=32,
408
+ validation_alias=AliasChoices(
409
+ 'worker_occupation_emoji',
410
+ 'workerOccupationEmoji',
411
+ ),
412
+ )
413
+ worker_status: ProjectAgentTaskStatus | None = Field(
414
+ default=None,
415
+ validation_alias=AliasChoices('worker_status', 'workerStatus'),
416
+ )
417
+
418
+ @field_validator('worker_occupation_emoji', mode='before')
419
+ @classmethod
420
+ def validate_worker_occupation_emoji(cls, value):
421
+ return validate_emoji_sequence(value, 'worker occupation emoji')
422
+
423
+ @model_validator(mode='after')
424
+ def validate_actual_model(self):
425
+ if self.status in {'completed', 'failed'} and not self.agent_id:
426
+ raise ValueError(
427
+ 'completed or failed activity requires the participating Agent ID'
428
+ )
429
+ if bool(self.actual_model_provider) != bool(self.actual_model):
430
+ raise ValueError('actual model provider and model must be reported together')
431
+ if bool(self.actual_executor_id) != bool(self.actual_model):
432
+ raise ValueError(
433
+ 'actual executor, model provider, and model must be reported together'
434
+ )
435
+ if self.actual_executor_id and not self.agent_id:
436
+ raise ValueError('actual execution report requires the participating Agent ID')
437
+ if self.worker_id and not self.agent_id:
438
+ raise ValueError('worker execution report requires the participating Agent ID')
439
+ if (self.worker_label or self.worker_occupation_emoji) and not self.worker_id:
440
+ raise ValueError(
441
+ 'worker label or occupation emoji requires a worker ID'
442
+ )
443
+ reject_credentials(self, 'project Agent task activity')
444
+ return self
445
+
446
+
447
+ class ProjectAgentRecruitmentRecord(StrictModel):
448
+ recruitment_id: str
449
+ personal_space_id: str
450
+ personal_project_id: str
451
+ task_id: str
452
+ coordinator_agent_id: str
453
+ hr_agent_id: str | None = None
454
+ position_kind: ProjectAgentPositionKind
455
+ work_kind: str
456
+ required_capabilities: list[str]
457
+ reason_code: ProjectAgentRoutingReason
458
+ reason: str
459
+ status: ProjectAgentRecruitmentStatus
460
+ confirmation_mode: ProjectAgentRecruitmentConfirmationMode
461
+ proposed_agent_id: str
462
+ proposed_profile: ProjectAgentProfile
463
+ trigger_source_application: SourceApplication | None = None
464
+ trigger_source_session_id: str | None = None
465
+ revision: int = Field(ge=0)
466
+ recruited_agent_id: str | None = None
467
+ test_source: str | None = Field(default=None, min_length=1, max_length=256)
468
+ cleanup_eligible: bool = False
469
+ created_at: datetime
470
+ updated_at: datetime
471
+ fulfilled_at: datetime | None = None
472
+
473
+
474
+ class ProjectAgentTaskRouteResult(StrictModel):
475
+ task: ProjectAgentTaskRecord
476
+ assigned_agent: ProjectAgentRecord | None = None
477
+ recruitment: ProjectAgentRecruitmentRecord | None = None
478
+ decision: Literal[
479
+ 'reused',
480
+ 'recruited',
481
+ 'unassigned',
482
+ 'awaiting_confirmation',
483
+ 'blocked',
484
+ ]
485
+ must_disclose_recruitment: bool = False
486
+ client_notice: str | None = None
487
+
488
+
489
+ class ProjectAgentRecruitmentPolicyRecord(StrictModel):
490
+ personal_space_id: str
491
+ confirmation_mode: ProjectAgentRecruitmentConfirmationMode = 'automatic'
492
+ updated_at: datetime | None = None
493
+
494
+
495
+ class ProjectAgentRecruitmentPolicyUpdate(StrictModel):
496
+ personal_space_id: str = Field(min_length=1, max_length=128)
497
+ confirmation_mode: ProjectAgentRecruitmentConfirmationMode
498
+
499
+
500
+ class ProjectAgentRecruitmentDecision(StrictModel):
501
+ personal_space_id: str = Field(min_length=1, max_length=128)
502
+ personal_project_id: str = Field(min_length=1, max_length=128)
503
+ recruitment_id: str = Field(min_length=1, max_length=128)
504
+ expected_revision: int = Field(ge=0)
505
+ decision: Literal['approve', 'cancel']
506
+ reason: str = Field(min_length=1, max_length=4096)
507
+
508
+ @model_validator(mode='after')
509
+ def reject_sensitive_values(self):
510
+ reject_credentials(self, 'project Agent recruitment decision')
511
+ return self
512
+
513
+
514
+ class ProjectAgentActivityTask(StrictModel):
515
+ task_id: str
516
+ title: str
517
+ status: Literal['completed', 'failed', 'cancelled']
518
+ summary: str
519
+ occurred_at: datetime
520
+ source_application: SourceApplication | None = None
521
+ actual_model_provider: str | None = None
522
+ actual_model: str | None = None
523
+ actual_executor_id: str | None = None
524
+ matched_executor_rule_id: str | None = None
525
+ worker_id: str | None = None
526
+ worker_label: str | None = None
527
+ worker_occupation_emoji: str | None = None
528
+ worker_status: ProjectAgentTaskStatus | None = None
529
+
530
+
531
+ class ProjectAgentActivityDay(StrictModel):
532
+ date: date
533
+ completed: int = Field(default=0, ge=0)
534
+ failed: int = Field(default=0, ge=0)
535
+ cancelled: int = Field(default=0, ge=0)
536
+ total: int = Field(default=0, ge=0)
537
+ tasks: list[ProjectAgentActivityTask] = Field(default_factory=list)
538
+
539
+
540
+ class ProjectAgentActivityResult(StrictModel):
541
+ agent_id: str
542
+ personal_space_id: str
543
+ from_date: date
544
+ to_date: date
545
+ days: list[ProjectAgentActivityDay] = Field(default_factory=list)