arkaos 4.36.0 → 4.38.0
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 +1 -1
- package/THE-ARKAOS-GUIDE.md +1 -1
- package/VERSION +1 -1
- package/arka/SKILL.md +1 -1
- package/config/skills-curated.yaml +2 -2
- package/config/skills-provenance.yaml +7 -0
- package/core/hooks/session_start.py +17 -3
- package/core/memory/semantic_store.py +58 -11
- package/core/memory/turn_capture.py +56 -11
- package/core/runtime/opencode.py +16 -5
- package/core/runtime/opencode_hooks.py +498 -0
- package/departments/dev/skills/animated-website/SKILL.md +4 -2
- package/departments/dev/skills/scroll-world/SKILL.md +619 -0
- package/departments/dev/skills/scroll-world/references/index-template.html +73 -0
- package/departments/dev/skills/scroll-world/references/knockout.py +89 -0
- package/departments/dev/skills/scroll-world/references/pipeline.md +213 -0
- package/departments/dev/skills/scroll-world/references/prompts.md +149 -0
- package/departments/dev/skills/scroll-world/references/scroll-world.LICENSE +21 -0
- package/departments/dev/skills/scroll-world/references/scrub-engine.js +448 -0
- package/harness/codex/AGENTS.md +1 -1
- package/harness/copilot/copilot-instructions.md +1 -1
- package/harness/cursor/rules/arkaos.mdc +2 -2
- package/harness/gemini/GEMINI.md +1 -1
- package/harness/opencode/AGENTS.md +1 -1
- package/harness/opencode/agents/arka-architect-gabriel.md +1 -1
- package/harness/opencode/agents/arka-brand-director-valentina.md +1 -1
- package/harness/opencode/agents/arka-cfo-helena.md +1 -1
- package/harness/opencode/agents/arka-chief-of-staff-afonso.md +1 -1
- package/harness/opencode/agents/arka-community-strategist-beatriz.md +1 -1
- package/harness/opencode/agents/arka-content-strategist-rafael.md +1 -1
- package/harness/opencode/agents/arka-conversion-strategist-ines.md +1 -1
- package/harness/opencode/agents/arka-coo-sofia.md +1 -1
- package/harness/opencode/agents/arka-copy-director-eduardo.md +1 -1
- package/harness/opencode/agents/arka-cqo-marta.md +1 -1
- package/harness/opencode/agents/arka-cto-marco.md +1 -1
- package/harness/opencode/agents/arka-design-ops-lead-iris.md +1 -1
- package/harness/opencode/agents/arka-ecom-director-ricardo.md +1 -1
- package/harness/opencode/agents/arka-knowledge-director-clara.md +1 -1
- package/harness/opencode/agents/arka-leadership-director-rodrigo.md +1 -1
- package/harness/opencode/agents/arka-marketing-director-luna.md +1 -1
- package/harness/opencode/agents/arka-ops-lead-daniel.md +1 -1
- package/harness/opencode/agents/arka-pm-director-carolina.md +1 -1
- package/harness/opencode/agents/arka-revops-lead-vicente.md +1 -1
- package/harness/opencode/agents/arka-saas-strategist-tiago.md +1 -1
- package/harness/opencode/agents/arka-sales-director-miguel.md +1 -1
- package/harness/opencode/agents/arka-strategy-director-tomas.md +1 -1
- package/harness/opencode/agents/arka-tech-director-francisca.md +1 -1
- package/harness/opencode/agents/arka-tech-lead-paulo.md +1 -1
- package/harness/opencode/agents/arka-video-producer-simao.md +1 -1
- package/harness/opencode/agents-meta.json +27 -0
- package/harness/opencode/opencode.json +23 -0
- package/harness/opencode/plugins/arka.ts +128 -0
- package/harness/zed/.rules +1 -1
- package/installer/adapters/opencode.js +142 -16
- package/installer/assets/opencode/arka.ts +128 -0
- package/knowledge/skills-manifest.json +14 -1
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/scripts/harness_gen.py +43 -3
package/README.md
CHANGED
package/THE-ARKAOS-GUIDE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# The ArkaOS Guide
|
|
2
2
|
|
|
3
|
-
> v4.
|
|
3
|
+
> v4.38.0 — 89 agents, 17 departments, 332 skills, 297 commands, 17 ADRs.
|
|
4
4
|
> One file, everything you need to start. Generated by `scripts/guide_gen.py` — never hand-edited.
|
|
5
5
|
|
|
6
6
|
## What it is
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
4.
|
|
1
|
+
4.38.0
|
package/arka/SKILL.md
CHANGED
|
@@ -47,7 +47,7 @@ complementary, not alternatives.
|
|
|
47
47
|
# ArkaOS — Main Orchestrator
|
|
48
48
|
|
|
49
49
|
> **The Operating System for AI Agent Teams**
|
|
50
|
-
> 89 agents. 17 departments.
|
|
50
|
+
> 89 agents. 17 departments. 332 skills. Multi-runtime. Dashboard. Knowledge RAG.
|
|
51
51
|
|
|
52
52
|
## ⛔ Evidence flow — 4 gates (NON-NEGOTIABLE)
|
|
53
53
|
|
|
@@ -24,8 +24,8 @@ curated_subskills:
|
|
|
24
24
|
community: [moderation]
|
|
25
25
|
content: [calendar, hook-write]
|
|
26
26
|
dev: [api-design, architecture-design, clean-code-review, code-review,
|
|
27
|
-
db-design, diagram, refactor-plan, runbook,
|
|
28
|
-
tdd-cycle, watch]
|
|
27
|
+
db-design, diagram, refactor-plan, runbook, scroll-world,
|
|
28
|
+
security-audit, spec, tdd-cycle, watch]
|
|
29
29
|
ecom: [cart-recovery, store-audit]
|
|
30
30
|
finance: [financial-model, unit-economics]
|
|
31
31
|
kb: [search-kb, zettelkasten-process]
|
|
@@ -91,6 +91,13 @@ derived:
|
|
|
91
91
|
origin: community
|
|
92
92
|
source: https://github.com/nutlope/hallmark
|
|
93
93
|
license: MIT
|
|
94
|
+
# Scroll-world — scroll-scrubbed "fly through the world" landing pages
|
|
95
|
+
# (Higgsfield pipeline + portable scrub engine), vendored verbatim with
|
|
96
|
+
# ArkaOS frontmatter; THIRD-PARTY-NOTICES records the trail.
|
|
97
|
+
departments/dev/skills/scroll-world:
|
|
98
|
+
origin: community
|
|
99
|
+
source: https://github.com/oso95/scroll-world
|
|
100
|
+
license: MIT
|
|
94
101
|
# Watch absorption campaign — video-input primitive derived from
|
|
95
102
|
# bradautomates/claude-video v0.2.0 (MIT), maintained natively.
|
|
96
103
|
departments/dev/skills/watch:
|
|
@@ -341,16 +341,30 @@ def build_recap(cwd: str, budget_ms: int = _BUDGET_MS) -> str:
|
|
|
341
341
|
if not records or (time.monotonic() - start) * 1000 > budget_ms:
|
|
342
342
|
return ""
|
|
343
343
|
lines = ["[SESSION-MEMORY] Prior turns (importance+recency — not semantic):"]
|
|
344
|
+
handoff = store.cross_runtime_handoff(project, "claude")
|
|
345
|
+
if handoff:
|
|
346
|
+
stamp = (
|
|
347
|
+
handoff.ts[11:16] + "Z" if len(handoff.ts) >= 16 else handoff.ts
|
|
348
|
+
)
|
|
349
|
+
h_summary = neutralize_summary(handoff.summary)[:_SUMMARY_CHARS]
|
|
350
|
+
tail = f": {h_summary}" if h_summary else ""
|
|
351
|
+
lines.append(
|
|
352
|
+
f"[arka:handoff] última sessão em {handoff.runtime}"
|
|
353
|
+
f" ({stamp}){tail}"
|
|
354
|
+
)
|
|
355
|
+
turn_lines = 0
|
|
344
356
|
for record in records:
|
|
345
357
|
summary = neutralize_summary(record.summary)[:_SUMMARY_CHARS]
|
|
346
358
|
if not summary:
|
|
347
359
|
continue
|
|
348
|
-
|
|
349
|
-
|
|
360
|
+
runtime = f" · {record.runtime}" if record.runtime else ""
|
|
361
|
+
lines.append(f"[SESSION-MEMORY] - {record.ts[:10]}{runtime}: {summary}")
|
|
362
|
+
turn_lines += 1
|
|
363
|
+
if turn_lines == 0:
|
|
350
364
|
return ""
|
|
351
365
|
backends = ",".join(sorted({r.embedding_backend for r in records})) or "none"
|
|
352
366
|
lines.append(
|
|
353
|
-
f"[SESSION-MEMORY] shown: {
|
|
367
|
+
f"[SESSION-MEMORY] shown: {turn_lines} turns ({project}),"
|
|
354
368
|
f" backends={backends}"
|
|
355
369
|
)
|
|
356
370
|
return "\n".join(lines)
|
|
@@ -46,6 +46,7 @@ class TurnRecord(BaseModel):
|
|
|
46
46
|
id: str = Field(default_factory=lambda: uuid.uuid4().hex)
|
|
47
47
|
ts: str = ""
|
|
48
48
|
session_id: str = ""
|
|
49
|
+
runtime: str = ""
|
|
49
50
|
project_name: str = ""
|
|
50
51
|
cwd: str = ""
|
|
51
52
|
summary: str = ""
|
|
@@ -110,6 +111,7 @@ class SessionMemoryStore:
|
|
|
110
111
|
id TEXT PRIMARY KEY,
|
|
111
112
|
ts TEXT NOT NULL,
|
|
112
113
|
session_id TEXT NOT NULL,
|
|
114
|
+
runtime TEXT NOT NULL DEFAULT '',
|
|
113
115
|
project_name TEXT NOT NULL DEFAULT '',
|
|
114
116
|
cwd TEXT NOT NULL DEFAULT '',
|
|
115
117
|
summary TEXT NOT NULL DEFAULT '',
|
|
@@ -122,13 +124,23 @@ class SessionMemoryStore:
|
|
|
122
124
|
dims INTEGER NOT NULL DEFAULT 0
|
|
123
125
|
)
|
|
124
126
|
""")
|
|
127
|
+
self._migrate(conn)
|
|
128
|
+
|
|
129
|
+
def _migrate(self, conn: sqlite3.Connection) -> None:
|
|
130
|
+
"""Additive column migrations for DBs created before a column
|
|
131
|
+
existed — CREATE TABLE IF NOT EXISTS never alters a live table."""
|
|
132
|
+
cols = {row[1] for row in conn.execute("PRAGMA table_info(turns)")}
|
|
133
|
+
if "runtime" not in cols:
|
|
125
134
|
conn.execute(
|
|
126
|
-
"
|
|
127
|
-
"ON turns (project_name, ts DESC)"
|
|
128
|
-
)
|
|
129
|
-
conn.execute(
|
|
130
|
-
"CREATE INDEX IF NOT EXISTS idx_turns_session ON turns (session_id)"
|
|
135
|
+
"ALTER TABLE turns ADD COLUMN runtime TEXT NOT NULL DEFAULT ''"
|
|
131
136
|
)
|
|
137
|
+
conn.execute(
|
|
138
|
+
"CREATE INDEX IF NOT EXISTS idx_turns_project_ts "
|
|
139
|
+
"ON turns (project_name, ts DESC)"
|
|
140
|
+
)
|
|
141
|
+
conn.execute(
|
|
142
|
+
"CREATE INDEX IF NOT EXISTS idx_turns_session ON turns (session_id)"
|
|
143
|
+
)
|
|
132
144
|
|
|
133
145
|
def save(self, record: TurnRecord) -> None:
|
|
134
146
|
row = record.model_dump()
|
|
@@ -139,12 +151,13 @@ class SessionMemoryStore:
|
|
|
139
151
|
)
|
|
140
152
|
with closing(self._conn()) as conn, conn:
|
|
141
153
|
conn.execute(
|
|
142
|
-
"INSERT OR REPLACE INTO turns (id, ts, session_id,
|
|
143
|
-
" cwd, summary, tools_used, file_paths,
|
|
144
|
-
" embedding_backend, embedding_model,
|
|
145
|
-
"
|
|
146
|
-
" :
|
|
147
|
-
" :
|
|
154
|
+
"INSERT OR REPLACE INTO turns (id, ts, session_id, runtime,"
|
|
155
|
+
" project_name, cwd, summary, tools_used, file_paths,"
|
|
156
|
+
" importance, embedding, embedding_backend, embedding_model,"
|
|
157
|
+
" dims)"
|
|
158
|
+
" VALUES (:id, :ts, :session_id, :runtime, :project_name,"
|
|
159
|
+
" :cwd, :summary, :tools_used, :file_paths, :importance,"
|
|
160
|
+
" :embedding, :embedding_backend, :embedding_model, :dims)",
|
|
148
161
|
row,
|
|
149
162
|
)
|
|
150
163
|
|
|
@@ -185,6 +198,40 @@ class SessionMemoryStore:
|
|
|
185
198
|
where = f"WHERE {' AND '.join(clauses)}" if clauses else ""
|
|
186
199
|
return where, params
|
|
187
200
|
|
|
201
|
+
def latest_turn(
|
|
202
|
+
self, project_name: str, exclude_session: str | None = None
|
|
203
|
+
) -> TurnRecord | None:
|
|
204
|
+
"""Newest turn in the project — the handoff-detection primitive."""
|
|
205
|
+
where, params = self._scope(project_name, exclude_session)
|
|
206
|
+
with closing(self._conn()) as conn:
|
|
207
|
+
row = conn.execute(
|
|
208
|
+
f"SELECT * FROM turns {where} ORDER BY ts DESC LIMIT 1",
|
|
209
|
+
params,
|
|
210
|
+
).fetchone()
|
|
211
|
+
return self._row_to_record(row) if row else None
|
|
212
|
+
|
|
213
|
+
def cross_runtime_handoff(
|
|
214
|
+
self,
|
|
215
|
+
project_name: str,
|
|
216
|
+
current_runtime: str,
|
|
217
|
+
exclude_session: str | None = None,
|
|
218
|
+
max_age_h: float = 24,
|
|
219
|
+
) -> TurnRecord | None:
|
|
220
|
+
"""Newest turn in the project captured by a DIFFERENT runtime,
|
|
221
|
+
fresh enough to matter as a continuation hint — the shared
|
|
222
|
+
handoff predicate for every runtime's entry point."""
|
|
223
|
+
latest = self.latest_turn(project_name, exclude_session=exclude_session)
|
|
224
|
+
if not latest or not latest.runtime or latest.runtime == current_runtime:
|
|
225
|
+
return None
|
|
226
|
+
try:
|
|
227
|
+
age_h = (
|
|
228
|
+
datetime.now(UTC)
|
|
229
|
+
- datetime.fromisoformat(latest.ts.replace("Z", "+00:00"))
|
|
230
|
+
).total_seconds() / 3600
|
|
231
|
+
except ValueError:
|
|
232
|
+
return None
|
|
233
|
+
return latest if age_h <= max_age_h else None
|
|
234
|
+
|
|
188
235
|
def keyword_search(
|
|
189
236
|
self, query: str, project_name: str | None = None, top_k: int = 3
|
|
190
237
|
) -> list[dict]:
|
|
@@ -161,7 +161,8 @@ def _precompute_cache(
|
|
|
161
161
|
"dims": record.dims,
|
|
162
162
|
"items": [
|
|
163
163
|
{"summary": i["summary"][:200], "project_name": i["project_name"],
|
|
164
|
-
"ts": i["ts"], "score": i["score"], "retrieval": i["retrieval"]
|
|
164
|
+
"ts": i["ts"], "score": i["score"], "retrieval": i["retrieval"],
|
|
165
|
+
"runtime": i.get("runtime", "")}
|
|
165
166
|
for i in items
|
|
166
167
|
],
|
|
167
168
|
}
|
|
@@ -190,14 +191,16 @@ def _maintenance(store: SessionMemoryStore) -> None:
|
|
|
190
191
|
store.prune()
|
|
191
192
|
|
|
192
193
|
|
|
193
|
-
def
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
194
|
+
def _persist_record(
|
|
195
|
+
session_id: str,
|
|
196
|
+
text: str,
|
|
197
|
+
cwd: str,
|
|
198
|
+
runtime: str,
|
|
199
|
+
tools: list[str],
|
|
200
|
+
paths: list[str],
|
|
201
|
+
) -> None:
|
|
202
|
+
"""Shared tail for every capture path: sanitize -> embed -> store ->
|
|
203
|
+
precompute the F1-A3 neighbour cache -> amortised maintenance."""
|
|
201
204
|
summary, sanitized_ok = _sanitized_summary(text)
|
|
202
205
|
tools = _safe_tool_names(tools, sanitized_ok)
|
|
203
206
|
if not sanitized_ok:
|
|
@@ -206,6 +209,7 @@ def capture_turn(session_id: str, transcript_path: str, cwd: str = "") -> int:
|
|
|
206
209
|
record = TurnRecord(
|
|
207
210
|
ts=datetime.now(UTC).isoformat(),
|
|
208
211
|
session_id=session_id,
|
|
212
|
+
runtime=runtime,
|
|
209
213
|
project_name=Path(cwd).name if cwd else "",
|
|
210
214
|
cwd=cwd,
|
|
211
215
|
summary=summary,
|
|
@@ -221,6 +225,31 @@ def capture_turn(session_id: str, transcript_path: str, cwd: str = "") -> int:
|
|
|
221
225
|
store.save(record)
|
|
222
226
|
_precompute_cache(store, record, session_id)
|
|
223
227
|
_maintenance(store)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def capture_turn(
|
|
231
|
+
session_id: str, transcript_path: str, cwd: str = "", runtime: str = "claude"
|
|
232
|
+
) -> int:
|
|
233
|
+
if not _config_enabled() or not session_id:
|
|
234
|
+
return 0
|
|
235
|
+
raw = _read_tail(transcript_path)
|
|
236
|
+
if not raw:
|
|
237
|
+
return 0
|
|
238
|
+
text = _last_assistant_text(transcript_path, raw)
|
|
239
|
+
tools, paths = _parse_tool_uses(raw)
|
|
240
|
+
_persist_record(session_id, text, cwd, runtime, tools, paths)
|
|
241
|
+
return 0
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def capture_text_turn(
|
|
245
|
+
session_id: str, text: str, cwd: str = "", runtime: str = "opencode"
|
|
246
|
+
) -> int:
|
|
247
|
+
"""Transcript-free capture for runtimes without a Claude-style JSONL
|
|
248
|
+
transcript on disk (OpenCode: the plugin holds the messages, not us).
|
|
249
|
+
The caller hands over the last assistant text directly."""
|
|
250
|
+
if not _config_enabled() or not session_id or not text.strip():
|
|
251
|
+
return 0
|
|
252
|
+
_persist_record(session_id, text, cwd, runtime, [], [])
|
|
224
253
|
return 0
|
|
225
254
|
|
|
226
255
|
|
|
@@ -261,14 +290,30 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
261
290
|
return run_maintenance()
|
|
262
291
|
except Exception: # detached worker must die quietly
|
|
263
292
|
return 0
|
|
293
|
+
if args and args[0] == "capture-text":
|
|
294
|
+
if len(args) < 4:
|
|
295
|
+
print(
|
|
296
|
+
"usage: python3 -m core.memory.turn_capture"
|
|
297
|
+
" capture-text <session_id> <cwd> <runtime> (text on stdin)"
|
|
298
|
+
)
|
|
299
|
+
return 2
|
|
300
|
+
try:
|
|
301
|
+
text = sys.stdin.read()
|
|
302
|
+
return capture_text_turn(args[1], text, args[2], args[3])
|
|
303
|
+
except Exception: # detached worker must die quietly
|
|
304
|
+
return 0
|
|
264
305
|
if len(args) < 2:
|
|
265
306
|
print(
|
|
266
307
|
"usage: python3 -m core.memory.turn_capture"
|
|
267
|
-
" (<session_id> <transcript_path> [cwd] |
|
|
308
|
+
" (<session_id> <transcript_path> [cwd] [runtime] |"
|
|
309
|
+
" capture-text <session_id> <cwd> <runtime> | maintenance)"
|
|
268
310
|
)
|
|
269
311
|
return 2
|
|
270
312
|
try:
|
|
271
|
-
return capture_turn(
|
|
313
|
+
return capture_turn(
|
|
314
|
+
args[0], args[1], args[2] if len(args) > 2 else "",
|
|
315
|
+
args[3] if len(args) > 3 else "claude",
|
|
316
|
+
)
|
|
272
317
|
except Exception: # detached worker must die quietly
|
|
273
318
|
return 0
|
|
274
319
|
|
package/core/runtime/opencode.py
CHANGED
|
@@ -2,9 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
OpenCode (opencode.ai) — open-source terminal AI coding agent with
|
|
4
4
|
native agents (markdown, ``~/.config/opencode/agents/``), custom
|
|
5
|
-
commands (``commands/``), MCP servers (``opencode.json``),
|
|
6
|
-
instructions
|
|
7
|
-
|
|
5
|
+
commands (``commands/``), MCP servers (``opencode.json``), AGENTS.md
|
|
6
|
+
instructions, and a TypeScript plugin system (``plugins/``). The
|
|
7
|
+
installer adapter (installer/adapters/opencode.js) deploys the
|
|
8
|
+
generated harness bundle onto those surfaces, including
|
|
9
|
+
``plugins/arka.ts``, which bridges OpenCode events into
|
|
10
|
+
``core.runtime.opencode_hooks`` (kb-first research gate, frontend
|
|
11
|
+
gate, MCP telemetry, stop-hook compliance, compaction context).
|
|
8
12
|
|
|
9
13
|
Headless invocation (live-verified against the installed binary,
|
|
10
14
|
opencode 1.18.4, ``opencode run --help`` + a real probe, 2026-07-23):
|
|
@@ -60,18 +64,25 @@ class OpenCodeAdapter(RuntimeAdapter):
|
|
|
60
64
|
config_dir=config_dir,
|
|
61
65
|
skills_dir=config_dir / "commands",
|
|
62
66
|
settings_file=config_dir / "opencode.json",
|
|
63
|
-
supports_hooks=
|
|
67
|
+
supports_hooks=(config_dir / "plugins" / "arka.ts").exists(),
|
|
64
68
|
supports_subagents=True,
|
|
65
69
|
supports_mcp=True,
|
|
66
70
|
max_context_tokens=200_000,
|
|
67
71
|
)
|
|
68
72
|
|
|
69
73
|
def capabilities(self) -> dict[str, bool]:
|
|
74
|
+
plugin = (
|
|
75
|
+
Path(expanduser("~")) / ".config" / "opencode" / "plugins" / "arka.ts"
|
|
76
|
+
)
|
|
70
77
|
return {
|
|
71
78
|
"agent_dispatch": False, # native agents exist; no ArkaOS wiring yet
|
|
72
79
|
"headless": True, # `opencode run` live-verified 2026-07-23
|
|
73
80
|
"file_ops": True, # terminal agent edits files natively
|
|
74
|
-
|
|
81
|
+
# plugins/arka.ts (deployed by the installer adapter) bridges
|
|
82
|
+
# prompt/pre_tool/post_tool/idle/compact events into
|
|
83
|
+
# core.runtime.opencode_hooks — kb-first, frontend gate,
|
|
84
|
+
# telemetry, compliance. True only when actually deployed.
|
|
85
|
+
"hooks": plugin.exists(),
|
|
75
86
|
}
|
|
76
87
|
|
|
77
88
|
def inject_context(self, layers: dict[str, str]) -> str:
|