fableforge-agent 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,63 @@
1
+ """
2
+ FableForge Agent — Self-improving AI agent framework with behavioral
3
+ observability and trust protocols.
4
+
5
+ Built on Hermes Agent (NousResearch) with Tiers 3-4: Agent Control Plane,
6
+ Behavioral Observability, and Bidirectional Trust Protocols.
7
+
8
+ Author: KingLabsA
9
+ - GitHub: https://github.com/KingLabsA
10
+ - Twitter: https://x.com/KingLabsA
11
+ - Email: kinglabsa@users.noreply.github.com
12
+ """
13
+
14
+ __version__ = "0.1.0"
15
+ __author__ = "KingLabsA"
16
+
17
+ # ── Re-exported Hermes symbols ──────────────────────────────────────────────
18
+ try:
19
+ from hermes_agent import AIAgent # noqa: F401
20
+ except ImportError:
21
+ try:
22
+ from run_agent import AIAgent # noqa: F401
23
+ except ImportError:
24
+ AIAgent = None # graceful fallback for partial installs
25
+
26
+ try:
27
+ from hermes_agent import ConversationLoop # noqa: F401
28
+ except ImportError:
29
+ try:
30
+ from agent.conversation_loop import ConversationLoop # noqa: F401
31
+ except ImportError:
32
+ ConversationLoop = None
33
+
34
+ try:
35
+ from hermes_agent import ToolRegistry # noqa: F401
36
+ except ImportError:
37
+ try:
38
+ from tools.registry import ToolRegistry, registry # noqa: F401
39
+ except ImportError:
40
+ ToolRegistry = None
41
+
42
+ try:
43
+ from hermes_agent import SessionDB # noqa: F401
44
+ except ImportError:
45
+ try:
46
+ from hermes_state import SessionDB # noqa: F401
47
+ except ImportError:
48
+ SessionDB = None
49
+
50
+ # ── FableForge additions ────────────────────────────────────────────────────
51
+ from fableforge_agent.control_plane.core import ControlPlane
52
+ from fableforge_agent.observability.core import BehavioralObservability
53
+ from fableforge_agent.trust.core import TrustProtocol
54
+
55
+ __all__ = [
56
+ "AIAgent",
57
+ "ConversationLoop",
58
+ "ToolRegistry",
59
+ "SessionDB",
60
+ "ControlPlane",
61
+ "BehavioralObservability",
62
+ "TrustProtocol",
63
+ ]
@@ -0,0 +1,184 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ FableForge Agent CLI — wraps Hermes Agent and adds FableForge commands for
4
+ the control plane, observability, trust, and audit.
5
+
6
+ Author: KingLabsA
7
+ - GitHub: https://github.com/KingLabsA
8
+ - Twitter: https://x.com/KingLabsA
9
+ - Email: kinglabsa@users.noreply.github.com
10
+ """
11
+
12
+ import argparse
13
+ import json
14
+ import os
15
+ import sys
16
+
17
+
18
+ def cmd_control_plane(args: argparse.Namespace) -> None:
19
+ port = args.port
20
+ host = args.host
21
+ try:
22
+ import uvicorn
23
+ from fastapi import FastAPI
24
+
25
+ app = FastAPI(title="FableForge Control Plane")
26
+
27
+ @app.get("/health")
28
+ def health():
29
+ return {"status": "ok"}
30
+
31
+ @app.get("/audit")
32
+ def get_audit():
33
+ from fableforge_agent.control_plane import AuditTrail
34
+ trail = AuditTrail()
35
+ return {"entries": trail.export("json")}
36
+
37
+ @app.post("/sandbox/execute")
38
+ async def sandbox_execute(body: dict):
39
+ from fableforge_agent.control_plane import Sandbox
40
+ sandbox = Sandbox(sandbox_type=body.get("backend", "mock"))
41
+ result = sandbox.execute(body.get("code", ""), timeout=body.get("timeout", 30))
42
+ return result.__dict__
43
+
44
+ uvicorn.run(app, host=host, port=port, log_level="info")
45
+ except ImportError as e:
46
+ print(f"Error: missing dependency — {e}", file=sys.stderr)
47
+ print("Install with: pip install 'fableforge-agent[server]'", file=sys.stderr)
48
+ sys.exit(1)
49
+
50
+
51
+ def cmd_audit(args: argparse.Namespace) -> None:
52
+ from fableforge_agent.control_plane import AuditTrail
53
+
54
+ trail = AuditTrail()
55
+ export_path = args.export
56
+ fmt = "json" if export_path and export_path.endswith(".json") else "markdown"
57
+ output = trail.export(fmt=fmt)
58
+ if export_path:
59
+ with open(export_path, "w") as f:
60
+ f.write(output)
61
+ print(f"Audit exported to {export_path}")
62
+ else:
63
+ print(output)
64
+
65
+
66
+ def cmd_trust(args: argparse.Namespace) -> None:
67
+ from fableforge_agent.trust import TrustProtocol
68
+
69
+ agent_id = args.agent_id or "cli-agent"
70
+ trust = TrustProtocol(agent_id=agent_id)
71
+ action = args.action
72
+
73
+ if action == "register":
74
+ pubkey = args.pubkey
75
+ if pubkey and os.path.exists(pubkey):
76
+ with open(pubkey) as f:
77
+ key_content = f.read().strip()
78
+ else:
79
+ key_content = pubkey or trust.public_key
80
+ trust.registry.register(agent_id, key_content, {"source": "cli"})
81
+ print(f"Registered agent: {agent_id}")
82
+ print(f" Public key fingerprint: {trust.public_key_fingerprint}")
83
+ elif action == "list":
84
+ agents = trust.registry.list_agents()
85
+ print("Registered agents:")
86
+ for aid in agents:
87
+ identity = trust.registry.resolve(aid)
88
+ print(f" - {aid} (trust level: {identity.trust_level if identity else '?'})")
89
+ elif action == "verify":
90
+ challenge = f"challenge-{agent_id}"
91
+ response = hashlib_response = __import__("hashlib").sha256(
92
+ f"{agent_id}:{challenge}:{trust.public_key}".encode()
93
+ ).hexdigest()
94
+ valid = trust.registry.verify_identity(agent_id, challenge, response)
95
+ print(f"Identity verification: {'PASS' if valid else 'FAIL'}")
96
+ else:
97
+ print(f"Unknown trust action: {action}")
98
+
99
+
100
+ def cmd_observe(args: argparse.Namespace) -> None:
101
+ try:
102
+ import uvicorn
103
+ from fastapi import FastAPI, WebSocket
104
+
105
+ app = FastAPI(title="FableForge Observability")
106
+
107
+ @app.websocket("/ws/observe")
108
+ async def observe_ws(websocket: WebSocket):
109
+ await websocket.accept()
110
+ from fableforge_agent.observability import BehavioralObservability
111
+ obs = BehavioralObservability(agent_id=args.agent_id or "observed")
112
+ while True:
113
+ data = await websocket.receive_json()
114
+ observation = obs.observe(data.get("action", "unknown"), data)
115
+ await websocket.send_json(observation.__dict__)
116
+
117
+ uvicorn.run(app, host=args.host, port=args.port, log_level="info")
118
+ except ImportError as e:
119
+ print(f"Error: missing dependency — {e}", file=sys.stderr)
120
+ sys.exit(1)
121
+
122
+
123
+ def main() -> None:
124
+ parser = argparse.ArgumentParser(
125
+ prog="fableforge",
126
+ description="FableForge Agent — self-improving AI agent with behavioral observability and trust",
127
+ epilog="Author: KingLabsA — https://github.com/KingLabsA",
128
+ )
129
+ parser.add_argument("--version", action="version", version="0.1.0")
130
+
131
+ subparsers = parser.add_subparsers(dest="command", help="Subcommands")
132
+
133
+ # control-plane
134
+ cp_parser = subparsers.add_parser("control-plane", help="Start the control plane API server")
135
+ cp_parser.add_argument("--host", default="127.0.0.1")
136
+ cp_parser.add_argument("--port", type=int, default=8000)
137
+
138
+ # audit
139
+ audit_parser = subparsers.add_parser("audit", help="Export audit trail")
140
+ audit_parser.add_argument("--export", dest="export_path", help="Export to file (.json or .md)")
141
+
142
+ # trust
143
+ trust_parser = subparsers.add_parser("trust", help="Trust registry management")
144
+ trust_sub = trust_parser.add_subparsers(dest="action", required=True)
145
+ reg_parser = trust_sub.add_parser("register", help="Register an agent")
146
+ reg_parser.add_argument("--agent-id", required=True)
147
+ reg_parser.add_argument("--pubkey", help="Public key path")
148
+ list_parser = trust_sub.add_parser("list", help="List registered agents")
149
+ list_parser.add_argument("--agent-id", default="cli-agent")
150
+ verify_parser = trust_sub.add_parser("verify", help="Verify identity")
151
+ verify_parser.add_argument("--agent-id", default="cli-agent")
152
+
153
+ # observe
154
+ obs_parser = subparsers.add_parser("observe", help="Start behavioral observability dashboard")
155
+ obs_parser.add_argument("--host", default="127.0.0.1")
156
+ obs_parser.add_argument("--port", type=int, default=8765)
157
+ obs_parser.add_argument("--agent-id", default="observed")
158
+
159
+ args = parser.parse_args()
160
+
161
+ if args.command == "control-plane":
162
+ cmd_control_plane(args)
163
+ elif args.command == "audit":
164
+ cmd_audit(args)
165
+ elif args.command == "trust":
166
+ cmd_trust(args)
167
+ elif args.command == "observe":
168
+ cmd_observe(args)
169
+ else:
170
+ # Default: delegate to Hermes CLI
171
+ try:
172
+ from hermes_cli.cli import main as hermes_main
173
+ hermes_main()
174
+ except ImportError:
175
+ try:
176
+ from cli import main as hermes_main
177
+ hermes_main()
178
+ except ImportError:
179
+ print("Hermes Agent not installed. Install with: pip install hermes-agent", file=sys.stderr)
180
+ sys.exit(1)
181
+
182
+
183
+ if __name__ == "__main__":
184
+ main()
@@ -0,0 +1,37 @@
1
+ """
2
+ Control Plane — Tier 3 of FableForge Agent.
3
+
4
+ Sandboxed execution, audit trails, incident response, authentication, and
5
+ credential management for agent workloads.
6
+
7
+ Author: KingLabsA
8
+ - GitHub: https://github.com/KingLabsA
9
+ - Twitter: https://x.com/KingLabsA
10
+ - Email: kinglabsa@users.noreply.github.com
11
+ """
12
+
13
+ from fableforge_agent.control_plane.core import (
14
+ ControlPlane,
15
+ Sandbox,
16
+ SandboxPolicy,
17
+ SandboxResult,
18
+ AuditTrail,
19
+ AuditEntry,
20
+ IncidentResponse,
21
+ )
22
+ from fableforge_agent.control_plane.auth import (
23
+ OAuthGate,
24
+ CredentialBroker,
25
+ )
26
+
27
+ __all__ = [
28
+ "ControlPlane",
29
+ "Sandbox",
30
+ "SandboxPolicy",
31
+ "SandboxResult",
32
+ "AuditTrail",
33
+ "AuditEntry",
34
+ "IncidentResponse",
35
+ "OAuthGate",
36
+ "CredentialBroker",
37
+ ]
@@ -0,0 +1,152 @@
1
+ """
2
+ Authentication and credential management for the agent control plane.
3
+
4
+ Author: KingLabsA
5
+ - GitHub: https://github.com/KingLabsA
6
+ - Twitter: https://x.com/KingLabsA
7
+ - Email: kinglabsa@users.noreply.github.com
8
+ """
9
+
10
+ import json
11
+ import os
12
+ import time
13
+ import uuid
14
+ from typing import Dict, List, Optional
15
+
16
+
17
+ # ═══════════════════════════════════════════════════════════════════════════════
18
+ # OAuthGate
19
+ # ═══════════════════════════════════════════════════════════════════════════════
20
+
21
+ class OAuthGate:
22
+ """Pluggable OAuth authentication and authorization for agent access control."""
23
+
24
+ def __init__(self, secrets_path: Optional[str] = None):
25
+ self._tokens: Dict[str, Dict] = {}
26
+ self._clients: Dict[str, str] = {}
27
+ self.secrets_path = secrets_path or os.path.expanduser("~/.fableforge/secrets.json")
28
+ self._load_secrets()
29
+
30
+ def _load_secrets(self) -> None:
31
+ try:
32
+ with open(self.secrets_path) as f:
33
+ data = json.load(f)
34
+ self._clients = data.get("clients", {})
35
+ except (FileNotFoundError, json.JSONDecodeError):
36
+ self._clients = {}
37
+
38
+ def _save_secrets(self) -> None:
39
+ os.makedirs(os.path.dirname(self.secrets_path), exist_ok=True)
40
+ with open(self.secrets_path, "w") as f:
41
+ json.dump({"clients": self._clients}, f, indent=2)
42
+
43
+ def authenticate(self, credentials: Dict) -> bool:
44
+ client_id = credentials.get("client_id")
45
+ client_secret = credentials.get("client_secret")
46
+ stored = self._clients.get(client_id)
47
+ if stored and stored == client_secret:
48
+ return True
49
+ if not self._clients:
50
+ return True
51
+ return False
52
+
53
+ def authorize(self, agent_id: str, action: str) -> bool:
54
+ token = self._tokens.get(agent_id)
55
+ if not token:
56
+ return False
57
+ if token.get("expires_at", 0) < time.time():
58
+ return False
59
+ return action in token.get("scopes", [])
60
+
61
+ def issue_token(self, agent_id: str, scopes: List[str]) -> str:
62
+ token = uuid.uuid4().hex
63
+ self._tokens[agent_id] = {
64
+ "token": token,
65
+ "scopes": scopes,
66
+ "expires_at": time.time() + 3600,
67
+ "issued_at": time.time(),
68
+ }
69
+ return token
70
+
71
+ def verify_token(self, token: str) -> Dict:
72
+ for agent_id, data in self._tokens.items():
73
+ if data.get("token") == token:
74
+ if data["expires_at"] > time.time():
75
+ return {"agent_id": agent_id, "scopes": data["scopes"], "valid": True}
76
+ return {"agent_id": agent_id, "valid": False, "reason": "expired"}
77
+ return {"valid": False, "reason": "unknown_token"}
78
+
79
+
80
+ # ═══════════════════════════════════════════════════════════════════════════════
81
+ # CredentialBroker
82
+ # ═════════════════════════════════════════════════════════════════════════════════
83
+
84
+ class CredentialBroker:
85
+ """Secure credential management with encrypted storage."""
86
+
87
+ def __init__(self, vault_path: Optional[str] = None):
88
+ self.vault_path = vault_path or os.path.expanduser("~/.fableforge/vault.json")
89
+ self._vault: Dict[str, Dict] = {}
90
+ self._load_vault()
91
+
92
+ def _load_vault(self) -> None:
93
+ try:
94
+ with open(self.vault_path) as f:
95
+ raw = f.read()
96
+ self._vault = json.loads(self._decrypt(raw))
97
+ except (FileNotFoundError, json.JSONDecodeError, Exception):
98
+ self._vault = {}
99
+
100
+ def _save_vault(self) -> None:
101
+ os.makedirs(os.path.dirname(self.vault_path), exist_ok=True)
102
+ with open(self.vault_path, "w") as f:
103
+ f.write(self._encrypt(json.dumps(self._vault, indent=2)))
104
+
105
+ def _encrypt(self, plaintext: str) -> str:
106
+ try:
107
+ from cryptography.fernet import Fernet
108
+ key = self._get_or_create_key()
109
+ return Fernet(key).encrypt(plaintext.encode()).decode()
110
+ except ImportError:
111
+ return plaintext
112
+
113
+ def _decrypt(self, ciphertext: str) -> str:
114
+ try:
115
+ from cryptography.fernet import Fernet
116
+ key = self._get_or_create_key()
117
+ return Fernet(key).decrypt(ciphertext.encode()).decode()
118
+ except (ImportError, Exception):
119
+ return ciphertext
120
+
121
+ def _get_or_create_key(self) -> bytes:
122
+ key_path = os.path.expanduser("~/.fableforge/vault.key")
123
+ try:
124
+ with open(key_path, "rb") as f:
125
+ return f.read()
126
+ except FileNotFoundError:
127
+ try:
128
+ from cryptography.fernet import Fernet
129
+ key = Fernet.generate_key()
130
+ os.makedirs(os.path.dirname(key_path), exist_ok=True)
131
+ with open(key_path, "wb") as f:
132
+ f.write(key)
133
+ return key
134
+ except ImportError:
135
+ return b"insecure-fallback-key-do-not-use-in-production"
136
+
137
+ def store(self, service: str, credential: Dict) -> None:
138
+ self._vault[service] = credential
139
+ self._save_vault()
140
+
141
+ def retrieve(self, service: str) -> Optional[Dict]:
142
+ return self._vault.get(service)
143
+
144
+ def rotate(self, service: str) -> None:
145
+ if service in self._vault:
146
+ old = self._vault[service]
147
+ rotated = {k: f"{k}-rotated-{uuid.uuid4().hex[:8]}" for k in old}
148
+ self._vault[service] = rotated
149
+ self._save_vault()
150
+
151
+ def list_services(self) -> List[str]:
152
+ return list(self._vault.keys())