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.
- package/README.md +77 -6
- package/README.zh-CN.md +69 -6
- package/compose.graphiti.yml +6 -6
- package/dist/web/assets/AboutPage-sLV9WChi.js +1 -0
- package/dist/web/assets/ConnectionsPage-BWn4ywGJ.js +1 -0
- package/dist/web/assets/KnowledgeEditDialog-CZ5ziPrD.js +1 -0
- package/dist/web/assets/KnowledgeInspector-CRQWOaos.js +1 -0
- package/dist/web/assets/KnowledgeOrganizerPage-Dfj3vIpU.js +1 -0
- package/dist/web/assets/KnowledgePage-BRYC9S5Y.js +1 -0
- package/dist/web/assets/KnowledgeWorkspace-ChEB5Bwk.js +4 -0
- package/dist/web/assets/OverviewPage-Cd66M0RT.js +1 -0
- package/dist/web/assets/PersonalProfilePage-RCFHL0Wz.js +1 -0
- package/dist/web/assets/PersonalProjectsPage-DEh8XWby.js +1 -0
- package/dist/web/assets/ProjectAgentsPage-Crh7PrXa.css +1 -0
- package/dist/web/assets/ProjectAgentsPage-DPnaH-TR.js +11 -0
- package/dist/web/assets/PublicProjectsPage-mstPEjHe.js +1 -0
- package/dist/web/assets/ReviewPage-6Y1SmfZy.js +1 -0
- package/dist/web/assets/SearchableSelect-D3HM8YF8.js +1 -0
- package/dist/web/assets/SettingsPage--LdTMUm7.js +1 -0
- package/dist/web/assets/SettingsPage-CwZp2ka4.css +1 -0
- package/dist/web/assets/WritingTastePage-CIpKswvs.js +1 -0
- package/dist/web/assets/{console-D_2zC4lm.js → console-BT1P2Db4.js} +1 -1
- package/dist/web/assets/{conversation-launcher-settings-DQmMH2j7.js → conversation-launcher-settings-DrJEotdf.js} +1 -1
- package/dist/web/assets/i18n-js6gfc7j.js +9 -0
- package/dist/web/assets/{identity-DmGcY3Y6.js → identity-CfDfanpa.js} +1 -1
- package/dist/web/assets/index-BnH5mcZx.css +1 -0
- package/dist/web/assets/index-GqJf1xqg.js +2 -0
- package/dist/web/assets/{model-CVoOdRsL.js → model-DJkLAJsg.js} +1 -1
- package/dist/web/assets/{useApi-CROJJdhE-CBNg4CdH.js → useApi-CROJJdhE-BdSti7Y2.js} +1 -1
- package/dist/web/assets/vue-router-Kk67EaCP.js +1 -0
- package/dist/web/index.html +6 -6
- package/graph-provider/fuli_graph/app.py +8 -0
- package/graph-provider/fuli_graph/collaboration_context.py +69 -6
- package/graph-provider/fuli_graph/knowledge_search.py +84 -5
- package/graph-provider/fuli_graph/model_validation.py +130 -0
- package/graph-provider/fuli_graph/models.py +24 -5
- package/graph-provider/fuli_graph/project_agent_access.py +54 -0
- package/graph-provider/fuli_graph/project_agent_coordination_models.py +22 -0
- package/graph-provider/fuli_graph/project_agent_executor_models.py +635 -0
- package/graph-provider/fuli_graph/project_agent_models.py +278 -0
- package/graph-provider/fuli_graph/project_agent_routes.py +626 -0
- package/graph-provider/fuli_graph/project_agent_task_models.py +545 -0
- package/graph-provider/fuli_graph/runtime.py +259 -0
- package/graph-provider/fuli_graph/search_projection.py +3 -0
- package/graph-provider/fuli_graph/store.py +6 -0
- package/graph-provider/fuli_graph/store_knowledge.py +62 -9
- package/graph-provider/fuli_graph/store_project_agent_coordination_policy.py +122 -0
- package/graph-provider/fuli_graph/store_project_agent_executor_learning.py +756 -0
- package/graph-provider/fuli_graph/store_project_agent_executor_routing.py +601 -0
- package/graph-provider/fuli_graph/store_project_agent_executors.py +1087 -0
- package/graph-provider/fuli_graph/store_project_agent_task_reads.py +458 -0
- package/graph-provider/fuli_graph/store_project_agent_task_recruitment.py +705 -0
- package/graph-provider/fuli_graph/store_project_agent_task_staffing.py +586 -0
- package/graph-provider/fuli_graph/store_project_agent_tasks.py +1151 -0
- package/graph-provider/fuli_graph/store_project_agents.py +965 -0
- package/graph-provider/pyproject.toml +2 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/skills/capturing-session-knowledge/SKILL.md +39 -0
- package/src/adaptive-runtime/executor-pool.js +127 -0
- package/src/adaptive-runtime/graph-services.js +166 -0
- package/src/adaptive-runtime/lease-client.js +335 -0
- package/src/adaptive-runtime/runtime-broker.js +318 -0
- package/src/adaptive-runtime/settings.js +111 -0
- package/src/adaptive-runtime/state.js +74 -0
- package/src/agent-tools/graph-definitions.js +419 -0
- package/src/agent-tools/graph-handlers.js +65 -0
- package/src/agent-tools/project-agent-definitions.js +307 -0
- package/src/backup/graph-backup.js +289 -0
- package/src/backup/identity-reconciliation.js +208 -0
- package/src/backup/runtime-adapters.js +269 -0
- package/src/cli/command-registry.js +6 -2
- package/src/cli/graph-data-command.js +112 -0
- package/src/cli/local-runtime-command.js +16 -2
- package/src/cli/setup-command.js +19 -3
- package/src/cli/update-command.js +4 -0
- package/src/cli.js +6 -0
- package/src/graphiti/collaboration-preference-projection.js +29 -1
- package/src/graphiti/collaboration-preference-workflow.js +121 -0
- package/src/graphiti/federated-application.js +53 -90
- package/src/graphiti/project-agent-control-plane.js +275 -0
- package/src/graphiti/project-agent-mapping.js +1024 -0
- package/src/graphiti/project-agent-workflows.js +576 -0
- package/src/graphiti/provider-client.js +282 -1
- package/src/http/api-router.js +63 -9
- package/src/http/graph-api-router.js +290 -0
- package/src/local-runtime/lifecycle.js +46 -10
- package/src/mcp/context-resources.js +15 -6
- package/src/mcp/create-mcp-server.js +51 -16
- package/src/mcp/instructions.js +7 -5
- package/src/mcp/runtime.js +17 -5
- package/src/mcp/session-id.js +38 -1
- package/src/mcp/tool-annotations.js +71 -0
- package/src/mcp-server.js +23 -6
- package/src/native-runtime/runtime.js +717 -0
- package/src/server.js +6 -2
- package/src/setup/agent-installation-status.js +7 -1
- package/src/setup/agents.js +3 -1
- package/src/setup/claude-code-config.js +3 -1
- package/src/setup/codex-bootstrap.js +11 -10
- package/src/setup/codex-config.js +7 -1
- package/src/setup/cursor-config.js +3 -1
- package/src/setup/graph-runtime.js +158 -13
- package/src/setup/neo4j-memory-profile.js +43 -0
- package/src/setup/options.js +38 -2
- package/src/setup/paths.js +14 -0
- package/src/setup/setup.js +37 -1
- package/src/system/resource-monitor.js +181 -16
- package/src/system/runtime-settings.js +10 -1
- package/src/system/system-service.js +27 -2
- package/dist/web/assets/AboutPage-Dpf5fRWv.js +0 -1
- package/dist/web/assets/ConnectionsPage-D21aUDCP.js +0 -1
- package/dist/web/assets/KnowledgeEditDialog-u7lpfN3j.js +0 -1
- package/dist/web/assets/KnowledgeInspector-k4xnHtXd.js +0 -1
- package/dist/web/assets/KnowledgeOrganizerPage-Bdj7Rn84.js +0 -1
- package/dist/web/assets/KnowledgePage-UHVAbC1W.js +0 -1
- package/dist/web/assets/KnowledgeWorkspace-Lq3d-MeC.js +0 -4
- package/dist/web/assets/OverviewPage-CycAgA1V.js +0 -1
- package/dist/web/assets/PersonalProfilePage-U9oIV5FT.js +0 -1
- package/dist/web/assets/PersonalProjectsPage-C5RpXKcA.js +0 -1
- package/dist/web/assets/PublicProjectsPage-DiWxm35E.js +0 -1
- package/dist/web/assets/ReviewPage-jxRfGSJK.js +0 -1
- package/dist/web/assets/SearchableSelect-B3-5IPup.js +0 -1
- package/dist/web/assets/SettingsPage-BqfGDOho.js +0 -1
- package/dist/web/assets/SettingsPage-CAfz4qiV.css +0 -1
- package/dist/web/assets/WritingTastePage-BJxhMqJR.js +0 -1
- package/dist/web/assets/i18n-CwqXDI1C.js +0 -5
- package/dist/web/assets/index-IUGLt4lG.js +0 -2
- package/dist/web/assets/index-X2j29j8i.css +0 -1
- package/dist/web/assets/vue-router-CWSsUUio.js +0 -1
|
@@ -0,0 +1,635 @@
|
|
|
1
|
+
"""Durable executor-directory and routing contracts for Project Agents.
|
|
2
|
+
|
|
3
|
+
The executor layer deliberately stores capabilities and observed availability,
|
|
4
|
+
not a hard-coded mapping from a work kind to a tool or vendor. A coordinator
|
|
5
|
+
can only select an executor after the directory, workspace permission,
|
|
6
|
+
preflight, capability, and (when required) health checks all pass.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import hashlib
|
|
10
|
+
import json
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
from typing import Literal
|
|
13
|
+
|
|
14
|
+
from pydantic import Field, field_validator, model_validator
|
|
15
|
+
|
|
16
|
+
from .model_validation import reject_credentials
|
|
17
|
+
from .models import SourceApplication, StrictModel
|
|
18
|
+
from .project_agent_models import (
|
|
19
|
+
ProjectAgentExecutorPolicy,
|
|
20
|
+
ProjectAgentModelStrategy,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
ProjectAgentExecutorRegistrationStatus = Literal[
|
|
25
|
+
'registered',
|
|
26
|
+
'disabled',
|
|
27
|
+
'revoked',
|
|
28
|
+
]
|
|
29
|
+
ProjectAgentExecutorPermissionStatus = Literal[
|
|
30
|
+
'pending',
|
|
31
|
+
'authorized',
|
|
32
|
+
'denied',
|
|
33
|
+
'revoked',
|
|
34
|
+
]
|
|
35
|
+
ProjectAgentExecutorPreflightStatus = Literal[
|
|
36
|
+
'not_run',
|
|
37
|
+
'passed',
|
|
38
|
+
'failed',
|
|
39
|
+
'expired',
|
|
40
|
+
]
|
|
41
|
+
ProjectAgentExecutorHealthStatus = Literal[
|
|
42
|
+
'unknown',
|
|
43
|
+
'healthy',
|
|
44
|
+
'degraded',
|
|
45
|
+
'unhealthy',
|
|
46
|
+
]
|
|
47
|
+
ProjectAgentExecutorScope = Literal['global', 'space', 'project', 'task']
|
|
48
|
+
ProjectAgentExecutorRuleStatus = Literal['active', 'disabled', 'ended']
|
|
49
|
+
ProjectAgentExecutorSelectionStatus = Literal[
|
|
50
|
+
'selected',
|
|
51
|
+
'fallback',
|
|
52
|
+
'blocked',
|
|
53
|
+
'unavailable',
|
|
54
|
+
]
|
|
55
|
+
ProjectAgentExecutorFallbackOutcome = Literal[
|
|
56
|
+
'not_needed',
|
|
57
|
+
'same_rule_candidate',
|
|
58
|
+
'global_priority',
|
|
59
|
+
'blocked_locked',
|
|
60
|
+
'blocked_no_candidate',
|
|
61
|
+
]
|
|
62
|
+
ProjectAgentExecutorEvidenceKind = Literal[
|
|
63
|
+
'terminal_outcome',
|
|
64
|
+
'rework_requested',
|
|
65
|
+
'repeated_negative_feedback',
|
|
66
|
+
'explicit_praise',
|
|
67
|
+
'test_passed',
|
|
68
|
+
'test_failed',
|
|
69
|
+
'acceptance_passed',
|
|
70
|
+
'acceptance_failed',
|
|
71
|
+
'explicit_rating',
|
|
72
|
+
]
|
|
73
|
+
ProjectAgentExecutorEvidenceSource = Literal[
|
|
74
|
+
'system_terminal',
|
|
75
|
+
'user_explicit',
|
|
76
|
+
'test_fact',
|
|
77
|
+
]
|
|
78
|
+
ProjectAgentExecutorTerminalOutcome = Literal[
|
|
79
|
+
'completed',
|
|
80
|
+
'failed',
|
|
81
|
+
'cancelled',
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def project_agent_model_strategy_key(
|
|
86
|
+
strategy: ProjectAgentModelStrategy,
|
|
87
|
+
) -> str:
|
|
88
|
+
"""Return the canonical, provider-neutral key for a model strategy."""
|
|
89
|
+
|
|
90
|
+
payload = strategy.model_dump(mode='json')
|
|
91
|
+
# Capability hints are a set of requirements, not an ordered preference.
|
|
92
|
+
# Canonicalizing them prevents equivalent provider-neutral strategies from
|
|
93
|
+
# creating separate evidence/aggregate buckets.
|
|
94
|
+
payload['capability_hints'] = sorted(
|
|
95
|
+
(value.casefold() for value in payload.get('capability_hints', [])),
|
|
96
|
+
)
|
|
97
|
+
encoded = json.dumps(
|
|
98
|
+
payload,
|
|
99
|
+
ensure_ascii=False,
|
|
100
|
+
sort_keys=True,
|
|
101
|
+
separators=(',', ':'),
|
|
102
|
+
).encode('utf-8')
|
|
103
|
+
return hashlib.sha256(encoded).hexdigest()
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _unique_text(value: list[str], label: str, *, maximum: int = 512) -> list[str]:
|
|
107
|
+
normalized = [item.strip() for item in value]
|
|
108
|
+
if any(not item or len(item) > maximum for item in normalized):
|
|
109
|
+
raise ValueError(f'{label} must contain 1 to {maximum} characters')
|
|
110
|
+
if len({item.casefold() for item in normalized}) != len(normalized):
|
|
111
|
+
raise ValueError(f'{label} must be unique')
|
|
112
|
+
return normalized
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _unique_ids(value: list[str], label: str = 'IDs') -> list[str]:
|
|
116
|
+
return _unique_text(value, label, maximum=128)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class ProjectAgentExecutorModelRecord(StrictModel):
|
|
120
|
+
"""A model actually advertised by an executor preflight.
|
|
121
|
+
|
|
122
|
+
``provider`` and ``model`` are observations from the executor. Routing
|
|
123
|
+
continues to use the provider-neutral ``ProjectAgentModelStrategy`` and
|
|
124
|
+
these fields are only selected after a preflight reports them available.
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
provider: str = Field(min_length=1, max_length=128)
|
|
128
|
+
model: str = Field(min_length=1, max_length=256)
|
|
129
|
+
capabilities: list[str] = Field(default_factory=list, max_length=32)
|
|
130
|
+
strategy_modes: list[Literal['adaptive', 'fast', 'balanced', 'deep']] = Field(
|
|
131
|
+
default_factory=list,
|
|
132
|
+
max_length=4,
|
|
133
|
+
)
|
|
134
|
+
reasoning_efforts: list[Literal['default', 'low', 'medium', 'high']] = Field(
|
|
135
|
+
default_factory=list,
|
|
136
|
+
max_length=4,
|
|
137
|
+
)
|
|
138
|
+
available: bool = True
|
|
139
|
+
observed_at: datetime | None = None
|
|
140
|
+
source_application: SourceApplication | None = None
|
|
141
|
+
unavailable_reason: str | None = Field(default=None, min_length=1, max_length=1024)
|
|
142
|
+
|
|
143
|
+
@field_validator('capabilities')
|
|
144
|
+
@classmethod
|
|
145
|
+
def validate_capabilities(cls, value: list[str]) -> list[str]:
|
|
146
|
+
return _unique_text(value, 'executor model capabilities')
|
|
147
|
+
|
|
148
|
+
@model_validator(mode='after')
|
|
149
|
+
def validate_availability(self):
|
|
150
|
+
if self.available and self.unavailable_reason:
|
|
151
|
+
raise ValueError('available model cannot have an unavailable reason')
|
|
152
|
+
if not self.available and not self.unavailable_reason:
|
|
153
|
+
raise ValueError('unavailable model requires a reason')
|
|
154
|
+
reject_credentials(self, 'executor model report')
|
|
155
|
+
return self
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class ProjectAgentExecutorRegistration(StrictModel):
|
|
159
|
+
personal_space_id: str = Field(min_length=1, max_length=128)
|
|
160
|
+
executor_id: str = Field(min_length=1, max_length=128)
|
|
161
|
+
display_name: str = Field(min_length=1, max_length=160)
|
|
162
|
+
executor_kind: str = Field(default='external', min_length=1, max_length=128)
|
|
163
|
+
capabilities: list[str] = Field(default_factory=list, max_length=64)
|
|
164
|
+
advertised_models: list[ProjectAgentExecutorModelRecord] = Field(
|
|
165
|
+
default_factory=list,
|
|
166
|
+
max_length=64,
|
|
167
|
+
)
|
|
168
|
+
global_priority: int = Field(default=100, ge=1, le=1_000_000)
|
|
169
|
+
health_required: bool = False
|
|
170
|
+
idempotency_key: str = Field(min_length=8, max_length=256)
|
|
171
|
+
source_application: SourceApplication | None = None
|
|
172
|
+
source_session_id: str | None = Field(default=None, min_length=1, max_length=256)
|
|
173
|
+
test_source: str | None = Field(default=None, min_length=1, max_length=256)
|
|
174
|
+
cleanup_eligible: bool = False
|
|
175
|
+
expected_revision: int | None = Field(default=None, ge=0)
|
|
176
|
+
|
|
177
|
+
@field_validator('capabilities')
|
|
178
|
+
@classmethod
|
|
179
|
+
def validate_capabilities(cls, value: list[str]) -> list[str]:
|
|
180
|
+
return _unique_text(value, 'executor capabilities')
|
|
181
|
+
|
|
182
|
+
@model_validator(mode='after')
|
|
183
|
+
def reject_sensitive_values(self):
|
|
184
|
+
reject_credentials(self, 'executor registration')
|
|
185
|
+
return self
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class ProjectAgentExecutorAuthorization(StrictModel):
|
|
189
|
+
personal_space_id: str = Field(min_length=1, max_length=128)
|
|
190
|
+
executor_id: str = Field(min_length=1, max_length=128)
|
|
191
|
+
status: ProjectAgentExecutorPermissionStatus
|
|
192
|
+
reason: str = Field(min_length=1, max_length=2048)
|
|
193
|
+
expected_revision: int | None = Field(default=None, ge=0)
|
|
194
|
+
idempotency_key: str = Field(min_length=8, max_length=256)
|
|
195
|
+
|
|
196
|
+
@model_validator(mode='after')
|
|
197
|
+
def reject_sensitive_values(self):
|
|
198
|
+
reject_credentials(self, 'executor workspace authorization')
|
|
199
|
+
return self
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
class ProjectAgentExecutorPreflightReport(StrictModel):
|
|
203
|
+
personal_space_id: str = Field(min_length=1, max_length=128)
|
|
204
|
+
executor_id: str = Field(min_length=1, max_length=128)
|
|
205
|
+
status: Literal['passed', 'failed']
|
|
206
|
+
workspace_permission: bool
|
|
207
|
+
capabilities: list[str] = Field(default_factory=list, max_length=64)
|
|
208
|
+
available_models: list[ProjectAgentExecutorModelRecord] = Field(
|
|
209
|
+
default_factory=list,
|
|
210
|
+
max_length=64,
|
|
211
|
+
)
|
|
212
|
+
reason: str | None = Field(default=None, min_length=1, max_length=2048)
|
|
213
|
+
checked_at: datetime
|
|
214
|
+
idempotency_key: str = Field(min_length=8, max_length=256)
|
|
215
|
+
source_application: SourceApplication | None = None
|
|
216
|
+
source_session_id: str | None = Field(default=None, min_length=1, max_length=256)
|
|
217
|
+
|
|
218
|
+
@field_validator('capabilities')
|
|
219
|
+
@classmethod
|
|
220
|
+
def validate_capabilities(cls, value: list[str]) -> list[str]:
|
|
221
|
+
return _unique_text(value, 'preflight capabilities')
|
|
222
|
+
|
|
223
|
+
@model_validator(mode='after')
|
|
224
|
+
def validate_report(self):
|
|
225
|
+
if self.status == 'passed':
|
|
226
|
+
if not self.workspace_permission:
|
|
227
|
+
raise ValueError('passed preflight requires workspace permission')
|
|
228
|
+
if not any(model.available for model in self.available_models):
|
|
229
|
+
raise ValueError('passed preflight requires an available model')
|
|
230
|
+
if self.status == 'failed' and not self.reason:
|
|
231
|
+
raise ValueError('failed preflight requires a reason')
|
|
232
|
+
reject_credentials(self, 'executor preflight report')
|
|
233
|
+
return self
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
class ProjectAgentExecutorHealthReport(StrictModel):
|
|
237
|
+
personal_space_id: str = Field(min_length=1, max_length=128)
|
|
238
|
+
executor_id: str = Field(min_length=1, max_length=128)
|
|
239
|
+
status: ProjectAgentExecutorHealthStatus
|
|
240
|
+
reason: str | None = Field(default=None, min_length=1, max_length=2048)
|
|
241
|
+
checked_at: datetime
|
|
242
|
+
idempotency_key: str = Field(min_length=8, max_length=256)
|
|
243
|
+
source_application: SourceApplication | None = None
|
|
244
|
+
source_session_id: str | None = Field(default=None, min_length=1, max_length=256)
|
|
245
|
+
|
|
246
|
+
@model_validator(mode='after')
|
|
247
|
+
def validate_report(self):
|
|
248
|
+
if self.status in {'degraded', 'unhealthy'} and not self.reason:
|
|
249
|
+
raise ValueError('degraded or unhealthy executor requires a reason')
|
|
250
|
+
reject_credentials(self, 'executor health report')
|
|
251
|
+
return self
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
class ProjectAgentExecutorRecord(StrictModel):
|
|
255
|
+
executor_id: str
|
|
256
|
+
display_name: str
|
|
257
|
+
executor_kind: str
|
|
258
|
+
registration_status: ProjectAgentExecutorRegistrationStatus
|
|
259
|
+
permission_status: ProjectAgentExecutorPermissionStatus
|
|
260
|
+
preflight_status: ProjectAgentExecutorPreflightStatus
|
|
261
|
+
health_status: ProjectAgentExecutorHealthStatus
|
|
262
|
+
health_required: bool = False
|
|
263
|
+
workspace_permission: bool = False
|
|
264
|
+
capabilities: list[str] = Field(default_factory=list)
|
|
265
|
+
available_models: list[ProjectAgentExecutorModelRecord] = Field(
|
|
266
|
+
default_factory=list,
|
|
267
|
+
)
|
|
268
|
+
global_priority: int = Field(ge=1)
|
|
269
|
+
revision: int = Field(default=0, ge=0)
|
|
270
|
+
permission_revision: int = Field(default=0, ge=0)
|
|
271
|
+
preflight_at: datetime | None = None
|
|
272
|
+
health_checked_at: datetime | None = None
|
|
273
|
+
registered_at: datetime
|
|
274
|
+
updated_at: datetime
|
|
275
|
+
test_source: str | None = None
|
|
276
|
+
cleanup_eligible: bool = False
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
class ProjectAgentExecutorPriorityUpdate(StrictModel):
|
|
280
|
+
executor_id: str = Field(min_length=1, max_length=128)
|
|
281
|
+
global_priority: int = Field(ge=1, le=1_000_000)
|
|
282
|
+
expected_revision: int = Field(ge=0)
|
|
283
|
+
idempotency_key: str = Field(min_length=8, max_length=256)
|
|
284
|
+
reason: str = Field(min_length=1, max_length=2048)
|
|
285
|
+
|
|
286
|
+
@model_validator(mode='after')
|
|
287
|
+
def reject_sensitive_values(self):
|
|
288
|
+
reject_credentials(self, 'executor priority update')
|
|
289
|
+
return self
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
class ProjectAgentExecutorRoutingRuleCreate(StrictModel):
|
|
293
|
+
scope: ProjectAgentExecutorScope
|
|
294
|
+
personal_space_id: str | None = Field(default=None, min_length=1, max_length=128)
|
|
295
|
+
personal_project_id: str | None = Field(default=None, min_length=1, max_length=128)
|
|
296
|
+
task_id: str | None = Field(default=None, min_length=1, max_length=128)
|
|
297
|
+
work_kind: str = Field(min_length=1, max_length=128)
|
|
298
|
+
required_capabilities: list[str] = Field(default_factory=list, max_length=32)
|
|
299
|
+
executor_ids: list[str] = Field(min_length=1, max_length=32)
|
|
300
|
+
model_strategy: ProjectAgentModelStrategy | None = None
|
|
301
|
+
priority: int = Field(default=100, ge=1, le=1_000_000)
|
|
302
|
+
reason: str = Field(min_length=1, max_length=2048)
|
|
303
|
+
idempotency_key: str = Field(min_length=8, max_length=256)
|
|
304
|
+
|
|
305
|
+
@field_validator('required_capabilities')
|
|
306
|
+
@classmethod
|
|
307
|
+
def validate_capabilities(cls, value: list[str]) -> list[str]:
|
|
308
|
+
return _unique_text(value, 'routing rule capabilities')
|
|
309
|
+
|
|
310
|
+
@field_validator('executor_ids')
|
|
311
|
+
@classmethod
|
|
312
|
+
def validate_executor_ids(cls, value: list[str]) -> list[str]:
|
|
313
|
+
return _unique_ids(value, 'routing rule executor IDs')
|
|
314
|
+
|
|
315
|
+
@model_validator(mode='after')
|
|
316
|
+
def validate_scope(self):
|
|
317
|
+
expected = {
|
|
318
|
+
'global': (False, False, False),
|
|
319
|
+
'space': (True, False, False),
|
|
320
|
+
'project': (True, True, False),
|
|
321
|
+
'task': (True, True, True),
|
|
322
|
+
}[self.scope]
|
|
323
|
+
actual = (
|
|
324
|
+
bool(self.personal_space_id),
|
|
325
|
+
bool(self.personal_project_id),
|
|
326
|
+
bool(self.task_id),
|
|
327
|
+
)
|
|
328
|
+
if actual != expected:
|
|
329
|
+
raise ValueError(
|
|
330
|
+
f'{self.scope} routing rule requires exactly the matching scope IDs'
|
|
331
|
+
)
|
|
332
|
+
reject_credentials(self, 'executor routing rule')
|
|
333
|
+
return self
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
class ProjectAgentExecutorRoutingRuleRecord(
|
|
337
|
+
ProjectAgentExecutorRoutingRuleCreate
|
|
338
|
+
):
|
|
339
|
+
rule_id: str
|
|
340
|
+
status: ProjectAgentExecutorRuleStatus = 'active'
|
|
341
|
+
revision: int = Field(default=0, ge=0)
|
|
342
|
+
created_at: datetime
|
|
343
|
+
updated_at: datetime
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
class ProjectAgentExecutorRoutingRuleUpdate(StrictModel):
|
|
347
|
+
personal_space_id: str = Field(min_length=1, max_length=128)
|
|
348
|
+
rule_id: str = Field(min_length=1, max_length=128)
|
|
349
|
+
expected_revision: int = Field(ge=0)
|
|
350
|
+
status: Literal['active', 'disabled', 'ended']
|
|
351
|
+
reason: str = Field(min_length=1, max_length=2048)
|
|
352
|
+
idempotency_key: str = Field(min_length=8, max_length=256)
|
|
353
|
+
|
|
354
|
+
@model_validator(mode='after')
|
|
355
|
+
def reject_sensitive_values(self):
|
|
356
|
+
reject_credentials(self, 'executor routing rule update')
|
|
357
|
+
return self
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
class ProjectAgentExecutorSelectionRequest(StrictModel):
|
|
361
|
+
personal_space_id: str = Field(min_length=1, max_length=128)
|
|
362
|
+
personal_project_id: str = Field(min_length=1, max_length=128)
|
|
363
|
+
task_id: str = Field(min_length=1, max_length=128)
|
|
364
|
+
agent_id: str = Field(min_length=1, max_length=128)
|
|
365
|
+
work_kind: str = Field(min_length=1, max_length=128)
|
|
366
|
+
required_capabilities: list[str] = Field(default_factory=list, max_length=32)
|
|
367
|
+
model_strategy: ProjectAgentModelStrategy = Field(
|
|
368
|
+
default_factory=ProjectAgentModelStrategy
|
|
369
|
+
)
|
|
370
|
+
executor_policy: ProjectAgentExecutorPolicy | None = None
|
|
371
|
+
idempotency_key: str = Field(min_length=8, max_length=256)
|
|
372
|
+
source_application: SourceApplication | None = None
|
|
373
|
+
source_session_id: str | None = Field(default=None, min_length=1, max_length=256)
|
|
374
|
+
|
|
375
|
+
@field_validator('required_capabilities')
|
|
376
|
+
@classmethod
|
|
377
|
+
def validate_capabilities(cls, value: list[str]) -> list[str]:
|
|
378
|
+
return _unique_text(value, 'required executor capabilities')
|
|
379
|
+
|
|
380
|
+
@model_validator(mode='after')
|
|
381
|
+
def reject_sensitive_values(self):
|
|
382
|
+
reject_credentials(self, 'executor selection request')
|
|
383
|
+
return self
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
class ProjectAgentExecutorSelection(StrictModel):
|
|
387
|
+
selection_id: str
|
|
388
|
+
task_id: str
|
|
389
|
+
personal_space_id: str
|
|
390
|
+
personal_project_id: str
|
|
391
|
+
agent_id: str
|
|
392
|
+
status: ProjectAgentExecutorSelectionStatus
|
|
393
|
+
outcome: ProjectAgentExecutorSelectionStatus
|
|
394
|
+
selected_executor_id: str | None = None
|
|
395
|
+
# ``executor_id`` is a compatibility alias consumed by the task router.
|
|
396
|
+
executor_id: str | None = None
|
|
397
|
+
selected_provider: str | None = None
|
|
398
|
+
selected_model: str | None = None
|
|
399
|
+
matched_rule_id: str | None = None
|
|
400
|
+
rule_id: str | None = None
|
|
401
|
+
matched_rule_scope: ProjectAgentExecutorScope | None = None
|
|
402
|
+
candidate_executor_ids: list[str] = Field(default_factory=list)
|
|
403
|
+
selection_reason: str
|
|
404
|
+
reason: str
|
|
405
|
+
fallback_outcome: ProjectAgentExecutorFallbackOutcome = 'not_needed'
|
|
406
|
+
fallback_from_executor_id: str | None = None
|
|
407
|
+
fallback_reason: str | None = None
|
|
408
|
+
blocked_reason: str | None = None
|
|
409
|
+
model_strategy: ProjectAgentModelStrategy
|
|
410
|
+
model_strategy_key: str = Field(min_length=64, max_length=64)
|
|
411
|
+
model_strategy_source: Literal[
|
|
412
|
+
'task',
|
|
413
|
+
'assignment',
|
|
414
|
+
'agent',
|
|
415
|
+
'routing_rule',
|
|
416
|
+
'coordinator',
|
|
417
|
+
] = 'agent'
|
|
418
|
+
executor_policy: ProjectAgentExecutorPolicy = Field(
|
|
419
|
+
default_factory=ProjectAgentExecutorPolicy
|
|
420
|
+
)
|
|
421
|
+
idempotency_key: str | None = Field(default=None, min_length=8, max_length=256)
|
|
422
|
+
payload_fingerprint: str | None = Field(default=None, min_length=16, max_length=128)
|
|
423
|
+
created_at: datetime
|
|
424
|
+
|
|
425
|
+
@model_validator(mode='after')
|
|
426
|
+
def validate_strategy_key(self):
|
|
427
|
+
if self.model_strategy_key != project_agent_model_strategy_key(
|
|
428
|
+
self.model_strategy
|
|
429
|
+
):
|
|
430
|
+
raise ValueError('model strategy key does not match model strategy')
|
|
431
|
+
return self
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
class ProjectAgentExecutorActualReport(StrictModel):
|
|
435
|
+
personal_space_id: str = Field(min_length=1, max_length=128)
|
|
436
|
+
personal_project_id: str = Field(min_length=1, max_length=128)
|
|
437
|
+
task_id: str = Field(min_length=1, max_length=128)
|
|
438
|
+
run_id: str = Field(min_length=1, max_length=128)
|
|
439
|
+
agent_id: str = Field(min_length=1, max_length=128)
|
|
440
|
+
executor_id: str = Field(min_length=1, max_length=128)
|
|
441
|
+
provider: str = Field(min_length=1, max_length=128)
|
|
442
|
+
model: str = Field(min_length=1, max_length=256)
|
|
443
|
+
model_strategy: ProjectAgentModelStrategy = Field(
|
|
444
|
+
default_factory=ProjectAgentModelStrategy
|
|
445
|
+
)
|
|
446
|
+
model_strategy_source: Literal[
|
|
447
|
+
'task',
|
|
448
|
+
'assignment',
|
|
449
|
+
'agent',
|
|
450
|
+
'routing_rule',
|
|
451
|
+
'coordinator',
|
|
452
|
+
] = 'agent'
|
|
453
|
+
matched_rule_id: str | None = Field(default=None, min_length=1, max_length=128)
|
|
454
|
+
fallback_reason: str | None = Field(default=None, min_length=1, max_length=2048)
|
|
455
|
+
idempotency_key: str = Field(min_length=8, max_length=256)
|
|
456
|
+
occurred_at: datetime
|
|
457
|
+
source_application: SourceApplication | None = None
|
|
458
|
+
source_session_id: str | None = Field(default=None, min_length=1, max_length=256)
|
|
459
|
+
|
|
460
|
+
@model_validator(mode='after')
|
|
461
|
+
def reject_sensitive_values(self):
|
|
462
|
+
reject_credentials(self, 'actual executor report')
|
|
463
|
+
return self
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
class ProjectAgentExecutorOutcomeEvidenceCreate(StrictModel):
|
|
467
|
+
personal_space_id: str = Field(min_length=1, max_length=128)
|
|
468
|
+
personal_project_id: str = Field(min_length=1, max_length=128)
|
|
469
|
+
work_kind: str = Field(min_length=1, max_length=128)
|
|
470
|
+
agent_id: str = Field(min_length=1, max_length=128)
|
|
471
|
+
executor_id: str = Field(min_length=1, max_length=128)
|
|
472
|
+
task_id: str = Field(min_length=1, max_length=128)
|
|
473
|
+
run_id: str | None = Field(default=None, min_length=1, max_length=128)
|
|
474
|
+
model_strategy: ProjectAgentModelStrategy = Field(
|
|
475
|
+
default_factory=ProjectAgentModelStrategy
|
|
476
|
+
)
|
|
477
|
+
model_strategy_key: str | None = Field(
|
|
478
|
+
default=None,
|
|
479
|
+
min_length=64,
|
|
480
|
+
max_length=64,
|
|
481
|
+
)
|
|
482
|
+
evidence_kind: ProjectAgentExecutorEvidenceKind
|
|
483
|
+
source: ProjectAgentExecutorEvidenceSource
|
|
484
|
+
terminal_outcome: ProjectAgentExecutorTerminalOutcome | None = None
|
|
485
|
+
rating: int | None = Field(default=None, ge=1, le=5)
|
|
486
|
+
reference_ids: list[str] = Field(default_factory=list, max_length=16)
|
|
487
|
+
note: str | None = Field(default=None, min_length=1, max_length=2048)
|
|
488
|
+
idempotency_key: str = Field(min_length=8, max_length=256)
|
|
489
|
+
occurred_at: datetime
|
|
490
|
+
|
|
491
|
+
@field_validator('reference_ids')
|
|
492
|
+
@classmethod
|
|
493
|
+
def validate_reference_ids(cls, value: list[str]) -> list[str]:
|
|
494
|
+
return _unique_ids(value, 'evidence reference IDs')
|
|
495
|
+
|
|
496
|
+
@model_validator(mode='after')
|
|
497
|
+
def validate_evidence(self):
|
|
498
|
+
expected_strategy_key = project_agent_model_strategy_key(self.model_strategy)
|
|
499
|
+
if self.model_strategy_key and self.model_strategy_key != expected_strategy_key:
|
|
500
|
+
raise ValueError('model strategy key does not match model strategy')
|
|
501
|
+
system_kinds = {'terminal_outcome'}
|
|
502
|
+
test_kinds = {'test_passed', 'test_failed'}
|
|
503
|
+
explicit_kinds = {
|
|
504
|
+
'rework_requested',
|
|
505
|
+
'repeated_negative_feedback',
|
|
506
|
+
'explicit_praise',
|
|
507
|
+
'acceptance_passed',
|
|
508
|
+
'acceptance_failed',
|
|
509
|
+
'explicit_rating',
|
|
510
|
+
}
|
|
511
|
+
if self.evidence_kind in system_kinds and self.source != 'system_terminal':
|
|
512
|
+
raise ValueError('terminal outcome evidence must come from the system')
|
|
513
|
+
if self.evidence_kind in test_kinds and self.source != 'test_fact':
|
|
514
|
+
raise ValueError('test evidence must be recorded as a test fact')
|
|
515
|
+
if self.evidence_kind in explicit_kinds and self.source != 'user_explicit':
|
|
516
|
+
raise ValueError('feedback evidence must be explicitly supplied by a user')
|
|
517
|
+
if self.evidence_kind == 'terminal_outcome' and not self.terminal_outcome:
|
|
518
|
+
raise ValueError('terminal outcome evidence requires a terminal outcome')
|
|
519
|
+
if self.evidence_kind in system_kinds | test_kinds and not self.reference_ids:
|
|
520
|
+
raise ValueError('system or test evidence requires an evidence reference')
|
|
521
|
+
if self.evidence_kind != 'terminal_outcome' and self.terminal_outcome:
|
|
522
|
+
raise ValueError('terminal outcome is only valid for terminal evidence')
|
|
523
|
+
if self.evidence_kind == 'explicit_rating' and self.rating is None:
|
|
524
|
+
raise ValueError('explicit rating evidence requires a rating')
|
|
525
|
+
if self.evidence_kind != 'explicit_rating' and self.rating is not None:
|
|
526
|
+
raise ValueError('rating is only valid for explicit rating evidence')
|
|
527
|
+
if self.evidence_kind in explicit_kinds and not self.reference_ids and not self.note:
|
|
528
|
+
raise ValueError('explicit feedback requires a reference or note')
|
|
529
|
+
reject_credentials(self, 'executor outcome evidence')
|
|
530
|
+
return self
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
class ProjectAgentExecutorOutcomeEvidenceRecord(
|
|
534
|
+
ProjectAgentExecutorOutcomeEvidenceCreate
|
|
535
|
+
):
|
|
536
|
+
# Stored evidence always exposes the canonical bucket key. The create
|
|
537
|
+
# contract keeps this optional for backwards-compatible callers; the store
|
|
538
|
+
# computes it before returning a durable record.
|
|
539
|
+
model_strategy_key: str = Field(min_length=64, max_length=64)
|
|
540
|
+
evidence_id: str
|
|
541
|
+
ignored: bool = False
|
|
542
|
+
ignored_reason: str | None = None
|
|
543
|
+
created_at: datetime
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
class ProjectAgentExecutorEvidenceContribution(StrictModel):
|
|
547
|
+
evidence_id: str
|
|
548
|
+
evidence_kind: ProjectAgentExecutorEvidenceKind
|
|
549
|
+
signal: Literal['success', 'failure', 'rework', 'rating', 'neutral']
|
|
550
|
+
value: float
|
|
551
|
+
decay_weight: float = Field(ge=0, le=1)
|
|
552
|
+
occurred_at: datetime
|
|
553
|
+
reference_ids: list[str] = Field(default_factory=list)
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
class ProjectAgentExecutorOutcomeAggregate(StrictModel):
|
|
557
|
+
personal_space_id: str
|
|
558
|
+
personal_project_id: str
|
|
559
|
+
work_kind: str
|
|
560
|
+
agent_id: str
|
|
561
|
+
executor_id: str
|
|
562
|
+
model_strategy: ProjectAgentModelStrategy
|
|
563
|
+
model_strategy_key: str = Field(min_length=64, max_length=64)
|
|
564
|
+
sample_count: int = Field(ge=0)
|
|
565
|
+
recent_count: int = Field(ge=0)
|
|
566
|
+
success_count: int = Field(ge=0)
|
|
567
|
+
rework_count: int = Field(ge=0)
|
|
568
|
+
failure_count: int = Field(ge=0)
|
|
569
|
+
rating_count: int = Field(ge=0)
|
|
570
|
+
average_rating: float | None = Field(default=None, ge=1, le=5)
|
|
571
|
+
neutral_due_to_insufficient_evidence: bool = False
|
|
572
|
+
ignored: bool = False
|
|
573
|
+
reset_at: datetime | None = None
|
|
574
|
+
as_of: datetime
|
|
575
|
+
decay_half_life_days: float = Field(gt=0)
|
|
576
|
+
weighted_success: float = Field(ge=0)
|
|
577
|
+
weighted_failure: float = Field(ge=0)
|
|
578
|
+
evidence_refs: list[str] = Field(default_factory=list)
|
|
579
|
+
evidence_contributions: list[ProjectAgentExecutorEvidenceContribution] = Field(
|
|
580
|
+
default_factory=list
|
|
581
|
+
)
|
|
582
|
+
|
|
583
|
+
@model_validator(mode='after')
|
|
584
|
+
def validate_strategy_key(self):
|
|
585
|
+
if self.model_strategy_key != project_agent_model_strategy_key(
|
|
586
|
+
self.model_strategy
|
|
587
|
+
):
|
|
588
|
+
raise ValueError('model strategy key does not match model strategy')
|
|
589
|
+
return self
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
class ProjectAgentExecutorEvidenceIgnore(StrictModel):
|
|
593
|
+
personal_space_id: str = Field(min_length=1, max_length=128)
|
|
594
|
+
personal_project_id: str = Field(min_length=1, max_length=128)
|
|
595
|
+
agent_id: str = Field(min_length=1, max_length=128)
|
|
596
|
+
evidence_id: str = Field(min_length=1, max_length=128)
|
|
597
|
+
reason: str = Field(min_length=1, max_length=2048)
|
|
598
|
+
idempotency_key: str = Field(min_length=8, max_length=256)
|
|
599
|
+
|
|
600
|
+
@model_validator(mode='after')
|
|
601
|
+
def reject_sensitive_values(self):
|
|
602
|
+
reject_credentials(self, 'executor evidence ignore')
|
|
603
|
+
return self
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
class ProjectAgentExecutorOutcomeReset(StrictModel):
|
|
607
|
+
personal_space_id: str = Field(min_length=1, max_length=128)
|
|
608
|
+
personal_project_id: str = Field(min_length=1, max_length=128)
|
|
609
|
+
work_kind: str = Field(min_length=1, max_length=128)
|
|
610
|
+
agent_id: str = Field(min_length=1, max_length=128)
|
|
611
|
+
executor_id: str = Field(min_length=1, max_length=128)
|
|
612
|
+
model_strategy: ProjectAgentModelStrategy = Field(
|
|
613
|
+
default_factory=ProjectAgentModelStrategy
|
|
614
|
+
)
|
|
615
|
+
model_strategy_key: str | None = Field(
|
|
616
|
+
default=None,
|
|
617
|
+
min_length=64,
|
|
618
|
+
max_length=64,
|
|
619
|
+
)
|
|
620
|
+
reason: str = Field(min_length=1, max_length=2048)
|
|
621
|
+
idempotency_key: str = Field(min_length=8, max_length=256)
|
|
622
|
+
reset_at: datetime
|
|
623
|
+
|
|
624
|
+
@model_validator(mode='after')
|
|
625
|
+
def reject_sensitive_values(self):
|
|
626
|
+
expected_strategy_key = project_agent_model_strategy_key(self.model_strategy)
|
|
627
|
+
if self.model_strategy_key and self.model_strategy_key != expected_strategy_key:
|
|
628
|
+
raise ValueError('model strategy key does not match model strategy')
|
|
629
|
+
reject_credentials(self, 'executor evidence reset')
|
|
630
|
+
return self
|
|
631
|
+
|
|
632
|
+
|
|
633
|
+
# Names used by the task store and by older integration code.
|
|
634
|
+
ProjectAgentExecutorDecision = ProjectAgentExecutorSelection
|
|
635
|
+
ProjectAgentExecutorRouteResult = ProjectAgentExecutorSelection
|