arkaos 2.47.0 → 2.49.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/VERSION CHANGED
@@ -1 +1 @@
1
- 2.47.0
1
+ 2.49.0
package/arka/SKILL.md CHANGED
@@ -188,6 +188,7 @@ violation (squad-routing, arka-supremacy, spec-driven, mandatory-qa).
188
188
  | `/arka status` | System status (version, departments, agents, active projects). Includes **LLM costs (24h)** section: top-line cost + cache hit rate + call count from `core.runtime.llm_cost_telemetry.summarise(period="today")`. Also includes **Enforcement (24h)** section: total calls, block rate, top blocked tools/reasons from `core.governance.enforcement_telemetry.summarise(period="today")` (PR19 v2.41.0). Plus **Reorganization (today)** section: today's proposal path + artifact count from `core.cognition.reorganizer_scheduler.status_summary()` (PR24 v2.46.0). |
189
189
  | `/arka costs [period]` | LLM cost visibility — aggregates telemetry by day/week/month/all, with top expensive sessions. See `arka/skills/costs/SKILL.md`. Shells out to `python -m core.runtime.llm_cost_telemetry_cli <period>`. |
190
190
  | `/arka enforcement [period]` | Enforcement compliance — aggregates flow-marker enforcement telemetry by day/week/month/all. Shows block rate, top blocked tools, top reasons. Shells out to `python -m core.governance.enforcement_telemetry_cli <period>`. |
191
+ | `/arka compliance [period]` | Behavior compliance summary (PR29 v2.48.0) — aggregates stop-hook telemetry by day/week/month/all. Shows rates for the four contracts: closing marker, `[arka:meta]` tag, KB citation pass, sycophancy clean. Shells out to `python -m core.governance.compliance_telemetry_cli <period>`. |
191
192
  | `/arka reorganize [--since-days N]` | Dreaming → Agent reorganizer. Aggregates recent KB pattern/anti-pattern/lesson artifacts (default last 7 days) into a markdown proposal at `~/.arkaos/reorganize-proposals/<date>.md`. **Propose-only** — never modifies agent YAMLs. Sanitizes client identifiers from titles and body excerpts; drops `tags:` field entirely to prevent project-name leaks. **Auto-fires on session start when today's proposal is missing** (PR24 v2.46.0 stale-aware trigger, 30s timeout, background). Shells out to `python -m core.cognition.reorganizer_cli [--since-days N] [--dry-run]`. |
192
193
  | `/arka standup` | Daily standup (projects, priorities, blockers, updates) |
193
194
  | `/arka monitor` | System health monitoring |
@@ -162,6 +162,38 @@ try:
162
162
  except Exception:
163
163
  pass
164
164
 
165
+ # PR30 v2.49.0 — Meta-tag soft block. Mirrors the KB cite-check
166
+ # pipeline. Records whether the closing message carried the required
167
+ # [arka:meta] one-liner; persists result to /tmp/arkaos-meta/<session>.json
168
+ # so the next UserPromptSubmit can surface a nudge if missing.
169
+ meta_passed = True
170
+ meta_reason = "trivial"
171
+ meta_suggestion: str | None = None
172
+ try:
173
+ from core.governance.meta_tag_check import check_meta_tag
174
+ mr = check_meta_tag(last)
175
+ meta_passed = mr.passed
176
+ meta_reason = mr.reason
177
+ meta_suggestion = mr.suggestion
178
+ if safe_sid:
179
+ prev_umask = os.umask(0o077)
180
+ try:
181
+ meta_dir = Path("/tmp/arkaos-meta")
182
+ meta_dir.mkdir(parents=True, exist_ok=True)
183
+ meta_path = meta_dir / f"{safe_sid}.json"
184
+ meta_path.write_text(
185
+ json.dumps({
186
+ "passed": mr.passed,
187
+ "reason": mr.reason,
188
+ "suggestion": mr.suggestion,
189
+ }),
190
+ encoding="utf-8",
191
+ )
192
+ finally:
193
+ os.umask(prev_umask)
194
+ except Exception:
195
+ pass
196
+
165
197
  entry = {
166
198
  "ts": datetime.now(timezone.utc).isoformat(),
167
199
  "session_id": session_id,
@@ -178,6 +210,8 @@ entry = {
178
210
  "kb_cite_reason": cite_reason,
179
211
  "kb_cite_count": cite_count,
180
212
  "kb_cite_topic_score": cite_topic_score,
213
+ "meta_tag_check_passed": meta_passed,
214
+ "meta_tag_check_reason": meta_reason,
181
215
  "mode": "warn",
182
216
  }
183
217
 
@@ -393,11 +393,31 @@ if [ -n "$SESSION_ID" ]; then
393
393
  fi
394
394
  fi
395
395
 
396
+ # ─── Meta-tag nudge (PR30 v2.49.0) ───────────────────────────────────────
397
+ # Mirror of the KB citation nudge but for the [arka:meta] one-liner
398
+ # contract. One-shot; deleted after read.
399
+ _META_TAG_NUDGE=""
400
+ if [ -n "$SESSION_ID" ]; then
401
+ _META_FILE="/tmp/arkaos-meta/${SESSION_ID}.json"
402
+ if [ -f "$_META_FILE" ]; then
403
+ if command -v jq &>/dev/null; then
404
+ _META_PASSED=$(jq -r '.passed' "$_META_FILE" 2>/dev/null)
405
+ _META_SUGGEST=$(jq -r '.suggestion // ""' "$_META_FILE" 2>/dev/null)
406
+ if [ "$_META_PASSED" = "false" ] && [ -n "$_META_SUGGEST" ] && [ "$_META_SUGGEST" != "null" ]; then
407
+ _META_TAG_NUDGE="[arka:suggest] ${_META_SUGGEST}"
408
+ fi
409
+ fi
410
+ rm -f "$_META_FILE" 2>/dev/null
411
+ fi
412
+ fi
413
+
396
414
  # ─── Output ──────────────────────────────────────────────────────────────
397
415
  _OUT_CONTEXT="${_ARKA_GREETING:-}${_SYNC_NOTICE:-}${_ROUTE_REMINDER}${_WORKFLOW_DIRECTIVE} $python_result"
398
416
  [ -n "$_HYGIENE" ] && _OUT_CONTEXT="$_OUT_CONTEXT $_HYGIENE"
399
417
  [ -n "$_KB_CITE_NUDGE" ] && _OUT_CONTEXT="$_OUT_CONTEXT
400
418
  $_KB_CITE_NUDGE"
419
+ [ -n "$_META_TAG_NUDGE" ] && _OUT_CONTEXT="$_OUT_CONTEXT
420
+ $_META_TAG_NUDGE"
401
421
  [ -n "$_ARKA_CONTEXT_HITS" ] && _OUT_CONTEXT="$_OUT_CONTEXT
402
422
  $_ARKA_CONTEXT_HITS"
403
423
  # Escape for JSON
@@ -0,0 +1,140 @@
1
+ """Behavior compliance telemetry summarizer (PR29 v2.48.0).
2
+
3
+ Reads the stop-hook entries in ``~/.arkaos/telemetry/enforcement.jsonl``
4
+ and reports compliance with the four contracts the session-start hook
5
+ establishes:
6
+
7
+ - closing_marker_found — [arka:phase:13] or [arka:trivial] present
8
+ - meta_tag_found — [arka:meta] one-liner present (PR12)
9
+ - kb_cite_passed — KB citation soft block result (PR18)
10
+ - sycophancy clean — inverse of sycophancy_is_flagged (PR13)
11
+
12
+ Mirrors the period vocabulary of ``enforcement_telemetry.summarise``
13
+ (today / week / month / all). Empty/missing file = no-op zero rates.
14
+ Null fields excluded from denominators so rates reflect *observed*
15
+ behavior, not ``unknown``-padded data.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import json
21
+ from dataclasses import dataclass
22
+ from datetime import datetime, timedelta, timezone
23
+ from pathlib import Path
24
+ from typing import Any, Iterable
25
+
26
+ DEFAULT_PATH: Path = Path.home() / ".arkaos" / "telemetry" / "enforcement.jsonl"
27
+ _VALID_PERIODS: frozenset[str] = frozenset({"today", "week", "month", "all"})
28
+ _STOP_EVENT: str = "stop-hook-flow-check"
29
+
30
+
31
+ @dataclass(frozen=True)
32
+ class ComplianceSummary:
33
+ """Compliance snapshot over a slice of stop-hook telemetry."""
34
+ period: str
35
+ stop_events: int
36
+ closing_marker_rate: float
37
+ meta_tag_rate: float
38
+ kb_cite_pass_rate: float
39
+ sycophancy_clean_rate: float
40
+ corrupt_line_count: int = 0
41
+
42
+
43
+ def summarise(period: str, *, path: Path | None = None) -> ComplianceSummary:
44
+ """Return a ComplianceSummary for the requested period."""
45
+ if period not in _VALID_PERIODS:
46
+ raise ValueError(f"invalid period: {period!r}")
47
+ src = path or DEFAULT_PATH
48
+ cutoff = _period_cutoff(period)
49
+ entries, corrupt = _read_stop_entries(src, cutoff)
50
+ return _build_summary(period, entries, corrupt)
51
+
52
+
53
+ def _period_cutoff(period: str, now: datetime | None = None) -> datetime | None:
54
+ ref = now or datetime.now(timezone.utc)
55
+ if period == "today":
56
+ return ref.replace(hour=0, minute=0, second=0, microsecond=0)
57
+ if period == "week":
58
+ return ref - timedelta(days=7)
59
+ if period == "month":
60
+ return ref - timedelta(days=30)
61
+ return None
62
+
63
+
64
+ def _read_stop_entries(
65
+ src: Path, cutoff: datetime | None,
66
+ ) -> tuple[list[dict[str, Any]], int]:
67
+ if not src.exists():
68
+ return [], 0
69
+ entries: list[dict[str, Any]] = []
70
+ corrupt = 0
71
+ try:
72
+ with src.open("r", encoding="utf-8", errors="replace") as fh:
73
+ for line in fh:
74
+ if not line.strip():
75
+ continue
76
+ try:
77
+ entry = json.loads(line)
78
+ except json.JSONDecodeError:
79
+ corrupt += 1
80
+ continue
81
+ if not isinstance(entry, dict):
82
+ corrupt += 1
83
+ continue
84
+ if entry.get("event") != _STOP_EVENT:
85
+ continue
86
+ if cutoff is not None and not _within_cutoff(entry, cutoff):
87
+ continue
88
+ entries.append(entry)
89
+ except OSError:
90
+ return entries, corrupt
91
+ return entries, corrupt
92
+
93
+
94
+ def _within_cutoff(entry: dict[str, Any], cutoff: datetime) -> bool:
95
+ ts = _parse_ts(entry.get("ts"))
96
+ return ts is not None and ts >= cutoff
97
+
98
+
99
+ def _parse_ts(raw: Any) -> datetime | None:
100
+ if not isinstance(raw, str) or not raw:
101
+ return None
102
+ try:
103
+ parsed = datetime.fromisoformat(raw.replace("Z", "+00:00"))
104
+ except ValueError:
105
+ return None
106
+ if parsed.tzinfo is None:
107
+ parsed = parsed.replace(tzinfo=timezone.utc)
108
+ return parsed
109
+
110
+
111
+ def _build_summary(
112
+ period: str,
113
+ entries: Iterable[dict[str, Any]],
114
+ corrupt: int,
115
+ ) -> ComplianceSummary:
116
+ rows = list(entries)
117
+ return ComplianceSummary(
118
+ period=period,
119
+ stop_events=len(rows),
120
+ closing_marker_rate=_true_rate(rows, "closing_marker_found"),
121
+ meta_tag_rate=_true_rate(rows, "meta_tag_found"),
122
+ kb_cite_pass_rate=_true_rate(rows, "kb_cite_passed"),
123
+ sycophancy_clean_rate=_false_rate(rows, "sycophancy_is_flagged"),
124
+ corrupt_line_count=corrupt,
125
+ )
126
+
127
+
128
+ def _true_rate(rows: list[dict[str, Any]], key: str) -> float:
129
+ observed = [r for r in rows if isinstance(r.get(key), bool)]
130
+ if not observed:
131
+ return 0.0
132
+ return sum(1 for r in observed if r[key] is True) / len(observed)
133
+
134
+
135
+ def _false_rate(rows: list[dict[str, Any]], key: str) -> float:
136
+ """Rate of `key is False` among rows where the field was observed."""
137
+ observed = [r for r in rows if isinstance(r.get(key), bool)]
138
+ if not observed:
139
+ return 0.0
140
+ return sum(1 for r in observed if r[key] is False) / len(observed)
@@ -0,0 +1,65 @@
1
+ """CLI entry for the behavior-compliance summarizer (PR29 v2.48.0).
2
+
3
+ Invoked as ``python -m core.governance.compliance_telemetry_cli <period>``.
4
+ Renders a markdown report covering the four stop-hook behavior contracts:
5
+ closing marker, meta tag, KB cite pass, sycophancy clean.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import sys
11
+
12
+ from core.governance.compliance_telemetry import (
13
+ ComplianceSummary,
14
+ summarise,
15
+ )
16
+
17
+
18
+ def _fmt_rate(value: float) -> str:
19
+ return f"{value * 100:.2f}%"
20
+
21
+
22
+ def _render(summary: ComplianceSummary) -> str:
23
+ lines = [
24
+ f"# Behavior compliance — {summary.period}",
25
+ "",
26
+ f"- Stop-hook events observed: **{summary.stop_events}**",
27
+ "",
28
+ "| Contract | Compliance rate |",
29
+ "|---|---|",
30
+ f"| `[arka:phase:13]` / `[arka:trivial]` closing marker "
31
+ f"| {_fmt_rate(summary.closing_marker_rate)} |",
32
+ f"| `[arka:meta]` one-liner (PR12 v2.34.0) "
33
+ f"| {_fmt_rate(summary.meta_tag_rate)} |",
34
+ f"| KB citation pass (PR18 v2.40.0) "
35
+ f"| {_fmt_rate(summary.kb_cite_pass_rate)} |",
36
+ f"| Sycophancy clean (inverse of flagged, PR13 v2.35.0) "
37
+ f"| {_fmt_rate(summary.sycophancy_clean_rate)} |",
38
+ ]
39
+ if summary.corrupt_line_count:
40
+ lines += [
41
+ "",
42
+ f"_(skipped {summary.corrupt_line_count} corrupt line(s) in telemetry)_",
43
+ ]
44
+ if summary.stop_events == 0:
45
+ lines += [
46
+ "",
47
+ "_(no stop-hook events in window — open a session and complete "
48
+ "a turn to populate telemetry)_",
49
+ ]
50
+ return "\n".join(lines)
51
+
52
+
53
+ def main(argv: list[str]) -> int:
54
+ period = argv[1] if len(argv) > 1 else "today"
55
+ try:
56
+ summary = summarise(period)
57
+ except ValueError as exc:
58
+ print(f"error: {exc}", file=sys.stderr)
59
+ return 2
60
+ print(_render(summary))
61
+ return 0
62
+
63
+
64
+ if __name__ == "__main__":
65
+ raise SystemExit(main(sys.argv))
@@ -0,0 +1,63 @@
1
+ """[arka:meta] one-liner soft-block check (PR30 v2.49.0).
2
+
3
+ Response-side classifier. Inspects an assistant response for the
4
+ ``[arka:meta] kb=N research=X persona=Y gap=Z critic=W`` one-liner
5
+ established by the session-start hook in PR12 v2.34.0.
6
+
7
+ Soft-block contract — never raises. Hooks consume MetaTagResult and
8
+ decide whether to surface a suggestion. Mirrors the shape of
9
+ ``core.governance.kb_cite_check`` (PR18 v2.40.0).
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import re
15
+ from dataclasses import dataclass
16
+
17
+ _META_TAG_RE: re.Pattern[str] = re.compile(r"\[arka:meta\]", re.IGNORECASE)
18
+ _BYPASS_DEFAULTS: tuple[str, ...] = ("[arka:trivial]",)
19
+ _TRIVIAL_WORD_THRESHOLD: int = 15
20
+ _SUGGESTION_TEXT: str = (
21
+ "Meta-tag missing — end substantive responses with a single "
22
+ "`[arka:meta] kb=N research=X persona=Y gap=Z critic=W` line. "
23
+ "Fields: kb=N (notes consulted), research=X (MCPs invoked or "
24
+ "'none'), persona=Y (advisor or 'orchestrator'), gap=Z (KB gap "
25
+ "or 'none'), critic=W (passed|failed|skipped)."
26
+ )
27
+
28
+
29
+ @dataclass(frozen=True)
30
+ class MetaTagResult:
31
+ """Verdict of a meta-tag check. Immutable; safe to log as JSON."""
32
+ passed: bool
33
+ reason: str
34
+ suggestion: str | None
35
+
36
+
37
+ def check_meta_tag(
38
+ response_text: str,
39
+ *,
40
+ bypass_markers: tuple[str, ...] = _BYPASS_DEFAULTS,
41
+ ) -> MetaTagResult:
42
+ """Classify whether a response carries the [arka:meta] one-liner.
43
+
44
+ Order matters: a SHORT response *with* the tag still counts as
45
+ `present` — the trivial-length bypass only short-circuits when
46
+ the tag genuinely isn't there.
47
+ """
48
+ text = response_text or ""
49
+ if _has_bypass_marker(text, bypass_markers):
50
+ return MetaTagResult(True, "trivial", None)
51
+ if _META_TAG_RE.search(text):
52
+ return MetaTagResult(True, "present", None)
53
+ if _is_trivial_length(text):
54
+ return MetaTagResult(True, "trivial", None)
55
+ return MetaTagResult(False, "missing", _SUGGESTION_TEXT)
56
+
57
+
58
+ def _has_bypass_marker(text: str, markers: tuple[str, ...]) -> bool:
59
+ return any(marker in text for marker in markers)
60
+
61
+
62
+ def _is_trivial_length(text: str) -> bool:
63
+ return len(text.split()) < _TRIVIAL_WORD_THRESHOLD
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arkaos",
3
- "version": "2.47.0",
3
+ "version": "2.49.0",
4
4
  "description": "The Operating System for AI Agent Teams",
5
5
  "type": "module",
6
6
  "bin": {
package/pyproject.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "arkaos-core"
3
- version = "2.47.0"
3
+ version = "2.49.0"
4
4
  description = "Core engine for ArkaOS — The Operating System for AI Agent Teams"
5
5
  readme = "README.md"
6
6
  license = {text = "MIT"}