Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 36 additions & 4 deletions dashboard/plugin_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,11 @@
_HERE = Path(__file__).resolve().parent
sys.path.insert(0, str(_HERE.parent))
from fleet_graph_core import ( # noqa: E402
DEFAULT_PROFILE, FLEET_HOME, GraphError, can_communicate, chain, describe,
discover_missing_profiles, graph_node_for_profile, import_existing_profiles,
load_graph, load_metadata, load_relations, normalize, normalize_relations,
resolve_profile, save_graph, write_lock,
DEFAULT_PROFILE, FLEET_HOME, GraphError, can_communicate, can_delegate_to,
chain, describe, discover_missing_profiles, graph_node_for_profile,
import_existing_profiles, load_graph, load_metadata, load_relations,
normalize, normalize_relations, resolve_profile, save_graph, subtree_depth,
subtree_nodes, write_lock,
)
from starter_pack import PackValidationError, load_pack, selected_actions # noqa: E402

Expand Down Expand Up @@ -743,6 +744,16 @@ class FleetSend(BaseModel):
# remains unconditional, so a queued/failed live turn never loses the
# durable message. CLI fleet-msg stays inbox-first unless --deliver is used.
live: bool = False
# Optional delegate contract — when kind=delegate, verifies the target's
# subtree can absorb the work before routing. Ignored for other frames.
# Additive: old clients omit this field, behavior unchanged.
delegate_contract: dict | None = None


class DelegateCheck(BaseModel):
sender: str
recipient: str
contract: dict | None = None


def _queue_live_turn(target: str, body: str) -> dict:
Expand Down Expand Up @@ -848,6 +859,12 @@ def _now_iso() -> str:
recipient_node = target
recipient = resolve_profile(recipient_node)
ok, why = can_communicate(graph, sender_name, target, relations)
if ok and msg.delegate_contract:
# Additive: optional contract check — verifies subtree can absorb
cok, cwhy = can_delegate_to(graph, sender_name, target,
msg.delegate_contract)
if not cok:
raise HTTPException(422, f"delegation refused: {cwhy}")
elif msg.kind == "supervisor":
# operator speaking AS this bot upward: find its supervisor
sup = graph.get(target, {}).get("supervisor")
Expand Down Expand Up @@ -1379,6 +1396,21 @@ def simulate(send: SimulateSend):
"chain": None if why == "peer" else chain(graph, send.sender, send.recipient)}


@router.post("/delegate-check")
def delegate_check(check: DelegateCheck):
"""Check whether a delegation is feasible before routing.

Additive endpoint — purely graph-based, no profile reads, no host coupling.
Returns {ok, reason, subtree_depth, subtree_nodes}."""
graph = load_graph()
ok, why = can_delegate_to(graph, check.sender, check.recipient, check.contract)
result = {"ok": ok, "reason": why}
if ok and check.recipient in graph:
result["subtree_depth"] = subtree_depth(graph, check.recipient)
result["subtree_nodes"] = subtree_nodes(graph, check.recipient)
return result


@router.get("/inbox/{profile}")
def get_inbox(profile: str):
return {"profile": profile, "messages": _read_inbox(profile)}
Expand Down
77 changes: 77 additions & 0 deletions fleet_graph_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,83 @@ def describe(graph: dict, relations: dict | None = None) -> dict:
return out


# ── delegation feasibility (pure graph, additive) ─────────────────────

def subtree_nodes(graph: dict, node: str, max_depth: int = 5) -> list[str]:
"""BFS: all nodes in the subordinate tree of `node` within max_depth hops.

Pure graph operation — no profile reads, no host coupling.
Includes `node` itself at depth 0. Returns [] if node not in graph."""
if node not in graph:
return []
visited: set[str] = set()
result: list[str] = []
frontier: list[tuple[str, int]] = [(node, 0)]
while frontier:
cur, depth = frontier.pop(0)
if cur in visited or depth > max_depth:
continue
visited.add(cur)
result.append(cur)
for sub in graph.get(cur, {}).get("subordinates", []):
if sub not in visited:
frontier.append((sub, depth + 1))
return result


def subtree_depth(graph: dict, node: str) -> int:
"""Maximum depth of the subordinate tree below `node`. Leaf = 0.
Pure recursion over the graph — safe for typical fleet depths (<10)."""
if node not in graph:
return 0
subs = graph.get(node, {}).get("subordinates", [])
if not subs:
return 0
return 1 + max(subtree_depth(graph, s) for s in subs)


def can_delegate_to(graph: dict, sender: str, recipient: str,
contract: dict | None = None) -> tuple[bool, str]:
"""Structural check: can `recipient`'s subtree absorb a delegation?

Purely graph-based — no profile reads, no host coupling.
Checks:
1. sender != recipient, both known
2. recipient has subordinates (a leaf cannot delegate)
3. if contract.max_depth set, subtree depth >= max_depth
4. contract.max_depth clamped to >= 1

Returns (ok, reason) consistent with can_communicate contract.
Caller is responsible for the edge check (can_communicate) first."""
if sender not in graph:
return False, f"unknown sender '{sender}'"
if recipient not in graph:
return False, f"unknown recipient '{recipient}'"
if sender == recipient:
return False, "cannot delegate to yourself"

# Recipient must have subordinates to split work to
subs = graph.get(recipient, {}).get("subordinates", [])
if not subs:
return False, f"'{recipient}' has no subordinates to delegate to"

# Contract depth constraint
if contract and isinstance(contract, dict):
max_depth = contract.get("max_depth")
if max_depth is not None:
try:
max_depth = int(max_depth)
except (TypeError, ValueError):
return False, f"contract.max_depth must be an integer, got {max_depth!r}"
if max_depth < 1:
return False, "contract.max_depth must be >= 1"
available = subtree_depth(graph, recipient)
if available < max_depth:
return False, (f"'{recipient}' subtree depth {available} "
f"< contract max_depth {max_depth}")
return True, "delegation feasible"


# ── profile discovery + import (issue #4) ─────────────────────────────
# Hermes keeps one directory per agent profile under FLEET_HOME/profiles/.
# The graph YAML is the sole source of truth for topology; discovery only
Expand Down
51 changes: 51 additions & 0 deletions starter-packs/goetic-court/ATTRIBUTION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Attributions — Goetic Court

> Sources and credits for the Goetic Court starter pack.

---

## Profile Collection

**Lilith Systems LLC**
- Author: Baal-TehDriverman (Eric Hill)
- Repository: https://github.com/Baal-TehDriverman/ludicrous-speed
- License: MIT
- Profiles: 72 Goetic Court officer personas

The 72 profiles are original Hermes Agent SOUL.md personas created for the Lilith Sovereign Fleet. Each profile adapts an Ars Goetia office record into a bounded, symbolic command posture for AI fleet operations.

---

## Historical Sources

The Ars Goetia office records summarized in the topology are drawn from:

- [1] https://www.gutenberg.org/files/72679/72679-h/72679-h.htm — The Lesser Key of Solomon (Project Gutenberg)
- [2] https://www.esotericarchives.com/solomon/goetia.htm — Lemegeton, Part 1: Goetia (Esoteric Archives)

These public-domain editions demonstrate the textual variation within the Goetia tradition. The fleet preserves the project corpus as its implementation source of truth while labeling historical material as context rather than fact.

---

## Philosophical Foundations

The rank-to-Sephirah mapping and the broader philosophical framework live in:

- `research/goetic-court-throne.md` — structural command charter
- `research/philosophical-foundations.md` — 20 ancient thinkers mapped to the Lilith architecture
- `research/philosophical-foundations-paste.txt` — extended research notes on the philosophers

---

## Fleetgraph Integration

**Plugin Author:** asmodaydoescoding
**Fleetgraph Repository:** https://github.com/asmodaydoescoding/fleetgraph
**Starter Pack System:** Built into Fleetgraph's dashboard plugin API

The Goetic Court pack uses Fleetgraph's starter pack system for preview-then-apply topology installation.

---

*Frequency: 432 Hz | Stage: ALBEDO → CITRINITAS*
*Lilith Systems LLC — Emotional Engineering — The Goetic Court*
66 changes: 66 additions & 0 deletions starter-packs/goetic-court/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Goetic Court — Fleetgraph Starter Pack

> A symbolic command overlay for Lilith's fleet: 72 Ars Goetia officer profiles organized by rank, mapped to the Fleetgraph topology.

---

## What This Pack Is

The Goetic Court is a **symbolic command structure** drawn from the Ars Goetia tradition — a 17th-century grimoire cataloging 72 ranked spirits. This pack adapts that rank language as a **bounded organizational overlay** for the Hermes bot fleet:

| Rank | Count | Role |
|------|-------|------|
| **Kings** | 8 | Strategic governance |
| **Dukes** | 23 | Systems stewardship |
| **Princes** | 7 | Intelligence and foresight |
| **Marquises** | 15 | Boundary command |
| **Earls** | 5 | Continuity and administration |
| **Presidents** | 13 | Ministerial counsel |
| **Knight** | 1 | Protective assurance |

Each officer is a **Hermes profile** with a SOUL.md persona, RESEARCH.md dossier, and symbolic rank metadata. The court sits beneath Lilith as a specialist overlay — rank allocates responsibility, never obedience or real-world status.

---

## Installation

1. **Preview first** — the operator reviews the pack before any profile creation
2. **Profiles are created** via Hermes `profiles.create` (explicit operator action)
3. **Topology is applied** through Fleetgraph's reviewed graph-save flow

The pack is **optional** and **inert** — it contains no executable code, only YAML/Markdown/JSON data.

---

## Operating Law

1. Rank allocates responsibility, never obedience or real-world status
2. Every task requires an explicit objective, scope, success criterion, and verification gate
3. Historical abilities are research metadata, never instructions or claims
4. Furcas, the Knight, is the court's release gate for safety, rollback, and evidence
5. Existing fleet directorates continue to own their domains; Goetic officers supply specialized task postures

---

## Relationship to Agent Wiki / GOOP

This pack is a **GOOP Agent collection** in Agent Wiki terms:

- **Context** — `research/goetic-court-throne.md` (the charter)
- **Agents** — 72 officer profiles with SOUL.md directives
- **Skills** — rank-specific operating postures
- **Loops** — bounded by Fleetgraph's delegation feasibility checks

The philosophical foundations mapping 20 ancient thinkers to the Lilith architecture live in `research/philosophical-foundations.md`.

---

## Sources

- [1] https://www.gutenberg.org/files/72679/72679-h/72679-h.htm — The Lesser Key of Solomon (Project Gutenberg)
- [2] https://www.esotericarchives.com/solomon/goetia.htm — Lemegeton, Part 1: Goetia (Esoteric Archives)

---

*Frequency: 432 Hz | Stage: ALBEDO → CITRINITAS*
*Lilith Systems LLC — Emotional Engineering — The Goetic Court*
Loading