@smilintux/skcapstone 0.10.0 → 0.12.5
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/.env.example +10 -4
- package/.github/workflows/ci.yml +2 -2
- package/.github/workflows/publish.yml +9 -2
- package/.openclaw-workspace.json +2 -2
- package/CLAUDE.md +37 -0
- package/MISSION.md +17 -2
- package/README.md +282 -3
- package/docker/Dockerfile +7 -7
- package/docker/compose-templates/dev-team.yml +12 -12
- package/docker/compose-templates/mini-team.yml +9 -9
- package/docker/compose-templates/ops-team.yml +10 -10
- package/docker/compose-templates/research-team.yml +10 -10
- package/docker/entrypoint.sh +4 -4
- package/docs/ADR-optional-integration-backbone.md +181 -0
- package/docs/ARCHITECTURE.md +186 -43
- package/docs/BOND_WITH_GROK.md +6 -6
- package/docs/CUSTOM_AGENT.md +123 -30
- package/docs/DREAMING.md +70 -0
- package/docs/GETTING_STARTED.md +7 -7
- package/docs/QUICKSTART.md +10 -6
- package/docs/SKJOULE_ARCHITECTURE.md +3 -3
- package/docs/SOUL_SWAPPER.md +5 -5
- package/docs/hammertime-audit.md +402 -0
- package/docs/sk-integration-HANDOFF.md +117 -0
- package/docs/skscheduler.md +155 -0
- package/docs/superpowers/examples/jobs.yaml +31 -0
- package/docs/superpowers/plans/2026-06-08-skscheduler.md +1265 -0
- package/docs/superpowers/specs/2026-06-08-skscheduler-design.md +186 -0
- package/examples/custom-bond-template.json +1 -1
- package/examples/grok-feb.json +1 -1
- package/examples/queen-ava-feb.json +1 -1
- package/launchd/{com.skcapstone.skcomm-heartbeat.plist → com.skcapstone.skcomms-heartbeat.plist} +4 -4
- package/launchd/{com.skcapstone.skcomm-queue-drain.plist → com.skcapstone.skcomms-queue-drain.plist} +4 -4
- package/launchd/install-launchd.sh +6 -6
- package/{openclaw-plugin → openclaw-plugin.archived-2026-04-23}/src/index.ts +3 -2
- package/package.json +1 -1
- package/pyproject.toml +16 -10
- package/scripts/archive-sessions.sh +7 -0
- package/scripts/check-updates.py +4 -4
- package/scripts/install-bundle.sh +8 -8
- package/scripts/install.ps1 +12 -11
- package/scripts/install.sh +159 -5
- package/scripts/model-fallback-monitor.sh +102 -0
- package/scripts/nvidia-proxy.mjs +78 -26
- package/scripts/refresh-anthropic-token.sh +172 -0
- package/scripts/release.sh +98 -0
- package/scripts/session-to-memory.py +219 -0
- package/scripts/skgateway.mjs +3 -3
- package/scripts/telegram-catchup-all.sh +12 -1
- package/scripts/verify_install.sh +2 -2
- package/scripts/wargov-ufo-capture/README.md +43 -0
- package/scripts/wargov-ufo-capture/cdp_capture_release2.py +273 -0
- package/scripts/wargov-ufo-capture/cdp_capture_splc_doj.py +246 -0
- package/scripts/wargov-ufo-capture/cdp_finish.py +271 -0
- package/scripts/wargov-ufo-capture/cdp_probe.py +188 -0
- package/scripts/wargov-ufo-capture/cdp_splc_pressrelease.py +101 -0
- package/scripts/wargov-ufo-capture/parse_csv.py +95 -0
- package/scripts/wargov-ufo-capture/pull_dvids.sh +107 -0
- package/scripts/watch-anthropic-token.sh +212 -0
- package/scripts/windows/install-tasks.ps1 +7 -7
- package/scripts/windows/skcapstone-task.xml +1 -1
- package/src/skcapstone/__init__.py +45 -3
- package/src/skcapstone/_cli_monolith.py +20 -15
- package/src/skcapstone/activity.py +5 -1
- package/src/skcapstone/agent_card.py +3 -2
- package/src/skcapstone/api.py +41 -40
- package/src/skcapstone/auction.py +14 -11
- package/src/skcapstone/backup.py +2 -1
- package/src/skcapstone/blueprint_registry.py +4 -3
- package/src/skcapstone/brain_first.py +238 -0
- package/src/skcapstone/changelog.py +1 -1
- package/src/skcapstone/chat.py +22 -17
- package/src/skcapstone/cli/__init__.py +9 -1
- package/src/skcapstone/cli/_common.py +1 -0
- package/src/skcapstone/cli/agents_spawner.py +5 -2
- package/src/skcapstone/cli/alerts.py +25 -4
- package/src/skcapstone/cli/bench.py +15 -15
- package/src/skcapstone/cli/chat.py +7 -4
- package/src/skcapstone/cli/consciousness.py +5 -2
- package/src/skcapstone/cli/context_cmd.py +18 -4
- package/src/skcapstone/cli/daemon.py +11 -7
- package/src/skcapstone/cli/gtd.py +26 -1
- package/src/skcapstone/cli/housekeeping.py +3 -3
- package/src/skcapstone/cli/identity_cmd.py +378 -0
- package/src/skcapstone/cli/joule_cmd.py +7 -3
- package/src/skcapstone/cli/memory.py +8 -6
- package/src/skcapstone/cli/peers_dir.py +1 -1
- package/src/skcapstone/cli/register_cmd.py +29 -3
- package/src/skcapstone/cli/scheduler_cmd.py +167 -0
- package/src/skcapstone/cli/session.py +25 -0
- package/src/skcapstone/cli/setup.py +96 -29
- package/src/skcapstone/cli/shell_cmd.py +53 -1
- package/src/skcapstone/cli/skills_cmd.py +2 -2
- package/src/skcapstone/cli/soul.py +8 -5
- package/src/skcapstone/cli/status.py +37 -11
- package/src/skcapstone/cli/telegram.py +21 -0
- package/src/skcapstone/cli/test_cmd.py +5 -5
- package/src/skcapstone/cli/test_connection.py +2 -2
- package/src/skcapstone/cli/upgrade_cmd.py +23 -14
- package/src/skcapstone/cli/version_cmd.py +1 -1
- package/src/skcapstone/cli/watch_cmd.py +9 -6
- package/src/skcapstone/cloud9_bridge.py +14 -14
- package/src/skcapstone/codex_setup.py +255 -0
- package/src/skcapstone/config_validator.py +7 -4
- package/src/skcapstone/consciousness_config.py +5 -1
- package/src/skcapstone/consciousness_loop.py +313 -273
- package/src/skcapstone/context_loader.py +121 -0
- package/src/skcapstone/coord_federation.py +2 -1
- package/src/skcapstone/coordination.py +23 -6
- package/src/skcapstone/crush_integration.py +2 -1
- package/src/skcapstone/daemon.py +132 -77
- package/src/skcapstone/dashboard.py +10 -10
- package/src/skcapstone/data/sk-agent-picker.sh +421 -0
- package/src/skcapstone/data/systemd/skcapstone-api.socket +9 -0
- package/src/skcapstone/data/systemd/skcapstone-memory-compress.service +18 -0
- package/src/skcapstone/data/systemd/skcapstone-memory-compress.timer +11 -0
- package/src/skcapstone/data/systemd/skcapstone.service +37 -0
- package/src/skcapstone/data/systemd/skcapstone@.service +50 -0
- package/src/skcapstone/data/systemd/skcomms-heartbeat.service +18 -0
- package/{systemd/skcomm-heartbeat.timer → src/skcapstone/data/systemd/skcomms-heartbeat.timer} +2 -2
- package/src/skcapstone/data/systemd/skcomms-queue-drain.service +17 -0
- package/{systemd/skcomm-queue-drain.timer → src/skcapstone/data/systemd/skcomms-queue-drain.timer} +2 -2
- package/src/skcapstone/defaults/claude/CLAUDE.md +67 -0
- package/src/skcapstone/defaults/claude/settings.json +74 -0
- package/src/skcapstone/defaults/lumina/config/claude-hooks.md +57 -0
- package/src/skcapstone/defaults/lumina/config/skgraph.yaml +55 -10
- package/src/skcapstone/defaults/lumina/config/skmemory.yaml +79 -13
- package/src/skcapstone/defaults/lumina/config/skvector.yaml +60 -9
- package/src/skcapstone/defaults/lumina/memory/long-term/18b9c0d1e2f3-cloud9-protocol.json +2 -2
- package/src/skcapstone/defaults/lumina/memory/long-term/a1b2c3d4e5f6-ecosystem-overview.json +2 -2
- package/src/skcapstone/defaults/lumina/memory/long-term/b2c3d4e5f6a7-five-pillars.json +9 -9
- package/src/skcapstone/defaults/lumina/memory/long-term/d4e5f6a7b8c9-site-directory.json +2 -2
- package/src/skcapstone/defaults/unhinged.json +13 -0
- package/src/skcapstone/discovery.py +43 -20
- package/src/skcapstone/doctor.py +941 -22
- package/src/skcapstone/dreaming.py +1183 -109
- package/src/skcapstone/emotion_tracker.py +2 -2
- package/src/skcapstone/export.py +4 -3
- package/src/skcapstone/fuse_mount.py +14 -12
- package/src/skcapstone/gui_installer.py +2 -2
- package/src/skcapstone/heartbeat.py +1 -1
- package/src/skcapstone/housekeeping.py +14 -14
- package/src/skcapstone/install_wizard.py +209 -7
- package/src/skcapstone/itil.py +13 -4
- package/src/skcapstone/kms_scheduler.py +10 -8
- package/src/skcapstone/launchd.py +19 -19
- package/src/skcapstone/mcp_launcher.py +15 -1
- package/src/skcapstone/mcp_server.py +83 -49
- package/src/skcapstone/mcp_tools/__init__.py +2 -0
- package/src/skcapstone/mcp_tools/_helpers.py +2 -2
- package/src/skcapstone/mcp_tools/ansible_tools.py +7 -4
- package/src/skcapstone/mcp_tools/brain_first_tools.py +90 -0
- package/src/skcapstone/mcp_tools/capauth_tools.py +7 -4
- package/src/skcapstone/mcp_tools/comm_tools.py +10 -10
- package/src/skcapstone/mcp_tools/coord_tools.py +8 -4
- package/src/skcapstone/mcp_tools/did_tools.py +11 -8
- package/src/skcapstone/mcp_tools/gtd_tools.py +4 -4
- package/src/skcapstone/mcp_tools/memory_tools.py +6 -2
- package/src/skcapstone/mcp_tools/notification_tools.py +22 -6
- package/src/skcapstone/mcp_tools/{skcomm_tools.py → skcomms_tools.py} +14 -14
- package/src/skcapstone/mcp_tools/soul_tools.py +8 -2
- package/src/skcapstone/mdns_discovery.py +2 -2
- package/src/skcapstone/memory_curator.py +1 -1
- package/src/skcapstone/memory_engine.py +10 -3
- package/src/skcapstone/metrics.py +30 -16
- package/src/skcapstone/migrate_memories.py +4 -3
- package/src/skcapstone/migrate_multi_agent.py +8 -7
- package/src/skcapstone/models.py +47 -5
- package/src/skcapstone/notifications.py +42 -18
- package/src/skcapstone/onboard.py +875 -121
- package/src/skcapstone/operator_link.py +170 -0
- package/src/skcapstone/peer_directory.py +4 -4
- package/src/skcapstone/peers.py +19 -19
- package/src/skcapstone/pillars/__init__.py +7 -5
- package/src/skcapstone/pillars/consciousness.py +191 -0
- package/src/skcapstone/pillars/identity.py +51 -7
- package/src/skcapstone/pillars/memory.py +9 -3
- package/src/skcapstone/pillars/sync.py +2 -2
- package/src/skcapstone/preflight.py +3 -3
- package/src/skcapstone/providers/docker.py +28 -28
- package/src/skcapstone/register.py +6 -6
- package/src/skcapstone/registry_client.py +5 -4
- package/src/skcapstone/runtime.py +14 -3
- package/src/skcapstone/scheduled_tasks.py +254 -19
- package/src/skcapstone/scheduler_jobs.py +456 -0
- package/src/skcapstone/scheduler_runner.py +239 -0
- package/src/skcapstone/scheduler_state.py +162 -0
- package/src/skcapstone/sdk.py +310 -0
- package/src/skcapstone/service_health.py +279 -39
- package/src/skcapstone/session_briefing.py +108 -0
- package/src/skcapstone/session_capture.py +1 -1
- package/src/skcapstone/shell.py +7 -1
- package/src/skcapstone/soul.py +3 -1
- package/src/skcapstone/soul_switch.py +3 -1
- package/src/skcapstone/summary.py +6 -6
- package/src/skcapstone/sync_engine.py +15 -15
- package/src/skcapstone/sync_watcher.py +2 -2
- package/src/skcapstone/systemd.py +55 -21
- package/src/skcapstone/team_comms.py +8 -8
- package/src/skcapstone/team_engine.py +1 -1
- package/src/skcapstone/testrunner.py +3 -3
- package/src/skcapstone/trust_graph.py +40 -5
- package/src/skcapstone/unified_search.py +15 -6
- package/src/skcapstone/uninstall_wizard.py +11 -3
- package/src/skcapstone/version_check.py +8 -4
- package/src/skcapstone/warmth_anchor.py +4 -2
- package/src/skcapstone/whoami.py +4 -4
- package/systemd/skcapstone.service +4 -6
- package/systemd/skcapstone@.service +7 -8
- package/systemd/skcomms-heartbeat.service +21 -0
- package/systemd/skcomms-heartbeat.timer +12 -0
- package/systemd/skcomms-queue-drain.service +17 -0
- package/systemd/skcomms-queue-drain.timer +12 -0
- package/tests/conftest.py +39 -0
- package/tests/integration/test_consciousness_e2e.py +39 -39
- package/tests/test_agent_card.py +1 -1
- package/tests/test_agent_home_scaffold.py +34 -0
- package/tests/test_alerts_consumer_topics.py +27 -0
- package/tests/test_backup.py +2 -1
- package/tests/test_chat.py +6 -6
- package/tests/test_claude_md.py +2 -2
- package/tests/test_cli_skills.py +10 -10
- package/tests/test_cli_test_cmd.py +4 -4
- package/tests/test_cli_test_connection.py +1 -1
- package/tests/test_cloud9_bridge.py +6 -6
- package/tests/test_consciousness_e2e.py +1 -1
- package/tests/test_consciousness_loop.py +10 -10
- package/tests/test_coordination.py +25 -0
- package/tests/test_cross_package.py +21 -21
- package/tests/test_daemon.py +4 -4
- package/tests/test_daemon_shutdown.py +1 -1
- package/tests/test_docker_provider.py +29 -29
- package/tests/test_doctor.py +400 -0
- package/tests/test_doctor_skscheduler.py +50 -0
- package/tests/test_dreaming_engine.py +147 -0
- package/tests/test_dreaming_gtd_capture.py +35 -0
- package/tests/test_e2e_automated.py +8 -5
- package/tests/test_fuse_mount.py +10 -10
- package/tests/test_gtd_brief.py +46 -0
- package/tests/test_gtd_malformed_tolerance.py +31 -0
- package/tests/test_housekeeping.py +15 -15
- package/tests/test_identity_migrate.py +251 -0
- package/tests/test_integration_backbone.py +598 -0
- package/tests/test_itil_gtd_lifecycle.py +37 -0
- package/tests/test_jobs_dropins.py +84 -0
- package/tests/test_mcp_server.py +82 -37
- package/tests/test_models.py +48 -4
- package/tests/test_multi_agent.py +31 -29
- package/tests/test_notifications.py +122 -32
- package/tests/test_onboard.py +63 -75
- package/tests/test_operator_link.py +78 -0
- package/tests/test_peers.py +14 -14
- package/tests/test_pillars.py +98 -0
- package/tests/test_preflight.py +3 -3
- package/tests/test_runtime.py +21 -0
- package/tests/test_scheduled_tasks.py +11 -6
- package/tests/test_scheduler_cli.py +47 -0
- package/tests/test_scheduler_features.py +133 -0
- package/tests/test_scheduler_integration.py +87 -0
- package/tests/test_scheduler_jobs.py +155 -0
- package/tests/test_scheduler_runner.py +64 -0
- package/tests/test_scheduler_state.py +57 -0
- package/tests/test_sdk.py +70 -0
- package/tests/test_service_health_incidents.py +34 -0
- package/tests/test_service_registry.py +52 -0
- package/tests/test_session_briefing.py +130 -0
- package/tests/test_snapshots.py +4 -4
- package/tests/test_sync_pipeline.py +26 -26
- package/tests/test_team_comms.py +2 -2
- package/tests/test_testrunner.py +2 -2
- package/tests/test_trust_graph.py +18 -0
- package/tests/test_unified_search.py +2 -2
- package/tests/test_version_check.py +10 -0
- package/tests/test_version_cmd.py +8 -8
- package/tests/test_whoami.py +1 -1
- package/systemd/skcomm-heartbeat.service +0 -18
- package/systemd/skcomm-queue-drain.service +0 -17
- /package/{openclaw-plugin → openclaw-plugin.archived-2026-04-23}/package.json +0 -0
- /package/{openclaw-plugin → openclaw-plugin.archived-2026-04-23}/src/openclaw.plugin.json +0 -0
package/src/skcapstone/api.py
CHANGED
|
@@ -139,7 +139,7 @@ class HealthResponse(BaseModel):
|
|
|
139
139
|
)
|
|
140
140
|
backend_health: Dict[str, Any] = Field(
|
|
141
141
|
default_factory=dict,
|
|
142
|
-
description="Per-transport liveness flags (e.g. {
|
|
142
|
+
description="Per-transport liveness flags (e.g. {skcomms: true}).",
|
|
143
143
|
)
|
|
144
144
|
disk_free_gb: float = Field(0.0, description="Free disk space in gigabytes.")
|
|
145
145
|
memory_usage_mb: float = Field(0.0, description="Current RSS memory usage in MB.")
|
|
@@ -507,7 +507,7 @@ def _check_bearer(
|
|
|
507
507
|
) -> str:
|
|
508
508
|
"""Validate a CapAuth Bearer token for privileged endpoints.
|
|
509
509
|
|
|
510
|
-
Attempts CapAuth validation first (if
|
|
510
|
+
Attempts CapAuth validation first (if skcomms is installed), then falls
|
|
511
511
|
back to skcapstone signed token verification.
|
|
512
512
|
|
|
513
513
|
Args:
|
|
@@ -527,7 +527,7 @@ def _check_bearer(
|
|
|
527
527
|
config = _ctx.get("config")
|
|
528
528
|
|
|
529
529
|
try:
|
|
530
|
-
from
|
|
530
|
+
from skcomms.capauth_validator import CapAuthValidator
|
|
531
531
|
|
|
532
532
|
fingerprint = CapAuthValidator(require_auth=True).validate(token_str)
|
|
533
533
|
except ImportError:
|
|
@@ -828,23 +828,23 @@ async def get_dashboard(
|
|
|
828
828
|
"consciousness": m.consciousness.value if hasattr(m, "consciousness") else "",
|
|
829
829
|
"version": m.version,
|
|
830
830
|
}
|
|
831
|
-
except Exception:
|
|
832
|
-
|
|
831
|
+
except Exception as exc:
|
|
832
|
+
logger.warning("Failed to read agent identity from runtime manifest: %s", exc)
|
|
833
833
|
if not agent:
|
|
834
834
|
try:
|
|
835
835
|
identity_path = config.home / "identity" / "identity.json"
|
|
836
836
|
if identity_path.exists():
|
|
837
837
|
agent = json.loads(identity_path.read_text(encoding="utf-8"))
|
|
838
|
-
except Exception:
|
|
839
|
-
|
|
838
|
+
except Exception as exc:
|
|
839
|
+
logger.warning("Failed to read identity.json for API status: %s", exc)
|
|
840
840
|
|
|
841
841
|
# Consciousness stats
|
|
842
842
|
c_stats: Dict[str, Any] = {}
|
|
843
843
|
if consciousness:
|
|
844
844
|
try:
|
|
845
845
|
c_stats = dict(consciousness.stats)
|
|
846
|
-
except Exception:
|
|
847
|
-
|
|
846
|
+
except Exception as exc:
|
|
847
|
+
logger.warning("Failed to read consciousness stats: %s", exc)
|
|
848
848
|
|
|
849
849
|
# Recent conversations
|
|
850
850
|
conversations: List[Dict[str, Any]] = []
|
|
@@ -862,8 +862,8 @@ async def get_dashboard(
|
|
|
862
862
|
"last": last.get("timestamp"),
|
|
863
863
|
"preview": preview,
|
|
864
864
|
})
|
|
865
|
-
except Exception:
|
|
866
|
-
|
|
865
|
+
except Exception as exc:
|
|
866
|
+
logger.warning("Failed to list recent conversations for API status: %s", exc)
|
|
867
867
|
|
|
868
868
|
daemon_summary = DaemonSummary(
|
|
869
869
|
running=snap.get("running", True),
|
|
@@ -924,8 +924,8 @@ async def get_capstone(
|
|
|
924
924
|
m = runtime.manifest
|
|
925
925
|
agent = {"name": m.identity.name, "fingerprint": m.identity.fingerprint or ""}
|
|
926
926
|
pillars = {k: v.value for k, v in m.pillar_summary.items()}
|
|
927
|
-
except Exception:
|
|
928
|
-
|
|
927
|
+
except Exception as exc:
|
|
928
|
+
logger.warning("Failed to read agent pillars from runtime manifest: %s", exc)
|
|
929
929
|
|
|
930
930
|
# Memory stats
|
|
931
931
|
memory = MemorySummary()
|
|
@@ -940,8 +940,8 @@ async def get_capstone(
|
|
|
940
940
|
long_term=ms.long_term,
|
|
941
941
|
status=ms.status.value,
|
|
942
942
|
)
|
|
943
|
-
except Exception:
|
|
944
|
-
|
|
943
|
+
except Exception as exc:
|
|
944
|
+
logger.warning("Failed to collect memory stats for capstone API: %s", exc)
|
|
945
945
|
|
|
946
946
|
# Coordination board
|
|
947
947
|
board: Dict[str, Any] = {"summary": {}, "active": []}
|
|
@@ -970,16 +970,16 @@ async def get_capstone(
|
|
|
970
970
|
if v.status.value in ("in_progress", "claimed")
|
|
971
971
|
],
|
|
972
972
|
}
|
|
973
|
-
except Exception:
|
|
974
|
-
|
|
973
|
+
except Exception as exc:
|
|
974
|
+
logger.warning("Failed to collect coordination board data for capstone API: %s", exc)
|
|
975
975
|
|
|
976
976
|
# Consciousness stats
|
|
977
977
|
c_stats: Dict[str, Any] = {}
|
|
978
978
|
if consciousness:
|
|
979
979
|
try:
|
|
980
980
|
c_stats = dict(consciousness.stats)
|
|
981
|
-
except Exception:
|
|
982
|
-
|
|
981
|
+
except Exception as exc:
|
|
982
|
+
logger.warning("Failed to read consciousness stats for capstone API: %s", exc)
|
|
983
983
|
|
|
984
984
|
return CapstoneResponse(
|
|
985
985
|
agent=agent,
|
|
@@ -1027,8 +1027,8 @@ async def get_activity_stream(
|
|
|
1027
1027
|
try:
|
|
1028
1028
|
for chunk in _activity.get_history_encoded():
|
|
1029
1029
|
yield chunk
|
|
1030
|
-
except Exception:
|
|
1031
|
-
|
|
1030
|
+
except Exception as exc:
|
|
1031
|
+
logger.warning("Failed to replay activity stream history: %s", exc)
|
|
1032
1032
|
# Stream live events; yield keep-alive comments on timeout
|
|
1033
1033
|
try:
|
|
1034
1034
|
while True:
|
|
@@ -1100,8 +1100,8 @@ async def list_household_agents(
|
|
|
1100
1100
|
if identity_path.exists():
|
|
1101
1101
|
try:
|
|
1102
1102
|
entry["identity"] = json.loads(identity_path.read_text(encoding="utf-8"))
|
|
1103
|
-
except Exception:
|
|
1104
|
-
|
|
1103
|
+
except Exception as exc:
|
|
1104
|
+
logger.warning("Failed to read identity for agent %s: %s", agent_name, exc)
|
|
1105
1105
|
|
|
1106
1106
|
hb: Optional[Dict[str, Any]] = None
|
|
1107
1107
|
hb_path = heartbeats_dir / f"{agent_name.lower()}.json"
|
|
@@ -1112,7 +1112,8 @@ async def list_household_agents(
|
|
|
1112
1112
|
hb["alive"] = alive
|
|
1113
1113
|
entry["heartbeat"] = hb
|
|
1114
1114
|
entry["status"] = hb.get("status", "unknown") if alive else "stale"
|
|
1115
|
-
except Exception:
|
|
1115
|
+
except Exception as exc:
|
|
1116
|
+
logger.warning("Failed to read heartbeat for agent %s: %s", agent_name, exc)
|
|
1116
1117
|
entry["status"] = "unknown"
|
|
1117
1118
|
else:
|
|
1118
1119
|
entry["status"] = "no_heartbeat"
|
|
@@ -1174,8 +1175,8 @@ async def get_household_agent(
|
|
|
1174
1175
|
if identity_path.exists():
|
|
1175
1176
|
try:
|
|
1176
1177
|
entry["identity"] = json.loads(identity_path.read_text(encoding="utf-8"))
|
|
1177
|
-
except Exception:
|
|
1178
|
-
|
|
1178
|
+
except Exception as exc:
|
|
1179
|
+
logger.warning("Failed to read identity for agent %s: %s", name, exc)
|
|
1179
1180
|
|
|
1180
1181
|
hb_path = config.shared_root / "heartbeats" / f"{name.lower()}.json"
|
|
1181
1182
|
if hb_path.exists():
|
|
@@ -1185,8 +1186,8 @@ async def get_household_agent(
|
|
|
1185
1186
|
hb["alive"] = alive
|
|
1186
1187
|
entry["heartbeat"] = hb
|
|
1187
1188
|
entry["status"] = hb.get("status", "unknown") if alive else "stale"
|
|
1188
|
-
except Exception:
|
|
1189
|
-
|
|
1189
|
+
except Exception as exc:
|
|
1190
|
+
logger.warning("Failed to read heartbeat for agent %s: %s", name, exc)
|
|
1190
1191
|
|
|
1191
1192
|
# Memory count
|
|
1192
1193
|
memory_dir = agent_dir / "memory"
|
|
@@ -1249,8 +1250,8 @@ async def list_conversations(
|
|
|
1249
1250
|
last_message_preview=(last_content or "")[:120],
|
|
1250
1251
|
)
|
|
1251
1252
|
)
|
|
1252
|
-
except Exception:
|
|
1253
|
-
|
|
1253
|
+
except Exception as exc:
|
|
1254
|
+
logger.warning("Failed to read conversation file %s: %s", cf, exc)
|
|
1254
1255
|
return ConversationsResponse(conversations=conversations)
|
|
1255
1256
|
|
|
1256
1257
|
|
|
@@ -1329,7 +1330,7 @@ async def send_message(
|
|
|
1329
1330
|
) -> SendMessageResponse:
|
|
1330
1331
|
"""Send a message to a named peer.
|
|
1331
1332
|
|
|
1332
|
-
Writes the message envelope to the
|
|
1333
|
+
Writes the message envelope to the SKComms outbox for delivery by the
|
|
1333
1334
|
transport layer. If the consciousness loop is running the message is
|
|
1334
1335
|
also processed inline to generate a reply.
|
|
1335
1336
|
|
|
@@ -1534,8 +1535,8 @@ def _load_first_argocd_doc(path: Path) -> Optional[dict]:
|
|
|
1534
1535
|
"sync_policy": spec.get("syncPolicy", {}),
|
|
1535
1536
|
"manifest_file": path.name,
|
|
1536
1537
|
}
|
|
1537
|
-
except Exception:
|
|
1538
|
-
|
|
1538
|
+
except Exception as exc:
|
|
1539
|
+
logger.warning("Failed to parse ArgoCD manifest %s: %s", path, exc)
|
|
1539
1540
|
return None
|
|
1540
1541
|
|
|
1541
1542
|
|
|
@@ -1618,8 +1619,8 @@ def _get_argocd_status() -> dict:
|
|
|
1618
1619
|
"last_synced": (item_status.get("operationState") or {}).get("finishedAt"),
|
|
1619
1620
|
}
|
|
1620
1621
|
source = "yaml+kubectl"
|
|
1621
|
-
except Exception:
|
|
1622
|
-
|
|
1622
|
+
except Exception as exc:
|
|
1623
|
+
logger.warning("kubectl ArgoCD status query failed (using yaml only): %s", exc)
|
|
1623
1624
|
|
|
1624
1625
|
# ── Merge and build output ───────────────────────────────────────────────
|
|
1625
1626
|
apps = []
|
|
@@ -1737,7 +1738,7 @@ async def websocket_logs(
|
|
|
1737
1738
|
config = _ctx.get("config")
|
|
1738
1739
|
|
|
1739
1740
|
try:
|
|
1740
|
-
from
|
|
1741
|
+
from skcomms.capauth_validator import CapAuthValidator
|
|
1741
1742
|
|
|
1742
1743
|
fingerprint = CapAuthValidator(require_auth=True).validate(token_str)
|
|
1743
1744
|
except ImportError:
|
|
@@ -1767,8 +1768,8 @@ async def websocket_logs(
|
|
|
1767
1768
|
tail_lines = list(deque(fh, maxlen=50))
|
|
1768
1769
|
for line in tail_lines:
|
|
1769
1770
|
await websocket.send_json({"type": "line", "line": line.rstrip("\n")})
|
|
1770
|
-
except Exception:
|
|
1771
|
-
|
|
1771
|
+
except Exception as exc:
|
|
1772
|
+
logger.warning("Failed to replay log tail history over websocket: %s", exc)
|
|
1772
1773
|
|
|
1773
1774
|
# Tail the log file and stream new lines
|
|
1774
1775
|
try:
|
|
@@ -1783,8 +1784,8 @@ async def websocket_logs(
|
|
|
1783
1784
|
for ln in chunk.splitlines():
|
|
1784
1785
|
await websocket.send_json({"type": "line", "line": ln})
|
|
1785
1786
|
offset = fh.tell()
|
|
1786
|
-
except Exception:
|
|
1787
|
-
|
|
1787
|
+
except Exception as exc:
|
|
1788
|
+
logger.warning("Log tail websocket read error: %s", exc)
|
|
1788
1789
|
await asyncio.sleep(0.5)
|
|
1789
1790
|
except WebSocketDisconnect:
|
|
1790
1791
|
pass
|
|
@@ -95,7 +95,8 @@ def _collect_local_bid(task_id: str, agent_name: str, shared_root: Path) -> Auct
|
|
|
95
95
|
import psutil # optional dep
|
|
96
96
|
|
|
97
97
|
cpu = psutil.cpu_percent(interval=0.1)
|
|
98
|
-
except Exception:
|
|
98
|
+
except Exception as e:
|
|
99
|
+
logger.warning("auction.py: %s", e)
|
|
99
100
|
cpu = 0.0
|
|
100
101
|
|
|
101
102
|
# Claimed-tasks count from agent file
|
|
@@ -107,8 +108,8 @@ def _collect_local_bid(task_id: str, agent_name: str, shared_root: Path) -> Auct
|
|
|
107
108
|
agent_file = board.load_agent(agent_name)
|
|
108
109
|
if agent_file:
|
|
109
110
|
claimed_count = len(agent_file.claimed_tasks)
|
|
110
|
-
except Exception:
|
|
111
|
-
|
|
111
|
+
except Exception as exc:
|
|
112
|
+
logger.warning("Failed to read claimed task count for agent %s: %s", agent_name, exc)
|
|
112
113
|
|
|
113
114
|
return AuctionBid(
|
|
114
115
|
task_id=task_id,
|
|
@@ -283,8 +284,8 @@ class AuctionManager:
|
|
|
283
284
|
ttl_seconds=3600,
|
|
284
285
|
tags=["auction", "no_bidders"],
|
|
285
286
|
)
|
|
286
|
-
except Exception:
|
|
287
|
-
|
|
287
|
+
except Exception as exc:
|
|
288
|
+
logger.warning("Failed to publish auction_no_bidders event for %s: %s", task_id, exc)
|
|
288
289
|
return None
|
|
289
290
|
|
|
290
291
|
winner_bid = min(record.bids, key=_load_score)
|
|
@@ -327,8 +328,8 @@ class AuctionManager:
|
|
|
327
328
|
ttl_seconds=3600,
|
|
328
329
|
tags=["auction", "resolved"],
|
|
329
330
|
)
|
|
330
|
-
except Exception:
|
|
331
|
-
|
|
331
|
+
except Exception as exc:
|
|
332
|
+
logger.warning("Failed to publish auction_resolved event for %s: %s", task_id, exc)
|
|
332
333
|
|
|
333
334
|
# Notify activity stream
|
|
334
335
|
try:
|
|
@@ -338,8 +339,8 @@ class AuctionManager:
|
|
|
338
339
|
"task.auction_resolved",
|
|
339
340
|
{"task_id": task_id, "winner": winner, "bids": len(record.bids)},
|
|
340
341
|
)
|
|
341
|
-
except Exception:
|
|
342
|
-
|
|
342
|
+
except Exception as exc:
|
|
343
|
+
logger.warning("Failed to push auction_resolved activity for %s: %s", task_id, exc)
|
|
343
344
|
|
|
344
345
|
return winner
|
|
345
346
|
|
|
@@ -361,7 +362,8 @@ class AuctionManager:
|
|
|
361
362
|
key=lambda p: p.stat().st_mtime,
|
|
362
363
|
reverse=True,
|
|
363
364
|
)[:limit]
|
|
364
|
-
except Exception:
|
|
365
|
+
except Exception as e:
|
|
366
|
+
logger.warning("auction.py: %s", e)
|
|
365
367
|
paths = []
|
|
366
368
|
|
|
367
369
|
for path in paths:
|
|
@@ -371,7 +373,8 @@ class AuctionManager:
|
|
|
371
373
|
json.loads(path.read_text(encoding="utf-8"))
|
|
372
374
|
)
|
|
373
375
|
)
|
|
374
|
-
except Exception:
|
|
376
|
+
except Exception as e:
|
|
377
|
+
logger.warning("auction.py: %s", e)
|
|
375
378
|
continue
|
|
376
379
|
|
|
377
380
|
return {
|
package/src/skcapstone/backup.py
CHANGED
|
@@ -183,8 +183,8 @@ class BlueprintRegistryClient:
|
|
|
183
183
|
error_body = ""
|
|
184
184
|
try:
|
|
185
185
|
error_body = exc.read().decode("utf-8", errors="replace")
|
|
186
|
-
except Exception:
|
|
187
|
-
|
|
186
|
+
except Exception as read_exc:
|
|
187
|
+
logger.debug("Failed to read error body from registry HTTP error: %s", read_exc)
|
|
188
188
|
msg = f"Registry API error {exc.code} for {method} {path}"
|
|
189
189
|
if error_body:
|
|
190
190
|
msg += f": {error_body[:500]}"
|
|
@@ -406,7 +406,8 @@ def _fetch_github_blueprints(query: str = "") -> Optional[list[dict[str, Any]]]:
|
|
|
406
406
|
)
|
|
407
407
|
with urllib.request.urlopen(cat_req, timeout=10) as resp:
|
|
408
408
|
files = json.loads(resp.read().decode("utf-8"))
|
|
409
|
-
except Exception:
|
|
409
|
+
except Exception as e:
|
|
410
|
+
logger.warning("blueprint_registry.py: %s", e)
|
|
410
411
|
continue
|
|
411
412
|
|
|
412
413
|
for file_entry in files:
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Brain-First Protocol — think before you act.
|
|
3
|
+
|
|
4
|
+
Before an agent acts on any task, it consults its memory to see if it
|
|
5
|
+
already knows something relevant. This avoids redundant work, surfaces
|
|
6
|
+
prior decisions, and grounds the agent in its own experience.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
from skcapstone.brain_first import brain_first_check
|
|
10
|
+
|
|
11
|
+
result = brain_first_check("deploy the monitoring stack")
|
|
12
|
+
if result.has_memories:
|
|
13
|
+
# use result.memories as additional context
|
|
14
|
+
...
|
|
15
|
+
|
|
16
|
+
Configuration (config.yaml):
|
|
17
|
+
brain_first:
|
|
18
|
+
enabled: true # master toggle (default: true)
|
|
19
|
+
max_results: 5 # how many memories to surface (default: 5)
|
|
20
|
+
min_importance: 0.3 # ignore low-importance memories (default: 0.3)
|
|
21
|
+
auto_inject: false # auto-prepend memories to MCP tool responses (default: false)
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import logging
|
|
27
|
+
import os
|
|
28
|
+
import re
|
|
29
|
+
from dataclasses import dataclass, field
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
from typing import Optional
|
|
32
|
+
|
|
33
|
+
logger = logging.getLogger("skcapstone.brain_first")
|
|
34
|
+
|
|
35
|
+
# Stop-words to strip from queries before searching memory
|
|
36
|
+
_STOP_WORDS = frozenset({
|
|
37
|
+
"a", "an", "the", "is", "are", "was", "were", "be", "been", "being",
|
|
38
|
+
"have", "has", "had", "do", "does", "did", "will", "would", "shall",
|
|
39
|
+
"should", "may", "might", "must", "can", "could", "to", "of", "in",
|
|
40
|
+
"for", "on", "with", "at", "by", "from", "as", "into", "through",
|
|
41
|
+
"during", "before", "after", "above", "below", "between", "out",
|
|
42
|
+
"off", "over", "under", "again", "further", "then", "once", "here",
|
|
43
|
+
"there", "when", "where", "why", "how", "all", "each", "every",
|
|
44
|
+
"both", "few", "more", "most", "other", "some", "such", "no", "nor",
|
|
45
|
+
"not", "only", "own", "same", "so", "than", "too", "very", "just",
|
|
46
|
+
"because", "but", "and", "or", "if", "while", "about", "up", "it",
|
|
47
|
+
"its", "this", "that", "these", "those", "i", "me", "my", "we",
|
|
48
|
+
"our", "you", "your", "he", "him", "his", "she", "her", "they",
|
|
49
|
+
"them", "their", "what", "which", "who", "whom",
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass
|
|
54
|
+
class BrainFirstConfig:
|
|
55
|
+
"""Configuration for the brain-first protocol."""
|
|
56
|
+
|
|
57
|
+
enabled: bool = True
|
|
58
|
+
max_results: int = 5
|
|
59
|
+
min_importance: float = 0.3
|
|
60
|
+
auto_inject: bool = False
|
|
61
|
+
|
|
62
|
+
@classmethod
|
|
63
|
+
def from_dict(cls, data: dict) -> "BrainFirstConfig":
|
|
64
|
+
"""Create config from a dict (e.g. from config.yaml brain_first section)."""
|
|
65
|
+
return cls(
|
|
66
|
+
enabled=data.get("enabled", True),
|
|
67
|
+
max_results=data.get("max_results", 5),
|
|
68
|
+
min_importance=data.get("min_importance", 0.3),
|
|
69
|
+
auto_inject=data.get("auto_inject", False),
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@dataclass
|
|
74
|
+
class BrainFirstResult:
|
|
75
|
+
"""Result of a brain-first memory consultation."""
|
|
76
|
+
|
|
77
|
+
query: str
|
|
78
|
+
keywords: list[str]
|
|
79
|
+
memories: list[dict] = field(default_factory=list)
|
|
80
|
+
enabled: bool = True
|
|
81
|
+
error: Optional[str] = None
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def has_memories(self) -> bool:
|
|
85
|
+
"""Whether any relevant memories were found."""
|
|
86
|
+
return len(self.memories) > 0
|
|
87
|
+
|
|
88
|
+
def as_context(self) -> str:
|
|
89
|
+
"""Format memories as a context block for injection into prompts."""
|
|
90
|
+
if not self.has_memories:
|
|
91
|
+
return ""
|
|
92
|
+
lines = ["[Brain-First: relevant memories found]"]
|
|
93
|
+
for i, mem in enumerate(self.memories, 1):
|
|
94
|
+
content = mem.get("content", "")[:200]
|
|
95
|
+
layer = mem.get("layer", "?")
|
|
96
|
+
importance = mem.get("importance", 0)
|
|
97
|
+
tags = ", ".join(mem.get("tags", []))
|
|
98
|
+
lines.append(
|
|
99
|
+
f" {i}. [{layer}|imp={importance:.1f}] {content}"
|
|
100
|
+
+ (f" tags: {tags}" if tags else "")
|
|
101
|
+
)
|
|
102
|
+
return "\n".join(lines)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def extract_keywords(text: str) -> list[str]:
|
|
106
|
+
"""Extract meaningful keywords from a text string.
|
|
107
|
+
|
|
108
|
+
Strips stop-words and short tokens, keeping domain-relevant terms.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
text: Input text (task title, prompt, etc.).
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
List of unique keywords, longest first.
|
|
115
|
+
"""
|
|
116
|
+
# Lowercase, split on non-alphanumeric
|
|
117
|
+
tokens = re.split(r"[^a-zA-Z0-9_-]+", text.lower())
|
|
118
|
+
# Filter: no stop-words, no short tokens
|
|
119
|
+
keywords = list(dict.fromkeys(
|
|
120
|
+
t for t in tokens if t and t not in _STOP_WORDS and len(t) > 2
|
|
121
|
+
))
|
|
122
|
+
# Sort longest first (longer terms tend to be more specific)
|
|
123
|
+
keywords.sort(key=len, reverse=True)
|
|
124
|
+
return keywords
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _load_config() -> BrainFirstConfig:
|
|
128
|
+
"""Load brain-first config from the agent's config.yaml.
|
|
129
|
+
|
|
130
|
+
Falls back to defaults if the file or section is missing.
|
|
131
|
+
"""
|
|
132
|
+
try:
|
|
133
|
+
import yaml
|
|
134
|
+
except ImportError:
|
|
135
|
+
return BrainFirstConfig()
|
|
136
|
+
|
|
137
|
+
from . import AGENT_HOME, SKCAPSTONE_AGENT
|
|
138
|
+
|
|
139
|
+
for base in [
|
|
140
|
+
Path(AGENT_HOME).expanduser() / "agents" / SKCAPSTONE_AGENT,
|
|
141
|
+
Path(AGENT_HOME).expanduser(),
|
|
142
|
+
]:
|
|
143
|
+
config_file = base / "config" / "config.yaml"
|
|
144
|
+
if config_file.exists():
|
|
145
|
+
try:
|
|
146
|
+
data = yaml.safe_load(config_file.read_text(encoding="utf-8")) or {}
|
|
147
|
+
bf_data = data.get("brain_first", {})
|
|
148
|
+
if bf_data:
|
|
149
|
+
return BrainFirstConfig.from_dict(bf_data)
|
|
150
|
+
except Exception as exc:
|
|
151
|
+
logger.debug("Failed to load brain_first config from %s: %s", config_file, exc)
|
|
152
|
+
|
|
153
|
+
return BrainFirstConfig()
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def brain_first_check(
|
|
157
|
+
context: str,
|
|
158
|
+
config: Optional[BrainFirstConfig] = None,
|
|
159
|
+
tags: Optional[list[str]] = None,
|
|
160
|
+
) -> BrainFirstResult:
|
|
161
|
+
"""Consult memory before acting on a task.
|
|
162
|
+
|
|
163
|
+
This is the core brain-first function. Given a task description or
|
|
164
|
+
prompt context, it extracts keywords, searches memory, and returns
|
|
165
|
+
any relevant memories that the agent should consider.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
context: The task description, prompt, or action context.
|
|
169
|
+
config: Override config (uses agent config.yaml if None).
|
|
170
|
+
tags: Optional tag filter for the memory search.
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
BrainFirstResult with any relevant memories.
|
|
174
|
+
"""
|
|
175
|
+
if config is None:
|
|
176
|
+
config = _load_config()
|
|
177
|
+
|
|
178
|
+
keywords = extract_keywords(context)
|
|
179
|
+
result = BrainFirstResult(
|
|
180
|
+
query=context,
|
|
181
|
+
keywords=keywords,
|
|
182
|
+
enabled=config.enabled,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
if not config.enabled:
|
|
186
|
+
result.error = "brain-first protocol disabled"
|
|
187
|
+
return result
|
|
188
|
+
|
|
189
|
+
if not keywords:
|
|
190
|
+
result.error = "no meaningful keywords extracted"
|
|
191
|
+
return result
|
|
192
|
+
|
|
193
|
+
# Build a search query from top keywords (limit to 6 to avoid noise)
|
|
194
|
+
search_query = " ".join(keywords[:6])
|
|
195
|
+
|
|
196
|
+
try:
|
|
197
|
+
from .memory_engine import search as memory_search
|
|
198
|
+
from .mcp_tools._helpers import _home
|
|
199
|
+
|
|
200
|
+
home = _home()
|
|
201
|
+
entries = memory_search(
|
|
202
|
+
home=home,
|
|
203
|
+
query=search_query,
|
|
204
|
+
tags=tags,
|
|
205
|
+
limit=config.max_results * 2, # over-fetch, then filter
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
# Filter by minimum importance
|
|
209
|
+
entries = [e for e in entries if e.importance >= config.min_importance]
|
|
210
|
+
|
|
211
|
+
# Truncate to max_results
|
|
212
|
+
entries = entries[:config.max_results]
|
|
213
|
+
|
|
214
|
+
result.memories = [
|
|
215
|
+
{
|
|
216
|
+
"memory_id": e.memory_id,
|
|
217
|
+
"content": e.content[:300],
|
|
218
|
+
"layer": e.layer.value,
|
|
219
|
+
"tags": e.tags,
|
|
220
|
+
"importance": e.importance,
|
|
221
|
+
"access_count": e.access_count,
|
|
222
|
+
"source": e.source,
|
|
223
|
+
}
|
|
224
|
+
for e in entries
|
|
225
|
+
]
|
|
226
|
+
|
|
227
|
+
logger.info(
|
|
228
|
+
"Brain-first check: %d memories found for %d keywords from '%s'",
|
|
229
|
+
len(result.memories),
|
|
230
|
+
len(keywords),
|
|
231
|
+
context[:80],
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
except Exception as exc:
|
|
235
|
+
result.error = f"memory search failed: {exc}"
|
|
236
|
+
logger.warning("Brain-first check failed: %s", exc)
|
|
237
|
+
|
|
238
|
+
return result
|
|
@@ -21,7 +21,7 @@ from .coordination import Board, TaskStatus
|
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
TAG_CATEGORIES = {
|
|
24
|
-
"feature": ["skcapstone", "skchat", "
|
|
24
|
+
"feature": ["skcapstone", "skchat", "skcomms", "skmemory", "capauth", "cloud9", "skworld"],
|
|
25
25
|
"security": ["security", "encryption", "capauth", "integrity", "quantum-resistant", "pgp"],
|
|
26
26
|
"infrastructure": ["ci", "docker", "systemd", "daemon", "syncthing", "pypi", "packaging"],
|
|
27
27
|
"documentation": ["documentation", "docs", "readme", "quickstart", "api"],
|