@smilintux/skcapstone 0.5.8 → 0.5.10

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smilintux/skcapstone",
3
- "version": "0.5.8",
3
+ "version": "0.5.10",
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",
@@ -314,22 +314,25 @@ def register_daemon_commands(main: click.Group) -> None:
314
314
  console.print()
315
315
 
316
316
  elif platform.system() == "Linux":
317
- from ..systemd import install_service, systemd_available
317
+ from ..systemd import install_service, systemd_available, SERVICE_NAME
318
318
 
319
319
  if not systemd_available():
320
320
  console.print("[red]systemd user session not available.[/]")
321
321
  console.print("[dim]This command requires a Linux system with systemd.[/]")
322
322
  raise SystemExit(1)
323
323
 
324
- console.print("\n[cyan]Installing skcapstone systemd service...[/]")
325
- result = install_service(start=start)
324
+ console.print(f"\n[cyan]Installing skcapstone systemd service for agent '{effective_agent}'...[/]")
325
+ result = install_service(agent_name=effective_agent, start=start)
326
+ svc_name = result.get("service_name", SERVICE_NAME)
326
327
 
327
328
  if result["installed"]:
328
- console.print("[green] Unit files installed.[/]")
329
+ console.print(f"[green] Unit files installed ({svc_name}).[/]")
329
330
  if result["enabled"]:
330
- console.print("[green] Service enabled at login.[/]")
331
+ console.print(f"[green] Service enabled at login.[/]")
331
332
  if result.get("started"):
332
- console.print("[green] Service started.[/]")
333
+ console.print(f"[green] Service started.[/]")
334
+ else:
335
+ console.print(f"[dim] Start: systemctl --user start {svc_name}[/]")
333
336
  console.print()
334
337
 
335
338
  if not result["installed"]:
@@ -6,8 +6,8 @@ Wants=network-online.target
6
6
 
7
7
  [Service]
8
8
  Type=simple
9
- ExecStart=%h/.skenv/bin/skcapstone daemon start --foreground
10
- ExecStop=%h/.skenv/bin/skcapstone daemon stop
9
+ ExecStart=%h/.local/bin/skcapstone daemon start --foreground
10
+ ExecStop=%h/.local/bin/skcapstone daemon stop
11
11
  ExecReload=/bin/kill -HUP $MAINPID
12
12
  Restart=on-failure
13
13
  RestartSec=10
@@ -16,7 +16,8 @@ MemoryMax=4G
16
16
  # Keep Ollama models warm for 5 minutes between requests
17
17
  Environment=PYTHONUNBUFFERED=1
18
18
  Environment=OLLAMA_KEEP_ALIVE=5m
19
- Environment=SKCAPSTONE_AGENT=lumina
19
+ Environment=SKCAPSTONE_AGENT=sovereign
20
+ Environment=SKCAPSTONE_HOME=%h/.skcapstone
20
21
  # Journal logging
21
22
  StandardOutput=journal
22
23
  StandardError=journal
@@ -26,7 +27,7 @@ SyslogIdentifier=skcapstone
26
27
  NoNewPrivileges=true
27
28
  ProtectSystem=strict
28
29
  ProtectHome=read-only
29
- ReadWritePaths=%h/.skcapstone %h/.skenv %h/.capauth %h/.cloud9 %h/.skcomm %h/.skchat
30
+ ReadWritePaths=%h/.skcapstone %h/.skenv %h/.local %h/.config %h/.capauth %h/.cloud9 %h/.skcomm %h/.skchat
30
31
  PrivateTmp=true
31
32
  ProtectKernelTunables=true
32
33
  ProtectControlGroups=true
@@ -20,8 +20,8 @@ Wants=network-online.target
20
20
 
21
21
  [Service]
22
22
  Type=notify
23
- ExecStart=skcapstone daemon start --agent %i --foreground
24
- ExecStop=skcapstone daemon stop --agent %i
23
+ ExecStart=%h/.local/bin/skcapstone daemon start --agent %i --foreground
24
+ ExecStop=%h/.local/bin/skcapstone daemon stop --agent %i
25
25
  ExecReload=/bin/kill -HUP $MAINPID
26
26
  Restart=on-failure
27
27
  RestartSec=10
@@ -41,7 +41,7 @@ SyslogIdentifier=skcapstone@%i
41
41
  NoNewPrivileges=true
42
42
  ProtectSystem=strict
43
43
  ProtectHome=read-only
44
- ReadWritePaths=%h/.skcapstone %h/.skmemory %h/.capauth %h/.cloud9 %h/.skcomm %h/.skchat
44
+ ReadWritePaths=%h/.skcapstone %h/.skmemory %h/.local %h/.config %h/.capauth %h/.cloud9 %h/.skcomm %h/.skchat
45
45
  PrivateTmp=true
46
46
  ProtectKernelTunables=true
47
47
  ProtectControlGroups=true
@@ -1008,7 +1008,7 @@ def _step_autostart_service(agent_name: str = "sovereign") -> bool:
1008
1008
  system = platform.system()
1009
1009
 
1010
1010
  if system == "Linux":
1011
- return _step_systemd_service_linux()
1011
+ return _step_systemd_service_linux(agent_name)
1012
1012
  elif system == "Darwin":
1013
1013
  return _step_launchd_service_macos(agent_name)
1014
1014
  else:
@@ -1019,8 +1019,17 @@ def _step_autostart_service(agent_name: str = "sovereign") -> bool:
1019
1019
  return False
1020
1020
 
1021
1021
 
1022
- def _step_systemd_service_linux() -> bool:
1023
- """Install systemd user service (Linux only)."""
1022
+ def _step_systemd_service_linux(agent_name: str = "sovereign") -> bool:
1023
+ """Install systemd user service for an agent (Linux only).
1024
+
1025
+ Uses the template unit ``skcapstone@.service`` so each agent
1026
+ gets its own independent service instance. Multiple agents can
1027
+ run simultaneously on the same machine.
1028
+
1029
+ Args:
1030
+ agent_name: Agent slug from onboarding (e.g. "jarvis").
1031
+ """
1032
+ service_name = f"skcapstone@{agent_name}.service"
1024
1033
  if not click.confirm(" Install systemd user service for auto-start at login?", default=False):
1025
1034
  click.echo(
1026
1035
  click.style(" ↷ ", fg="bright_black")
@@ -1036,12 +1045,15 @@ def _step_systemd_service_linux() -> bool:
1036
1045
  click.echo(click.style(" ", fg="bright_black") + "Try: systemctl --user status")
1037
1046
  return False
1038
1047
 
1039
- result = install_service(enable=True, start=False)
1048
+ result = install_service(agent_name=agent_name, enable=True, start=False)
1040
1049
  if result.get("installed"):
1041
- click.echo(click.style(" ✓ ", fg="green") + "Systemd service installed")
1050
+ click.echo(click.style(" ✓ ", fg="green") + f"Systemd service installed")
1042
1051
  if result.get("enabled"):
1043
- click.echo(click.style(" ✓ ", fg="green") + "Service enabled — auto-starts at login")
1044
- click.echo(click.style(" ", fg="bright_black") + "Start now: systemctl --user start skcapstone")
1052
+ click.echo(click.style(" ✓ ", fg="green") + f"Service enabled — auto-starts at login")
1053
+ click.echo(
1054
+ click.style(" ", fg="bright_black")
1055
+ + f"Start now: systemctl --user start {service_name}"
1056
+ )
1045
1057
  return True
1046
1058
  else:
1047
1059
  click.echo(click.style(" ✗ ", fg="red") + "Service install failed")
@@ -28,7 +28,11 @@ def initialize_consciousness(home: Path) -> ConsciousnessState:
28
28
  ConsciousnessState with current status.
29
29
  """
30
30
  agent_name = os.environ.get("SKCAPSTONE_AGENT", "lumina")
31
- whisper_dir = home / "agents" / agent_name / "skwhisper"
31
+ # home may be the agent dir (~/.skcapstone/agents/jarvis/) or the
32
+ # shared root (~/.skcapstone/). Check for skwhisper/ directly first.
33
+ whisper_dir = home / "skwhisper"
34
+ if not whisper_dir.exists():
35
+ whisper_dir = home / "agents" / agent_name / "skwhisper"
32
36
 
33
37
  state = ConsciousnessState()
34
38
 
@@ -81,11 +85,16 @@ def initialize_consciousness(home: Path) -> ConsciousnessState:
81
85
  pass
82
86
 
83
87
  # Check if consciousness daemon is running (systemd)
84
- # Accept either the legacy skwhisper service or the skcapstone service
88
+ # Check template instance (skcapstone@<agent>), legacy single-agent, and skwhisper
85
89
  try:
86
90
  import subprocess
87
91
 
88
- for service_name in ("skcapstone", "skwhisper"):
92
+ service_candidates = [
93
+ f"skcapstone@{agent_name}", # multi-agent template unit
94
+ "skcapstone", # legacy single-agent unit
95
+ "skwhisper", # standalone skwhisper daemon
96
+ ]
97
+ for service_name in service_candidates:
89
98
  result = subprocess.run(
90
99
  ["systemctl", "--user", "is-active", service_name],
91
100
  capture_output=True,
@@ -121,24 +121,32 @@ def systemd_available() -> bool:
121
121
 
122
122
 
123
123
  def install_service(
124
+ agent_name: Optional[str] = None,
124
125
  unit_dir: Optional[Path] = None,
125
126
  source_dir: Optional[Path] = None,
126
127
  enable: bool = True,
127
128
  start: bool = True,
128
129
  ) -> dict:
129
- """Install the skcapstone systemd user service.
130
+ """Install the skcapstone systemd user service for an agent.
130
131
 
131
- Copies unit files to ~/.config/systemd/user/, reloads the
132
- daemon, and optionally enables + starts the service.
132
+ When *agent_name* is provided, installs the template unit
133
+ ``skcapstone@.service`` and enables ``skcapstone@<agent>.service``.
134
+ This supports multiple agents running as independent services on
135
+ the same machine.
136
+
137
+ When *agent_name* is None, falls back to the single-agent
138
+ ``skcapstone.service`` unit for backwards compatibility.
133
139
 
134
140
  Args:
141
+ agent_name: Agent name (e.g. "jarvis"). Uses template unit.
135
142
  unit_dir: Target directory for unit files.
136
143
  source_dir: Directory containing the .service/.socket files.
137
144
  enable: Whether to enable the service at login.
138
145
  start: Whether to start the service immediately.
139
146
 
140
147
  Returns:
141
- dict: Result with 'installed', 'enabled', 'started' bools.
148
+ dict: Result with 'installed', 'enabled', 'started' bools
149
+ and 'service_name' for the installed unit.
142
150
  """
143
151
  _require_linux()
144
152
  target = unit_dir or SYSTEMD_USER_DIR
@@ -146,15 +154,41 @@ def install_service(
146
154
 
147
155
  target.mkdir(parents=True, exist_ok=True)
148
156
 
149
- result = {"installed": False, "enabled": False, "started": False, "timers_enabled": False}
150
-
151
- service_src = source / SERVICE_NAME
152
- if not service_src.exists():
153
- logger.error("Service unit not found: %s", service_src)
154
- return result
155
-
156
- copied = 0
157
+ result = {
158
+ "installed": False,
159
+ "enabled": False,
160
+ "started": False,
161
+ "timers_enabled": False,
162
+ "service_name": "",
163
+ }
164
+
165
+ # Determine which unit to use
166
+ if agent_name:
167
+ # Multi-agent: use template unit skcapstone@.service
168
+ template_name = "skcapstone@.service"
169
+ instance_name = f"skcapstone@{agent_name}.service"
170
+ template_src = source / template_name
171
+ if not template_src.exists():
172
+ logger.error("Template unit not found: %s", template_src)
173
+ return result
174
+ shutil.copy2(template_src, target / template_name)
175
+ service_unit = instance_name
176
+ result["service_name"] = instance_name
177
+ else:
178
+ # Single-agent fallback
179
+ service_src = source / SERVICE_NAME
180
+ if not service_src.exists():
181
+ logger.error("Service unit not found: %s", service_src)
182
+ return result
183
+ shutil.copy2(service_src, target / SERVICE_NAME)
184
+ service_unit = SERVICE_NAME
185
+ result["service_name"] = SERVICE_NAME
186
+
187
+ # Copy ancillary units (socket, timers, etc.)
188
+ copied = 1 # already copied the main unit
157
189
  for unit_name in ALL_UNITS:
190
+ if unit_name == SERVICE_NAME:
191
+ continue # already handled above
158
192
  src = source / unit_name
159
193
  if src.exists():
160
194
  shutil.copy2(src, target / unit_name)
@@ -162,10 +196,10 @@ def install_service(
162
196
 
163
197
  _systemctl("daemon-reload")
164
198
  result["installed"] = True
165
- logger.info("Installed %d unit file(s) to %s", copied, target)
199
+ logger.info("Installed %d unit file(s) to %s (service: %s)", copied, target, service_unit)
166
200
 
167
201
  if enable:
168
- r = _systemctl("enable", SERVICE_NAME)
202
+ r = _systemctl("enable", service_unit)
169
203
  result["enabled"] = r.returncode == 0
170
204
 
171
205
  timers_ok = True
@@ -177,7 +211,7 @@ def install_service(
177
211
  result["timers_enabled"] = timers_ok
178
212
 
179
213
  if start:
180
- r = _systemctl("start", SERVICE_NAME)
214
+ r = _systemctl("start", service_unit)
181
215
  result["started"] = r.returncode == 0
182
216
 
183
217
  for timer in TIMER_UNITS: