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.
Files changed (80) hide show
  1. package/VERSION +1 -1
  2. package/arka/SKILL.md +9 -67
  3. package/arka/skills/comfyui/SKILL.md +50 -12
  4. package/arka/skills/conclave/SKILL.md +43 -141
  5. package/arka/skills/conclave/references/advisors.md +36 -0
  6. package/arka/skills/human-writing/SKILL.md +15 -100
  7. package/arka/skills/human-writing/references/forbidden-patterns.md +32 -0
  8. package/config/hooks/post-tool-use.sh +72 -0
  9. package/config/hooks/session-start.sh +16 -0
  10. package/config/hooks/user-prompt-submit.ps1 +9 -3
  11. package/config/hooks/user-prompt-submit.sh +108 -26
  12. package/core/agents/__pycache__/behavior_enforcer.cpython-313.pyc +0 -0
  13. package/core/agents/__pycache__/dna_registry.cpython-313.pyc +0 -0
  14. package/core/agents/adapters/__pycache__/disc_adapter.cpython-313.pyc +0 -0
  15. package/core/agents/adapters/disc_adapter.py +149 -0
  16. package/core/agents/behavior_enforcer.py +255 -0
  17. package/core/agents/dna_registry.py +235 -0
  18. package/core/forge/__init__.py +36 -0
  19. package/core/forge/__pycache__/__init__.cpython-313.pyc +0 -0
  20. package/core/forge/__pycache__/orchestrator.cpython-313.pyc +0 -0
  21. package/core/forge/__pycache__/runtime_dispatcher.cpython-313.pyc +0 -0
  22. package/core/forge/orchestrator.py +770 -0
  23. package/core/forge/runtime_dispatcher.py +465 -0
  24. package/core/governance/__pycache__/quality_api.cpython-313.pyc +0 -0
  25. package/core/governance/__pycache__/quality_router.cpython-313.pyc +0 -0
  26. package/core/governance/__pycache__/review_workflow.cpython-313.pyc +0 -0
  27. package/core/governance/quality_api.py +280 -0
  28. package/core/governance/quality_router.py +304 -0
  29. package/core/governance/review_workflow.py +386 -0
  30. package/core/memory/__pycache__/compressor.cpython-313.pyc +0 -0
  31. package/core/memory/__pycache__/rehydrator.cpython-313.pyc +0 -0
  32. package/core/memory/__pycache__/session_store.cpython-313.pyc +0 -0
  33. package/core/memory/compressor.py +269 -0
  34. package/core/memory/rehydrator.py +204 -0
  35. package/core/memory/session_store.py +256 -0
  36. package/core/runtime/__pycache__/context_compactor.cpython-313.pyc +0 -0
  37. package/core/runtime/__pycache__/subagent.cpython-313.pyc +0 -0
  38. package/core/runtime/context_compactor.py +63 -0
  39. package/core/runtime/subagent.py +13 -0
  40. package/core/synapse/__init__.py +10 -3
  41. package/core/synapse/__pycache__/__init__.cpython-313.pyc +0 -0
  42. package/core/synapse/__pycache__/engine.cpython-313.pyc +0 -0
  43. package/core/synapse/__pycache__/kb_cache.cpython-313.pyc +0 -0
  44. package/core/synapse/__pycache__/layers.cpython-313.pyc +0 -0
  45. package/core/synapse/engine.py +27 -16
  46. package/core/synapse/kb_cache.py +382 -0
  47. package/core/synapse/layers.py +253 -50
  48. package/core/sync/__pycache__/schema.cpython-313.pyc +0 -0
  49. package/core/sync/__pycache__/self_healing.cpython-313.pyc +0 -0
  50. package/core/workflow/__pycache__/announcer.cpython-313.pyc +0 -0
  51. package/core/workflow/__pycache__/dashboard.cpython-313.pyc +0 -0
  52. package/core/workflow/__pycache__/enforcer.cpython-313.pyc +0 -0
  53. package/core/workflow/__pycache__/rules_registry.cpython-313.pyc +0 -0
  54. package/core/workflow/__pycache__/state.cpython-313.pyc +0 -0
  55. package/core/workflow/announcer.py +246 -0
  56. package/core/workflow/dashboard.py +194 -0
  57. package/core/workflow/enforcer.py +234 -0
  58. package/core/workflow/recovery.py +196 -0
  59. package/core/workflow/rules_registry.py +484 -0
  60. package/core/workflow/session_summary.py +204 -0
  61. package/core/workflow/state.py +12 -2
  62. package/departments/dev/SKILL.md +10 -42
  63. package/departments/dev/skills/agent-design/SKILL.md +6 -26
  64. package/departments/dev/skills/ci-cd-pipeline/SKILL.md +6 -29
  65. package/departments/dev/skills/db-schema/SKILL.md +6 -24
  66. package/departments/dev/skills/dependency-audit/SKILL.md +1 -3
  67. package/departments/dev/skills/incident/SKILL.md +6 -24
  68. package/departments/dev/skills/mcp-builder/SKILL.md +4 -17
  69. package/departments/dev/skills/observability/SKILL.md +6 -29
  70. package/departments/dev/skills/performance-profiler/SKILL.md +5 -26
  71. package/departments/dev/skills/rag-architect/SKILL.md +5 -23
  72. package/departments/dev/skills/release/SKILL.md +4 -17
  73. package/departments/dev/skills/spec/SKILL.md +47 -148
  74. package/departments/landing/skills/landing-gen/SKILL.md +3 -15
  75. package/departments/marketing/skills/cold-email/SKILL.md +5 -17
  76. package/departments/marketing/skills/programmatic-seo/SKILL.md +6 -21
  77. package/departments/strategy/skills/board-advisor/SKILL.md +7 -21
  78. package/package.json +1 -1
  79. package/pyproject.toml +1 -1
  80. 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 3 seconds so a stuck bridge cannot burn the
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(3000)) {
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
- bridge_output=$(echo "{\"user_input\":$(echo "$user_input" | python3 -c "import sys,json; print(json.dumps(sys.stdin.read()))" 2>/dev/null || echo '""')}" \
97
- | ARKAOS_ROOT="$ARKAOS_ROOT" python3 "$BRIDGE_SCRIPT" --root "$ARKAOS_ROOT" 2>/dev/null)
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
- if [ -n "$bridge_output" ]; then
100
- python_result=$(echo "$bridge_output" | python3 -c "import sys,json; print(json.loads(sys.stdin.read()).get('context_string',''))" 2>/dev/null)
101
- fi
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
- # Append workflow state to synapse context
104
- _WF_READER="$ARKAOS_ROOT/core/workflow/state_reader.sh"
105
- if [ -f "$_WF_READER" ] && bash "$_WF_READER" active 2>/dev/null; then
106
- _WF_SUM=$(bash "$_WF_READER" summary 2>/dev/null)
107
- _WF_N=$(echo "$_WF_SUM" | cut -d'|' -f1)
108
- _WF_P=$(echo "$_WF_SUM" | cut -d'|' -f2)
109
- _WF_B=$(echo "$_WF_SUM" | cut -d'|' -f4)
110
- _WF_V=$(echo "$_WF_SUM" | cut -d'|' -f5)
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
- # --- Forge Context Injection ---
117
- _FORGE_ACTIVE="$HOME/.arkaos/plans/active.yaml"
118
- if [ -f "$_FORGE_ACTIVE" ]; then
119
- _FORGE_ID=$(cat "$_FORGE_ACTIVE" 2>/dev/null)
120
- _FORGE_FILE="$HOME/.arkaos/plans/${_FORGE_ID}.yaml"
121
- if [ -f "$_FORGE_FILE" ] && command -v python3 &>/dev/null; then
122
- _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)
123
- _FORGE_TAG="[forge:${_FORGE_ID}] [forge-status:${_FORGE_STATUS}]"
124
- python_result="${python_result} ${_FORGE_TAG}"
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)
@@ -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)