arkaos 4.41.0 → 4.42.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/THE-ARKAOS-GUIDE.md +1 -1
- package/VERSION +1 -1
- package/config/claude-agents/marta-cqo.md +10 -2
- package/core/evals/record_cli.py +179 -24
- package/core/evals/runner_cli.py +5 -2
- package/core/evals/verdict_labels.py +19 -6
- package/core/governance/aggregate_guard.py +537 -0
- package/core/governance/redo_counter.py +13 -8
- package/core/governance/reviewer_ledger.py +60 -12
- package/departments/quality/SKILL.md +5 -2
- 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/zed/.rules +1 -1
- package/knowledge/skills-manifest.json +1 -1
- package/package.json +1 -1
- package/pyproject.toml +1 -1
package/THE-ARKAOS-GUIDE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# The ArkaOS Guide
|
|
2
2
|
|
|
3
|
-
> v4.
|
|
3
|
+
> v4.42.0 — 89 agents, 17 departments, 332 skills, 297 commands, 18 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.42.0
|
|
@@ -46,8 +46,16 @@ evidence report, never from model size.
|
|
|
46
46
|
through this agent bypasses the department SKILL's step 6: write your
|
|
47
47
|
final QGVerdict JSON to a temp file and run
|
|
48
48
|
`~/.arkaos/bin/arka-py -m core.evals.record_cli --file <f> --kind qg
|
|
49
|
-
--department <dept> --deliverable "<title>"`.
|
|
50
|
-
|
|
49
|
+
--session-id <session> --department <dept> --deliverable "<title>"`.
|
|
50
|
+
It fails LOUDLY for three distinct reasons, each with its own
|
|
51
|
+
remedy: invalid JSON (fix the JSON and re-run), a missing
|
|
52
|
+
--session-id (pass the session id — the anti-self-approval guard
|
|
53
|
+
reads that session's reviewer ledger), or a guard refusal (the
|
|
54
|
+
ledger cannot support your aggregate: quorum, digest, blocker
|
|
55
|
+
coverage, or an APPROVED verdict standing over a rejecting
|
|
56
|
+
reviewer — read the stderr reasons and fix the REVIEW, not the
|
|
57
|
+
JSON; if the reason names AGGREGATE.json or the session id, fix
|
|
58
|
+
that instead). Never skip.
|
|
51
59
|
Every review feeds `~/.arkaos/telemetry/qg-verdicts.jsonl`, redo
|
|
52
60
|
verdicts included (a REJECTED→APPROVED pair is two labels).
|
|
53
61
|
|
package/core/evals/record_cli.py
CHANGED
|
@@ -1,11 +1,30 @@
|
|
|
1
1
|
"""Record a QGVerdict or gate JudgeVerdict as an eval label.
|
|
2
2
|
|
|
3
|
-
Reads the
|
|
4
|
-
|
|
5
|
-
``--kind judge`` → judge-verdicts.jsonl). Invoked by
|
|
6
|
-
right after a
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
Reads the verdict JSON from stdin (or --file) and appends it to the
|
|
4
|
+
matching label corpus (``--kind qg``/``--kind reviewer`` →
|
|
5
|
+
qg-verdicts.jsonl, ``--kind judge`` → judge-verdicts.jsonl). Invoked by
|
|
6
|
+
the orchestrator right after a verdict lands (see the Quality Gate and
|
|
7
|
+
flow skill instructions), closing the "labels gratuitos" loop from the
|
|
8
|
+
evals ADR.
|
|
9
|
+
|
|
10
|
+
``--kind qg`` is the AGGREGATE path and runs the anti-self-approval
|
|
11
|
+
guard (PR-B3): ``--session-id`` is mandatory and validated, the
|
|
12
|
+
session's reviewer ledger must hold at least two hook-captured
|
|
13
|
+
reviewer verdicts, no evidence_digest present on both sides may
|
|
14
|
+
disagree (an absent digest warns — PR-B4 makes dispatch populate it),
|
|
15
|
+
no CONFIRMED reviewer blocker may disappear silently, and an APPROVED
|
|
16
|
+
aggregate may not stand over a rejecting reviewer. The label records
|
|
17
|
+
ONLY once AGGREGATE.json is verifiably on disk — an
|
|
18
|
+
accepted-but-unwritten aggregate is a refusal, not a success.
|
|
19
|
+
|
|
20
|
+
``--kind reviewer`` records an individual reviewer's verdict as a
|
|
21
|
+
label and cross-references the ledger by ``verdict_digest`` — it never
|
|
22
|
+
writes ledger records (only the hook capture may; anything the
|
|
23
|
+
orchestrator could write itself would let it fabricate its own
|
|
24
|
+
quorum), and it refuses aggregator identities: the aggregator's
|
|
25
|
+
verdict enters through the guarded ``--kind qg`` path, never as a
|
|
26
|
+
"reviewer" label. For reviewer identities it remains an unguarded
|
|
27
|
+
label path — the cross-reference is provenance, not admission.
|
|
9
28
|
|
|
10
29
|
Unlike the underlying writer (telemetry contract: never raises), this
|
|
11
30
|
explicit CLI fails LOUDLY on invalid verdict JSON — a malformed label
|
|
@@ -18,6 +37,7 @@ import argparse
|
|
|
18
37
|
import json
|
|
19
38
|
import sys
|
|
20
39
|
from pathlib import Path
|
|
40
|
+
from typing import TYPE_CHECKING
|
|
21
41
|
|
|
22
42
|
from pydantic import ValidationError
|
|
23
43
|
|
|
@@ -25,51 +45,186 @@ from core.evals.verdict_labels import record_judge_label, record_verdict_label
|
|
|
25
45
|
from core.governance.judge import JudgeVerdict
|
|
26
46
|
from core.governance.qg_verdict import QGVerdict
|
|
27
47
|
|
|
48
|
+
if TYPE_CHECKING: # the runtime import stays lazy (guard pulls pydantic)
|
|
49
|
+
from core.governance.aggregate_guard import GuardResult
|
|
28
50
|
|
|
29
|
-
|
|
51
|
+
|
|
52
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
30
53
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
31
54
|
parser.add_argument("--file", help="verdict JSON file (default: stdin)")
|
|
32
55
|
parser.add_argument(
|
|
33
56
|
"--kind",
|
|
34
|
-
choices=("qg", "judge"),
|
|
57
|
+
choices=("qg", "reviewer", "judge"),
|
|
35
58
|
default="qg",
|
|
36
|
-
help=
|
|
59
|
+
help=(
|
|
60
|
+
"qg = aggregate QGVerdict, guarded (default); reviewer = "
|
|
61
|
+
"individual reviewer QGVerdict; judge = gate JudgeVerdict"
|
|
62
|
+
),
|
|
37
63
|
)
|
|
38
64
|
parser.add_argument("--deliverable", default="")
|
|
39
65
|
parser.add_argument("--department", default="")
|
|
40
66
|
parser.add_argument("--eval-task-id", default="")
|
|
41
67
|
parser.add_argument("--session-id", default="")
|
|
42
|
-
|
|
68
|
+
parser.add_argument("--round", default="", dest="round_label")
|
|
69
|
+
parser.add_argument("--head", default="")
|
|
70
|
+
return parser
|
|
43
71
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
72
|
+
|
|
73
|
+
def main(argv: list[str] | None = None) -> int:
|
|
74
|
+
args = _build_parser().parse_args(argv)
|
|
75
|
+
raw = _read_raw(args)
|
|
76
|
+
if raw is None:
|
|
77
|
+
return 1
|
|
49
78
|
if args.kind == "judge":
|
|
50
79
|
return _record_judge(raw, args)
|
|
51
|
-
|
|
80
|
+
verdict = _parse_qg(raw)
|
|
81
|
+
if verdict is None:
|
|
82
|
+
return 1
|
|
83
|
+
if args.kind == "reviewer":
|
|
84
|
+
return _record_reviewer(verdict, args)
|
|
85
|
+
return _record_aggregate(verdict, args)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _read_raw(args: argparse.Namespace) -> str | None:
|
|
89
|
+
"""Verdict text, or None after a LOUD error (never a traceback)."""
|
|
90
|
+
if not args.file:
|
|
91
|
+
return sys.stdin.read()
|
|
92
|
+
try:
|
|
93
|
+
return Path(args.file).read_text(encoding="utf-8")
|
|
94
|
+
except (OSError, UnicodeDecodeError) as exc:
|
|
95
|
+
print(f"error: cannot read --file — {exc}", file=sys.stderr)
|
|
96
|
+
return None
|
|
52
97
|
|
|
53
98
|
|
|
54
|
-
def
|
|
99
|
+
def _parse_qg(raw: str) -> QGVerdict | None:
|
|
55
100
|
try:
|
|
56
|
-
|
|
101
|
+
return QGVerdict.model_validate(json.loads(raw))
|
|
57
102
|
except (json.JSONDecodeError, ValidationError) as exc:
|
|
58
103
|
print(f"error: invalid QGVerdict JSON — {exc}", file=sys.stderr)
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _refuse(reasons: list[str]) -> int:
|
|
108
|
+
for reason in reasons:
|
|
109
|
+
print(f"refused: {reason}", file=sys.stderr)
|
|
110
|
+
print(
|
|
111
|
+
"error: aggregate refused — nothing recorded (anti-self-"
|
|
112
|
+
"approval guard, PR-B3)",
|
|
113
|
+
file=sys.stderr,
|
|
114
|
+
)
|
|
115
|
+
return 1
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _drive_redo(verdict: QGVerdict, session_id: str) -> str:
|
|
119
|
+
from core.governance import redo_counter
|
|
120
|
+
|
|
121
|
+
if verdict.verdict == "REJECTED":
|
|
122
|
+
return redo_counter.record_rejected(session_id).to_message()
|
|
123
|
+
redo_counter.reset(session_id)
|
|
124
|
+
return ""
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _record_aggregate(verdict: QGVerdict, args: argparse.Namespace) -> int:
|
|
128
|
+
from core.governance.aggregate_guard import check_aggregate, write_aggregate
|
|
129
|
+
|
|
130
|
+
if not args.session_id:
|
|
131
|
+
print(
|
|
132
|
+
"error: --kind qg records an AGGREGATE and requires "
|
|
133
|
+
"--session-id (the guard reads that session's reviewer ledger)",
|
|
134
|
+
file=sys.stderr,
|
|
135
|
+
)
|
|
136
|
+
return 1
|
|
137
|
+
dumped = verdict.model_dump()
|
|
138
|
+
result = check_aggregate(dumped, args.session_id)
|
|
139
|
+
if not result.ok:
|
|
140
|
+
return _refuse(result.reasons)
|
|
141
|
+
aggregate_path = write_aggregate(args.session_id, dumped, result)
|
|
142
|
+
if aggregate_path is None:
|
|
143
|
+
# An accepted-but-unwritten aggregate recorded anyway would be
|
|
144
|
+
# the incident shape again, one filesystem error away.
|
|
145
|
+
return _refuse([
|
|
146
|
+
"AGGREGATE.json could not be written and verified on disk "
|
|
147
|
+
"— a label without its artifact is the incident shape"
|
|
148
|
+
])
|
|
149
|
+
_record_label(verdict, args)
|
|
150
|
+
print(json.dumps(_accepted_payload(verdict, args, aggregate_path, result)))
|
|
151
|
+
return 0
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _accepted_payload(
|
|
155
|
+
verdict: QGVerdict,
|
|
156
|
+
args: argparse.Namespace,
|
|
157
|
+
aggregate_path: Path,
|
|
158
|
+
result: GuardResult,
|
|
159
|
+
) -> dict:
|
|
160
|
+
return {
|
|
161
|
+
"recorded": True,
|
|
162
|
+
"verdict": verdict.verdict,
|
|
163
|
+
"eval_task_id": args.eval_task_id,
|
|
164
|
+
"aggregate": str(aggregate_path),
|
|
165
|
+
"reviewers": result.reviewers,
|
|
166
|
+
"warnings": result.warnings,
|
|
167
|
+
"redo": _drive_redo(verdict, args.session_id),
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _record_reviewer(verdict: QGVerdict, args: argparse.Namespace) -> int:
|
|
172
|
+
from core.governance.reviewer_ledger import AGGREGATOR_IDS
|
|
173
|
+
|
|
174
|
+
if verdict.reviewer in AGGREGATOR_IDS:
|
|
175
|
+
print(
|
|
176
|
+
f"error: {verdict.reviewer!r} is an aggregator identity — "
|
|
177
|
+
"an aggregator's verdict is recorded via --kind qg, which "
|
|
178
|
+
"is guarded; recording it as a 'reviewer' label would repeat "
|
|
179
|
+
"the 2026-07-20 incident under a different flag",
|
|
180
|
+
file=sys.stderr,
|
|
181
|
+
)
|
|
59
182
|
return 1
|
|
183
|
+
_record_label(verdict, args)
|
|
184
|
+
print(json.dumps({
|
|
185
|
+
"recorded": True,
|
|
186
|
+
"verdict": verdict.verdict,
|
|
187
|
+
"reviewer": verdict.reviewer,
|
|
188
|
+
"ledger_match": _ledger_match(verdict, args.session_id),
|
|
189
|
+
}))
|
|
190
|
+
return 0
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _ledger_match(verdict: QGVerdict, session_id: str) -> str:
|
|
194
|
+
"""Name of the hook-captured record carrying this exact verdict."""
|
|
195
|
+
if not session_id:
|
|
196
|
+
return ""
|
|
197
|
+
try:
|
|
198
|
+
from core.governance.aggregate_guard import _session_records
|
|
199
|
+
from core.governance.qg_digest import verdict_digest
|
|
200
|
+
|
|
201
|
+
target = verdict_digest(verdict.model_dump())
|
|
202
|
+
for name, record in _session_records(session_id):
|
|
203
|
+
their = record.get("verdict")
|
|
204
|
+
if not isinstance(their, dict):
|
|
205
|
+
continue
|
|
206
|
+
try: # digest the NORMALIZED dump — raw dicts differ on defaults
|
|
207
|
+
normalized = QGVerdict.model_validate(their).model_dump()
|
|
208
|
+
except ValidationError:
|
|
209
|
+
continue
|
|
210
|
+
if verdict_digest(normalized) == target:
|
|
211
|
+
return name
|
|
212
|
+
except Exception: # cross-reference is provenance, never a blocker
|
|
213
|
+
return ""
|
|
214
|
+
return ""
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _record_label(verdict: QGVerdict, args: argparse.Namespace) -> None:
|
|
60
218
|
record_verdict_label(
|
|
61
219
|
verdict,
|
|
62
220
|
deliverable=args.deliverable,
|
|
63
221
|
department=args.department,
|
|
64
222
|
eval_task_id=args.eval_task_id,
|
|
65
223
|
session_id=args.session_id,
|
|
224
|
+
kind=args.kind,
|
|
225
|
+
round_label=args.round_label,
|
|
226
|
+
head=args.head,
|
|
66
227
|
)
|
|
67
|
-
print(json.dumps({
|
|
68
|
-
"recorded": True,
|
|
69
|
-
"verdict": verdict.verdict,
|
|
70
|
-
"eval_task_id": args.eval_task_id,
|
|
71
|
-
}))
|
|
72
|
-
return 0
|
|
73
228
|
|
|
74
229
|
|
|
75
230
|
def _record_judge(raw: str, args: argparse.Namespace) -> int:
|
package/core/evals/runner_cli.py
CHANGED
|
@@ -65,8 +65,11 @@ def _dispatch_prompt(task: EvalTask) -> str:
|
|
|
65
65
|
f"O deliverable será julgado pelo Quality Gate contra estas "
|
|
66
66
|
f"propriedades verificáveis:\n{properties}{rubric}\n\n"
|
|
67
67
|
f"Após o verdict, regista o label com:\n"
|
|
68
|
-
f" arka-py -m core.evals.record_cli --
|
|
69
|
-
f"--
|
|
68
|
+
f" arka-py -m core.evals.record_cli --kind qg "
|
|
69
|
+
f"--session-id <session> --eval-task-id {task.id} "
|
|
70
|
+
f"--department {task.department} < verdict.json\n"
|
|
71
|
+
f"(--session-id é obrigatório no caminho do agregado — o guard "
|
|
72
|
+
f"anti-self-approval lê o ledger de reviewers dessa sessão)"
|
|
70
73
|
)
|
|
71
74
|
|
|
72
75
|
|
|
@@ -17,7 +17,7 @@ from __future__ import annotations
|
|
|
17
17
|
|
|
18
18
|
import json
|
|
19
19
|
import os
|
|
20
|
-
from datetime import
|
|
20
|
+
from datetime import UTC, datetime
|
|
21
21
|
from pathlib import Path
|
|
22
22
|
from typing import Any
|
|
23
23
|
|
|
@@ -49,20 +49,33 @@ def record_verdict_label(
|
|
|
49
49
|
department: str = "",
|
|
50
50
|
eval_task_id: str = "",
|
|
51
51
|
session_id: str = "",
|
|
52
|
+
kind: str = "",
|
|
53
|
+
round_label: str = "",
|
|
54
|
+
head: str = "",
|
|
52
55
|
) -> None:
|
|
53
|
-
"""Append one labeled QG example. Never raises (telemetry contract).
|
|
56
|
+
"""Append one labeled QG example. Never raises (telemetry contract).
|
|
57
|
+
|
|
58
|
+
``kind``/``round_label``/``head`` live on the envelope, not the
|
|
59
|
+
schema: QGVerdict drops unknown fields, so without them the corpus
|
|
60
|
+
could not distinguish rounds of one PR (QG r13 register).
|
|
61
|
+
``round_label`` persists under the ``round`` key — the parameter
|
|
62
|
+
avoids shadowing the builtin.
|
|
63
|
+
"""
|
|
54
64
|
try:
|
|
55
65
|
entry: dict[str, Any] = {
|
|
56
|
-
"ts": datetime.now(
|
|
66
|
+
"ts": datetime.now(UTC).isoformat(),
|
|
57
67
|
"deliverable": str(deliverable or ""),
|
|
58
68
|
"department": str(department or ""),
|
|
59
69
|
"eval_task_id": str(eval_task_id or ""),
|
|
60
70
|
"session_id": str(session_id or ""),
|
|
71
|
+
"kind": str(kind or ""),
|
|
72
|
+
"round": str(round_label or ""),
|
|
73
|
+
"head": str(head or ""),
|
|
61
74
|
**verdict.model_dump(),
|
|
62
75
|
}
|
|
63
76
|
with _locked_append(_labels_path()) as fh:
|
|
64
77
|
fh.write(json.dumps(entry, ensure_ascii=False) + "\n")
|
|
65
|
-
except Exception:
|
|
78
|
+
except Exception:
|
|
66
79
|
return
|
|
67
80
|
|
|
68
81
|
|
|
@@ -75,7 +88,7 @@ def record_judge_label(
|
|
|
75
88
|
"""Append one labeled gate-judge example. Never raises."""
|
|
76
89
|
try:
|
|
77
90
|
entry: dict[str, Any] = {
|
|
78
|
-
"ts": datetime.now(
|
|
91
|
+
"ts": datetime.now(UTC).isoformat(),
|
|
79
92
|
"deliverable": str(deliverable or ""),
|
|
80
93
|
"department": str(department or ""),
|
|
81
94
|
"session_id": str(session_id or ""),
|
|
@@ -83,7 +96,7 @@ def record_judge_label(
|
|
|
83
96
|
}
|
|
84
97
|
with _locked_append(_judge_labels_path()) as fh:
|
|
85
98
|
fh.write(json.dumps(entry, ensure_ascii=False) + "\n")
|
|
86
|
-
except Exception:
|
|
99
|
+
except Exception:
|
|
87
100
|
return
|
|
88
101
|
|
|
89
102
|
|