arkaos 2.17.0 → 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 +9 -3
- package/config/hooks/user-prompt-submit.sh +108 -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/runtime/context_compactor.py +63 -0
- package/core/runtime/subagent.py +13 -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__/schema.cpython-313.pyc +0 -0
- 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
|
@@ -229,6 +229,78 @@ add_violation('sequential-validation', 'Code written before implementation phase
|
|
|
229
229
|
fi
|
|
230
230
|
fi
|
|
231
231
|
|
|
232
|
+
# ─── ArkaOS Enforcement Engine (All 14 Rules) ─────────────────────────────────
|
|
233
|
+
# Uses core/workflow/enforcer.py to check ALL 14 NON-NEGOTIABLE rules
|
|
234
|
+
# BLOCK violations halt operation; ESCALATE violations alert Tier 0
|
|
235
|
+
# Gotchas auto-recovery is ALWAYS active (SIM per Sprint 3 decision)
|
|
236
|
+
|
|
237
|
+
if [ -z "${ARKAOS_PY:-}" ]; then
|
|
238
|
+
[ -f "$HOME/.arkaos/venv/bin/python3" ] && ARKAOS_PY="$HOME/.arkaos/venv/bin/python3"
|
|
239
|
+
[ -z "${ARKAOS_PY:-}" ] && [ -f "$HOME/.arkaos/.venv/bin/python3" ] && ARKAOS_PY="$HOME/.arkaos/.venv/bin/python3"
|
|
240
|
+
[ -z "${ARKAOS_PY:-}" ] && ARKAOS_PY=$(command -v python3 2>/dev/null)
|
|
241
|
+
fi
|
|
242
|
+
[ -z "${ARKAOS_ROOT:-}" ] && ARKAOS_ROOT=$(cat "$HOME/.arkaos/.repo-path" 2>/dev/null)
|
|
243
|
+
|
|
244
|
+
if [ -n "$ARKAOS_PY" ] && [ -n "$ARKAOS_ROOT" ] && [ -f "$ARKAOS_ROOT/core/workflow/enforcer.py" ]; then
|
|
245
|
+
ENFORCER_OUTPUT=$(PYTHONPATH="$ARKAOS_ROOT" $ARKAOS_PY -c "
|
|
246
|
+
import json, sys
|
|
247
|
+
from core.workflow.enforcer import enforce_tool
|
|
248
|
+
from core.workflow.state import add_violation
|
|
249
|
+
|
|
250
|
+
input_data = json.loads(sys.stdin.read())
|
|
251
|
+
tool_name = input_data.get('tool_name', '')
|
|
252
|
+
command = input_data.get('command', '')
|
|
253
|
+
file_path = input_data.get('file_path', '')
|
|
254
|
+
user_input = input_data.get('user_input', '')
|
|
255
|
+
|
|
256
|
+
extra = {}
|
|
257
|
+
if tool_name == 'Bash':
|
|
258
|
+
import subprocess
|
|
259
|
+
try:
|
|
260
|
+
branch = subprocess.check_output(['git', 'branch', '--show-current'], text=True, stderr=subprocess.DEVNULL).strip()
|
|
261
|
+
extra['git_branch'] = branch
|
|
262
|
+
except:
|
|
263
|
+
extra['git_branch'] = ''
|
|
264
|
+
|
|
265
|
+
result = enforce_tool(
|
|
266
|
+
tool_name=tool_name,
|
|
267
|
+
command=command,
|
|
268
|
+
file_path=file_path,
|
|
269
|
+
user_input=user_input,
|
|
270
|
+
**extra
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
if result.violations:
|
|
274
|
+
for v in result.violations:
|
|
275
|
+
try:
|
|
276
|
+
add_violation(v.rule_id, v.message, v.tool, v.file_path, v.severity)
|
|
277
|
+
except:
|
|
278
|
+
pass
|
|
279
|
+
|
|
280
|
+
print(json.dumps({
|
|
281
|
+
'violations': [v.to_dict() for v in result.violations],
|
|
282
|
+
'blocked': result.blocked,
|
|
283
|
+
'escalated': result.escalated,
|
|
284
|
+
'messages': result.messages
|
|
285
|
+
}))
|
|
286
|
+
else:
|
|
287
|
+
print(json.dumps({'violations': [], 'blocked': False, 'escalated': False, 'messages': []}))
|
|
288
|
+
" <<< "$input" 2>/dev/null)
|
|
289
|
+
|
|
290
|
+
if [ -n "$ENFORCER_OUTPUT" ] && echo "$ENFORCER_OUTPUT" | jq -e '.violations | length > 0' &>/dev/null; then
|
|
291
|
+
ENFORCER_BLOCKED=$(echo "$ENFORCER_OUTPUT" | jq -r '.blocked')
|
|
292
|
+
ENFORCER_MESSAGES=$(echo "$ENFORCER_OUTPUT" | jq -r '.messages | join("|")')
|
|
293
|
+
|
|
294
|
+
for msg in $(echo "$ENFORCER_MESSAGES" | tr '|' '\n'); do
|
|
295
|
+
[ -n "$msg" ] && VIOLATION_MSG="${VIOLATION_MSG}${VIOLATION_MSG:+$'\n'}${msg}"
|
|
296
|
+
done
|
|
297
|
+
|
|
298
|
+
if [ "$ENFORCER_BLOCKED" = "true" ]; then
|
|
299
|
+
VIOLATION_MSG="🔴 BLOCK: ${VIOLATION_MSG}"
|
|
300
|
+
fi
|
|
301
|
+
fi
|
|
302
|
+
fi
|
|
303
|
+
|
|
232
304
|
# --- Forge Violation Detection ---
|
|
233
305
|
_FORGE_ACTIVE="$HOME/.arkaos/plans/active.yaml"
|
|
234
306
|
|
|
@@ -89,6 +89,22 @@ MSG+="ArkaOS v${VERSION} | 65 agents | 17 departments | 244+ skills"
|
|
|
89
89
|
[ -n "$_FORGE_LINE" ] && MSG+="\\n${_FORGE_LINE}"
|
|
90
90
|
MSG+="${DRIFT}"
|
|
91
91
|
|
|
92
|
+
# --- Session Memory Resume Context ---
|
|
93
|
+
if command -v python3 &>/dev/null && [ -n "$REPO" ]; then
|
|
94
|
+
_SESSION_CTX=$(cd "$REPO" && python3 -c "
|
|
95
|
+
import sys
|
|
96
|
+
sys.path.insert(0, '$REPO')
|
|
97
|
+
try:
|
|
98
|
+
from core.memory.rehydrator import build_resume_context
|
|
99
|
+
ctx = build_resume_context()
|
|
100
|
+
if ctx:
|
|
101
|
+
print('\\n[SESSION] ' + ctx.replace('\\n', '\\n[SESSION] '))
|
|
102
|
+
except Exception:
|
|
103
|
+
pass
|
|
104
|
+
" 2>/dev/null)
|
|
105
|
+
[ -n "$_SESSION_CTX" ] && MSG+="\\n${_SESSION_CTX}"
|
|
106
|
+
fi
|
|
107
|
+
|
|
92
108
|
# ─── Output as systemMessage (same protocol as claude-mem) ─────────────
|
|
93
109
|
python3 -c "
|
|
94
110
|
import json
|
|
@@ -199,9 +199,9 @@ if ($python -and (Test-Path -LiteralPath $bridgeScript)) {
|
|
|
199
199
|
$proc = [System.Diagnostics.Process]::Start($psi)
|
|
200
200
|
$proc.StandardInput.Write($bridgeInput)
|
|
201
201
|
$proc.StandardInput.Close()
|
|
202
|
-
# Cap the bridge call at
|
|
202
|
+
# Cap the bridge call at 8 seconds so a stuck bridge cannot burn the
|
|
203
203
|
# full 10-second hook budget.
|
|
204
|
-
if (-not $proc.WaitForExit(
|
|
204
|
+
if (-not $proc.WaitForExit(8000)) {
|
|
205
205
|
try { $proc.Kill() } catch { }
|
|
206
206
|
} else {
|
|
207
207
|
$bridgeOutput = $proc.StandardOutput.ReadToEnd()
|
|
@@ -263,8 +263,14 @@ if (-not $pythonResult) {
|
|
|
263
263
|
$pythonResult = (@($l0, $l4, $l7) | Where-Object { $_ }) -join ' '
|
|
264
264
|
}
|
|
265
265
|
|
|
266
|
+
# --- Persistent Routing Reminder -------------------------------------------
|
|
267
|
+
# High-salience tag - ensures squad routing persists across conversation turns,
|
|
268
|
+
# and enforces visible KB citation when chunks are present.
|
|
269
|
+
# See: docs/superpowers/specs/2026-04-14-persistent-routing-reminder-design.md
|
|
270
|
+
$routeReminder = '[arka:route] Every response MUST route through a department squad. No generic assistant replies. Announce the squad before responding. When [knowledge:N chunks] is present in this context, you MUST cite at least one source and acknowledge KB was consulted; if absent on a non-trivial ArkaOS topic, query Obsidian before responding.'
|
|
271
|
+
|
|
266
272
|
# --- Output ----------------------------------------------------------------
|
|
267
|
-
$additionalContext = "$syncNotice$pythonResult"
|
|
273
|
+
$additionalContext = "$syncNotice$routeReminder $pythonResult"
|
|
268
274
|
[pscustomobject]@{ additionalContext = $additionalContext } | ConvertTo-Json -Compress
|
|
269
275
|
|
|
270
276
|
# --- Metrics (JSONL append) ------------------------------------------------
|
|
@@ -40,6 +40,9 @@ if [ -f "$ARKAOS_VERSION_FILE" ]; then
|
|
|
40
40
|
if [ "$_CURRENT_VERSION" != "$_SYNCED_VERSION" ]; then
|
|
41
41
|
_SYNC_NOTICE="[arka:update-available] ArkaOS v${_CURRENT_VERSION} installed (synced: ${_SYNCED_VERSION}). Run /arka update to sync all projects. "
|
|
42
42
|
fi
|
|
43
|
+
if [ -n "${ARKAOS_DEBUG:-}" ]; then
|
|
44
|
+
echo "[DEBUG] Detected version: ${_CURRENT_VERSION:-unknown}, synced: ${_SYNCED_VERSION:-none}" >&2
|
|
45
|
+
fi
|
|
43
46
|
fi
|
|
44
47
|
fi
|
|
45
48
|
|
|
@@ -92,42 +95,110 @@ fi
|
|
|
92
95
|
python_result=""
|
|
93
96
|
BRIDGE_SCRIPT="${ARKAOS_ROOT}/scripts/synapse-bridge.py"
|
|
94
97
|
|
|
98
|
+
# Determine which path we're using for debug output
|
|
99
|
+
if [ -n "${ARKAOS_DEBUG:-}" ]; then
|
|
100
|
+
echo "[DEBUG] ARKAOS_ROOT=${ARKAOS_ROOT}" >&2
|
|
101
|
+
echo "[DEBUG] BRIDGE_SCRIPT=${BRIDGE_SCRIPT}" >&2
|
|
102
|
+
fi
|
|
103
|
+
|
|
95
104
|
if command -v python3 &>/dev/null && [ -f "$BRIDGE_SCRIPT" ]; then
|
|
96
|
-
|
|
97
|
-
|
|
105
|
+
# Validate ARKAOS_ROOT before calling bridge
|
|
106
|
+
if [ ! -d "$ARKAOS_ROOT" ]; then
|
|
107
|
+
if [ -n "${ARKAOS_DEBUG:-}" ]; then
|
|
108
|
+
echo "[DEBUG] ARKAOS_ROOT is not a valid directory, skipping Python bridge" >&2
|
|
109
|
+
fi
|
|
110
|
+
else
|
|
111
|
+
_bridge_start=$(date +%s%N 2>/dev/null || echo "0")
|
|
112
|
+
bridge_output=$(echo "{\"user_input\":$(echo "$user_input" | python3 -c "import sys,json; print(json.dumps(sys.stdin.read()))" 2>/dev/null || echo '""')}" \
|
|
113
|
+
| ARKAOS_ROOT="$ARKAOS_ROOT" python3 "$BRIDGE_SCRIPT" --root "$ARKAOS_ROOT" 2>/dev/null)
|
|
114
|
+
_bridge_status=$?
|
|
98
115
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
116
|
+
if [ -n "${ARKAOS_DEBUG:-}" ]; then
|
|
117
|
+
if [ "$_bridge_start" != "0" ] && [ $(date +%s%N 2>/dev/null || echo "0") != "0" ]; then
|
|
118
|
+
_bridge_ms=$(( ($(date +%s%N) - _bridge_start) / 1000000 ))
|
|
119
|
+
echo "[DEBUG] bridge completed in ${_bridge_ms}ms, exit=$_bridge_status" >&2
|
|
120
|
+
fi
|
|
121
|
+
echo "[DEBUG] bridge_output length=${#bridge_output}" >&2
|
|
122
|
+
fi
|
|
102
123
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
_WF_TAG="[workflow:${_WF_N}] [phase:${_WF_P}] [branch:${_WF_B}] [violations:${_WF_V}]"
|
|
112
|
-
[ "$_WF_V" != "0" ] && _WF_TAG="WARNING: ${_WF_V} workflow violation(s). $_WF_TAG"
|
|
113
|
-
python_result="${python_result} ${_WF_TAG}"
|
|
124
|
+
if [ -n "$bridge_output" ] && [ $_bridge_status -eq 0 ]; then
|
|
125
|
+
python_result=$(echo "$bridge_output" | python3 -c "import sys,json; print(json.loads(sys.stdin.read()).get('context_string',''))" 2>/dev/null)
|
|
126
|
+
if [ -n "${ARKAOS_DEBUG:-}" ]; then
|
|
127
|
+
echo "[DEBUG] python_result length=${#python_result}" >&2
|
|
128
|
+
fi
|
|
129
|
+
elif [ -n "${ARKAOS_DEBUG:-}" ]; then
|
|
130
|
+
echo "[DEBUG] bridge failed or returned empty, exit=$_bridge_status" >&2
|
|
131
|
+
fi
|
|
114
132
|
fi
|
|
115
133
|
|
|
116
|
-
#
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
134
|
+
# Append workflow state to synapse context (always, if python result was set)
|
|
135
|
+
if [ -n "$python_result" ]; then
|
|
136
|
+
_WF_READER="$ARKAOS_ROOT/core/workflow/state_reader.sh"
|
|
137
|
+
if [ -f "$_WF_READER" ] && bash "$_WF_READER" active 2>/dev/null; then
|
|
138
|
+
_WF_SUM=$(bash "$_WF_READER" summary 2>/dev/null)
|
|
139
|
+
_WF_N=$(echo "$_WF_SUM" | cut -d'|' -f1)
|
|
140
|
+
_WF_P=$(echo "$_WF_SUM" | cut -d'|' -f2)
|
|
141
|
+
_WF_B=$(echo "$_WF_SUM" | cut -d'|' -f4)
|
|
142
|
+
_WF_V=$(echo "$_WF_SUM" | cut -d'|' -f5)
|
|
143
|
+
_WF_TAG="[workflow:${_WF_N}] [phase:${_WF_P}] [branch:${_WF_B}] [violations:${_WF_V}]"
|
|
144
|
+
[ "$_WF_V" != "0" ] && _WF_TAG="WARNING: ${_WF_V} workflow violation(s). $_WF_TAG"
|
|
145
|
+
python_result="${python_result} ${_WF_TAG}"
|
|
146
|
+
fi
|
|
147
|
+
|
|
148
|
+
# --- Forge Context Injection ---
|
|
149
|
+
_FORGE_ACTIVE="$HOME/.arkaos/plans/active.yaml"
|
|
150
|
+
if [ -f "$_FORGE_ACTIVE" ]; then
|
|
151
|
+
_FORGE_ID=$(cat "$_FORGE_ACTIVE" 2>/dev/null)
|
|
152
|
+
_FORGE_FILE="$HOME/.arkaos/plans/${_FORGE_ID}.yaml"
|
|
153
|
+
if [ -f "$_FORGE_FILE" ] && command -v python3 &>/dev/null; then
|
|
154
|
+
_FORGE_STATUS=$(FORGE_FILE="$_FORGE_FILE" python3 -c "import yaml,os; d=yaml.safe_load(open(os.environ['FORGE_FILE'])); print(d.get('status',''))" 2>/dev/null)
|
|
155
|
+
_FORGE_TAG="[forge:${_FORGE_ID}] [forge-status:${_FORGE_STATUS}]"
|
|
156
|
+
python_result="${python_result} ${_FORGE_TAG}"
|
|
157
|
+
fi
|
|
158
|
+
fi
|
|
159
|
+
|
|
160
|
+
# --- Knowledge Auto-Inject (On-Demand via Session Cache) ---
|
|
161
|
+
if [ -n "$python_result" ] && [[ "$python_result" == *"[knowledge:"* ]]; then
|
|
162
|
+
_KB_SESSION_ID="${ARKAOS_SESSION_ID:-${CLAUDE_SESSION_ID:-bridge-$$}}"
|
|
163
|
+
_KB_PROJECT_HASH=$(echo "$ARKAOS_ROOT" | md5sum 2>/dev/null | cut -c1-12 || echo "default")
|
|
164
|
+
_KB_CACHE_DIR="/tmp/arkaos-kb-${_KB_PROJECT_HASH}"
|
|
165
|
+
|
|
166
|
+
if [ -n "${ARKAOS_DEBUG:-}" ]; then
|
|
167
|
+
echo "[DEBUG] KB session_id=${_KB_SESSION_ID}, project_hash=${_KB_PROJECT_HASH}" >&2
|
|
168
|
+
fi
|
|
169
|
+
|
|
170
|
+
if [ -d "$_KB_CACHE_DIR" ] && command -v python3 &>/dev/null; then
|
|
171
|
+
_KB_CONTENT=$(python3 -c "
|
|
172
|
+
import sys
|
|
173
|
+
sys.path.insert(0, '$ARKAOS_ROOT')
|
|
174
|
+
from core.synapse.kb_cache import KBSessionCache
|
|
175
|
+
cache = KBSessionCache(session_id='$_KB_SESSION_ID', project_path='$ARKAOS_ROOT')
|
|
176
|
+
results = cache.get_overlap('''$user_input''', threshold=0.3)
|
|
177
|
+
if results:
|
|
178
|
+
snippets = []
|
|
179
|
+
for r in results[:3]:
|
|
180
|
+
src = r.get('source', '').split('/')[-1] if r.get('source') else ''
|
|
181
|
+
txt = r.get('text', '')[:200].replace('\n', ' ')
|
|
182
|
+
snippets.append(f'{src}: {txt}' if src else txt)
|
|
183
|
+
print(' | '.join(snippets))
|
|
184
|
+
" 2>/dev/null)
|
|
185
|
+
|
|
186
|
+
if [ -n "$_KB_CONTENT" ]; then
|
|
187
|
+
if [ -n "${ARKAOS_DEBUG:-}" ]; then
|
|
188
|
+
echo "[DEBUG] KB auto-inject: ${#_KB_CONTENT} chars of knowledge" >&2
|
|
189
|
+
fi
|
|
190
|
+
python_result="${_KB_CONTENT} ${python_result}"
|
|
191
|
+
fi
|
|
192
|
+
fi
|
|
125
193
|
fi
|
|
126
194
|
fi
|
|
127
195
|
fi
|
|
128
196
|
|
|
129
197
|
# ─── Fallback: Bash-only context (if Python unavailable) ────────────────
|
|
130
198
|
if [ -z "$python_result" ]; then
|
|
199
|
+
if [ -n "${ARKAOS_DEBUG:-}" ]; then
|
|
200
|
+
echo "[DEBUG] Using bash fallback (python_result was empty)" >&2
|
|
201
|
+
fi
|
|
131
202
|
# L0: Constitution (cached)
|
|
132
203
|
L0=""
|
|
133
204
|
L0_CACHE="$CACHE_DIR/l0-constitution"
|
|
@@ -193,8 +264,19 @@ if [ -f "$_HYGIENE_SCRIPT" ]; then
|
|
|
193
264
|
bash "$_HYGIENE_SCRIPT" 2>/dev/null)
|
|
194
265
|
fi
|
|
195
266
|
|
|
267
|
+
# ─── Persistent Routing Reminder ────────────────────────────────────────
|
|
268
|
+
# High-salience tag — ensures squad routing persists across conversation turns,
|
|
269
|
+
# not just on turn 1 when /arka skill content is fresh. See spec:
|
|
270
|
+
# docs/superpowers/specs/2026-04-14-persistent-routing-reminder-design.md
|
|
271
|
+
_ROUTE_REMINDER="
|
|
272
|
+
[ARKA:ROUTE]
|
|
273
|
+
EVERY response MUST route through a department squad.
|
|
274
|
+
NO generic assistant replies. Announce the squad before responding.
|
|
275
|
+
When [knowledge:N chunks] is present, cite at least one source.
|
|
276
|
+
If [knowledge:N chunks] is absent on a non-trivial ArkaOS topic, query Obsidian first."
|
|
277
|
+
|
|
196
278
|
# ─── Output ──────────────────────────────────────────────────────────────
|
|
197
|
-
_OUT_CONTEXT="${_ARKA_GREETING:-}${_SYNC_NOTICE:-}$python_result"
|
|
279
|
+
_OUT_CONTEXT="${_ARKA_GREETING:-}${_SYNC_NOTICE:-}${_ROUTE_REMINDER} $python_result"
|
|
198
280
|
[ -n "$_HYGIENE" ] && _OUT_CONTEXT="$_OUT_CONTEXT $_HYGIENE"
|
|
199
281
|
# Escape for JSON
|
|
200
282
|
_OUT_JSON=$(python3 -c "import json,sys; print(json.dumps(sys.stdin.read()))" <<< "$_OUT_CONTEXT" 2>/dev/null)
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""DISC Communication Adapter — adapt messages based on recipient's DISC profile.
|
|
2
|
+
|
|
3
|
+
When sending a message to another agent, this adapter modifies the message
|
|
4
|
+
tone and structure to match the recipient's preferred communication style.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from core.agents.dna_registry import DNARegistry, get_registry
|
|
8
|
+
from core.agents.schema import Agent, DISCType
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
TONE_ADAPTERS = {
|
|
12
|
+
DISCType.D: {
|
|
13
|
+
"prefix": "",
|
|
14
|
+
"suffix": "",
|
|
15
|
+
"structure": "bullet_summary_first",
|
|
16
|
+
"urgency_words": ["critical", "key", "essential", "must", "need"],
|
|
17
|
+
"peace_words": [],
|
|
18
|
+
"opening": "Direct.",
|
|
19
|
+
},
|
|
20
|
+
DISCType.I: {
|
|
21
|
+
"prefix": "",
|
|
22
|
+
"suffix": "",
|
|
23
|
+
"structure": "friendly_narrative",
|
|
24
|
+
"urgency_words": ["exciting", "great", "amazing", "fantastic"],
|
|
25
|
+
"peace_words": [],
|
|
26
|
+
"opening": "Hey!",
|
|
27
|
+
},
|
|
28
|
+
DISCType.S: {
|
|
29
|
+
"prefix": "",
|
|
30
|
+
"suffix": "",
|
|
31
|
+
"structure": "supportive_explanation",
|
|
32
|
+
"urgency_words": [],
|
|
33
|
+
"peace_words": ["steady", "reliable", "together", "support", "help"],
|
|
34
|
+
"opening": "",
|
|
35
|
+
},
|
|
36
|
+
DISCType.C: {
|
|
37
|
+
"prefix": "",
|
|
38
|
+
"suffix": "",
|
|
39
|
+
"structure": "structured_data_first",
|
|
40
|
+
"urgency_words": [],
|
|
41
|
+
"peace_words": ["thorough", "precise", "accurate", "detailed"],
|
|
42
|
+
"opening": "Analysis:",
|
|
43
|
+
},
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class DISCAdapter:
|
|
48
|
+
"""Adapt messages for recipients based on their DISC profile."""
|
|
49
|
+
|
|
50
|
+
def __init__(self, registry: DNARegistry | None = None):
|
|
51
|
+
self.registry = registry or get_registry()
|
|
52
|
+
|
|
53
|
+
def adapt_message(
|
|
54
|
+
self,
|
|
55
|
+
message: str,
|
|
56
|
+
sender_id: str,
|
|
57
|
+
recipient_id: str,
|
|
58
|
+
) -> str:
|
|
59
|
+
"""Adapt a message for the recipient's DISC profile.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
message: The original message to adapt
|
|
63
|
+
sender_id: ID of the sending agent
|
|
64
|
+
recipient_id: ID of the receiving agent
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
Adapted message optimized for the recipient
|
|
68
|
+
"""
|
|
69
|
+
sender = self.registry.get(sender_id)
|
|
70
|
+
recipient = self.registry.get(recipient_id)
|
|
71
|
+
|
|
72
|
+
if not sender or not recipient:
|
|
73
|
+
return message
|
|
74
|
+
|
|
75
|
+
recipient_disc = recipient.behavioral_dna.disc.primary
|
|
76
|
+
adapter = TONE_ADAPTERS.get(recipient_disc)
|
|
77
|
+
|
|
78
|
+
if not adapter:
|
|
79
|
+
return message
|
|
80
|
+
|
|
81
|
+
adapted = message
|
|
82
|
+
|
|
83
|
+
if adapter["opening"]:
|
|
84
|
+
if not adapted.startswith(adapter["opening"]):
|
|
85
|
+
adapted = f"{adapter['opening']} {adapted}"
|
|
86
|
+
|
|
87
|
+
return adapted
|
|
88
|
+
|
|
89
|
+
def get_opening(self, recipient_id: str) -> str:
|
|
90
|
+
"""Get the appropriate opening for a recipient."""
|
|
91
|
+
recipient = self.registry.get(recipient_id)
|
|
92
|
+
if not recipient:
|
|
93
|
+
return ""
|
|
94
|
+
|
|
95
|
+
disc = recipient.behavioral_dna.disc.primary
|
|
96
|
+
return TONE_ADAPTERS.get(disc, {}).get("opening", "")
|
|
97
|
+
|
|
98
|
+
def get_structure_hint(self, recipient_id: str) -> str:
|
|
99
|
+
"""Get the recommended message structure for a recipient."""
|
|
100
|
+
recipient = self.registry.get(recipient_id)
|
|
101
|
+
if not recipient:
|
|
102
|
+
return "standard"
|
|
103
|
+
|
|
104
|
+
disc = recipient.behavioral_dna.disc.primary
|
|
105
|
+
return TONE_ADAPTERS.get(disc, {}).get("structure", "standard")
|
|
106
|
+
|
|
107
|
+
def adapt_for_disc(
|
|
108
|
+
self,
|
|
109
|
+
message: str,
|
|
110
|
+
target_disc: DISCType,
|
|
111
|
+
) -> str:
|
|
112
|
+
"""Adapt a message directly for a DISC type.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
message: Original message
|
|
116
|
+
target_disc: Target DISC type
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
Adapted message
|
|
120
|
+
"""
|
|
121
|
+
adapter = TONE_ADAPTERS.get(target_disc, {})
|
|
122
|
+
if not adapter:
|
|
123
|
+
return message
|
|
124
|
+
|
|
125
|
+
adapted = message
|
|
126
|
+
if adapter["opening"]:
|
|
127
|
+
if not adapted.startswith(adapter["opening"]):
|
|
128
|
+
adapted = f"{adapter['opening']} {adapted}"
|
|
129
|
+
|
|
130
|
+
return adapted
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def adapt_message(
|
|
134
|
+
message: str,
|
|
135
|
+
sender_id: str,
|
|
136
|
+
recipient_id: str,
|
|
137
|
+
) -> str:
|
|
138
|
+
"""Convenience function to adapt a message between two agents.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
message: Original message
|
|
142
|
+
sender_id: ID of the sender
|
|
143
|
+
recipient_id: ID of the recipient
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
Adapted message
|
|
147
|
+
"""
|
|
148
|
+
adapter = DISCAdapter()
|
|
149
|
+
return adapter.adapt_message(message, sender_id, recipient_id)
|