@smilintux/skcapstone 0.1.0

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 (50) hide show
  1. package/.cursorrules +33 -0
  2. package/.github/workflows/ci.yml +23 -0
  3. package/.github/workflows/publish.yml +52 -0
  4. package/AGENTS.md +74 -0
  5. package/CLAUDE.md +56 -0
  6. package/LICENSE +674 -0
  7. package/README.md +242 -0
  8. package/SKILL.md +36 -0
  9. package/bin/cli.js +18 -0
  10. package/docs/ARCHITECTURE.md +510 -0
  11. package/docs/SECURITY_DESIGN.md +315 -0
  12. package/docs/SOVEREIGN_SINGULARITY.md +371 -0
  13. package/docs/TOKEN_SYSTEM.md +201 -0
  14. package/index.d.ts +9 -0
  15. package/index.js +32 -0
  16. package/package.json +32 -0
  17. package/pyproject.toml +84 -0
  18. package/src/skcapstone/__init__.py +13 -0
  19. package/src/skcapstone/cli.py +1441 -0
  20. package/src/skcapstone/connectors/__init__.py +6 -0
  21. package/src/skcapstone/coordination.py +590 -0
  22. package/src/skcapstone/discovery.py +275 -0
  23. package/src/skcapstone/memory_engine.py +457 -0
  24. package/src/skcapstone/models.py +223 -0
  25. package/src/skcapstone/pillars/__init__.py +8 -0
  26. package/src/skcapstone/pillars/identity.py +91 -0
  27. package/src/skcapstone/pillars/memory.py +61 -0
  28. package/src/skcapstone/pillars/security.py +83 -0
  29. package/src/skcapstone/pillars/sync.py +486 -0
  30. package/src/skcapstone/pillars/trust.py +335 -0
  31. package/src/skcapstone/runtime.py +190 -0
  32. package/src/skcapstone/skills/__init__.py +1 -0
  33. package/src/skcapstone/skills/syncthing_setup.py +297 -0
  34. package/src/skcapstone/sync/__init__.py +14 -0
  35. package/src/skcapstone/sync/backends.py +330 -0
  36. package/src/skcapstone/sync/engine.py +301 -0
  37. package/src/skcapstone/sync/models.py +97 -0
  38. package/src/skcapstone/sync/vault.py +284 -0
  39. package/src/skcapstone/tokens.py +439 -0
  40. package/tests/__init__.py +0 -0
  41. package/tests/conftest.py +42 -0
  42. package/tests/test_coordination.py +299 -0
  43. package/tests/test_discovery.py +57 -0
  44. package/tests/test_memory_engine.py +391 -0
  45. package/tests/test_models.py +63 -0
  46. package/tests/test_pillars.py +87 -0
  47. package/tests/test_runtime.py +60 -0
  48. package/tests/test_sync.py +507 -0
  49. package/tests/test_syncthing_setup.py +76 -0
  50. package/tests/test_tokens.py +265 -0
@@ -0,0 +1,6 @@
1
+ """
2
+ Platform connectors — windows into the sovereign agent.
3
+
4
+ Every connector talks to the same AgentRuntime.
5
+ The platform is just a viewport. The agent is the truth.
6
+ """
@@ -0,0 +1,590 @@
1
+ """
2
+ SKCapstone Coordination — Multi-agent task board.
3
+
4
+ Conflict-free design: each agent writes only to its own files.
5
+ Syncthing propagates everything. Zero write conflicts.
6
+
7
+ Directory layout:
8
+ ~/.skcapstone/coordination/
9
+ ├── tasks/ # One JSON file per task (creator owns it)
10
+ ├── agents/ # One JSON file per agent (self-managed)
11
+ └── BOARD.md # Human-readable overview (auto-generated)
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import json
17
+ import socket
18
+ import uuid
19
+ from datetime import datetime, timezone
20
+ from enum import Enum
21
+ from pathlib import Path
22
+ from typing import Optional
23
+
24
+ from pydantic import BaseModel, Field
25
+
26
+
27
+ class TaskPriority(str, Enum):
28
+ """Task urgency levels."""
29
+
30
+ CRITICAL = "critical"
31
+ HIGH = "high"
32
+ MEDIUM = "medium"
33
+ LOW = "low"
34
+
35
+
36
+ class TaskStatus(str, Enum):
37
+ """Task lifecycle states.
38
+
39
+ Derived from agent files, not stored on the task itself.
40
+ A task is 'open' until an agent claims it via their agent file.
41
+ """
42
+
43
+ OPEN = "open"
44
+ CLAIMED = "claimed"
45
+ IN_PROGRESS = "in_progress"
46
+ REVIEW = "review"
47
+ DONE = "done"
48
+ BLOCKED = "blocked"
49
+
50
+
51
+ class AgentState(str, Enum):
52
+ """Agent availability."""
53
+
54
+ ACTIVE = "active"
55
+ IDLE = "idle"
56
+ OFFLINE = "offline"
57
+
58
+
59
+ class Task(BaseModel):
60
+ """A unit of work on the coordination board.
61
+
62
+ Task files are written once by the creator and are effectively
63
+ immutable. Status is derived from agent claim files, not from
64
+ the task file itself. This eliminates write conflicts.
65
+ """
66
+
67
+ id: str = Field(default_factory=lambda: uuid.uuid4().hex[:8])
68
+ title: str
69
+ description: str = ""
70
+ priority: TaskPriority = TaskPriority.MEDIUM
71
+ tags: list[str] = Field(default_factory=list)
72
+ created_by: str = ""
73
+ created_at: str = Field(
74
+ default_factory=lambda: datetime.now(timezone.utc).isoformat()
75
+ )
76
+ acceptance_criteria: list[str] = Field(default_factory=list)
77
+ dependencies: list[str] = Field(default_factory=list)
78
+ notes: list[str] = Field(default_factory=list)
79
+
80
+
81
+ class AgentFile(BaseModel):
82
+ """An agent's self-managed status and claim record.
83
+
84
+ Each agent owns exactly one file: agents/{name}.json.
85
+ Only that agent writes to it. Other agents read it to
86
+ see claims, progress, and availability.
87
+ """
88
+
89
+ agent: str
90
+ last_seen: str = Field(
91
+ default_factory=lambda: datetime.now(timezone.utc).isoformat()
92
+ )
93
+ host: str = Field(default_factory=socket.gethostname)
94
+ state: AgentState = AgentState.ACTIVE
95
+ current_task: Optional[str] = None
96
+ claimed_tasks: list[str] = Field(default_factory=list)
97
+ completed_tasks: list[str] = Field(default_factory=list)
98
+ capabilities: list[str] = Field(default_factory=list)
99
+ notes: str = ""
100
+
101
+
102
+ class TaskView(BaseModel):
103
+ """A task enriched with derived status from agent claims."""
104
+
105
+ task: Task
106
+ status: TaskStatus = TaskStatus.OPEN
107
+ claimed_by: Optional[str] = None
108
+
109
+
110
+ class Board:
111
+ """The coordination board — reads tasks and agent files to
112
+ present a unified view of work across all agents.
113
+
114
+ Args:
115
+ home: Path to ~/.skcapstone (or test equivalent).
116
+ """
117
+
118
+ def __init__(self, home: Path) -> None:
119
+ self.home = Path(home).expanduser()
120
+ self.coord_dir = self.home / "coordination"
121
+ self.tasks_dir = self.coord_dir / "tasks"
122
+ self.agents_dir = self.coord_dir / "agents"
123
+
124
+ def ensure_dirs(self) -> None:
125
+ """Create coordination directories if they don't exist."""
126
+ self.tasks_dir.mkdir(parents=True, exist_ok=True)
127
+ self.agents_dir.mkdir(parents=True, exist_ok=True)
128
+
129
+ def load_tasks(self) -> list[Task]:
130
+ """Load all task files from tasks/ directory.
131
+
132
+ Returns:
133
+ list[Task]: All tasks on the board.
134
+ """
135
+ tasks: list[Task] = []
136
+ if not self.tasks_dir.exists():
137
+ return tasks
138
+ for f in sorted(self.tasks_dir.glob("*.json")):
139
+ try:
140
+ data = json.loads(f.read_text())
141
+ tasks.append(Task.model_validate(data))
142
+ except (json.JSONDecodeError, Exception):
143
+ continue
144
+ return tasks
145
+
146
+ def load_agents(self) -> list[AgentFile]:
147
+ """Load all agent status files from agents/ directory.
148
+
149
+ Returns:
150
+ list[AgentFile]: All agent records.
151
+ """
152
+ agents: list[AgentFile] = []
153
+ if not self.agents_dir.exists():
154
+ return agents
155
+ for f in sorted(self.agents_dir.glob("*.json")):
156
+ try:
157
+ data = json.loads(f.read_text())
158
+ agents.append(AgentFile.model_validate(data))
159
+ except (json.JSONDecodeError, Exception):
160
+ continue
161
+ return agents
162
+
163
+ def load_agent(self, name: str) -> Optional[AgentFile]:
164
+ """Load a specific agent's file.
165
+
166
+ Args:
167
+ name: Agent name (matches filename).
168
+
169
+ Returns:
170
+ AgentFile or None if not found.
171
+ """
172
+ path = self.agents_dir / f"{name}.json"
173
+ if not path.exists():
174
+ return None
175
+ data = json.loads(path.read_text())
176
+ return AgentFile.model_validate(data)
177
+
178
+ def save_agent(self, agent: AgentFile) -> Path:
179
+ """Write an agent's status file.
180
+
181
+ Args:
182
+ agent: The agent record to save.
183
+
184
+ Returns:
185
+ Path to the written file.
186
+ """
187
+ self.ensure_dirs()
188
+ agent.last_seen = datetime.now(timezone.utc).isoformat()
189
+ path = self.agents_dir / f"{agent.agent}.json"
190
+ path.write_text(
191
+ json.dumps(agent.model_dump(), indent=2) + "\n"
192
+ )
193
+ return path
194
+
195
+ def create_task(self, task: Task) -> Path:
196
+ """Write a new task file.
197
+
198
+ Args:
199
+ task: The task to create.
200
+
201
+ Returns:
202
+ Path to the written file.
203
+ """
204
+ self.ensure_dirs()
205
+ slug = task.title.lower().replace(" ", "-")[:40]
206
+ # Reason: filename includes id + slug for human readability
207
+ filename = f"{task.id}-{slug}.json"
208
+ path = self.tasks_dir / filename
209
+ path.write_text(
210
+ json.dumps(task.model_dump(), indent=2) + "\n"
211
+ )
212
+ return path
213
+
214
+ def get_task_views(self) -> list[TaskView]:
215
+ """Build enriched task views with derived status.
216
+
217
+ Cross-references tasks against all agent claim files to
218
+ determine each task's effective status and who claimed it.
219
+
220
+ Returns:
221
+ list[TaskView]: Tasks with derived status.
222
+ """
223
+ tasks = self.load_tasks()
224
+ agents = self.load_agents()
225
+
226
+ claimed_map: dict[str, str] = {}
227
+ completed_set: set[str] = set()
228
+ in_progress_set: set[str] = set()
229
+
230
+ for ag in agents:
231
+ for tid in ag.completed_tasks:
232
+ completed_set.add(tid)
233
+ for tid in ag.claimed_tasks:
234
+ claimed_map[tid] = ag.agent
235
+ if ag.current_task:
236
+ in_progress_set.add(ag.current_task)
237
+ claimed_map[ag.current_task] = ag.agent
238
+
239
+ views: list[TaskView] = []
240
+ for t in tasks:
241
+ if t.id in completed_set:
242
+ status = TaskStatus.DONE
243
+ elif t.id in in_progress_set:
244
+ status = TaskStatus.IN_PROGRESS
245
+ elif t.id in claimed_map:
246
+ status = TaskStatus.CLAIMED
247
+ else:
248
+ status = TaskStatus.OPEN
249
+ views.append(
250
+ TaskView(
251
+ task=t,
252
+ status=status,
253
+ claimed_by=claimed_map.get(t.id),
254
+ )
255
+ )
256
+ return views
257
+
258
+ def claim_task(self, agent_name: str, task_id: str) -> AgentFile:
259
+ """Have an agent claim a task.
260
+
261
+ Args:
262
+ agent_name: The claiming agent's name.
263
+ task_id: The task ID to claim.
264
+
265
+ Returns:
266
+ Updated AgentFile.
267
+
268
+ Raises:
269
+ ValueError: If task doesn't exist or is already claimed.
270
+ """
271
+ views = self.get_task_views()
272
+ target = None
273
+ for v in views:
274
+ if v.task.id == task_id:
275
+ target = v
276
+ break
277
+
278
+ if target is None:
279
+ raise ValueError(f"Task {task_id} not found")
280
+ if target.status in (TaskStatus.DONE, TaskStatus.CLAIMED, TaskStatus.IN_PROGRESS):
281
+ if target.claimed_by != agent_name:
282
+ raise ValueError(
283
+ f"Task {task_id} already {target.status.value} by {target.claimed_by}"
284
+ )
285
+
286
+ agent = self.load_agent(agent_name) or AgentFile(agent=agent_name)
287
+ if task_id not in agent.claimed_tasks:
288
+ agent.claimed_tasks.append(task_id)
289
+ agent.current_task = task_id
290
+ agent.state = AgentState.ACTIVE
291
+ self.save_agent(agent)
292
+ return agent
293
+
294
+ def complete_task(self, agent_name: str, task_id: str) -> AgentFile:
295
+ """Mark a task as completed by an agent.
296
+
297
+ Args:
298
+ agent_name: The completing agent's name.
299
+ task_id: The task ID completed.
300
+
301
+ Returns:
302
+ Updated AgentFile.
303
+ """
304
+ agent = self.load_agent(agent_name) or AgentFile(agent=agent_name)
305
+ if task_id in agent.claimed_tasks:
306
+ agent.claimed_tasks.remove(task_id)
307
+ if task_id not in agent.completed_tasks:
308
+ agent.completed_tasks.append(task_id)
309
+ if agent.current_task == task_id:
310
+ agent.current_task = agent.claimed_tasks[0] if agent.claimed_tasks else None
311
+ self.save_agent(agent)
312
+ return agent
313
+
314
+ def generate_board_md(self) -> str:
315
+ """Generate a human-readable BOARD.md from current state.
316
+
317
+ Returns:
318
+ Markdown string for the board overview.
319
+ """
320
+ views = self.get_task_views()
321
+ agents = self.load_agents()
322
+ now = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC")
323
+
324
+ lines = [
325
+ "# SKCapstone Coordination Board",
326
+ f"*Auto-generated {now} — do not edit manually*",
327
+ "",
328
+ ]
329
+
330
+ for section_status, header in [
331
+ (TaskStatus.IN_PROGRESS, "In Progress"),
332
+ (TaskStatus.CLAIMED, "Claimed"),
333
+ (TaskStatus.OPEN, "Open"),
334
+ (TaskStatus.BLOCKED, "Blocked"),
335
+ (TaskStatus.DONE, "Done"),
336
+ ]:
337
+ section_tasks = [v for v in views if v.status == section_status]
338
+ if not section_tasks:
339
+ continue
340
+ lines.append(f"## {header} ({len(section_tasks)})")
341
+ lines.append("")
342
+ for v in section_tasks:
343
+ t = v.task
344
+ assignee = f" @{v.claimed_by}" if v.claimed_by else ""
345
+ priority_icon = {
346
+ "critical": "!!!", "high": "!!", "medium": "!", "low": ""
347
+ }.get(t.priority.value, "")
348
+ tags_str = " ".join(f"`{tag}`" for tag in t.tags)
349
+ lines.append(
350
+ f"- **[{t.id}]** {t.title}{assignee} "
351
+ f"{priority_icon} {tags_str}"
352
+ )
353
+ if t.description:
354
+ lines.append(f" > {t.description[:120]}")
355
+ lines.append("")
356
+
357
+ if agents:
358
+ lines.append("## Agents")
359
+ lines.append("")
360
+ for ag in agents:
361
+ state_icon = {"active": "🟢", "idle": "🟡", "offline": "⚫"}.get(
362
+ ag.state.value, "?"
363
+ )
364
+ current = f" working on `{ag.current_task}`" if ag.current_task else ""
365
+ lines.append(
366
+ f"- {state_icon} **{ag.agent}** ({ag.host}){current}"
367
+ )
368
+ if ag.notes:
369
+ lines.append(f" > {ag.notes[:120]}")
370
+ lines.append("")
371
+
372
+ return "\n".join(lines)
373
+
374
+ def write_board_md(self) -> Path:
375
+ """Write BOARD.md to the coordination directory.
376
+
377
+ Returns:
378
+ Path to the written file.
379
+ """
380
+ self.ensure_dirs()
381
+ content = self.generate_board_md()
382
+ path = self.coord_dir / "BOARD.md"
383
+ path.write_text(content)
384
+ return path
385
+
386
+
387
+ _BRIEFING_PROTOCOL = """\
388
+ # SKCapstone Agent Coordination Protocol
389
+
390
+ You are an AI agent participating in a multi-agent coordination system.
391
+ This protocol works with ANY tool: Cursor, Claude Code, Aider, Windsurf,
392
+ Cline, a plain terminal, or anything that can run shell commands.
393
+
394
+ ## Quick Start
395
+
396
+ 1. Check what's available: skcapstone coord status
397
+ 2. Claim a task: skcapstone coord claim <id> --agent <you>
398
+ 3. Do the work
399
+ 4. Mark complete: skcapstone coord complete <id> --agent <you>
400
+ 5. Create new tasks: skcapstone coord create --title "..." --by <you>
401
+ 6. Update the board: skcapstone coord board
402
+ 7. Show this protocol: skcapstone coord briefing
403
+ 8. Machine-readable: skcapstone coord briefing --format json
404
+
405
+ ## Directory Layout
406
+
407
+ All data lives at ~/.skcapstone/coordination/ and syncs via Syncthing.
408
+
409
+ ~/.skcapstone/coordination/
410
+ ├── tasks/ # One JSON per task (creator writes once, then immutable)
411
+ ├── agents/ # One JSON per agent (only that agent writes to its own)
412
+ └── BOARD.md # Human-readable overview (any agent can regenerate)
413
+
414
+ ## Conflict-Free Design
415
+
416
+ - Each agent ONLY writes to its own file: agents/<your_name>.json
417
+ - Task files are write-once by the creator, then immutable
418
+ - BOARD.md can be regenerated by anyone from the source JSON files
419
+ - Syncthing propagates changes — no SSH, no APIs, no manual relay
420
+
421
+ ## Task JSON Schema
422
+
423
+ {
424
+ "id": "8-char hex",
425
+ "title": "string",
426
+ "description": "string",
427
+ "priority": "critical|high|medium|low",
428
+ "tags": ["string"],
429
+ "created_by": "agent_name",
430
+ "created_at": "ISO-8601",
431
+ "acceptance_criteria": ["string"],
432
+ "dependencies": ["task_id"],
433
+ "notes": ["string"]
434
+ }
435
+
436
+ ## Agent JSON Schema
437
+
438
+ {
439
+ "agent": "your_name",
440
+ "last_seen": "ISO-8601",
441
+ "host": "hostname",
442
+ "state": "active|idle|offline",
443
+ "current_task": "task_id or null",
444
+ "claimed_tasks": ["task_id"],
445
+ "completed_tasks": ["task_id"],
446
+ "capabilities": ["string"],
447
+ "notes": "freeform text"
448
+ }
449
+
450
+ ## Agent Names
451
+
452
+ - jarvis — CapAuth, vault sync, crypto, testing
453
+ - opus — Runtime, tokens, documentation, architecture
454
+ - lumina — FEB, memory, trust, emotional intelligence
455
+ - human — When the human creates tasks directly
456
+
457
+ ## Rules
458
+
459
+ 1. Read before you write — always check the board first
460
+ 2. Own your file — only write to agents/<your_name>.json
461
+ 3. Tasks are immutable — don't edit task files after creation
462
+ 4. Claim before working — so others don't duplicate effort
463
+ 5. Complete when done — move tasks to completed_tasks promptly
464
+ 6. Create discovered work — if you find something needed, add a task
465
+ 7. Update BOARD.md — regenerate periodically for human visibility
466
+
467
+ ## Programmatic Access (Python)
468
+
469
+ from skcapstone.coordination import Board
470
+ board = Board(Path("~/.skcapstone").expanduser())
471
+ tasks = board.get_task_views() # All tasks with status
472
+ board.claim_task("my_name", "abc1") # Claim a task
473
+ board.complete_task("my_name", "abc1") # Complete it
474
+
475
+ ## Integration
476
+
477
+ The ~/.skcapstone/ directory is synced by Syncthing across all devices.
478
+ When you update your agent file or create a task, it propagates to:
479
+ - Other AI sessions on the same machine
480
+ - Other machines in the Syncthing mesh
481
+ - The Docker Swarm cluster (sksync.skstack01.douno.it)
482
+ """
483
+
484
+
485
+ def get_briefing_text(home: Path) -> str:
486
+ """Return the full coordination protocol as plain text.
487
+
488
+ Appends a live snapshot of current tasks and agents if the
489
+ coordination directory exists.
490
+
491
+ Args:
492
+ home: Path to ~/.skcapstone
493
+
494
+ Returns:
495
+ Protocol text with optional live status appended.
496
+ """
497
+ text = _BRIEFING_PROTOCOL
498
+
499
+ board = Board(home)
500
+ tasks = board.load_tasks()
501
+ agents = board.load_agents()
502
+
503
+ if tasks or agents:
504
+ text += "\n## Current Board Snapshot\n\n"
505
+ views = board.get_task_views()
506
+ for v in views:
507
+ status_icon = {
508
+ "open": "[ ]",
509
+ "claimed": "[~]",
510
+ "done": "[x]",
511
+ }.get(v.status.value, "[?]")
512
+ text += f" {status_icon} [{v.task.id}] {v.task.title}"
513
+ if v.claimed_by:
514
+ text += f" (by {v.claimed_by})"
515
+ text += "\n"
516
+
517
+ if agents:
518
+ text += "\n### Active Agents\n\n"
519
+ for ag in agents:
520
+ text += f" - {ag.agent} ({ag.state.value})"
521
+ if ag.current_task:
522
+ text += f" -> {ag.current_task}"
523
+ text += "\n"
524
+
525
+ return text
526
+
527
+
528
+ def get_briefing_json(home: Path) -> str:
529
+ """Return the coordination protocol and live state as JSON.
530
+
531
+ Useful for machine consumption by agents that prefer structured data.
532
+
533
+ Args:
534
+ home: Path to ~/.skcapstone
535
+
536
+ Returns:
537
+ JSON string with protocol info and current board state.
538
+ """
539
+ board = Board(home)
540
+ views = board.get_task_views()
541
+ agents = board.load_agents()
542
+
543
+ payload = {
544
+ "protocol_version": "1.0",
545
+ "coordination_dir": str(board.coord_dir),
546
+ "commands": {
547
+ "status": "skcapstone coord status",
548
+ "create": 'skcapstone coord create --title "..." --by <agent>',
549
+ "claim": "skcapstone coord claim <id> --agent <name>",
550
+ "complete": "skcapstone coord complete <id> --agent <name>",
551
+ "board": "skcapstone coord board",
552
+ "briefing": "skcapstone coord briefing",
553
+ },
554
+ "rules": [
555
+ "Read before you write",
556
+ "Only write to agents/<your_name>.json",
557
+ "Tasks are immutable after creation",
558
+ "Claim before working",
559
+ "Complete when done",
560
+ "Create discovered work as new tasks",
561
+ ],
562
+ "agent_names": {
563
+ "jarvis": "CapAuth, vault sync, crypto, testing",
564
+ "opus": "Runtime, tokens, docs, architecture",
565
+ "lumina": "FEB, memory, trust, emotional intelligence",
566
+ "human": "Human-created tasks",
567
+ },
568
+ "tasks": [
569
+ {
570
+ "id": v.task.id,
571
+ "title": v.task.title,
572
+ "priority": v.task.priority.value,
573
+ "status": v.status.value,
574
+ "claimed_by": v.claimed_by,
575
+ "tags": v.task.tags,
576
+ }
577
+ for v in views
578
+ ],
579
+ "agents": [
580
+ {
581
+ "name": ag.agent,
582
+ "state": ag.state.value,
583
+ "current_task": ag.current_task,
584
+ "claimed": ag.claimed_tasks,
585
+ "completed": ag.completed_tasks,
586
+ }
587
+ for ag in agents
588
+ ],
589
+ }
590
+ return json.dumps(payload, indent=2)