fuli-context 0.7.6 → 0.7.7

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 (146) hide show
  1. package/README.md +69 -13
  2. package/README.zh-CN.md +60 -14
  3. package/dist/web/assets/{AboutPage-sLV9WChi.js → AboutPage-BcZeFcHe.js} +1 -1
  4. package/dist/web/assets/{ConnectionsPage-BWn4ywGJ.js → ConnectionsPage-xAQYl1SQ.js} +1 -1
  5. package/dist/web/assets/EmployeeRecruitDialog-CbPAmhy7.js +1 -0
  6. package/dist/web/assets/EmployeeRecruitDialog-CfFlyKSh.css +1 -0
  7. package/dist/web/assets/EmployeeWorkbenchPage-BQvTLoc8.css +1 -0
  8. package/dist/web/assets/EmployeeWorkbenchPage-DF3EL0Ui.js +1 -0
  9. package/dist/web/assets/KnowledgeEditDialog-UnnBDt0I.js +1 -0
  10. package/dist/web/assets/{KnowledgeEditDialog-DWUV_NPp.css → KnowledgeEditDialog-iv-dgUeD.css} +1 -1
  11. package/dist/web/assets/{KnowledgeInspector-CRQWOaos.js → KnowledgeInspector-BqPJH5Rr.js} +1 -1
  12. package/dist/web/assets/{KnowledgeOrganizerPage-Dfj3vIpU.js → KnowledgeOrganizerPage-CyvRflXM.js} +1 -1
  13. package/dist/web/assets/KnowledgePage-59eczTqe.js +1 -0
  14. package/dist/web/assets/{KnowledgeWorkspace-ChEB5Bwk.js → KnowledgeWorkspace-CfIjhOog.js} +1 -1
  15. package/dist/web/assets/{OverviewPage-Cd66M0RT.js → OverviewPage-Xyfm5xkZ.js} +1 -1
  16. package/dist/web/assets/{PersonalProfilePage-RCFHL0Wz.js → PersonalProfilePage-DB9mByB3.js} +1 -1
  17. package/dist/web/assets/PersonalProjectsPage-_-YXVi5O.js +1 -0
  18. package/dist/web/assets/ProjectAgentsPage-C3vW6wmk.css +1 -0
  19. package/dist/web/assets/ProjectAgentsPage-DeeQByPX.js +11 -0
  20. package/dist/web/assets/{PublicProjectsPage-mstPEjHe.js → PublicProjectsPage-BEZ1YPs3.js} +1 -1
  21. package/dist/web/assets/{ReviewPage-6Y1SmfZy.js → ReviewPage-CQYGwFBH.js} +1 -1
  22. package/dist/web/assets/SearchableSelect-D27H0MoP.js +1 -0
  23. package/dist/web/assets/{SettingsPage--LdTMUm7.js → SettingsPage-BlEZ03Yi.js} +1 -1
  24. package/dist/web/assets/{WritingTastePage-CIpKswvs.js → WritingTastePage-DvRt_nmP.js} +1 -1
  25. package/dist/web/assets/catalog-DFvIaHVS.js +1 -0
  26. package/dist/web/assets/{console-BT1P2Db4.js → console-tOxOl8Vq.js} +1 -1
  27. package/dist/web/assets/conversation-launcher-settings-DuFSpPAo.js +1 -0
  28. package/dist/web/assets/{i18n-js6gfc7j.js → i18n-CgBHZthI.js} +3 -3
  29. package/dist/web/assets/{identity-CfDfanpa.js → identity-CFo_Uw1H.js} +1 -1
  30. package/dist/web/assets/index-CJc5ppXH.css +1 -0
  31. package/dist/web/assets/index-yCi3cz2l.js +2 -0
  32. package/dist/web/assets/{model-DJkLAJsg.js → model-BtnVr7fX.js} +1 -1
  33. package/dist/web/assets/{useApi-CROJJdhE-BdSti7Y2.js → useApi-CROJJdhE-B6MHm_Av.js} +1 -1
  34. package/dist/web/assets/{vue-router-Kk67EaCP.js → vue-router-CzHm-y-x.js} +1 -1
  35. package/dist/web/index.html +7 -6
  36. package/docs/employee-agents.md +99 -0
  37. package/docs/project-agent-memory.md +479 -0
  38. package/graph-provider/fuli_graph/knowledge_audit_models.py +171 -0
  39. package/graph-provider/fuli_graph/model_base.py +5 -0
  40. package/graph-provider/fuli_graph/model_validation.py +32 -0
  41. package/graph-provider/fuli_graph/models.py +26 -170
  42. package/graph-provider/fuli_graph/project_agent_context_models.py +29 -0
  43. package/graph-provider/fuli_graph/project_agent_executor_models.py +35 -7
  44. package/graph-provider/fuli_graph/project_agent_memory_models.py +79 -0
  45. package/graph-provider/fuli_graph/project_agent_memory_routes.py +46 -0
  46. package/graph-provider/fuli_graph/project_agent_models.py +9 -0
  47. package/graph-provider/fuli_graph/project_agent_recruitment_values.py +48 -0
  48. package/graph-provider/fuli_graph/project_agent_routes.py +21 -1
  49. package/graph-provider/fuli_graph/project_agent_task_models.py +217 -29
  50. package/graph-provider/fuli_graph/runtime.py +37 -1
  51. package/graph-provider/fuli_graph/store.py +6 -0
  52. package/graph-provider/fuli_graph/store_project_agent_context.py +65 -0
  53. package/graph-provider/fuli_graph/store_project_agent_executor_learning.py +175 -11
  54. package/graph-provider/fuli_graph/store_project_agent_executor_routing.py +33 -10
  55. package/graph-provider/fuli_graph/store_project_agent_executors.py +181 -14
  56. package/graph-provider/fuli_graph/store_project_agent_memory.py +174 -0
  57. package/graph-provider/fuli_graph/store_project_agent_task_activity.py +423 -0
  58. package/graph-provider/fuli_graph/store_project_agent_task_persistence.py +152 -0
  59. package/graph-provider/fuli_graph/store_project_agent_task_reads.py +187 -21
  60. package/graph-provider/fuli_graph/store_project_agent_task_recruitment.py +748 -162
  61. package/graph-provider/fuli_graph/store_project_agent_task_staffing.py +230 -18
  62. package/graph-provider/fuli_graph/store_project_agent_tasks.py +261 -468
  63. package/graph-provider/fuli_graph/store_project_agents.py +214 -46
  64. package/graph-provider/fuli_graph/store_task_contexts.py +217 -0
  65. package/graph-provider/fuli_graph/store_transactions.py +40 -0
  66. package/graph-provider/fuli_graph/task_context_models.py +50 -0
  67. package/graph-provider/fuli_graph/task_context_routes.py +31 -0
  68. package/graph-provider/fuli_graph/workflow_observation_models.py +1 -1
  69. package/graph-provider/pyproject.toml +1 -1
  70. package/npm-shrinkwrap.json +8 -8
  71. package/package.json +5 -2
  72. package/skills/capturing-session-knowledge/SKILL.md +18 -6
  73. package/src/adaptive-runtime/executor-pool.js +46 -5
  74. package/src/adaptive-runtime/graph-services.js +10 -3
  75. package/src/adaptive-runtime/lease-client.js +66 -14
  76. package/src/adaptive-runtime/runtime-broker.js +24 -9
  77. package/src/agent-tools/employee-definitions.js +43 -0
  78. package/src/agent-tools/graph-definitions.js +25 -6
  79. package/src/agent-tools/graph-handlers.js +11 -0
  80. package/src/agent-tools/project-agent-definitions.js +35 -4
  81. package/src/agent-tools/project-agent-memory-definitions.js +36 -0
  82. package/src/agent-tools.js +6 -2
  83. package/src/agents/codex/lifecycle-hook.js +107 -0
  84. package/src/agents/codex/lifecycle-hooks.js +231 -0
  85. package/src/agents/cursor/lifecycle-hook.js +133 -0
  86. package/src/agents/cursor/lifecycle-hooks.js +50 -0
  87. package/src/agents/lifecycle-message.js +19 -0
  88. package/src/app/agent-request-context.js +23 -0
  89. package/src/cli/command-registry.js +2 -0
  90. package/src/cli/employee-command.js +17 -0
  91. package/src/cli/remote-mcp-command.js +282 -0
  92. package/src/cli/setup-command.js +5 -0
  93. package/src/cli.js +10 -0
  94. package/src/employees/catalog/jefa.json +21 -0
  95. package/src/employees/install-package.js +77 -0
  96. package/src/employees/management-policy.js +88 -0
  97. package/src/employees/manifest.js +53 -0
  98. package/src/employees/package-registry.js +107 -0
  99. package/src/employees/project-selection.js +50 -0
  100. package/src/employees/recruitment.js +199 -0
  101. package/src/employees/service.js +83 -0
  102. package/src/employees/task-entry.js +39 -0
  103. package/src/graphiti/agent-knowledge-workflows.js +135 -25
  104. package/src/graphiti/federated-application.js +78 -35
  105. package/src/graphiti/project-agent-control-plane.js +20 -3
  106. package/src/graphiti/project-agent-mapping.js +155 -5
  107. package/src/graphiti/project-agent-memory.js +115 -0
  108. package/src/graphiti/project-agent-task-entry.js +126 -0
  109. package/src/graphiti/project-agent-workflows.js +30 -14
  110. package/src/graphiti/provider-client.js +66 -4
  111. package/src/http/api-router.js +25 -5
  112. package/src/http/employee-api-router.js +64 -0
  113. package/src/http/error-mapping.js +4 -0
  114. package/src/http/graph-api-router.js +2 -0
  115. package/src/http/request-policy.js +6 -3
  116. package/src/http/static-handler.js +4 -3
  117. package/src/mcp/create-mcp-server.js +98 -37
  118. package/src/mcp/direct-call.js +38 -0
  119. package/src/mcp/instructions.js +8 -7
  120. package/src/mcp/provider-task-context-registry.js +74 -0
  121. package/src/mcp/remote-http-server.js +761 -0
  122. package/src/mcp/session-id.js +29 -3
  123. package/src/mcp/task-context-registry.js +88 -29
  124. package/src/mcp/tool-annotations.js +10 -2
  125. package/src/mcp/tool-result.js +8 -3
  126. package/src/mcp-server.js +20 -8
  127. package/src/native-runtime/runtime.js +58 -8
  128. package/src/server.js +76 -28
  129. package/src/setup/agent-installation-status.js +38 -6
  130. package/src/setup/agents.js +20 -8
  131. package/src/setup/claude-code-config.js +55 -13
  132. package/src/setup/codex-bootstrap.js +11 -10
  133. package/src/setup/codex-config.js +74 -7
  134. package/src/setup/config-backup.js +18 -2
  135. package/src/setup/cursor-config.js +22 -5
  136. package/src/setup/setup.js +30 -22
  137. package/src/storage/json-file.js +11 -3
  138. package/dist/web/assets/KnowledgeEditDialog-CZ5ziPrD.js +0 -1
  139. package/dist/web/assets/KnowledgePage-BRYC9S5Y.js +0 -1
  140. package/dist/web/assets/PersonalProjectsPage-DEh8XWby.js +0 -1
  141. package/dist/web/assets/ProjectAgentsPage-Crh7PrXa.css +0 -1
  142. package/dist/web/assets/ProjectAgentsPage-DPnaH-TR.js +0 -11
  143. package/dist/web/assets/SearchableSelect-D3HM8YF8.js +0 -1
  144. package/dist/web/assets/conversation-launcher-settings-DrJEotdf.js +0 -1
  145. package/dist/web/assets/index-BnH5mcZx.css +0 -1
  146. package/dist/web/assets/index-GqJf1xqg.js +0 -2
@@ -0,0 +1,171 @@
1
+ from datetime import datetime
2
+ from typing import Any, Literal
3
+
4
+ from pydantic import Field, model_validator
5
+
6
+ from .model_base import StrictModel
7
+
8
+
9
+ KnowledgeItemKind = Literal['entity', 'relationship']
10
+ KnowledgeOperationActor = Literal['human', 'agent']
11
+ HumanChangeStatus = Literal['none', 'unseen', 'viewed', 'reviewed']
12
+ KnowledgeAuditAction = Literal[
13
+ 'human_change',
14
+ 'agent_view',
15
+ 'agent_review',
16
+ 'knowledge_used',
17
+ 'knowledge_feedback',
18
+ ]
19
+ KnowledgeUseKind = Literal['cited', 'applied']
20
+ KnowledgeFeedbackKind = Literal[
21
+ 'rejected',
22
+ 'validation_failed',
23
+ 'contradicted',
24
+ 'outdated',
25
+ ]
26
+ KnowledgeRevisionAction = Literal[
27
+ 'confirm',
28
+ 'update',
29
+ 'invalidate',
30
+ 'link_replacement',
31
+ 'restore',
32
+ 'scope_change',
33
+ 'batch_confirm',
34
+ 'promote_common',
35
+ 'replace_common_duplicate',
36
+ ]
37
+ PreferenceScope = Literal['global', 'project']
38
+
39
+
40
+ class KnowledgeAssignmentChange(StrictModel):
41
+ personal_space_id: str = Field(min_length=1, max_length=128)
42
+ item_kind: KnowledgeItemKind
43
+ target_project_id: str = Field(min_length=1, max_length=128)
44
+ reason: str = Field(min_length=1, max_length=2000)
45
+ operation_actor: KnowledgeOperationActor = 'agent'
46
+
47
+
48
+ class PreferenceScopeChange(StrictModel):
49
+ personal_space_id: str = Field(min_length=1, max_length=128)
50
+ item_kind: KnowledgeItemKind
51
+ scope: PreferenceScope
52
+ project_id: str | None = Field(default=None, min_length=1, max_length=128)
53
+ reason: str = Field(min_length=1, max_length=2000)
54
+ operation_actor: KnowledgeOperationActor = 'agent'
55
+
56
+ @model_validator(mode='after')
57
+ def validate_project_scope(self):
58
+ if self.scope == 'project' and not self.project_id:
59
+ raise ValueError('project_id is required for project preference scope')
60
+ if self.scope == 'global' and self.project_id:
61
+ raise ValueError('project_id is only valid for project preference scope')
62
+ return self
63
+
64
+
65
+ class KnowledgeRevisionRecord(StrictModel):
66
+ id: str
67
+ item_id: str
68
+ item_kind: KnowledgeItemKind
69
+ action: KnowledgeRevisionAction
70
+ reason: str
71
+ previous: dict[str, Any] = Field(default_factory=dict)
72
+ current: dict[str, Any] = Field(default_factory=dict)
73
+ created_at: datetime
74
+
75
+
76
+ class KnowledgeAuditRecord(StrictModel):
77
+ id: str
78
+ item_id: str
79
+ item_kind: KnowledgeItemKind
80
+ action: KnowledgeAuditAction
81
+ human_change_version: int = Field(default=0, ge=0)
82
+ reason: str
83
+ tool_name: str | None = None
84
+ task_id: str | None = None
85
+ session_id: str | None = None
86
+ use_kind: KnowledgeUseKind | None = None
87
+ feedback_kind: KnowledgeFeedbackKind | None = None
88
+ reported_by_kind: Literal[
89
+ 'user',
90
+ 'agent',
91
+ 'authoritative_source',
92
+ ] | None = None
93
+ evidence_summary: str | None = None
94
+ source_uri: str | None = None
95
+ usage_generation: int | None = Field(default=None, ge=1)
96
+ conflict_check: Literal['no_conflict', 'conflict'] | None = None
97
+ classification_check: Literal['reasonable', 'needs_change'] | None = None
98
+ outcome: Literal[
99
+ 'pending_review',
100
+ 'requires_attention',
101
+ 'reviewed',
102
+ 'agent_confirmed',
103
+ ] | None = None
104
+ created_at: datetime
105
+
106
+
107
+ class KnowledgeAgentViewItem(StrictModel):
108
+ item_id: str = Field(min_length=1, max_length=256)
109
+ item_kind: KnowledgeItemKind
110
+
111
+
112
+ class KnowledgeAgentViewCreate(StrictModel):
113
+ personal_space_id: str = Field(min_length=1, max_length=128)
114
+ tool_name: str = Field(min_length=1, max_length=128)
115
+ items: list[KnowledgeAgentViewItem] = Field(max_length=200)
116
+
117
+
118
+ class KnowledgeAgentViewResult(StrictModel):
119
+ recorded_count: int = Field(ge=0, le=200)
120
+ item_keys: list[str] = Field(max_length=200)
121
+
122
+
123
+ class KnowledgeAgentReviewCreate(StrictModel):
124
+ personal_space_id: str = Field(min_length=1, max_length=128)
125
+ item_kind: KnowledgeItemKind
126
+ human_change_version: int = Field(ge=1)
127
+ conflict_check: Literal['no_conflict', 'conflict']
128
+ classification_check: Literal['reasonable', 'needs_change']
129
+ note: str = Field(min_length=1, max_length=2000)
130
+
131
+
132
+ class KnowledgeHumanChangeSearchRequest(StrictModel):
133
+ personal_space_id: str = Field(min_length=1, max_length=128)
134
+ query: str = Field(default='', max_length=2048)
135
+ status: Literal[
136
+ 'all',
137
+ 'human_changed',
138
+ 'unseen',
139
+ 'viewed',
140
+ 'reviewed',
141
+ ] = 'all'
142
+ limit: int = Field(default=50, ge=1, le=200)
143
+
144
+
145
+ class KnowledgeHumanChangeItem(StrictModel):
146
+ item_id: str
147
+ item_kind: KnowledgeItemKind
148
+ title: str
149
+ body: str
150
+ type: str
151
+ human_change_status: HumanChangeStatus
152
+ human_change_version: int = Field(ge=1)
153
+ last_human_changed_at: datetime
154
+ last_agent_viewed_at: datetime | None = None
155
+ last_agent_reviewed_at: datetime | None = None
156
+ audit_events: list[KnowledgeAuditRecord] = Field(default_factory=list)
157
+
158
+
159
+ class KnowledgeHumanChangeSearchResult(StrictModel):
160
+ items: list[KnowledgeHumanChangeItem] = Field(default_factory=list)
161
+
162
+
163
+ class KnowledgeAssignmentRecord(StrictModel):
164
+ id: str
165
+ item_id: str
166
+ item_kind: KnowledgeItemKind
167
+ project_id: str
168
+ previous_project_id: str | None = None
169
+ reason: str
170
+ created_at: datetime
171
+ updated_at: datetime
@@ -0,0 +1,5 @@
1
+ from pydantic import BaseModel, ConfigDict
2
+
3
+
4
+ class StrictModel(BaseModel):
5
+ model_config = ConfigDict(extra='forbid', str_strip_whitespace=True)
@@ -1,6 +1,7 @@
1
1
  import json
2
2
  import re
3
3
  from typing import Any
4
+ from urllib.parse import urlsplit
4
5
 
5
6
 
6
7
  _CREDENTIAL_PATTERNS = (
@@ -162,6 +163,37 @@ def normalize_provider_url(value: str) -> str:
162
163
  return value.rstrip('/')
163
164
 
164
165
 
166
+ def normalize_source_session_url(value: str | None) -> str | None:
167
+ """Allow auditable session links without accepting credential-bearing URLs."""
168
+
169
+ if value is None:
170
+ return None
171
+ normalized = value.strip()
172
+ parsed = urlsplit(normalized)
173
+ is_codex_thread = (
174
+ parsed.scheme == 'codex'
175
+ and parsed.netloc == 'threads'
176
+ and bool(parsed.path.strip('/'))
177
+ )
178
+ is_https = parsed.scheme == 'https' and bool(parsed.netloc)
179
+ is_loopback_http = (
180
+ parsed.scheme == 'http'
181
+ and parsed.hostname in {'127.0.0.1', 'localhost', '::1'}
182
+ )
183
+ if (
184
+ not normalized
185
+ or parsed.username
186
+ or parsed.password
187
+ or parsed.query
188
+ or parsed.fragment
189
+ or not (is_codex_thread or is_https or is_loopback_http)
190
+ ):
191
+ raise ValueError(
192
+ 'source session URL must be a Codex thread, HTTPS, or loopback URL'
193
+ )
194
+ return normalized
195
+
196
+
165
197
  def complete_epistemic_state(
166
198
  *,
167
199
  origin_quadrant: str,
@@ -4,13 +4,34 @@ from urllib.parse import urlsplit
4
4
 
5
5
  from pydantic import (
6
6
  AliasChoices,
7
- BaseModel,
8
- ConfigDict,
9
7
  Field,
10
8
  field_validator,
11
9
  model_validator,
12
10
  )
13
11
 
12
+ from .knowledge_audit_models import (
13
+ HumanChangeStatus,
14
+ KnowledgeAgentReviewCreate,
15
+ KnowledgeAgentViewCreate,
16
+ KnowledgeAgentViewItem,
17
+ KnowledgeAgentViewResult,
18
+ KnowledgeAssignmentChange,
19
+ KnowledgeAssignmentRecord,
20
+ KnowledgeAuditAction,
21
+ KnowledgeAuditRecord,
22
+ KnowledgeFeedbackKind,
23
+ KnowledgeHumanChangeItem,
24
+ KnowledgeHumanChangeSearchRequest,
25
+ KnowledgeHumanChangeSearchResult,
26
+ KnowledgeItemKind,
27
+ KnowledgeOperationActor,
28
+ KnowledgeRevisionAction,
29
+ KnowledgeRevisionRecord,
30
+ KnowledgeUseKind,
31
+ PreferenceScope,
32
+ PreferenceScopeChange,
33
+ )
34
+ from .model_base import StrictModel
14
35
  from .model_validation import (
15
36
  complete_epistemic_state,
16
37
  normalize_provider_url,
@@ -52,39 +73,11 @@ ProjectRelationType = Literal[
52
73
  'RELATED_TO',
53
74
  ]
54
75
  ProjectRelationStatus = Literal['pending', 'active', 'rejected']
55
- KnowledgeItemKind = Literal['entity', 'relationship']
56
- KnowledgeOperationActor = Literal['human', 'agent']
57
- HumanChangeStatus = Literal['none', 'unseen', 'viewed', 'reviewed']
58
- KnowledgeAuditAction = Literal[
59
- 'human_change',
60
- 'agent_view',
61
- 'agent_review',
62
- 'knowledge_used',
63
- 'knowledge_feedback',
64
- ]
65
- KnowledgeUseKind = Literal['cited', 'applied']
66
- KnowledgeFeedbackKind = Literal[
67
- 'rejected',
68
- 'validation_failed',
69
- 'contradicted',
70
- 'outdated',
71
- ]
72
76
  KnowledgeInheritanceMode = Literal[
73
77
  'local_only',
74
78
  'descendants',
75
79
  'selected_projects',
76
80
  ]
77
- KnowledgeRevisionAction = Literal[
78
- 'confirm',
79
- 'update',
80
- 'invalidate',
81
- 'link_replacement',
82
- 'restore',
83
- 'scope_change',
84
- 'batch_confirm',
85
- 'promote_common',
86
- 'replace_common_duplicate',
87
- ]
88
81
  KnowledgeContentRevisionAction = Literal[
89
82
  'confirm',
90
83
  'update',
@@ -103,7 +96,6 @@ EpistemicStatus = Literal['confirmed', 'observed', 'exploratory']
103
96
  ConfirmationStatus = Literal['confirmed', 'agent_confirmed', 'pending']
104
97
  ConfirmationActorKind = Literal['user', 'agent', 'authoritative_source', 'import']
105
98
  PersonalProfileAspect = Literal['taste', 'personality', 'judgment_preference']
106
- PreferenceScope = Literal['global', 'project']
107
99
  ScopedPreferenceScope = Literal['global', 'project', 'agent']
108
100
  PreferenceConflictQueueStatus = Literal['ai_pending', 'resolved']
109
101
  PreferenceConflictResolutionAction = Literal[
@@ -112,11 +104,9 @@ PreferenceConflictResolutionAction = Literal[
112
104
  'keep_right',
113
105
  'split_scope',
114
106
  ]
115
- SourceApplication = Literal['codex', 'claude_code', 'cursor', 'kiro', 'other']
116
-
117
-
118
- class StrictModel(BaseModel):
119
- model_config = ConfigDict(extra='forbid', str_strip_whitespace=True)
107
+ SourceApplication = Literal[
108
+ 'codex', 'claude', 'claude_code', 'cursor', 'kiro', 'other'
109
+ ]
120
110
 
121
111
 
122
112
  def _validated_source_uri(value: str) -> str:
@@ -1163,137 +1153,3 @@ class KnowledgeBatchConfirmationResult(StrictModel):
1163
1153
  confirmed_count: int = Field(ge=0, le=200)
1164
1154
  confirmed_at: datetime
1165
1155
  item_keys: list[str] = Field(max_length=200)
1166
-
1167
-
1168
- class KnowledgeAssignmentChange(StrictModel):
1169
- personal_space_id: str = Field(min_length=1, max_length=128)
1170
- item_kind: KnowledgeItemKind
1171
- target_project_id: str = Field(min_length=1, max_length=128)
1172
- reason: str = Field(min_length=1, max_length=2000)
1173
- operation_actor: KnowledgeOperationActor = 'agent'
1174
-
1175
-
1176
- class PreferenceScopeChange(StrictModel):
1177
- personal_space_id: str = Field(min_length=1, max_length=128)
1178
- item_kind: KnowledgeItemKind
1179
- scope: PreferenceScope
1180
- project_id: str | None = Field(default=None, min_length=1, max_length=128)
1181
- reason: str = Field(min_length=1, max_length=2000)
1182
- operation_actor: KnowledgeOperationActor = 'agent'
1183
-
1184
- @model_validator(mode='after')
1185
- def validate_project_scope(self):
1186
- if self.scope == 'project' and not self.project_id:
1187
- raise ValueError('project_id is required for project preference scope')
1188
- if self.scope == 'global' and self.project_id:
1189
- raise ValueError('project_id is only valid for project preference scope')
1190
- return self
1191
-
1192
-
1193
- class KnowledgeRevisionRecord(StrictModel):
1194
- id: str
1195
- item_id: str
1196
- item_kind: KnowledgeItemKind
1197
- action: KnowledgeRevisionAction
1198
- reason: str
1199
- previous: dict[str, Any] = Field(default_factory=dict)
1200
- current: dict[str, Any] = Field(default_factory=dict)
1201
- created_at: datetime
1202
-
1203
-
1204
- class KnowledgeAuditRecord(StrictModel):
1205
- id: str
1206
- item_id: str
1207
- item_kind: KnowledgeItemKind
1208
- action: KnowledgeAuditAction
1209
- human_change_version: int = Field(default=0, ge=0)
1210
- reason: str
1211
- tool_name: str | None = None
1212
- task_id: str | None = None
1213
- session_id: str | None = None
1214
- use_kind: KnowledgeUseKind | None = None
1215
- feedback_kind: KnowledgeFeedbackKind | None = None
1216
- reported_by_kind: Literal[
1217
- 'user',
1218
- 'agent',
1219
- 'authoritative_source',
1220
- ] | None = None
1221
- evidence_summary: str | None = None
1222
- source_uri: str | None = None
1223
- usage_generation: int | None = Field(default=None, ge=1)
1224
- conflict_check: Literal['no_conflict', 'conflict'] | None = None
1225
- classification_check: Literal['reasonable', 'needs_change'] | None = None
1226
- outcome: Literal[
1227
- 'pending_review',
1228
- 'requires_attention',
1229
- 'reviewed',
1230
- 'agent_confirmed',
1231
- ] | None = None
1232
- created_at: datetime
1233
-
1234
-
1235
- class KnowledgeAgentViewItem(StrictModel):
1236
- item_id: str = Field(min_length=1, max_length=256)
1237
- item_kind: KnowledgeItemKind
1238
-
1239
-
1240
- class KnowledgeAgentViewCreate(StrictModel):
1241
- personal_space_id: str = Field(min_length=1, max_length=128)
1242
- tool_name: str = Field(min_length=1, max_length=128)
1243
- items: list[KnowledgeAgentViewItem] = Field(max_length=200)
1244
-
1245
-
1246
- class KnowledgeAgentViewResult(StrictModel):
1247
- recorded_count: int = Field(ge=0, le=200)
1248
- item_keys: list[str] = Field(max_length=200)
1249
-
1250
-
1251
- class KnowledgeAgentReviewCreate(StrictModel):
1252
- personal_space_id: str = Field(min_length=1, max_length=128)
1253
- item_kind: KnowledgeItemKind
1254
- human_change_version: int = Field(ge=1)
1255
- conflict_check: Literal['no_conflict', 'conflict']
1256
- classification_check: Literal['reasonable', 'needs_change']
1257
- note: str = Field(min_length=1, max_length=2000)
1258
-
1259
-
1260
- class KnowledgeHumanChangeSearchRequest(StrictModel):
1261
- personal_space_id: str = Field(min_length=1, max_length=128)
1262
- query: str = Field(default='', max_length=2048)
1263
- status: Literal[
1264
- 'all',
1265
- 'human_changed',
1266
- 'unseen',
1267
- 'viewed',
1268
- 'reviewed',
1269
- ] = 'all'
1270
- limit: int = Field(default=50, ge=1, le=200)
1271
-
1272
-
1273
- class KnowledgeHumanChangeItem(StrictModel):
1274
- item_id: str
1275
- item_kind: KnowledgeItemKind
1276
- title: str
1277
- body: str
1278
- type: str
1279
- human_change_status: HumanChangeStatus
1280
- human_change_version: int = Field(ge=1)
1281
- last_human_changed_at: datetime
1282
- last_agent_viewed_at: datetime | None = None
1283
- last_agent_reviewed_at: datetime | None = None
1284
- audit_events: list[KnowledgeAuditRecord] = Field(default_factory=list)
1285
-
1286
-
1287
- class KnowledgeHumanChangeSearchResult(StrictModel):
1288
- items: list[KnowledgeHumanChangeItem] = Field(default_factory=list)
1289
-
1290
-
1291
- class KnowledgeAssignmentRecord(StrictModel):
1292
- id: str
1293
- item_id: str
1294
- item_kind: KnowledgeItemKind
1295
- project_id: str
1296
- previous_project_id: str | None = None
1297
- reason: str
1298
- created_at: datetime
1299
- updated_at: datetime
@@ -0,0 +1,29 @@
1
+ """Read-only task-entry selection; never submits a task or spawns a worker."""
2
+
3
+ from typing import Literal
4
+
5
+ from pydantic import Field
6
+
7
+ from .models import SourceApplication, StrictModel
8
+ from .project_agent_memory_models import MemoryNote
9
+ from .project_agent_models import ProjectAgentRecord
10
+
11
+
12
+ class ProjectAgentContextRequest(StrictModel):
13
+ personal_space_id: str = Field(min_length=1, max_length=128)
14
+ personal_project_id: str = Field(min_length=1, max_length=128)
15
+ agent_id: str | None = Field(default=None, min_length=1, max_length=128)
16
+ session_id: str | None = Field(default=None, min_length=1, max_length=256)
17
+ turn_id: str | None = Field(default=None, min_length=1, max_length=256)
18
+ work_kind: str = Field(default='project_context', min_length=1, max_length=128)
19
+ required_capabilities: list[MemoryNote] = Field(default_factory=list, max_length=16)
20
+ source_application: SourceApplication = 'other'
21
+
22
+
23
+ class ProjectAgentContextResolution(StrictModel):
24
+ status: Literal['ready', 'manual_selection', 'unassigned', 'agent_unavailable']
25
+ reason: str
26
+ match_basis: list[str] = Field(default_factory=list)
27
+ candidate_count: int = 0
28
+ agent: ProjectAgentRecord | None = None
29
+ worker_started: Literal[False] = False
@@ -11,7 +11,7 @@ import json
11
11
  from datetime import datetime
12
12
  from typing import Literal
13
13
 
14
- from pydantic import Field, field_validator, model_validator
14
+ from pydantic import AwareDatetime, Field, field_validator, model_validator
15
15
 
16
16
  from .model_validation import reject_credentials
17
17
  from .models import SourceApplication, StrictModel
@@ -81,6 +81,26 @@ ProjectAgentExecutorTerminalOutcome = Literal[
81
81
  'cancelled',
82
82
  ]
83
83
 
84
+ _CAPABILITY_ALIASES = {
85
+ 'code': 'coding',
86
+ 'development': 'coding',
87
+ 'implementation': 'coding',
88
+ 'software_development': 'coding',
89
+ 'software_implementation': 'coding',
90
+ 'test': 'testing',
91
+ 'tests': 'testing',
92
+ 'run_test': 'testing',
93
+ 'run_tests': 'testing',
94
+ 'test_execution': 'testing',
95
+ }
96
+
97
+
98
+ def project_agent_capability_key(value: str) -> str:
99
+ """Return one provider-neutral key for common capability vocabulary."""
100
+
101
+ key = '_'.join(value.casefold().replace('-', ' ').split())
102
+ return _CAPABILITY_ALIASES.get(key, key)
103
+
84
104
 
85
105
  def project_agent_model_strategy_key(
86
106
  strategy: ProjectAgentModelStrategy,
@@ -92,7 +112,10 @@ def project_agent_model_strategy_key(
92
112
  # Canonicalizing them prevents equivalent provider-neutral strategies from
93
113
  # creating separate evidence/aggregate buckets.
94
114
  payload['capability_hints'] = sorted(
95
- (value.casefold() for value in payload.get('capability_hints', [])),
115
+ (
116
+ project_agent_capability_key(value)
117
+ for value in payload.get('capability_hints', [])
118
+ ),
96
119
  )
97
120
  encoded = json.dumps(
98
121
  payload,
@@ -210,7 +233,7 @@ class ProjectAgentExecutorPreflightReport(StrictModel):
210
233
  max_length=64,
211
234
  )
212
235
  reason: str | None = Field(default=None, min_length=1, max_length=2048)
213
- checked_at: datetime
236
+ checked_at: AwareDatetime
214
237
  idempotency_key: str = Field(min_length=8, max_length=256)
215
238
  source_application: SourceApplication | None = None
216
239
  source_session_id: str | None = Field(default=None, min_length=1, max_length=256)
@@ -238,7 +261,7 @@ class ProjectAgentExecutorHealthReport(StrictModel):
238
261
  executor_id: str = Field(min_length=1, max_length=128)
239
262
  status: ProjectAgentExecutorHealthStatus
240
263
  reason: str | None = Field(default=None, min_length=1, max_length=2048)
241
- checked_at: datetime
264
+ checked_at: AwareDatetime
242
265
  idempotency_key: str = Field(min_length=8, max_length=256)
243
266
  source_application: SourceApplication | None = None
244
267
  source_session_id: str | None = Field(default=None, min_length=1, max_length=256)
@@ -277,6 +300,7 @@ class ProjectAgentExecutorRecord(StrictModel):
277
300
 
278
301
 
279
302
  class ProjectAgentExecutorPriorityUpdate(StrictModel):
303
+ personal_space_id: str = Field(min_length=1, max_length=128)
280
304
  executor_id: str = Field(min_length=1, max_length=128)
281
305
  global_priority: int = Field(ge=1, le=1_000_000)
282
306
  expected_revision: int = Field(ge=0)
@@ -453,7 +477,7 @@ class ProjectAgentExecutorActualReport(StrictModel):
453
477
  matched_rule_id: str | None = Field(default=None, min_length=1, max_length=128)
454
478
  fallback_reason: str | None = Field(default=None, min_length=1, max_length=2048)
455
479
  idempotency_key: str = Field(min_length=8, max_length=256)
456
- occurred_at: datetime
480
+ occurred_at: AwareDatetime
457
481
  source_application: SourceApplication | None = None
458
482
  source_session_id: str | None = Field(default=None, min_length=1, max_length=256)
459
483
 
@@ -486,7 +510,7 @@ class ProjectAgentExecutorOutcomeEvidenceCreate(StrictModel):
486
510
  reference_ids: list[str] = Field(default_factory=list, max_length=16)
487
511
  note: str | None = Field(default=None, min_length=1, max_length=2048)
488
512
  idempotency_key: str = Field(min_length=8, max_length=256)
489
- occurred_at: datetime
513
+ occurred_at: AwareDatetime
490
514
 
491
515
  @field_validator('reference_ids')
492
516
  @classmethod
@@ -537,6 +561,9 @@ class ProjectAgentExecutorOutcomeEvidenceRecord(
537
561
  # contract keeps this optional for backwards-compatible callers; the store
538
562
  # computes it before returning a durable record.
539
563
  model_strategy_key: str = Field(min_length=64, max_length=64)
564
+ # Legacy unknown-timezone records remain inspectable/ignorable; new writes
565
+ # require an explicit offset and must not guess the caller's local zone.
566
+ occurred_at: datetime
540
567
  evidence_id: str
541
568
  ignored: bool = False
542
569
  ignored_reason: str | None = None
@@ -579,6 +606,7 @@ class ProjectAgentExecutorOutcomeAggregate(StrictModel):
579
606
  evidence_contributions: list[ProjectAgentExecutorEvidenceContribution] = Field(
580
607
  default_factory=list
581
608
  )
609
+ validation_warnings: list[str] = Field(default_factory=list)
582
610
 
583
611
  @model_validator(mode='after')
584
612
  def validate_strategy_key(self):
@@ -619,7 +647,7 @@ class ProjectAgentExecutorOutcomeReset(StrictModel):
619
647
  )
620
648
  reason: str = Field(min_length=1, max_length=2048)
621
649
  idempotency_key: str = Field(min_length=8, max_length=256)
622
- reset_at: datetime
650
+ reset_at: AwareDatetime
623
651
 
624
652
  @model_validator(mode='after')
625
653
  def reject_sensitive_values(self):
@@ -0,0 +1,79 @@
1
+ """Provider-neutral contracts for private Agent working context.
2
+
3
+ Working notes are not confirmed project knowledge or user preferences. Their
4
+ scope and revision travel with every response, regardless of the calling host.
5
+ """
6
+
7
+ from datetime import datetime
8
+ import json
9
+ from typing import Annotated, Literal
10
+
11
+ from pydantic import Field, StringConstraints, model_validator
12
+
13
+ from .model_validation import reject_credentials
14
+ from .models import SourceApplication, StrictModel
15
+
16
+
17
+ MemoryNote = Annotated[
18
+ str, StringConstraints(strip_whitespace=True, min_length=1, max_length=1024)
19
+ ]
20
+
21
+
22
+ class ProjectAgentWorkingMemory(StrictModel):
23
+ summary: Annotated[
24
+ str, StringConstraints(strip_whitespace=True, min_length=1, max_length=4000)
25
+ ]
26
+ decisions: list[MemoryNote] = Field(default_factory=list, max_length=12)
27
+ open_threads: list[MemoryNote] = Field(default_factory=list, max_length=12)
28
+ next_actions: list[MemoryNote] = Field(default_factory=list, max_length=12)
29
+
30
+ @model_validator(mode='after')
31
+ def reject_sensitive_values(self):
32
+ reject_credentials(self, 'Agent working memory')
33
+ if len(json.dumps(self.model_dump(), ensure_ascii=False).encode()) > 32 * 1024:
34
+ raise ValueError('Agent working memory must fit within 32 KiB; distill the notes')
35
+ return self
36
+
37
+
38
+ class ProjectAgentMemoryWrite(StrictModel):
39
+ personal_space_id: str = Field(min_length=1, max_length=128)
40
+ personal_project_id: str = Field(min_length=1, max_length=128)
41
+ agent_id: str = Field(min_length=1, max_length=128)
42
+ expected_revision: int = Field(ge=0)
43
+ idempotency_key: str = Field(min_length=8, max_length=256)
44
+ memory: ProjectAgentWorkingMemory
45
+ source_application: SourceApplication
46
+ source_session_id: str | None = Field(default=None, min_length=1, max_length=256)
47
+ task_id: str | None = Field(default=None, min_length=1, max_length=128)
48
+
49
+ @model_validator(mode='after')
50
+ def reject_sensitive_values(self):
51
+ reject_credentials(self, 'Agent memory checkpoint')
52
+ return self
53
+
54
+
55
+ class ProjectAgentMemoryRecord(StrictModel):
56
+ checkpoint_id: str
57
+ personal_space_id: str
58
+ personal_project_id: str
59
+ agent_id: str
60
+ revision: int = Field(ge=1)
61
+ memory: ProjectAgentWorkingMemory
62
+ source_application: SourceApplication
63
+ source_session_id: str | None = None
64
+ task_id: str | None = None
65
+ created_at: datetime
66
+
67
+
68
+ class ProjectAgentMemoryView(StrictModel):
69
+ personal_space_id: str
70
+ personal_project_id: str
71
+ agent_id: str
72
+ scope: Literal['private_agent_project'] = 'private_agent_project'
73
+ storage: Literal['neo4j'] = 'neo4j'
74
+ authority: Literal['working_context_not_confirmed_knowledge'] = (
75
+ 'working_context_not_confirmed_knowledge'
76
+ )
77
+ revision: int = Field(default=0, ge=0)
78
+ current: ProjectAgentMemoryRecord | None = None
79
+ history: list[ProjectAgentMemoryRecord] = Field(default_factory=list)