arkaos 4.17.0 → 4.18.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/VERSION +1 -1
- package/arka/SKILL.md +1 -1
- package/config/agents-provenance.yaml +126 -0
- package/config/skills-provenance.yaml +20 -1
- package/core/agents/provenance.py +117 -0
- package/core/agents/registry_gen.py +75 -57
- package/core/cognition/insights/store.py +114 -7
- package/core/cognition/memory/schemas.py +45 -5
- package/core/hooks/pre_tool_use.py +59 -0
- package/core/provenance.py +108 -0
- package/core/skills/provenance.py +31 -69
- package/core/workflow/config_guard.py +177 -0
- package/core/workflow/transcript_scope.py +46 -0
- package/departments/dev/skills/click-path-audit/SKILL.md +76 -0
- package/departments/dev/skills/pr-test-analyzer/SKILL.md +79 -0
- package/departments/dev/skills/silent-failure-hunter/SKILL.md +72 -0
- package/departments/dev/skills/type-design-analyzer/SKILL.md +73 -0
- package/knowledge/agents-registry-v2.json +259 -1
- package/knowledge/skills-manifest.json +61 -1
- package/package.json +1 -1
- package/pyproject.toml +1 -1
package/README.md
CHANGED
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
4.
|
|
1
|
+
4.18.0
|
package/arka/SKILL.md
CHANGED
|
@@ -25,7 +25,7 @@ measures citation compliance per turn.
|
|
|
25
25
|
# ArkaOS — Main Orchestrator
|
|
26
26
|
|
|
27
27
|
> **The Operating System for AI Agent Teams**
|
|
28
|
-
> 86 agents. 17 departments.
|
|
28
|
+
> 86 agents. 17 departments. 279 skills. Multi-runtime. Dashboard. Knowledge RAG.
|
|
29
29
|
|
|
30
30
|
## ⛔ Evidence flow — 4 gates (NON-NEGOTIABLE)
|
|
31
31
|
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# Agent provenance classification — the control that makes omission impossible.
|
|
2
|
+
#
|
|
3
|
+
# Mirror of config/skills-provenance.yaml for the agent tree. The parser
|
|
4
|
+
# (core/agents/provenance.py) closes every way of writing a BROKEN
|
|
5
|
+
# provenance block, but it cannot see an agent that never writes one:
|
|
6
|
+
# absence reads as first-party, which is exactly what a forgotten ECC
|
|
7
|
+
# agent port looks like.
|
|
8
|
+
#
|
|
9
|
+
# EVERY agent YAML must be CLASSIFIED — listed in `first_party` below,
|
|
10
|
+
# listed in `derived` below, or declaring a `provenance:` block in its
|
|
11
|
+
# own YAML. An agent in none of the three fails CI by name.
|
|
12
|
+
# See tests/python/test_agent_provenance.py.
|
|
13
|
+
#
|
|
14
|
+
# `first_party` is the baseline: the agents that existed on the cut date,
|
|
15
|
+
# snapshotted once so they need no edits. CLOSED set — do not add to it.
|
|
16
|
+
# A new agent declares its origin in its own YAML; a ported one lands in
|
|
17
|
+
# `derived` with its licence written down, in a diff, in front of a
|
|
18
|
+
# reviewer.
|
|
19
|
+
#
|
|
20
|
+
# What the baseline is and is not:
|
|
21
|
+
# ASSERTED: these are ArkaOS originals.
|
|
22
|
+
# PROVEN: none can be ECC-derived — every one was created before
|
|
23
|
+
# 2026-07-14, the day we first read that repository (git log
|
|
24
|
+
# --diff-filter=A; verified once).
|
|
25
|
+
# The property the control rests on is that the set is CLOSED:
|
|
26
|
+
# anything new must classify itself.
|
|
27
|
+
#
|
|
28
|
+
# Keys are repo-relative agent YAML paths.
|
|
29
|
+
#
|
|
30
|
+
# derived format:
|
|
31
|
+
# departments/<dept>/agents/<slug>.yaml:
|
|
32
|
+
# origin: <lowercase-slug> # e.g. ecc-derived, community
|
|
33
|
+
# source: https://...
|
|
34
|
+
# license: MIT
|
|
35
|
+
|
|
36
|
+
baseline_date: 2026-07-14
|
|
37
|
+
|
|
38
|
+
derived: {}
|
|
39
|
+
|
|
40
|
+
first_party:
|
|
41
|
+
- departments/brand/agents/brand-director.yaml
|
|
42
|
+
- departments/brand/agents/brand-strategist.yaml
|
|
43
|
+
- departments/brand/agents/design-ops/design-ops-lead.yaml
|
|
44
|
+
- departments/brand/agents/design-ops/extraction-script-writer.yaml
|
|
45
|
+
- departments/brand/agents/design-ops/shadcn-padronizer.yaml
|
|
46
|
+
- departments/brand/agents/design-ops/wcag-auditor.yaml
|
|
47
|
+
- departments/brand/agents/ux-designer.yaml
|
|
48
|
+
- departments/brand/agents/ux-researcher.yaml
|
|
49
|
+
- departments/brand/agents/ux-strategist.yaml
|
|
50
|
+
- departments/brand/agents/visual-designer.yaml
|
|
51
|
+
- departments/community/agents/community-manager.yaml
|
|
52
|
+
- departments/community/agents/community-strategist.yaml
|
|
53
|
+
- departments/community/agents/engagement-designer.yaml
|
|
54
|
+
- departments/content/agents/content-strategist.yaml
|
|
55
|
+
- departments/content/agents/production/content-researcher.yaml
|
|
56
|
+
- departments/content/agents/production/info-compiler.yaml
|
|
57
|
+
- departments/content/agents/production/trends-analyst.yaml
|
|
58
|
+
- departments/content/agents/production/video-producer.yaml
|
|
59
|
+
- departments/content/agents/repurpose-distributor.yaml
|
|
60
|
+
- departments/content/agents/scriptwriter.yaml
|
|
61
|
+
- departments/content/agents/viral-engineer.yaml
|
|
62
|
+
- departments/dev/agents/ai-engineering/ai-engineering-lead.yaml
|
|
63
|
+
- departments/dev/agents/architect.yaml
|
|
64
|
+
- departments/dev/agents/backend-core/laravel-eng.yaml
|
|
65
|
+
- departments/dev/agents/backend-core/node-ts-eng.yaml
|
|
66
|
+
- departments/dev/agents/backend-core/python-eng.yaml
|
|
67
|
+
- departments/dev/agents/backend-dev.yaml
|
|
68
|
+
- departments/dev/agents/cto.yaml
|
|
69
|
+
- departments/dev/agents/data-platform/etl-eng.yaml
|
|
70
|
+
- departments/dev/agents/dba.yaml
|
|
71
|
+
- departments/dev/agents/devops-eng.yaml
|
|
72
|
+
- departments/dev/agents/frontend-dev.yaml
|
|
73
|
+
- departments/dev/agents/qa-eng.yaml
|
|
74
|
+
- departments/dev/agents/research-assistant.yaml
|
|
75
|
+
- departments/dev/agents/security-eng.yaml
|
|
76
|
+
- departments/dev/agents/tech-lead.yaml
|
|
77
|
+
- departments/ecom/agents/commerce-engineer.yaml
|
|
78
|
+
- departments/ecom/agents/cro-specialist.yaml
|
|
79
|
+
- departments/ecom/agents/ecom-director.yaml
|
|
80
|
+
- departments/ecom/agents/retention-manager.yaml
|
|
81
|
+
- departments/finance/agents/cfo.yaml
|
|
82
|
+
- departments/finance/agents/financial-analyst.yaml
|
|
83
|
+
- departments/finance/agents/investment-strategist.yaml
|
|
84
|
+
- departments/kb/agents/data-collector.yaml
|
|
85
|
+
- departments/kb/agents/knowledge-curator.yaml
|
|
86
|
+
- departments/kb/agents/knowledge-director.yaml
|
|
87
|
+
- departments/kb/agents/research-analyst.yaml
|
|
88
|
+
- departments/landing/agents/affiliate-manager.yaml
|
|
89
|
+
- departments/landing/agents/conversion-strategist.yaml
|
|
90
|
+
- departments/landing/agents/cro-specialist.yaml
|
|
91
|
+
- departments/landing/agents/sales-copywriter.yaml
|
|
92
|
+
- departments/leadership/agents/culture-coach.yaml
|
|
93
|
+
- departments/leadership/agents/hr-specialist.yaml
|
|
94
|
+
- departments/leadership/agents/leadership-director.yaml
|
|
95
|
+
- departments/marketing/agents/content-marketer.yaml
|
|
96
|
+
- departments/marketing/agents/marketing-director.yaml
|
|
97
|
+
- departments/marketing/agents/paid-specialist.yaml
|
|
98
|
+
- departments/marketing/agents/seo-specialist.yaml
|
|
99
|
+
- departments/ops/agents/automation-engineer.yaml
|
|
100
|
+
- departments/ops/agents/doc-writer.yaml
|
|
101
|
+
- departments/ops/agents/ops-lead.yaml
|
|
102
|
+
- departments/org/agents/chief-of-staff.yaml
|
|
103
|
+
- departments/org/agents/coo.yaml
|
|
104
|
+
- departments/org/agents/okr-steward.yaml
|
|
105
|
+
- departments/org/agents/org-designer.yaml
|
|
106
|
+
- departments/org/agents/people-ops.yaml
|
|
107
|
+
- departments/pm/agents/pm-director.yaml
|
|
108
|
+
- departments/pm/agents/product-owner.yaml
|
|
109
|
+
- departments/pm/agents/scrum-master.yaml
|
|
110
|
+
- departments/pm/agents/strategic-pm.yaml
|
|
111
|
+
- departments/quality/agents/copy-director.yaml
|
|
112
|
+
- departments/quality/agents/cqo.yaml
|
|
113
|
+
- departments/quality/agents/tech-director.yaml
|
|
114
|
+
- departments/saas/agents/cs-manager.yaml
|
|
115
|
+
- departments/saas/agents/growth-engineer.yaml
|
|
116
|
+
- departments/saas/agents/metrics-analyst.yaml
|
|
117
|
+
- departments/saas/agents/revops-lead.yaml
|
|
118
|
+
- departments/saas/agents/saas-strategist.yaml
|
|
119
|
+
- departments/sales/agents/sales-closer.yaml
|
|
120
|
+
- departments/sales/agents/sales-director.yaml
|
|
121
|
+
- departments/sales/agents/sales-ops.yaml
|
|
122
|
+
- departments/sales/agents/sdr.yaml
|
|
123
|
+
- departments/strategy/agents/business-model-designer.yaml
|
|
124
|
+
- departments/strategy/agents/decision-quality.yaml
|
|
125
|
+
- departments/strategy/agents/market-analyst.yaml
|
|
126
|
+
- departments/strategy/agents/strategy-director.yaml
|
|
@@ -58,7 +58,26 @@
|
|
|
58
58
|
|
|
59
59
|
baseline_date: 2026-07-14
|
|
60
60
|
|
|
61
|
-
derived:
|
|
61
|
+
derived:
|
|
62
|
+
# ECC review lenses (Track C1) — specialised review skills ArkaOS
|
|
63
|
+
# lacked, ported from the MIT-licensed everything-claude-code repo and
|
|
64
|
+
# rewritten in ArkaOS voice with ArkaOS personas.
|
|
65
|
+
departments/dev/skills/silent-failure-hunter:
|
|
66
|
+
origin: ecc-derived
|
|
67
|
+
source: https://github.com/affaan-m/ecc
|
|
68
|
+
license: MIT
|
|
69
|
+
departments/dev/skills/type-design-analyzer:
|
|
70
|
+
origin: ecc-derived
|
|
71
|
+
source: https://github.com/affaan-m/ecc
|
|
72
|
+
license: MIT
|
|
73
|
+
departments/dev/skills/pr-test-analyzer:
|
|
74
|
+
origin: ecc-derived
|
|
75
|
+
source: https://github.com/affaan-m/ecc
|
|
76
|
+
license: MIT
|
|
77
|
+
departments/dev/skills/click-path-audit:
|
|
78
|
+
origin: ecc-derived
|
|
79
|
+
source: https://github.com/affaan-m/ecc
|
|
80
|
+
license: MIT
|
|
62
81
|
|
|
63
82
|
first_party:
|
|
64
83
|
- arka
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""Agent provenance — supply-chain lineage for every agent YAML.
|
|
2
|
+
|
|
3
|
+
The skill tree got provenance in v4.17.0; agents did not, and the ECC
|
|
4
|
+
teardown puts ~15 derived agents on the roadmap. An ECC agent ported
|
|
5
|
+
today would land with exactly the hole skill provenance closes: no
|
|
6
|
+
origin, no licence trail, indistinguishable from an ArkaOS original.
|
|
7
|
+
This is the agent-side mirror, and it must land before the first agent
|
|
8
|
+
port.
|
|
9
|
+
|
|
10
|
+
An agent YAML may carry a top-level ``provenance`` block:
|
|
11
|
+
|
|
12
|
+
id: silent-failure-hunter
|
|
13
|
+
name: ...
|
|
14
|
+
provenance:
|
|
15
|
+
origin: ecc-derived
|
|
16
|
+
source: https://github.com/affaan-m/ecc
|
|
17
|
+
license: MIT
|
|
18
|
+
|
|
19
|
+
Absent means first-party. The validation rules are the shared
|
|
20
|
+
``core.provenance`` core (https source, licence required for derived,
|
|
21
|
+
no trail on ``arkaos``). This module only reads the YAML.
|
|
22
|
+
|
|
23
|
+
As with skills, no parser can see the omission vector — a port that
|
|
24
|
+
never writes the block reads as first-party. ``config/agents-provenance.yaml``
|
|
25
|
+
classifies every agent as baseline, derived, or self-declaring, and
|
|
26
|
+
``tests/python/test_agent_provenance.py`` fails CI on any agent that is
|
|
27
|
+
none of the three.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
from pathlib import Path
|
|
33
|
+
|
|
34
|
+
import yaml
|
|
35
|
+
|
|
36
|
+
from core.provenance import (
|
|
37
|
+
FIRST_PARTY,
|
|
38
|
+
Provenance,
|
|
39
|
+
ProvenanceError,
|
|
40
|
+
provenance_from_block,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
__all__ = [
|
|
44
|
+
"FIRST_PARTY",
|
|
45
|
+
"Provenance",
|
|
46
|
+
"ProvenanceError",
|
|
47
|
+
"agent_provenance",
|
|
48
|
+
"declares_provenance",
|
|
49
|
+
"provenance_from_yaml",
|
|
50
|
+
"provenance_issues_from_yaml",
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _provenance_block(data: dict) -> dict | None:
|
|
55
|
+
"""The ``provenance`` mapping from a loaded agent YAML.
|
|
56
|
+
|
|
57
|
+
Present-but-not-a-mapping is an error, never a silent first-party
|
|
58
|
+
fallback — the same fail-closed rule the skill parser uses.
|
|
59
|
+
"""
|
|
60
|
+
if "provenance" not in data:
|
|
61
|
+
return None
|
|
62
|
+
block = data["provenance"]
|
|
63
|
+
if not isinstance(block, dict):
|
|
64
|
+
raise ProvenanceError(
|
|
65
|
+
"provenance must be a mapping of origin/source/license"
|
|
66
|
+
)
|
|
67
|
+
return block
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def provenance_from_yaml(data: dict) -> Provenance:
|
|
71
|
+
"""Parse the provenance of a loaded agent YAML mapping.
|
|
72
|
+
|
|
73
|
+
Raises ``ProvenanceError``/``ValidationError`` when the block is
|
|
74
|
+
present but incoherent.
|
|
75
|
+
"""
|
|
76
|
+
if not isinstance(data, dict):
|
|
77
|
+
raise ProvenanceError("agent YAML is not a mapping")
|
|
78
|
+
return provenance_from_block(_provenance_block(data))
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def declares_provenance(data: dict) -> bool:
|
|
82
|
+
"""Did the author WRITE a provenance block? (vs the first-party
|
|
83
|
+
default). The classification control needs this distinction, which
|
|
84
|
+
``provenance_from_yaml`` collapses."""
|
|
85
|
+
if not isinstance(data, dict):
|
|
86
|
+
return False
|
|
87
|
+
try:
|
|
88
|
+
return _provenance_block(data) is not None
|
|
89
|
+
except ProvenanceError:
|
|
90
|
+
return True # broken but attempted
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _load_yaml(path: Path) -> dict:
|
|
94
|
+
try:
|
|
95
|
+
data = yaml.safe_load(path.read_text(encoding="utf-8"))
|
|
96
|
+
except (OSError, yaml.YAMLError) as exc:
|
|
97
|
+
raise ProvenanceError(f"{path}: cannot read agent YAML — {exc}") from exc
|
|
98
|
+
return data if isinstance(data, dict) else {}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def agent_provenance(path: str | Path) -> Provenance:
|
|
102
|
+
"""Provenance of an agent YAML file. Raises on an incoherent block."""
|
|
103
|
+
return provenance_from_yaml(_load_yaml(Path(path)))
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def provenance_issues_from_yaml(data: dict) -> list[str]:
|
|
107
|
+
"""Non-raising validation of a loaded agent YAML. Empty = clean."""
|
|
108
|
+
from pydantic import ValidationError
|
|
109
|
+
try:
|
|
110
|
+
provenance_from_yaml(data)
|
|
111
|
+
except ProvenanceError as exc:
|
|
112
|
+
return [str(exc)]
|
|
113
|
+
except ValidationError as exc:
|
|
114
|
+
return [
|
|
115
|
+
err["msg"].removeprefix("Value error, ") for err in exc.errors()
|
|
116
|
+
]
|
|
117
|
+
return []
|
|
@@ -11,6 +11,77 @@ from datetime import datetime
|
|
|
11
11
|
from pathlib import Path
|
|
12
12
|
|
|
13
13
|
from core.agents.loader import load_agent
|
|
14
|
+
from core.agents.provenance import Provenance, agent_provenance
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _provenance_entry(prov: Provenance) -> dict:
|
|
18
|
+
"""The full origin/source/licence trail for the registry — not just
|
|
19
|
+
origin, so the shipped file can answer 'what licence is this under?'."""
|
|
20
|
+
entry = {"origin": prov.origin}
|
|
21
|
+
if not prov.is_first_party:
|
|
22
|
+
entry["source"] = prov.source
|
|
23
|
+
entry["license"] = prov.license
|
|
24
|
+
return entry
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _dna_dict(dna) -> dict:
|
|
28
|
+
"""The four-framework behavioral DNA, flattened for the registry."""
|
|
29
|
+
return {
|
|
30
|
+
"disc": {
|
|
31
|
+
"primary": dna.disc.primary.value,
|
|
32
|
+
"secondary": dna.disc.secondary.value,
|
|
33
|
+
"label": dna.disc.label,
|
|
34
|
+
},
|
|
35
|
+
"enneagram": {
|
|
36
|
+
"type": dna.enneagram.type.value,
|
|
37
|
+
"wing": dna.enneagram.wing,
|
|
38
|
+
"label": dna.enneagram.label,
|
|
39
|
+
},
|
|
40
|
+
"big_five": {
|
|
41
|
+
"O": dna.big_five.openness,
|
|
42
|
+
"C": dna.big_five.conscientiousness,
|
|
43
|
+
"E": dna.big_five.extraversion,
|
|
44
|
+
"A": dna.big_five.agreeableness,
|
|
45
|
+
"N": dna.big_five.neuroticism,
|
|
46
|
+
},
|
|
47
|
+
"mbti": dna.mbti.type.value,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _agent_entry(agent, prov: Provenance, yaml_file: Path, root: Path) -> dict:
|
|
52
|
+
"""One registry entry for a loaded agent + its provenance."""
|
|
53
|
+
return {
|
|
54
|
+
"id": agent.id,
|
|
55
|
+
"name": agent.name,
|
|
56
|
+
"role": agent.role,
|
|
57
|
+
"department": agent.department,
|
|
58
|
+
"tier": agent.tier,
|
|
59
|
+
"model": agent.get_model(),
|
|
60
|
+
"provenance": _provenance_entry(prov),
|
|
61
|
+
"parent_squad": agent.parent_squad,
|
|
62
|
+
"sub_squad_role": agent.sub_squad_role,
|
|
63
|
+
**_dna_dict(agent.behavioral_dna),
|
|
64
|
+
"authority": {
|
|
65
|
+
k: v for k, v in agent.authority.model_dump().items()
|
|
66
|
+
if v and v != [] and k not in ("delegates_to", "escalates_to")
|
|
67
|
+
},
|
|
68
|
+
"expertise_domains": agent.expertise.domains,
|
|
69
|
+
"frameworks": agent.expertise.frameworks,
|
|
70
|
+
"knowledge_sources": agent.expertise.knowledge_sources,
|
|
71
|
+
"file": str(yaml_file.relative_to(root)),
|
|
72
|
+
"memory_path": agent.memory_path,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _summaries(agents: list[dict]) -> dict:
|
|
77
|
+
"""Tier, department, and DISC-primary counts for the registry meta."""
|
|
78
|
+
tiers, depts, disc = {}, {}, {}
|
|
79
|
+
for a in agents:
|
|
80
|
+
tiers[a["tier"]] = tiers.get(a["tier"], 0) + 1
|
|
81
|
+
depts[a["department"]] = depts.get(a["department"], 0) + 1
|
|
82
|
+
primary = a["disc"]["primary"]
|
|
83
|
+
disc[primary] = disc.get(primary, 0) + 1
|
|
84
|
+
return {"tiers": tiers, "departments": depts, "disc_distribution": disc}
|
|
14
85
|
|
|
15
86
|
|
|
16
87
|
def generate_registry(departments_dir: str | Path, output_path: str | Path) -> dict:
|
|
@@ -34,72 +105,19 @@ def generate_registry(departments_dir: str | Path, output_path: str | Path) -> d
|
|
|
34
105
|
for yaml_file in sorted(departments_dir.glob("*/agents/**/*.yaml")):
|
|
35
106
|
try:
|
|
36
107
|
agent = load_agent(yaml_file)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
"role": agent.role,
|
|
41
|
-
"department": agent.department,
|
|
42
|
-
"tier": agent.tier,
|
|
43
|
-
"model": agent.get_model(),
|
|
44
|
-
"parent_squad": agent.parent_squad,
|
|
45
|
-
"sub_squad_role": agent.sub_squad_role,
|
|
46
|
-
"disc": {
|
|
47
|
-
"primary": agent.behavioral_dna.disc.primary.value,
|
|
48
|
-
"secondary": agent.behavioral_dna.disc.secondary.value,
|
|
49
|
-
"label": agent.behavioral_dna.disc.label,
|
|
50
|
-
},
|
|
51
|
-
"enneagram": {
|
|
52
|
-
"type": agent.behavioral_dna.enneagram.type.value,
|
|
53
|
-
"wing": agent.behavioral_dna.enneagram.wing,
|
|
54
|
-
"label": agent.behavioral_dna.enneagram.label,
|
|
55
|
-
},
|
|
56
|
-
"big_five": {
|
|
57
|
-
"O": agent.behavioral_dna.big_five.openness,
|
|
58
|
-
"C": agent.behavioral_dna.big_five.conscientiousness,
|
|
59
|
-
"E": agent.behavioral_dna.big_five.extraversion,
|
|
60
|
-
"A": agent.behavioral_dna.big_five.agreeableness,
|
|
61
|
-
"N": agent.behavioral_dna.big_five.neuroticism,
|
|
62
|
-
},
|
|
63
|
-
"mbti": agent.behavioral_dna.mbti.type.value,
|
|
64
|
-
"authority": {
|
|
65
|
-
k: v for k, v in agent.authority.model_dump().items()
|
|
66
|
-
if v and v != [] and k not in ("delegates_to", "escalates_to")
|
|
67
|
-
},
|
|
68
|
-
"expertise_domains": agent.expertise.domains,
|
|
69
|
-
"frameworks": agent.expertise.frameworks,
|
|
70
|
-
"knowledge_sources": agent.expertise.knowledge_sources,
|
|
71
|
-
"file": str(yaml_file.relative_to(departments_dir.parent)),
|
|
72
|
-
"memory_path": agent.memory_path,
|
|
73
|
-
}
|
|
74
|
-
agents.append(entry)
|
|
108
|
+
prov = agent_provenance(yaml_file)
|
|
109
|
+
agents.append(_agent_entry(
|
|
110
|
+
agent, prov, yaml_file, departments_dir.parent))
|
|
75
111
|
except Exception as e:
|
|
76
112
|
errors.append(f"{yaml_file.name}: {e}")
|
|
77
113
|
|
|
78
|
-
# Build tier summary
|
|
79
|
-
tier_counts = {}
|
|
80
|
-
for a in agents:
|
|
81
|
-
tier_counts[a["tier"]] = tier_counts.get(a["tier"], 0) + 1
|
|
82
|
-
|
|
83
|
-
# Build department summary
|
|
84
|
-
dept_counts = {}
|
|
85
|
-
for a in agents:
|
|
86
|
-
dept_counts[a["department"]] = dept_counts.get(a["department"], 0) + 1
|
|
87
|
-
|
|
88
|
-
# Build DISC distribution
|
|
89
|
-
disc_counts = {}
|
|
90
|
-
for a in agents:
|
|
91
|
-
p = a["disc"]["primary"]
|
|
92
|
-
disc_counts[p] = disc_counts.get(p, 0) + 1
|
|
93
|
-
|
|
94
114
|
registry = {
|
|
95
115
|
"_meta": {
|
|
96
116
|
"version": "2.0.0",
|
|
97
117
|
"generated": datetime.now().isoformat(),
|
|
98
118
|
"total_agents": len(agents),
|
|
99
119
|
"generator": "core/agents/registry_gen.py",
|
|
100
|
-
|
|
101
|
-
"departments": dept_counts,
|
|
102
|
-
"disc_distribution": disc_counts,
|
|
120
|
+
**_summaries(agents),
|
|
103
121
|
},
|
|
104
122
|
"agents": agents,
|
|
105
123
|
}
|
|
@@ -5,10 +5,14 @@ project filtering, presentation lifecycle, and dismissal analytics.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import sqlite3
|
|
8
|
-
from datetime import
|
|
8
|
+
from datetime import UTC, datetime
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
|
|
11
|
-
from core.cognition.memory.schemas import
|
|
11
|
+
from core.cognition.memory.schemas import (
|
|
12
|
+
INSTINCT_CONFIDENCE_MAX,
|
|
13
|
+
INSTINCT_CONFIDENCE_MIN,
|
|
14
|
+
ActionableInsight,
|
|
15
|
+
)
|
|
12
16
|
|
|
13
17
|
|
|
14
18
|
class InsightStore:
|
|
@@ -26,6 +30,27 @@ class InsightStore:
|
|
|
26
30
|
conn.execute("PRAGMA journal_mode=WAL")
|
|
27
31
|
return conn
|
|
28
32
|
|
|
33
|
+
# Instinct columns added after the original 12-column schema. An
|
|
34
|
+
# operator's existing DB has the old shape, so these are ALTER-added
|
|
35
|
+
# with defaults rather than baked into CREATE TABLE — a fresh install
|
|
36
|
+
# gets them from CREATE, an upgrade gets them from _migrate.
|
|
37
|
+
#
|
|
38
|
+
# Each entry carries the FULL, literal ALTER statement — never an
|
|
39
|
+
# f-string built from parts. SQLite cannot parameterise a DDL
|
|
40
|
+
# identifier, so the only safe construction is a constant statement,
|
|
41
|
+
# and a constant statement is also the only one a scanner can trust:
|
|
42
|
+
# there is no interpolation site to become an injection the day these
|
|
43
|
+
# names turn dynamic.
|
|
44
|
+
_INSTINCT_COLUMNS = (
|
|
45
|
+
("confidence",
|
|
46
|
+
"ALTER TABLE insights ADD COLUMN confidence REAL NOT NULL DEFAULT 0.5"),
|
|
47
|
+
("scope",
|
|
48
|
+
"ALTER TABLE insights ADD COLUMN scope TEXT NOT NULL DEFAULT 'project'"),
|
|
49
|
+
("evidence_count",
|
|
50
|
+
"ALTER TABLE insights ADD COLUMN evidence_count "
|
|
51
|
+
"INTEGER NOT NULL DEFAULT 1"),
|
|
52
|
+
)
|
|
53
|
+
|
|
29
54
|
def _init_db(self) -> None:
|
|
30
55
|
with self._conn() as conn:
|
|
31
56
|
conn.execute("""
|
|
@@ -41,9 +66,13 @@ class InsightStore:
|
|
|
41
66
|
recommendation TEXT NOT NULL,
|
|
42
67
|
context TEXT NOT NULL,
|
|
43
68
|
status TEXT NOT NULL DEFAULT 'pending',
|
|
44
|
-
presented_at TEXT
|
|
69
|
+
presented_at TEXT,
|
|
70
|
+
confidence REAL NOT NULL DEFAULT 0.5,
|
|
71
|
+
scope TEXT NOT NULL DEFAULT 'project',
|
|
72
|
+
evidence_count INTEGER NOT NULL DEFAULT 1
|
|
45
73
|
)
|
|
46
74
|
""")
|
|
75
|
+
self._migrate(conn)
|
|
47
76
|
conn.execute(
|
|
48
77
|
"CREATE INDEX IF NOT EXISTS idx_insights_project ON insights (project)"
|
|
49
78
|
)
|
|
@@ -51,6 +80,16 @@ class InsightStore:
|
|
|
51
80
|
"CREATE INDEX IF NOT EXISTS idx_insights_status ON insights (status)"
|
|
52
81
|
)
|
|
53
82
|
|
|
83
|
+
def _migrate(self, conn: sqlite3.Connection) -> None:
|
|
84
|
+
"""Add instinct columns to a pre-existing table. Idempotent."""
|
|
85
|
+
existing = {
|
|
86
|
+
row["name"]
|
|
87
|
+
for row in conn.execute("PRAGMA table_info(insights)").fetchall()
|
|
88
|
+
}
|
|
89
|
+
for name, alter_stmt in self._INSTINCT_COLUMNS:
|
|
90
|
+
if name not in existing:
|
|
91
|
+
conn.execute(alter_stmt)
|
|
92
|
+
|
|
54
93
|
def _row_to_insight(self, row: sqlite3.Row) -> ActionableInsight:
|
|
55
94
|
data = dict(row)
|
|
56
95
|
# Map trigger_source back to trigger (SQL keyword workaround)
|
|
@@ -69,8 +108,8 @@ class InsightStore:
|
|
|
69
108
|
INSERT OR REPLACE INTO insights
|
|
70
109
|
(id, project, trigger_source, date_generated, category,
|
|
71
110
|
severity, title, description, recommendation, context,
|
|
72
|
-
status, presented_at)
|
|
73
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
111
|
+
status, presented_at, confidence, scope, evidence_count)
|
|
112
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
74
113
|
""",
|
|
75
114
|
(
|
|
76
115
|
insight.id,
|
|
@@ -85,6 +124,9 @@ class InsightStore:
|
|
|
85
124
|
insight.context,
|
|
86
125
|
insight.status,
|
|
87
126
|
insight.presented_at.isoformat() if insight.presented_at else None,
|
|
127
|
+
insight.confidence,
|
|
128
|
+
insight.scope,
|
|
129
|
+
insight.evidence_count,
|
|
88
130
|
),
|
|
89
131
|
)
|
|
90
132
|
|
|
@@ -130,11 +172,12 @@ class InsightStore:
|
|
|
130
172
|
"""Mark insights as presented and record the timestamp."""
|
|
131
173
|
if not ids:
|
|
132
174
|
return
|
|
133
|
-
now = datetime.now(
|
|
175
|
+
now = datetime.now(UTC).isoformat()
|
|
134
176
|
placeholders = ",".join("?" * len(ids))
|
|
135
177
|
with self._conn() as conn:
|
|
136
178
|
conn.execute(
|
|
137
|
-
|
|
179
|
+
"UPDATE insights SET status = 'presented', presented_at = ? "
|
|
180
|
+
f"WHERE id IN ({placeholders})",
|
|
138
181
|
[now, *ids],
|
|
139
182
|
)
|
|
140
183
|
|
|
@@ -151,5 +194,69 @@ class InsightStore:
|
|
|
151
194
|
).fetchall()
|
|
152
195
|
return {r["category"]: r["cnt"] for r in rows}
|
|
153
196
|
|
|
197
|
+
def reinforce(self, insight_id: str, step: float = 0.1) -> None:
|
|
198
|
+
"""Corroboration: raise confidence (clamped) and count evidence.
|
|
199
|
+
|
|
200
|
+
The clamp lives in ``ActionableInsight``; the SQL bounds mirror it
|
|
201
|
+
so a direct UPDATE cannot push a value past the band either.
|
|
202
|
+
"""
|
|
203
|
+
with self._conn() as conn:
|
|
204
|
+
conn.execute(
|
|
205
|
+
"""
|
|
206
|
+
UPDATE insights
|
|
207
|
+
SET confidence = MIN(?, confidence + ?),
|
|
208
|
+
evidence_count = evidence_count + 1
|
|
209
|
+
WHERE id = ?
|
|
210
|
+
""",
|
|
211
|
+
(INSTINCT_CONFIDENCE_MAX, step, insight_id),
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
def weaken(self, insight_id: str, step: float = 0.2) -> None:
|
|
215
|
+
"""Contradiction: lower confidence (clamped to the band floor).
|
|
216
|
+
|
|
217
|
+
A correction cuts harder than a corroboration lifts — the default
|
|
218
|
+
step is larger — because one contradiction outweighs several
|
|
219
|
+
passive confirmations.
|
|
220
|
+
"""
|
|
221
|
+
with self._conn() as conn:
|
|
222
|
+
conn.execute(
|
|
223
|
+
"UPDATE insights SET confidence = MAX(?, confidence - ?) WHERE id = ?",
|
|
224
|
+
(INSTINCT_CONFIDENCE_MIN, step, insight_id),
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
def promotable(
|
|
228
|
+
self, min_projects: int = 2, min_confidence: float = 0.8
|
|
229
|
+
) -> list[str]:
|
|
230
|
+
"""Titles seen as project-scoped across >= min_projects distinct
|
|
231
|
+
projects with mean confidence >= min_confidence.
|
|
232
|
+
|
|
233
|
+
The signal B7 (``/arka promote``) consumes: a pattern that has
|
|
234
|
+
proven itself in more than one project has earned global scope.
|
|
235
|
+
Grouped by title because that is the instinct's stable identity
|
|
236
|
+
across projects (the id is per-occurrence).
|
|
237
|
+
"""
|
|
238
|
+
with self._conn() as conn:
|
|
239
|
+
rows = conn.execute(
|
|
240
|
+
"""
|
|
241
|
+
SELECT title FROM insights
|
|
242
|
+
WHERE scope = 'project'
|
|
243
|
+
GROUP BY title
|
|
244
|
+
HAVING COUNT(DISTINCT project) >= ?
|
|
245
|
+
AND AVG(confidence) >= ?
|
|
246
|
+
""",
|
|
247
|
+
(min_projects, min_confidence),
|
|
248
|
+
).fetchall()
|
|
249
|
+
return [r["title"] for r in rows]
|
|
250
|
+
|
|
251
|
+
def promote_to_global(self, title: str) -> int:
|
|
252
|
+
"""Flip every project-scoped insight with this title to global.
|
|
253
|
+
Returns the number of rows promoted."""
|
|
254
|
+
with self._conn() as conn:
|
|
255
|
+
cur = conn.execute(
|
|
256
|
+
"UPDATE insights SET scope = 'global' WHERE title = ? AND scope = 'project'",
|
|
257
|
+
(title,),
|
|
258
|
+
)
|
|
259
|
+
return cur.rowcount
|
|
260
|
+
|
|
154
261
|
def close(self) -> None:
|
|
155
262
|
"""No-op — connections are opened per-operation."""
|