arkaos 2.17.1 → 2.17.4
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/VERSION +1 -1
- package/arka/SKILL.md +9 -67
- package/arka/skills/comfyui/SKILL.md +50 -12
- package/arka/skills/conclave/SKILL.md +43 -141
- package/arka/skills/conclave/references/advisors.md +36 -0
- package/arka/skills/human-writing/SKILL.md +15 -100
- package/arka/skills/human-writing/references/forbidden-patterns.md +32 -0
- package/config/hooks/post-tool-use.sh +72 -0
- package/config/hooks/session-start.sh +16 -0
- package/config/hooks/user-prompt-submit.ps1 +2 -2
- package/config/hooks/user-prompt-submit.sh +102 -26
- package/core/agents/__pycache__/behavior_enforcer.cpython-313.pyc +0 -0
- package/core/agents/__pycache__/dna_registry.cpython-313.pyc +0 -0
- package/core/agents/adapters/__pycache__/disc_adapter.cpython-313.pyc +0 -0
- package/core/agents/adapters/disc_adapter.py +149 -0
- package/core/agents/behavior_enforcer.py +255 -0
- package/core/agents/dna_registry.py +235 -0
- package/core/forge/__init__.py +36 -0
- package/core/forge/__pycache__/__init__.cpython-313.pyc +0 -0
- package/core/forge/__pycache__/orchestrator.cpython-313.pyc +0 -0
- package/core/forge/__pycache__/runtime_dispatcher.cpython-313.pyc +0 -0
- package/core/forge/orchestrator.py +770 -0
- package/core/forge/runtime_dispatcher.py +465 -0
- package/core/governance/__pycache__/quality_api.cpython-313.pyc +0 -0
- package/core/governance/__pycache__/quality_router.cpython-313.pyc +0 -0
- package/core/governance/__pycache__/review_workflow.cpython-313.pyc +0 -0
- package/core/governance/quality_api.py +280 -0
- package/core/governance/quality_router.py +304 -0
- package/core/governance/review_workflow.py +386 -0
- package/core/memory/__pycache__/compressor.cpython-313.pyc +0 -0
- package/core/memory/__pycache__/rehydrator.cpython-313.pyc +0 -0
- package/core/memory/__pycache__/session_store.cpython-313.pyc +0 -0
- package/core/memory/compressor.py +269 -0
- package/core/memory/rehydrator.py +204 -0
- package/core/memory/session_store.py +256 -0
- package/core/runtime/__pycache__/context_compactor.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/subagent.cpython-313.pyc +0 -0
- package/core/synapse/__init__.py +10 -3
- package/core/synapse/__pycache__/__init__.cpython-313.pyc +0 -0
- package/core/synapse/__pycache__/engine.cpython-313.pyc +0 -0
- package/core/synapse/__pycache__/kb_cache.cpython-313.pyc +0 -0
- package/core/synapse/__pycache__/layers.cpython-313.pyc +0 -0
- package/core/synapse/engine.py +27 -16
- package/core/synapse/kb_cache.py +382 -0
- package/core/synapse/layers.py +253 -50
- package/core/sync/__pycache__/self_healing.cpython-313.pyc +0 -0
- package/core/workflow/__pycache__/announcer.cpython-313.pyc +0 -0
- package/core/workflow/__pycache__/dashboard.cpython-313.pyc +0 -0
- package/core/workflow/__pycache__/enforcer.cpython-313.pyc +0 -0
- package/core/workflow/__pycache__/rules_registry.cpython-313.pyc +0 -0
- package/core/workflow/__pycache__/state.cpython-313.pyc +0 -0
- package/core/workflow/announcer.py +246 -0
- package/core/workflow/dashboard.py +194 -0
- package/core/workflow/enforcer.py +234 -0
- package/core/workflow/recovery.py +196 -0
- package/core/workflow/rules_registry.py +484 -0
- package/core/workflow/session_summary.py +204 -0
- package/core/workflow/state.py +12 -2
- package/departments/dev/SKILL.md +10 -42
- package/departments/dev/skills/agent-design/SKILL.md +6 -26
- package/departments/dev/skills/ci-cd-pipeline/SKILL.md +6 -29
- package/departments/dev/skills/db-schema/SKILL.md +6 -24
- package/departments/dev/skills/dependency-audit/SKILL.md +1 -3
- package/departments/dev/skills/incident/SKILL.md +6 -24
- package/departments/dev/skills/mcp-builder/SKILL.md +4 -17
- package/departments/dev/skills/observability/SKILL.md +6 -29
- package/departments/dev/skills/performance-profiler/SKILL.md +5 -26
- package/departments/dev/skills/rag-architect/SKILL.md +5 -23
- package/departments/dev/skills/release/SKILL.md +4 -17
- package/departments/dev/skills/spec/SKILL.md +47 -148
- package/departments/landing/skills/landing-gen/SKILL.md +3 -15
- package/departments/marketing/skills/cold-email/SKILL.md +5 -17
- package/departments/marketing/skills/programmatic-seo/SKILL.md +6 -21
- package/departments/strategy/skills/board-advisor/SKILL.md +7 -21
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/scripts/synapse-bridge.py +27 -3
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
"""Knowledge session cache — project-scoped on-demand knowledge retrieval.
|
|
2
|
+
|
|
3
|
+
Provides session-scoped caching for L3.5 knowledge retrieval results.
|
|
4
|
+
Enables automatic knowledge injection when topics overlap across conversation turns
|
|
5
|
+
within the same project context.
|
|
6
|
+
|
|
7
|
+
Architecture:
|
|
8
|
+
- Path: /tmp/arkaos-kb-{project_hash}/{session_id}.json
|
|
9
|
+
- Max entries per session: 50
|
|
10
|
+
- TTL: 30 minutes
|
|
11
|
+
- Project-scoped via cwd hash (isolates knowledge by project)
|
|
12
|
+
- Auto-inject when Jaccard topic overlap >= 0.3
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import hashlib
|
|
16
|
+
import json
|
|
17
|
+
import time
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Any, Optional
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
STOP_WORDS = {
|
|
23
|
+
"a",
|
|
24
|
+
"an",
|
|
25
|
+
"the",
|
|
26
|
+
"and",
|
|
27
|
+
"or",
|
|
28
|
+
"but",
|
|
29
|
+
"in",
|
|
30
|
+
"on",
|
|
31
|
+
"at",
|
|
32
|
+
"to",
|
|
33
|
+
"for",
|
|
34
|
+
"of",
|
|
35
|
+
"with",
|
|
36
|
+
"by",
|
|
37
|
+
"from",
|
|
38
|
+
"as",
|
|
39
|
+
"is",
|
|
40
|
+
"was",
|
|
41
|
+
"are",
|
|
42
|
+
"were",
|
|
43
|
+
"been",
|
|
44
|
+
"be",
|
|
45
|
+
"have",
|
|
46
|
+
"has",
|
|
47
|
+
"had",
|
|
48
|
+
"do",
|
|
49
|
+
"does",
|
|
50
|
+
"did",
|
|
51
|
+
"will",
|
|
52
|
+
"would",
|
|
53
|
+
"could",
|
|
54
|
+
"should",
|
|
55
|
+
"may",
|
|
56
|
+
"might",
|
|
57
|
+
"must",
|
|
58
|
+
"shall",
|
|
59
|
+
"can",
|
|
60
|
+
"need",
|
|
61
|
+
"dare",
|
|
62
|
+
"ought",
|
|
63
|
+
"used",
|
|
64
|
+
"it",
|
|
65
|
+
"its",
|
|
66
|
+
"this",
|
|
67
|
+
"that",
|
|
68
|
+
"these",
|
|
69
|
+
"those",
|
|
70
|
+
"i",
|
|
71
|
+
"you",
|
|
72
|
+
"he",
|
|
73
|
+
"she",
|
|
74
|
+
"we",
|
|
75
|
+
"they",
|
|
76
|
+
"what",
|
|
77
|
+
"which",
|
|
78
|
+
"who",
|
|
79
|
+
"whom",
|
|
80
|
+
"whose",
|
|
81
|
+
"where",
|
|
82
|
+
"when",
|
|
83
|
+
"why",
|
|
84
|
+
"how",
|
|
85
|
+
"all",
|
|
86
|
+
"each",
|
|
87
|
+
"every",
|
|
88
|
+
"both",
|
|
89
|
+
"few",
|
|
90
|
+
"more",
|
|
91
|
+
"most",
|
|
92
|
+
"other",
|
|
93
|
+
"some",
|
|
94
|
+
"such",
|
|
95
|
+
"no",
|
|
96
|
+
"nor",
|
|
97
|
+
"not",
|
|
98
|
+
"only",
|
|
99
|
+
"own",
|
|
100
|
+
"same",
|
|
101
|
+
"so",
|
|
102
|
+
"than",
|
|
103
|
+
"too",
|
|
104
|
+
"very",
|
|
105
|
+
"just",
|
|
106
|
+
"also",
|
|
107
|
+
"now",
|
|
108
|
+
"here",
|
|
109
|
+
"there",
|
|
110
|
+
"then",
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class KBSessionCache:
|
|
115
|
+
"""Project-scoped session cache for knowledge retrieval.
|
|
116
|
+
|
|
117
|
+
Stores L3.5 search results per session with topic extraction for
|
|
118
|
+
overlap-based auto-injection.
|
|
119
|
+
|
|
120
|
+
Usage:
|
|
121
|
+
cache = KBSessionCache(session_id="abc123", project_path="/path/to/project")
|
|
122
|
+
cache.store("deploy arkaos on kubernetes", [{"text": "...", "source": "..."}])
|
|
123
|
+
results = cache.retrieve(topics={"deploy", "kubernetes"})
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
def __init__(
|
|
127
|
+
self,
|
|
128
|
+
session_id: str,
|
|
129
|
+
project_path: Optional[str] = None,
|
|
130
|
+
cache_dir: Optional[str] = None,
|
|
131
|
+
max_entries: int = 50,
|
|
132
|
+
ttl_seconds: int = 1800,
|
|
133
|
+
) -> None:
|
|
134
|
+
self._session_id = session_id
|
|
135
|
+
self._max_entries = max_entries
|
|
136
|
+
self._ttl_seconds = ttl_seconds
|
|
137
|
+
|
|
138
|
+
if cache_dir:
|
|
139
|
+
self._cache_dir = Path(cache_dir)
|
|
140
|
+
else:
|
|
141
|
+
project_hash = self._hash_project(project_path or "")
|
|
142
|
+
self._cache_dir = Path("/tmp") / f"arkaos-kb-{project_hash}"
|
|
143
|
+
|
|
144
|
+
self._cache_file = self._cache_dir / f"{session_id}.json"
|
|
145
|
+
self._ensure_dir()
|
|
146
|
+
|
|
147
|
+
@staticmethod
|
|
148
|
+
def _hash_project(path: str) -> str:
|
|
149
|
+
"""Create short hash of project path for directory naming."""
|
|
150
|
+
if not path:
|
|
151
|
+
return "default"
|
|
152
|
+
h = hashlib.md5(path.encode(), usedforsecurity=False)
|
|
153
|
+
return h.hexdigest()[:12]
|
|
154
|
+
|
|
155
|
+
def _ensure_dir(self) -> None:
|
|
156
|
+
"""Ensure cache directory exists."""
|
|
157
|
+
self._cache_dir.mkdir(parents=True, exist_ok=True)
|
|
158
|
+
|
|
159
|
+
def _load(self) -> dict[str, Any]:
|
|
160
|
+
"""Load cache from disk."""
|
|
161
|
+
if not self._cache_file.exists():
|
|
162
|
+
return {}
|
|
163
|
+
try:
|
|
164
|
+
return json.loads(self._cache_file.read_text())
|
|
165
|
+
except (json.JSONDecodeError, OSError):
|
|
166
|
+
return {}
|
|
167
|
+
|
|
168
|
+
def _save(self, data: dict[str, Any]) -> None:
|
|
169
|
+
"""Save cache to disk."""
|
|
170
|
+
self._cache_file.write_text(json.dumps(data, indent=2))
|
|
171
|
+
|
|
172
|
+
def extract_topics(self, query: str) -> set[str]:
|
|
173
|
+
"""Extract key topics from a query string.
|
|
174
|
+
|
|
175
|
+
Removes stop words and extracts nouns, verbs, and key phrases.
|
|
176
|
+
Used for Jaccard overlap calculation.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
query: Raw query string
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
Set of topic keywords
|
|
183
|
+
"""
|
|
184
|
+
if not query:
|
|
185
|
+
return set()
|
|
186
|
+
|
|
187
|
+
words = query.lower().split()
|
|
188
|
+
topics = {w.strip(".,!?;:()[]{}") for w in words if len(w) > 2 and w not in STOP_WORDS}
|
|
189
|
+
return topics
|
|
190
|
+
|
|
191
|
+
@staticmethod
|
|
192
|
+
def jaccard(topics1: set[str], topics2: set[str]) -> float:
|
|
193
|
+
"""Calculate Jaccard similarity between two topic sets.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
topics1: First set of topics
|
|
197
|
+
topics2: Second set of topics
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
Jaccard coefficient between 0.0 and 1.0
|
|
201
|
+
"""
|
|
202
|
+
if not topics1 or not topics2:
|
|
203
|
+
return 0.0
|
|
204
|
+
intersection = len(topics1 & topics2)
|
|
205
|
+
union = len(topics1 | topics2)
|
|
206
|
+
return intersection / union if union > 0 else 0.0
|
|
207
|
+
|
|
208
|
+
def store(self, query: str, results: list[dict]) -> set[str]:
|
|
209
|
+
"""Store knowledge retrieval results in session cache.
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
query: The original user query
|
|
213
|
+
results: List of dicts with 'text', 'source', 'heading', 'score'
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
Set of extracted topics for this query (for overlap detection)
|
|
217
|
+
"""
|
|
218
|
+
if not results:
|
|
219
|
+
return set()
|
|
220
|
+
|
|
221
|
+
topics = self.extract_topics(query)
|
|
222
|
+
now = time.time()
|
|
223
|
+
|
|
224
|
+
cache = self._load()
|
|
225
|
+
|
|
226
|
+
entry = {
|
|
227
|
+
"query": query,
|
|
228
|
+
"topics": list(topics),
|
|
229
|
+
"normalized_topics": sorted(topics),
|
|
230
|
+
"snippets": [
|
|
231
|
+
{
|
|
232
|
+
"text": r.get("text", "")[:300],
|
|
233
|
+
"source": r.get("source", ""),
|
|
234
|
+
"heading": r.get("heading", ""),
|
|
235
|
+
"score": r.get("score", 0.0),
|
|
236
|
+
}
|
|
237
|
+
for r in results
|
|
238
|
+
],
|
|
239
|
+
"timestamp": now,
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
query_hash = hashlib.sha256(query.lower().encode(), usedforsecurity=False).hexdigest()[:16]
|
|
243
|
+
cache[query_hash] = entry
|
|
244
|
+
|
|
245
|
+
cache["_meta"] = {
|
|
246
|
+
"session_id": self._session_id,
|
|
247
|
+
"updated_at": now,
|
|
248
|
+
"entry_count": len(cache) - 1,
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if len(cache) - 1 > self._max_entries:
|
|
252
|
+
cache = self._evict_oldest(cache)
|
|
253
|
+
|
|
254
|
+
self._save(cache)
|
|
255
|
+
return topics
|
|
256
|
+
|
|
257
|
+
def retrieve(
|
|
258
|
+
self,
|
|
259
|
+
query: Optional[str] = None,
|
|
260
|
+
topics: Optional[set[str]] = None,
|
|
261
|
+
threshold: float = 0.3,
|
|
262
|
+
) -> list[dict]:
|
|
263
|
+
"""Retrieve cached knowledge by query or topic overlap.
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
query: Optional exact query to retrieve
|
|
267
|
+
topics: Optional topic set for Jaccard matching
|
|
268
|
+
threshold: Minimum Jaccard similarity for auto-inject (default 0.3)
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
List of snippet dicts from the best matching cached entry
|
|
272
|
+
"""
|
|
273
|
+
cache = self._load()
|
|
274
|
+
if not cache or "_meta" not in cache:
|
|
275
|
+
return []
|
|
276
|
+
|
|
277
|
+
now = time.time()
|
|
278
|
+
candidates: list[tuple[float, dict]] = []
|
|
279
|
+
|
|
280
|
+
for query_hash, entry in cache.items():
|
|
281
|
+
if query_hash == "_meta":
|
|
282
|
+
continue
|
|
283
|
+
|
|
284
|
+
entry_age = now - entry.get("timestamp", 0)
|
|
285
|
+
if entry_age > self._ttl_seconds:
|
|
286
|
+
continue
|
|
287
|
+
|
|
288
|
+
if query:
|
|
289
|
+
entry_hash = hashlib.sha256(
|
|
290
|
+
query.lower().encode(), usedforsecurity=False
|
|
291
|
+
).hexdigest()[:16]
|
|
292
|
+
if entry_hash == query_hash:
|
|
293
|
+
return entry.get("snippets", [])
|
|
294
|
+
|
|
295
|
+
if topics:
|
|
296
|
+
entry_topics = set(entry.get("normalized_topics", []))
|
|
297
|
+
score = self.jaccard(topics, entry_topics)
|
|
298
|
+
if score >= threshold:
|
|
299
|
+
candidates.append((score, entry))
|
|
300
|
+
|
|
301
|
+
if not candidates:
|
|
302
|
+
return []
|
|
303
|
+
|
|
304
|
+
candidates.sort(key=lambda x: x[0], reverse=True)
|
|
305
|
+
best = candidates[0][1]
|
|
306
|
+
return best.get("snippets", [])
|
|
307
|
+
|
|
308
|
+
def get_overlap(self, query: str, threshold: float = 0.3) -> list[dict]:
|
|
309
|
+
"""Get overlapping knowledge for a query using Jaccard similarity.
|
|
310
|
+
|
|
311
|
+
Convenience method that extracts topics and retrieves.
|
|
312
|
+
|
|
313
|
+
Args:
|
|
314
|
+
query: Query string to match against cached entries
|
|
315
|
+
threshold: Minimum Jaccard similarity (default 0.3)
|
|
316
|
+
|
|
317
|
+
Returns:
|
|
318
|
+
List of snippet dicts from matching entries
|
|
319
|
+
"""
|
|
320
|
+
topics = self.extract_topics(query)
|
|
321
|
+
return self.retrieve(topics=topics, threshold=threshold)
|
|
322
|
+
|
|
323
|
+
def _evict_oldest(self, cache: dict[str, Any]) -> dict[str, Any]:
|
|
324
|
+
"""Evict oldest entries when cache exceeds max_entries."""
|
|
325
|
+
entries = [(k, v) for k, v in cache.items() if k != "_meta"]
|
|
326
|
+
entries.sort(key=lambda x: x[1].get("timestamp", 0))
|
|
327
|
+
|
|
328
|
+
keep_count = self._max_entries - 1
|
|
329
|
+
keep_entries = dict(entries[-keep_count:]) if len(entries) > keep_count else dict(entries)
|
|
330
|
+
keep_entries["_meta"] = cache.get("_meta", {})
|
|
331
|
+
|
|
332
|
+
return keep_entries
|
|
333
|
+
|
|
334
|
+
def clear(self) -> None:
|
|
335
|
+
"""Clear all cached entries for this session."""
|
|
336
|
+
if self._cache_file.exists():
|
|
337
|
+
self._cache_file.unlink()
|
|
338
|
+
|
|
339
|
+
def stats(self) -> dict[str, Any]:
|
|
340
|
+
"""Get cache statistics."""
|
|
341
|
+
cache = self._load()
|
|
342
|
+
now = time.time()
|
|
343
|
+
valid_entries = 0
|
|
344
|
+
oldest = None
|
|
345
|
+
newest = None
|
|
346
|
+
|
|
347
|
+
for k, v in cache.items():
|
|
348
|
+
if k == "_meta":
|
|
349
|
+
continue
|
|
350
|
+
age = now - v.get("timestamp", 0)
|
|
351
|
+
if age <= self._ttl_seconds:
|
|
352
|
+
valid_entries += 1
|
|
353
|
+
if oldest is None or v.get("timestamp", 0) < oldest:
|
|
354
|
+
oldest = v.get("timestamp", 0)
|
|
355
|
+
if newest is None or v.get("timestamp", 0) > newest:
|
|
356
|
+
newest = v.get("timestamp", 0)
|
|
357
|
+
|
|
358
|
+
return {
|
|
359
|
+
"session_id": self._session_id,
|
|
360
|
+
"cache_dir": str(self._cache_dir),
|
|
361
|
+
"total_entries": len(cache) - 1,
|
|
362
|
+
"valid_entries": valid_entries,
|
|
363
|
+
"oldest_entry": oldest,
|
|
364
|
+
"newest_entry": newest,
|
|
365
|
+
"ttl_seconds": self._ttl_seconds,
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def extract_topics(query: str) -> set[str]:
|
|
370
|
+
"""Standalone topic extraction function.
|
|
371
|
+
|
|
372
|
+
Convenience wrapper for use in external contexts.
|
|
373
|
+
"""
|
|
374
|
+
return KBSessionCache(session_id="", project_path="").extract_topics(query)
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def jaccard_similarity(topics1: set[str], topics2: set[str]) -> float:
|
|
378
|
+
"""Standalone Jaccard similarity calculation.
|
|
379
|
+
|
|
380
|
+
Convenience wrapper for use in external contexts.
|
|
381
|
+
"""
|
|
382
|
+
return KBSessionCache.jaccard(topics1, topics2)
|