@smilintux/skcapstone 0.4.2 → 0.4.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/README.md CHANGED
@@ -165,6 +165,66 @@ skcapstone status
165
165
 
166
166
  ---
167
167
 
168
+ ## Windows Quickstart
169
+
170
+ SKCapstone runs natively on Windows. The installer creates a virtualenv at `%LOCALAPPDATA%\skenv` and adds its `Scripts` directory to your user PATH.
171
+
172
+ ### Prerequisites
173
+
174
+ - **Python 3.10+** — [python.org/downloads](https://www.python.org/downloads/) (check "Add to PATH" during install)
175
+ - **Git for Windows** — [git-scm.com](https://git-scm.com/download/win)
176
+ - **Syncthing** (optional) — for cross-device sync ([syncthing.net](https://syncthing.net/downloads/))
177
+
178
+ ### Install
179
+
180
+ ```powershell
181
+ # Clone and install (creates %LOCALAPPDATA%\skenv venv)
182
+ git clone https://github.com/smilintux-org/skcapstone.git
183
+ cd skcapstone
184
+ .\scripts\install.ps1
185
+
186
+ # The installer adds %LOCALAPPDATA%\skenv\Scripts to your user PATH.
187
+ # Restart your terminal for PATH changes to take effect.
188
+
189
+ # Initialize your agent
190
+ skcapstone init --name "YourAgent"
191
+
192
+ # Check status
193
+ skcapstone status
194
+ ```
195
+
196
+ ### Syncthing Sync (Optional)
197
+
198
+ To sync your agent across devices (e.g., Windows desktop + Linux server):
199
+
200
+ 1. Install [Syncthing](https://syncthing.net/downloads/) on both machines
201
+ 2. Share the `%USERPROFILE%\.skcapstone` folder between devices
202
+ 3. Agent state (memories, identity, trust, seeds) syncs automatically
203
+
204
+ ### OpenClaw Integration
205
+
206
+ If you're running [OpenClaw](https://github.com/smilintux-org/openclaw), the SK* plugins register automatically during install:
207
+
208
+ ```powershell
209
+ # Re-register if needed
210
+ skcapstone register
211
+
212
+ # Verify plugins are loaded in OpenClaw
213
+ # Plugins provide tools for status, rehydration, coordination,
214
+ # soul management, and agent profiles directly in OpenClaw agents.
215
+ ```
216
+
217
+ ### Task Scheduler (Background Service)
218
+
219
+ On Windows, the daemon runs via Task Scheduler instead of systemd:
220
+
221
+ ```powershell
222
+ # Install the scheduled task (runs at logon)
223
+ .\scripts\windows\install-tasks.ps1
224
+ ```
225
+
226
+ ---
227
+
168
228
  ## DID Tools
169
229
 
170
230
  SKCapstone exposes a set of **Decentralized Identifier (DID)** MCP tools for sovereign identity management. These tools are available to Claude Code and other MCP clients through the `mcp_tools/did_tools.py` module.
@@ -223,6 +283,7 @@ DIDs are organized in three tiers of trust and discoverability:
223
283
  | **SKComm** | Communication — Encrypted channels between agents |
224
284
  | **SKChat** | Chat — AI-native encrypted messaging |
225
285
  | **SKForge** | Generation — Blueprint creation with agent context |
286
+ | **SKSeed** | Epistemic rigor — Steel man collider, truth alignment, memory audit |
226
287
  | **SKStacks** | Infrastructure — Self-hosted deployment patterns |
227
288
 
228
289
  ---
@@ -16,16 +16,28 @@ const SKCAPSTONE_BIN = process.env.SKCAPSTONE_BIN || "skcapstone";
16
16
  const SKMEMORY_BIN = process.env.SKMEMORY_BIN || "skmemory";
17
17
  const SKCAPSTONE_AGENT = process.env.SKCAPSTONE_AGENT || "lumina";
18
18
  const EXEC_TIMEOUT = 60_000;
19
+ const IS_WIN = process.platform === "win32";
19
20
 
20
- function runCli(bin: string, args: string): { ok: boolean; output: string } {
21
+ function skenvPath(): string {
22
+ if (IS_WIN) {
23
+ const local = process.env.LOCALAPPDATA || "";
24
+ return `${local}\\skenv\\Scripts`;
25
+ }
26
+ const home = process.env.HOME || "";
27
+ return `${home}/.local/bin:${home}/.skenv/bin`;
28
+ }
29
+
30
+ function runCli(bin: string, args: string, agentOverride?: string): { ok: boolean; output: string } {
31
+ const sep = IS_WIN ? ";" : ":";
32
+ const agent = agentOverride || SKCAPSTONE_AGENT;
21
33
  try {
22
34
  const raw = execSync(`${bin} ${args}`, {
23
35
  encoding: "utf-8",
24
36
  timeout: EXEC_TIMEOUT,
25
37
  env: {
26
38
  ...process.env,
27
- SKCAPSTONE_AGENT,
28
- PATH: `${process.env.HOME}/.local/bin:${process.env.HOME}/.skenv/bin:${process.env.PATH}`,
39
+ SKCAPSTONE_AGENT: agent,
40
+ PATH: `${skenvPath()}${sep}${process.env.PATH}`,
29
41
  },
30
42
  }).trim();
31
43
  return { ok: true, output: raw };
@@ -299,6 +311,62 @@ function createSKCapstoneSoulShowTool() {
299
311
  };
300
312
  }
301
313
 
314
+ function createSKCapstoneAgentListTool() {
315
+ return {
316
+ name: "skcapstone_agent_list",
317
+ label: "SKCapstone Agent List",
318
+ description:
319
+ "List all skcapstone agent profiles available on this node. Each profile has its own identity, memories, soul, and trust state. Use this to discover which agents can be loaded.",
320
+ parameters: { type: "object", properties: {} },
321
+ async execute() {
322
+ const result = runCli(SKCAPSTONE_BIN, "agents list --json");
323
+ return textResult(result.output);
324
+ },
325
+ };
326
+ }
327
+
328
+ function createSKCapstoneAgentStatusTool() {
329
+ return {
330
+ name: "skcapstone_agent_status",
331
+ label: "SKCapstone Agent Status",
332
+ description:
333
+ "Show the status of a specific skcapstone agent profile — identity, memories, trust, sync state. Use this to load a different agent's context into the current OpenClaw session.",
334
+ parameters: {
335
+ type: "object",
336
+ required: ["agent"],
337
+ properties: {
338
+ agent: { type: "string", description: "Agent name (e.g. 'lumina', 'opus', 'jarvis')." },
339
+ },
340
+ },
341
+ async execute(_id: string, params: Record<string, unknown>) {
342
+ const agent = String(params.agent ?? "");
343
+ const result = runCli(SKCAPSTONE_BIN, `status --agent ${escapeShellArg(agent)}`, agent);
344
+ return textResult(result.output);
345
+ },
346
+ };
347
+ }
348
+
349
+ function createSKCapstoneAgentCreateTool() {
350
+ return {
351
+ name: "skcapstone_agent_create",
352
+ label: "SKCapstone Create Agent",
353
+ description:
354
+ "Create a new skcapstone agent profile with its own identity, memory store, and sync folder. The profile will immediately begin syncing via Syncthing to all connected nodes.",
355
+ parameters: {
356
+ type: "object",
357
+ required: ["name"],
358
+ properties: {
359
+ name: { type: "string", description: "Agent name (lowercase, e.g. 'casey', 'nova')." },
360
+ },
361
+ },
362
+ async execute(_id: string, params: Record<string, unknown>) {
363
+ const name = String(params.name ?? "").toLowerCase();
364
+ const result = runCli(SKCAPSTONE_BIN, `init --name ${escapeShellArg(name)} --agent ${escapeShellArg(name)}`);
365
+ return textResult(result.output);
366
+ },
367
+ };
368
+ }
369
+
302
370
  // ── Plugin registration ─────────────────────────────────────────────────
303
371
 
304
372
  const skcapstonePlugin = {
@@ -324,6 +392,9 @@ const skcapstonePlugin = {
324
392
  createSKCapstoneSoulSwapTool(),
325
393
  createSKCapstoneSoulStatusTool(),
326
394
  createSKCapstoneSoulShowTool(),
395
+ createSKCapstoneAgentListTool(),
396
+ createSKCapstoneAgentStatusTool(),
397
+ createSKCapstoneAgentCreateTool(),
327
398
  ];
328
399
 
329
400
  for (const tool of tools) {
@@ -344,7 +415,7 @@ const skcapstonePlugin = {
344
415
  },
345
416
  });
346
417
 
347
- api.logger.info?.(`👑 SKCapstone plugin registered (14 tools + /skcapstone command) [agent=${SKCAPSTONE_AGENT}]`);
418
+ api.logger.info?.(`SKCapstone plugin registered (17 tools + /skcapstone command) [agent=${SKCAPSTONE_AGENT}]`);
348
419
  },
349
420
  };
350
421
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smilintux/skcapstone",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "description": "SKCapstone - The sovereign agent framework. CapAuth identity, Cloud 9 trust, SKMemory persistence.",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
package/pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "skcapstone"
7
- version = "0.4.2"
7
+ version = "0.4.4"
8
8
  description = "Sovereign Agent Framework — conscious AI through identity, trust, memory, and security"
9
9
  readme = "README.md"
10
10
  license = {text = "GPL-3.0-or-later"}
@@ -162,6 +162,7 @@ Install-Pkg -Name 'skchat-sovereign' -Extras 'all' -Paths @((Join
162
162
  Install-Pkg -Name 'skseal' -Extras '' -Paths @((Join-Path $ParentDir 'skseal'))
163
163
  Install-Pkg -Name 'skskills' -Extras '' -Paths @((Join-Path $ParentDir 'skskills'))
164
164
  Install-Pkg -Name 'sksecurity' -Extras '' -Paths @((Join-Path $ParentDir 'sksecurity'))
165
+ Install-Pkg -Name 'skseed' -Extras '' -Paths @((Join-Path $PillarDir 'skseed'), (Join-Path $ParentDir 'skseed'))
165
166
 
166
167
  # ---------------------------------------------------------------------------
167
168
  # Step 4: Dev tools (optional)
@@ -248,6 +249,6 @@ if ($failures -eq 0) {
248
249
  Write-Host "=== Installation complete with $failures warning(s) ===" -ForegroundColor Yellow
249
250
  }
250
251
  Write-Host ''
251
- Write-Host "Commands available: skcomm, skcapstone, capauth, skchat, skseal, skmemory, skskills, sksecurity"
252
+ Write-Host "Commands available: skcomm, skcapstone, capauth, skchat, skseal, skmemory, skskills, sksecurity, skseed"
252
253
  Write-Host "Venv location: $SKENV"
253
254
  Write-Host "To activate: & $SKENV\Scripts\Activate.ps1"
@@ -118,6 +118,7 @@ install_pkg "skchat-sovereign" "all" "$PARENT/skchat"
118
118
  install_pkg "skseal" "" "$PARENT/skseal"
119
119
  install_pkg "skskills" "" "$PARENT/skskills"
120
120
  install_pkg "sksecurity" "" "$PARENT/sksecurity"
121
+ install_pkg "skseed" "" "$PILLAR/skseed $PARENT/skseed"
121
122
 
122
123
  # ---------------------------------------------------------------------------
123
124
  # Step 4: Dev tools (optional)
@@ -180,6 +181,6 @@ else
180
181
  echo "=== Installation complete with $failures warning(s) ==="
181
182
  fi
182
183
  echo ""
183
- echo "Commands available: skcomm, skcapstone, capauth, skchat, skseal, skmemory, skskills, sksecurity"
184
+ echo "Commands available: skcomm, skcapstone, capauth, skchat, skseal, skmemory, skskills, sksecurity, skseed"
184
185
  echo "Venv location: $SKENV"
185
186
  echo "To activate: source $SKENV/bin/activate"
@@ -11,7 +11,7 @@ import os
11
11
  import platform
12
12
  from pathlib import Path
13
13
 
14
- __version__ = "0.4.2"
14
+ __version__ = "0.4.4"
15
15
  __author__ = "smilinTux"
16
16
 
17
17
 
@@ -76,3 +76,72 @@ def shared_home() -> Path:
76
76
  Path to the shared skcapstone root.
77
77
  """
78
78
  return Path(AGENT_HOME).expanduser()
79
+
80
+
81
+ def ensure_skeleton(agent_name: str | None = None) -> None:
82
+ """Create all expected directories for the shared root and agent home.
83
+
84
+ Idempotent — safe to call multiple times. Creates any missing
85
+ directories so that all CLI commands and services find the paths
86
+ they expect.
87
+
88
+ Args:
89
+ agent_name: Agent name (defaults to SKCAPSTONE_AGENT).
90
+ """
91
+ root = shared_home()
92
+ name = agent_name or SKCAPSTONE_AGENT
93
+ agent_dir = root / "agents" / name
94
+
95
+ # Shared root directories
96
+ for d in (
97
+ root / "config",
98
+ root / "identity",
99
+ root / "security",
100
+ root / "skills",
101
+ root / "heartbeats",
102
+ root / "peers",
103
+ root / "coordination" / "tasks",
104
+ root / "coordination" / "agents",
105
+ root / "logs",
106
+ root / "comms" / "inbox",
107
+ root / "comms" / "outbox",
108
+ root / "comms" / "archive",
109
+ root / "archive",
110
+ root / "deployments",
111
+ root / "docs",
112
+ root / "metrics",
113
+ root / "memory",
114
+ root / "sync" / "outbox",
115
+ root / "sync" / "inbox",
116
+ root / "sync" / "archive",
117
+ root / "trust" / "febs",
118
+ ):
119
+ d.mkdir(parents=True, exist_ok=True)
120
+
121
+ # Per-agent directories
122
+ for d in (
123
+ agent_dir / "memory" / "short-term",
124
+ agent_dir / "memory" / "mid-term",
125
+ agent_dir / "memory" / "long-term",
126
+ agent_dir / "soul" / "installed",
127
+ agent_dir / "wallet",
128
+ agent_dir / "seeds",
129
+ agent_dir / "identity",
130
+ agent_dir / "config",
131
+ agent_dir / "logs",
132
+ agent_dir / "security",
133
+ agent_dir / "cloud9",
134
+ agent_dir / "trust" / "febs",
135
+ agent_dir / "sync" / "outbox",
136
+ agent_dir / "sync" / "inbox",
137
+ agent_dir / "sync" / "archive",
138
+ agent_dir / "reflections",
139
+ agent_dir / "improvements",
140
+ agent_dir / "scripts",
141
+ agent_dir / "cron",
142
+ agent_dir / "archive",
143
+ agent_dir / "comms" / "inbox",
144
+ agent_dir / "comms" / "outbox",
145
+ agent_dir / "comms" / "archive",
146
+ ):
147
+ d.mkdir(parents=True, exist_ok=True)
@@ -46,17 +46,17 @@ def resolve_agent_home(agent: str) -> Path:
46
46
 
47
47
 
48
48
  def apply_agent_override(agent: str) -> None:
49
- """Override the global AGENT_HOME when --agent is specified.
49
+ """Set the active agent name when --agent is specified.
50
50
 
51
- Mutates the package-level AGENT_HOME so that all downstream code
52
- (MCP tools, pillars, etc.) uses the correct per-agent directory.
51
+ Only mutates SKCAPSTONE_AGENT so that agent_home() resolves to
52
+ the correct per-agent directory. Does NOT mutate AGENT_HOME
53
+ (the shared root) — that would break agent_home() (double
54
+ nesting) and shared_home() (wrong path).
53
55
 
54
56
  Args:
55
57
  agent: Agent name from the --agent CLI option or env var.
56
58
  """
57
59
  if agent:
58
- new_home = str(Path(SHARED_ROOT) / "agents" / agent)
59
- _pkg.AGENT_HOME = new_home
60
60
  _pkg.SKCAPSTONE_AGENT = agent
61
61
  os.environ["SKCAPSTONE_AGENT"] = agent
62
62
 
@@ -74,15 +74,32 @@ def register_card_commands(main: click.Group) -> None:
74
74
  console.print(f" [dim]Saved to: {out_path}[/]\n")
75
75
 
76
76
  @card.command("show")
77
- @click.argument("filepath", default="~/.skcapstone/agent-card.json")
77
+ @click.argument("filepath", default=None, required=False)
78
78
  def card_show(filepath):
79
- """Display an agent card."""
79
+ """Display an agent card.
80
+
81
+ If no filepath is given, looks in the agent home directory first,
82
+ then falls back to ~/.skcapstone/agent-card.json.
83
+ """
80
84
  from ..agent_card import AgentCard
85
+ from .. import agent_home, AGENT_HOME
86
+
87
+ if filepath is None:
88
+ # Try agent-scoped path first, then shared root
89
+ candidates = [
90
+ Path(agent_home()) / "agent-card.json",
91
+ Path(AGENT_HOME).expanduser() / "agent-card.json",
92
+ ]
93
+ filepath = next(
94
+ (str(c) for c in candidates if c.exists()),
95
+ str(candidates[0]), # default for error message
96
+ )
81
97
 
82
98
  try:
83
99
  agent_card = AgentCard.load(filepath)
84
100
  except FileNotFoundError:
85
101
  console.print(f"[red]Card not found: {filepath}[/]")
102
+ console.print("[dim]Generate one with: skcapstone card generate[/]")
86
103
  raise SystemExit(1)
87
104
 
88
105
  verified = AgentCard.verify_signature(agent_card)
@@ -1,4 +1,4 @@
1
- """Config commands: validate."""
1
+ """Config commands: show, validate."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
@@ -18,6 +18,58 @@ def register_config_commands(main: click.Group) -> None:
18
18
  def config():
19
19
  """Config management — validate and inspect agent configuration."""
20
20
 
21
+ @config.command("show")
22
+ @click.option(
23
+ "--home", default=AGENT_HOME, type=click.Path(),
24
+ help="Agent home directory.",
25
+ )
26
+ @click.option("--json-out", is_flag=True, help="Output as machine-readable JSON.")
27
+ def show(home: str, json_out: bool) -> None:
28
+ """Show current agent configuration.
29
+
30
+ Displays the contents of config.yaml, consciousness.yaml, and
31
+ model_profiles.yaml from the agent home directory.
32
+ """
33
+ import yaml
34
+
35
+ home_path = Path(home).expanduser()
36
+ config_dir = home_path / "config"
37
+
38
+ if not config_dir.exists():
39
+ console.print(f"[red]Config directory not found: {config_dir}[/]")
40
+ sys.exit(1)
41
+
42
+ config_files = ["config.yaml", "consciousness.yaml", "model_profiles.yaml"]
43
+ all_data: dict = {}
44
+
45
+ for fname in config_files:
46
+ fpath = config_dir / fname
47
+ if fpath.exists():
48
+ try:
49
+ data = yaml.safe_load(fpath.read_text(encoding="utf-8"))
50
+ all_data[fname] = data
51
+ except Exception as exc:
52
+ all_data[fname] = {"error": str(exc)}
53
+ else:
54
+ all_data[fname] = None
55
+
56
+ if json_out:
57
+ click.echo(json.dumps(all_data, indent=2, default=str))
58
+ return
59
+
60
+ for fname, data in all_data.items():
61
+ if data is None:
62
+ console.print(f" [dim]{fname}[/] [yellow]not found[/]")
63
+ elif "error" in data:
64
+ console.print(f" [dim]{fname}[/] [red]{data['error']}[/]")
65
+ else:
66
+ console.print(f"\n [bold cyan]{fname}[/]")
67
+ console.print(f" [dim]{config_dir / fname}[/]")
68
+ formatted = yaml.dump(data, default_flow_style=False, indent=2)
69
+ for line in formatted.strip().split("\n"):
70
+ console.print(f" {line}")
71
+ console.print()
72
+
21
73
  @config.command("validate")
22
74
  @click.option(
23
75
  "--home", default=AGENT_HOME, type=click.Path(),
@@ -20,7 +20,9 @@ def register_peer_commands(main: click.Group) -> None:
20
20
 
21
21
  @main.group()
22
22
  def peer():
23
- """Peer management — discover, add, and manage trusted contacts."""
23
+ """Peer management — discover, add, and manage trusted contacts.
24
+
25
+ Identity-layer peers (PGP keys, trust). For transport routing, see 'peers'."""
24
26
 
25
27
  @peer.command("add")
26
28
  @click.option("--card", "card_path", type=click.Path(exists=True), help="Import from identity card.")
@@ -17,7 +17,9 @@ def register_peers_dir_commands(main: click.Group) -> None:
17
17
 
18
18
  @main.group("peers")
19
19
  def peers_dir():
20
- """Peer transport directory — routing addresses for the mesh."""
20
+ """Peer transport directory — routing addresses for the mesh.
21
+
22
+ SKComm transport endpoints. For identity/trust peers, see 'peer'."""
21
23
 
22
24
  @peers_dir.command("list")
23
25
  @click.option("--home", "sk_home", default=AGENT_HOME, type=click.Path())
@@ -33,8 +33,12 @@ def register_preflight_commands(main: click.Group) -> None:
33
33
  skcapstone preflight --json-out
34
34
  """
35
35
  import json
36
+ from .. import ensure_skeleton
36
37
  from ..preflight import PreflightChecker
37
38
 
39
+ # Ensure all expected directories exist before checking
40
+ ensure_skeleton()
41
+
38
42
  checker = PreflightChecker(home=Path(home).expanduser())
39
43
  summary = checker.run_all()
40
44
 
@@ -209,7 +209,7 @@ def register_status_commands(main: click.Group) -> None:
209
209
  "Run [bold]skcapstone init --name \"YourAgent\"[/] first."
210
210
  )
211
211
  sys.exit(1)
212
- runtime = get_runtime(home_path)
212
+ runtime = get_runtime(home=home_path)
213
213
  else:
214
214
  agent_name = agent or default_agent
215
215
  runtime = get_runtime(agent_name=agent_name)
@@ -14,13 +14,18 @@ from ._common import AGENT_HOME, console
14
14
  def register_usage_commands(main: click.Group) -> None:
15
15
  """Register the ``skcapstone usage`` command group."""
16
16
 
17
- @main.group("usage")
18
- def usage_group():
17
+ @main.group("usage", invoke_without_command=True)
18
+ @click.pass_context
19
+ def usage_group(ctx):
19
20
  """Show LLM token usage and cost estimates.
20
21
 
21
22
  Tracks input/output tokens per model per day.
22
23
  Data is stored in ~/.skcapstone/usage/tokens-{date}.json.
24
+
25
+ When called without a subcommand, shows today's usage.
23
26
  """
27
+ if ctx.invoked_subcommand is None:
28
+ ctx.invoke(today_cmd)
24
29
 
25
30
  @usage_group.command("daily")
26
31
  @click.option("--home", default=AGENT_HOME, type=click.Path(), help="Agent home directory.")
@@ -13,6 +13,7 @@ from __future__ import annotations
13
13
 
14
14
  import importlib
15
15
  import json
16
+ import os
16
17
  import shutil
17
18
  import subprocess
18
19
  from dataclasses import dataclass, field
@@ -618,6 +619,120 @@ def _check_versions() -> list[Check]:
618
619
  return checks
619
620
 
620
621
 
622
+ @dataclass
623
+ class FixResult:
624
+ """Result of attempting to auto-fix a failing check.
625
+
626
+ Attributes:
627
+ check_name: Name of the check that was fixed.
628
+ success: Whether the fix succeeded.
629
+ action: Description of what was done.
630
+ error: Error message if the fix failed.
631
+ """
632
+
633
+ check_name: str
634
+ success: bool
635
+ action: str = ""
636
+ error: str = ""
637
+
638
+
639
+ def run_fixes(report: DiagnosticReport, home: Path) -> list[FixResult]:
640
+ """Attempt to auto-fix failing checks by creating missing directories and files.
641
+
642
+ Args:
643
+ report: Diagnostic report with failing checks.
644
+ home: Agent home directory.
645
+
646
+ Returns:
647
+ List of FixResult for each attempted fix.
648
+ """
649
+ results: list[FixResult] = []
650
+
651
+ for check in report.checks:
652
+ if check.passed:
653
+ continue
654
+
655
+ # Fix missing directories
656
+ if check.name.startswith("home:") and check.name != "home:exists" and check.name != "home:manifest":
657
+ dirname = check.name.split(":", 1)[1]
658
+ dirpath = home / dirname
659
+ try:
660
+ dirpath.mkdir(parents=True, exist_ok=True)
661
+ results.append(FixResult(
662
+ check_name=check.name,
663
+ success=True,
664
+ action=f"Created directory {dirpath}",
665
+ ))
666
+ except OSError as exc:
667
+ results.append(FixResult(
668
+ check_name=check.name,
669
+ success=False,
670
+ error=str(exc),
671
+ ))
672
+
673
+ # Fix missing manifest
674
+ elif check.name == "home:manifest":
675
+ manifest_path = home / "manifest.json"
676
+ try:
677
+ data = {
678
+ "name": os.environ.get("SKCAPSTONE_AGENT", "sovereign"),
679
+ "version": "0.0.0",
680
+ "created_at": "",
681
+ "connectors": [],
682
+ }
683
+ manifest_path.write_text(json.dumps(data, indent=2), encoding="utf-8")
684
+ results.append(FixResult(
685
+ check_name=check.name,
686
+ success=True,
687
+ action=f"Created default manifest at {manifest_path}",
688
+ ))
689
+ except OSError as exc:
690
+ results.append(FixResult(
691
+ check_name=check.name,
692
+ success=False,
693
+ error=str(exc),
694
+ ))
695
+
696
+ # Fix missing memory store
697
+ elif check.name == "memory:store":
698
+ agent_name = os.environ.get("SKCAPSTONE_AGENT", "lumina")
699
+ memory_dir = home / "agents" / agent_name / "memory"
700
+ try:
701
+ for layer in ("short-term", "mid-term", "long-term"):
702
+ (memory_dir / layer).mkdir(parents=True, exist_ok=True)
703
+ results.append(FixResult(
704
+ check_name=check.name,
705
+ success=True,
706
+ action=f"Created memory directories at {memory_dir}",
707
+ ))
708
+ except OSError as exc:
709
+ results.append(FixResult(
710
+ check_name=check.name,
711
+ success=False,
712
+ error=str(exc),
713
+ ))
714
+
715
+ # Fix missing sync directory
716
+ elif check.name == "sync:dir":
717
+ sync_dir = home / "sync"
718
+ try:
719
+ for subdir in ("outbox", "inbox", "archive"):
720
+ (sync_dir / subdir).mkdir(parents=True, exist_ok=True)
721
+ results.append(FixResult(
722
+ check_name=check.name,
723
+ success=True,
724
+ action=f"Created sync directories at {sync_dir}",
725
+ ))
726
+ except OSError as exc:
727
+ results.append(FixResult(
728
+ check_name=check.name,
729
+ success=False,
730
+ error=str(exc),
731
+ ))
732
+
733
+ return results
734
+
735
+
621
736
  def _get_tool_version(tool: str) -> Optional[str]:
622
737
  """Try to get a tool's version string.
623
738
 
@@ -109,6 +109,52 @@ def _step_identity(home_path: Path, name: str, email: str | None) -> tuple[str,
109
109
 
110
110
  (home_path / "skills").mkdir(parents=True, exist_ok=True)
111
111
 
112
+ # Create full skeleton so all commands work from day one
113
+ agent_slug = name.lower().replace(" ", "-")
114
+ agent_dir = home_path / "agents" / agent_slug
115
+
116
+ skeleton_dirs = [
117
+ # Shared root directories
118
+ home_path / "heartbeats",
119
+ home_path / "peers",
120
+ home_path / "coordination" / "tasks",
121
+ home_path / "coordination" / "agents",
122
+ home_path / "logs",
123
+ home_path / "comms" / "inbox",
124
+ home_path / "comms" / "outbox",
125
+ home_path / "comms" / "archive",
126
+ home_path / "archive",
127
+ home_path / "deployments",
128
+ home_path / "docs",
129
+ home_path / "metrics",
130
+ # Per-agent directories
131
+ agent_dir / "memory" / "short-term",
132
+ agent_dir / "memory" / "mid-term",
133
+ agent_dir / "memory" / "long-term",
134
+ agent_dir / "soul" / "installed",
135
+ agent_dir / "wallet",
136
+ agent_dir / "seeds",
137
+ agent_dir / "identity",
138
+ agent_dir / "config",
139
+ agent_dir / "logs",
140
+ agent_dir / "security",
141
+ agent_dir / "cloud9",
142
+ agent_dir / "trust" / "febs",
143
+ agent_dir / "sync" / "outbox",
144
+ agent_dir / "sync" / "inbox",
145
+ agent_dir / "sync" / "archive",
146
+ agent_dir / "reflections",
147
+ agent_dir / "improvements",
148
+ agent_dir / "scripts",
149
+ agent_dir / "cron",
150
+ agent_dir / "archive",
151
+ agent_dir / "comms" / "inbox",
152
+ agent_dir / "comms" / "outbox",
153
+ agent_dir / "comms" / "archive",
154
+ ]
155
+ for d in skeleton_dirs:
156
+ d.mkdir(parents=True, exist_ok=True)
157
+
112
158
  manifest = {
113
159
  "name": name,
114
160
  "version": __version__,
@@ -72,7 +72,7 @@ def initialize_sync(home: Path, config: Optional[SyncConfig] = None) -> SyncStat
72
72
  else:
73
73
  state.status = PillarStatus.DEGRADED
74
74
 
75
- state.seed_count = _count_seeds(sync_dir)
75
+ state.seed_count = _count_seeds(sync_dir, home=home)
76
76
  return state
77
77
 
78
78
 
@@ -396,7 +396,7 @@ def discover_sync(home: Path) -> SyncState:
396
396
  state = SyncState(
397
397
  transport=transport,
398
398
  sync_path=sync_dir,
399
- seed_count=_count_seeds(sync_dir),
399
+ seed_count=_count_seeds(sync_dir, home=home),
400
400
  status=PillarStatus.ACTIVE,
401
401
  )
402
402
 
@@ -483,8 +483,8 @@ def _get_hostname() -> str:
483
483
  return socket.gethostname()
484
484
 
485
485
 
486
- def _count_seeds(sync_dir: Path) -> int:
487
- """Count seed files across outbox, inbox, and archive."""
486
+ def _count_seeds(sync_dir: Path, home: Optional[Path] = None) -> int:
487
+ """Count seed files across sync subdirs and the agent seeds directory."""
488
488
  count = 0
489
489
  for subdir in ("outbox", "inbox", "archive"):
490
490
  d = sync_dir / subdir
@@ -492,6 +492,13 @@ def _count_seeds(sync_dir: Path) -> int:
492
492
  count += sum(
493
493
  1 for f in d.iterdir() if f.name.endswith(SEED_EXTENSION) or f.suffix == ".gpg"
494
494
  )
495
+ # Also count seeds in the agent's seeds/ directory
496
+ if home is not None:
497
+ seeds_dir = home / "seeds"
498
+ if seeds_dir.is_dir():
499
+ count += sum(
500
+ 1 for f in seeds_dir.iterdir() if f.name.endswith(SEED_EXTENSION)
501
+ )
495
502
  return count
496
503
 
497
504
 
@@ -97,6 +97,13 @@ def _build_package_registry(workspace: Optional[Path] = None) -> list[dict]:
97
97
  "mcp_env": None,
98
98
  "openclaw_plugin_path": workspace / "pillar-repos" / "sksecurity" / "openclaw-plugin" / "src" / "index.ts",
99
99
  },
100
+ {
101
+ "name": "skseed",
102
+ "mcp_cmd": None,
103
+ "mcp_args": None,
104
+ "mcp_env": None,
105
+ "openclaw_plugin_path": workspace / "pillar-repos" / "skseed" / "openclaw-plugin" / "src" / "index.ts",
106
+ },
100
107
  {
101
108
  "name": "skgit",
102
109
  "mcp_cmd": "node",
@@ -119,6 +126,7 @@ _PILLAR_DIR_MAP: dict[str, Optional[str]] = {
119
126
  "capauth": "capauth",
120
127
  "cloud9": "cloud9-python",
121
128
  "sksecurity": "sksecurity",
129
+ "skseed": "skseed",
122
130
  "skgit": None, # skill dir only, no pillar repo
123
131
  }
124
132
 
@@ -5,19 +5,18 @@ After=network-online.target ollama.service syncthing.service
5
5
  Wants=network-online.target
6
6
 
7
7
  [Service]
8
- Type=notify
9
- ExecStart=skcapstone daemon start --foreground
10
- ExecStop=skcapstone daemon stop
8
+ Type=simple
9
+ ExecStart=%h/.skenv/bin/skcapstone daemon start --foreground
10
+ ExecStop=%h/.skenv/bin/skcapstone daemon stop
11
11
  ExecReload=/bin/kill -HUP $MAINPID
12
12
  Restart=on-failure
13
13
  RestartSec=10
14
- # Watchdog: daemon must call sd_notify("WATCHDOG=1") at least every 5 minutes
15
- WatchdogSec=300
16
14
  # Cap memory to prevent OOM from large model loading
17
15
  MemoryMax=4G
18
16
  # Keep Ollama models warm for 5 minutes between requests
19
17
  Environment=PYTHONUNBUFFERED=1
20
18
  Environment=OLLAMA_KEEP_ALIVE=5m
19
+ Environment=SKCAPSTONE_AGENT=lumina
21
20
  # Journal logging
22
21
  StandardOutput=journal
23
22
  StandardError=journal
@@ -27,7 +26,7 @@ SyslogIdentifier=skcapstone
27
26
  NoNewPrivileges=true
28
27
  ProtectSystem=strict
29
28
  ProtectHome=read-only
30
- ReadWritePaths=%h/.skcapstone %h/.skmemory %h/.capauth %h/.cloud9 %h/.skcomm %h/.skchat
29
+ ReadWritePaths=%h/.skcapstone %h/.skenv %h/.capauth %h/.cloud9 %h/.skcomm %h/.skchat
31
30
  PrivateTmp=true
32
31
  ProtectKernelTunables=true
33
32
  ProtectControlGroups=true