HumboldtJoker commited on
Commit
68e7196
·
verified ·
1 Parent(s): d4175a4

Clean up nested duplicate: v2/v2/engine/planner.py

Browse files
Files changed (1) hide show
  1. v2/v2/engine/planner.py +0 -219
v2/v2/engine/planner.py DELETED
@@ -1,219 +0,0 @@
1
- """Rivet's planner — from a request to a BDI intention to a SkillDAG.
2
-
3
- This is the piece that makes Rivet an agent rather than a chat endpoint.
4
- Every request is classified, turned into an explicit intention (recorded
5
- in the BDI store with the beliefs it rests on), and compiled into a
6
- Kintsugi SkillDAG whose structure enforces the discipline:
7
-
8
- - migration questions CANNOT reach the model without a schema/safety
9
- pass first — the DAG edge makes it structural, not aspirational
10
- - auth questions always carry a security review artifact
11
- - every plan terminates in the discipline gate; there is no path from
12
- the model's draft to the user that bypasses it
13
-
14
- Intent classification is deterministic keyword scoring. A misclassified
15
- intent degrades to a *stricter* plan, never a looser one: the gate runs
16
- its full check suite regardless of which plan produced the draft.
17
- """
18
-
19
- import re
20
- from dataclasses import dataclass
21
- from datetime import datetime, timezone
22
-
23
- from kintsugi_core import (
24
- BDIIntention,
25
- BDIStore,
26
- DAGBuilder,
27
- IntentionStatus,
28
- SkillDAG,
29
- SkillRegistry,
30
- )
31
-
32
-
33
- @dataclass
34
- class Plan:
35
- intent: str
36
- intention_id: str
37
- dag: SkillDAG
38
- rationale: str
39
-
40
-
41
- INTENT_KEYWORDS = {
42
- "migration": [
43
- "migration", "migrate", "alter table", "add column", "drop",
44
- "schema", "create table", "constraint", "index", "pg-boss",
45
- "postgres", "database change", "column", "table", "retire",
46
- "deprecate",
47
- ],
48
- "auth": [
49
- "auth", "jwt", "token", "login", "session cookie", "middleware",
50
- "permission", "admin", "moderator", "webhook", "signature",
51
- "verifytoken", "bearer",
52
- ],
53
- "codegen": [
54
- "write", "implement", "add a", "create a", "generate", "fix",
55
- "refactor", "build", "endpoint", "handler", "component",
56
- ],
57
- "review": [
58
- "review", "check this", "audit", "look at", "is this safe",
59
- "what's wrong", "debug", "why does", "why is",
60
- ],
61
- }
62
-
63
- # Plan templates in DAGBuilder.from_intention() step format.
64
- # Layers: 0 = evidence gathering (parallel), 1 = synthesis,
65
- # 2 = verification, 3 = gate. Every template ends at the gate.
66
- PLAN_TEMPLATES = {
67
- "migration": [
68
- {"id": "analyze", "skill": "code_analysis", "layer": 0,
69
- "inputs": ["question"], "outputs": ["analysis"]},
70
- {"id": "migration_check", "skill": "migration_safety", "layer": 0,
71
- "inputs": ["question"], "outputs": ["migration_report"]},
72
- {"id": "synthesize", "skill": "synthesis", "layer": 1,
73
- "inputs": ["question", "analysis", "migration_report"],
74
- "outputs": ["draft"],
75
- "depends_on": ["analyze", "migration_check"]},
76
- {"id": "gate", "skill": "discipline_gate", "layer": 2,
77
- "inputs": ["question", "draft", "analysis", "migration_report"],
78
- "outputs": ["final"], "depends_on": ["synthesize"]},
79
- ],
80
- "auth": [
81
- {"id": "analyze", "skill": "code_analysis", "layer": 0,
82
- "inputs": ["question"], "outputs": ["analysis"]},
83
- {"id": "security", "skill": "security_review", "layer": 0,
84
- "inputs": ["question"], "outputs": ["security_report"]},
85
- {"id": "synthesize", "skill": "synthesis", "layer": 1,
86
- "inputs": ["question", "analysis", "security_report"],
87
- "outputs": ["draft"], "depends_on": ["analyze", "security"]},
88
- {"id": "gate", "skill": "discipline_gate", "layer": 2,
89
- "inputs": ["question", "draft", "analysis", "security_report"],
90
- "outputs": ["final"], "depends_on": ["synthesize"]},
91
- ],
92
- "codegen": [
93
- {"id": "analyze", "skill": "code_analysis", "layer": 0,
94
- "inputs": ["question"], "outputs": ["analysis"]},
95
- {"id": "security", "skill": "security_review", "layer": 0,
96
- "inputs": ["question"], "outputs": ["security_report"]},
97
- {"id": "synthesize", "skill": "synthesis", "layer": 1,
98
- "inputs": ["question", "analysis", "security_report"],
99
- "outputs": ["draft"], "depends_on": ["analyze", "security"]},
100
- {"id": "verify", "skill": "test_runner", "layer": 2,
101
- "inputs": ["question", "draft"], "outputs": ["verification"],
102
- "depends_on": ["synthesize"]},
103
- {"id": "gate", "skill": "discipline_gate", "layer": 3,
104
- "inputs": ["question", "draft", "analysis", "security_report",
105
- "verification"],
106
- "outputs": ["final"], "depends_on": ["verify"]},
107
- ],
108
- "review": [
109
- {"id": "analyze", "skill": "code_analysis", "layer": 0,
110
- "inputs": ["question"], "outputs": ["analysis"]},
111
- {"id": "security", "skill": "security_review", "layer": 0,
112
- "inputs": ["question"], "outputs": ["security_report"]},
113
- {"id": "migration_check", "skill": "migration_safety", "layer": 0,
114
- "inputs": ["question"], "outputs": ["migration_report"]},
115
- {"id": "synthesize", "skill": "synthesis", "layer": 1,
116
- "inputs": ["question", "analysis", "security_report",
117
- "migration_report"],
118
- "outputs": ["draft"],
119
- "depends_on": ["analyze", "security", "migration_check"]},
120
- {"id": "gate", "skill": "discipline_gate", "layer": 2,
121
- "inputs": ["question", "draft", "analysis", "security_report",
122
- "migration_report"],
123
- "outputs": ["final"], "depends_on": ["synthesize"]},
124
- ],
125
- "question": [
126
- {"id": "synthesize", "skill": "synthesis", "layer": 0,
127
- "inputs": ["question"], "outputs": ["draft"]},
128
- {"id": "gate", "skill": "discipline_gate", "layer": 1,
129
- "inputs": ["question", "draft"], "outputs": ["final"],
130
- "depends_on": ["synthesize"]},
131
- ],
132
- }
133
-
134
-
135
- def classify_intent(question: str) -> str:
136
- """Deterministic keyword scoring. Ties break toward the stricter plan
137
- (migration > auth > review > codegen > question)."""
138
- q = question.lower()
139
- scores = {}
140
- for intent, keywords in INTENT_KEYWORDS.items():
141
- scores[intent] = sum(1 for kw in keywords if kw in q)
142
- # SQL in the question forces the migration plan regardless of phrasing
143
- if re.search(r"\b(alter|create|drop|truncate)\s+table\b", q):
144
- scores["migration"] = scores.get("migration", 0) + 10
145
-
146
- strictness = ["migration", "auth", "review", "codegen"]
147
- best, best_score = "question", 0
148
- for intent in strictness:
149
- if scores.get(intent, 0) > best_score:
150
- best, best_score = intent, scores[intent]
151
- return best
152
-
153
-
154
- class Planner:
155
- def __init__(self, bdi: BDIStore, registry: SkillRegistry):
156
- self.bdi = bdi
157
- self.registry = registry
158
- self._counter = 0
159
-
160
- def form_plan(self, question: str, user_id: str) -> Plan:
161
- intent = classify_intent(question)
162
- steps = PLAN_TEMPLATES[intent]
163
-
164
- dag = DAGBuilder.from_intention(
165
- {"goal": question, "steps": steps},
166
- self.registry,
167
- strategy="quality",
168
- )
169
- errors = dag.validate(self.registry)
170
- if errors:
171
- raise RuntimeError(f"Plan for intent '{intent}' is invalid: {errors}")
172
-
173
- # Record the intention in the BDI store, linked to the beliefs
174
- # this plan rests on.
175
- self._counter += 1
176
- intention_id = f"intention_{user_id}_{self._counter}"
177
- relevant_beliefs = self._relevant_beliefs(intent)
178
- self.bdi.add_intention(BDIIntention(
179
- id=intention_id,
180
- goal=f"[{intent}] {question[:200]}",
181
- status=IntentionStatus.ACTIVE,
182
- belief_ids=relevant_beliefs,
183
- desire_ids=[d.id for d in self.bdi.list_desires()],
184
- created_at=datetime.now(timezone.utc),
185
- ))
186
-
187
- skills = " -> ".join(
188
- f"L{s['layer']}:{s['skill']}" for s in steps
189
- )
190
- return Plan(
191
- intent=intent,
192
- intention_id=intention_id,
193
- dag=dag,
194
- rationale=f"intent={intent}; plan: {skills}",
195
- )
196
-
197
- def complete_plan(self, intention_id: str, success: bool) -> None:
198
- status = IntentionStatus.COMPLETED if success else IntentionStatus.FAILED
199
- try:
200
- self.bdi.update_intention(
201
- intention_id, status=status,
202
- progress=1.0 if success else 0.0,
203
- )
204
- except KeyError:
205
- pass
206
-
207
- def _relevant_beliefs(self, intent: str) -> list:
208
- tag_map = {
209
- "migration": {"constraint", "schema", "audit"},
210
- "auth": {"constraint", "audit", "auth_flow", "architecture"},
211
- "codegen": {"architecture", "audit"},
212
- "review": {"constraint", "audit", "architecture"},
213
- "question": {"architecture"},
214
- }
215
- wanted = tag_map.get(intent, set())
216
- return [
217
- b.id for b in self.bdi.list_beliefs()
218
- if wanted & set(b.tags)
219
- ]