@drdeeks/character-kit 1.0.1

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 (123) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +178 -0
  3. package/deploy/ack_monitor.py +140 -0
  4. package/deploy/ack_watchdog.py +110 -0
  5. package/deploy/agent-character-monitor.service +21 -0
  6. package/deploy/agent-character-watchdog.service +21 -0
  7. package/deploy/agent-enforcer-proof.service +18 -0
  8. package/deploy/agent-enforcer.service +53 -0
  9. package/deploy/deploy-ack-services.sh +47 -0
  10. package/deploy/deploy-agent-enforcer.sh +133 -0
  11. package/deploy/proof-self-respawn.sh +59 -0
  12. package/node/bin/ack.js +383 -0
  13. package/node/bin/aik.js +520 -0
  14. package/node/bin/install.js +409 -0
  15. package/node/enforcer/agent_enforcer_daemon.js +690 -0
  16. package/node/examples/.agent/constitution.yaml +38 -0
  17. package/node/examples/.agent/enforcer.yaml +33 -0
  18. package/node/examples/.agent/habits/affirm_character_each_action.yaml +9 -0
  19. package/node/examples/.agent/habits/audit_not_silent.yaml +9 -0
  20. package/node/examples/.agent/habits/binding_map_one_core.yaml +9 -0
  21. package/node/examples/.agent/habits/character_hash_visible.yaml +9 -0
  22. package/node/examples/.agent/habits/check_duplication_before_debug.yaml +9 -0
  23. package/node/examples/.agent/habits/complete-thoroughly.yaml +16 -0
  24. package/node/examples/.agent/habits/consistent-info-across-files.yaml +18 -0
  25. package/node/examples/.agent/habits/document-for-next-agent.yaml +15 -0
  26. package/node/examples/.agent/habits/documented_rollback.yaml +9 -0
  27. package/node/examples/.agent/habits/drift_signal_detection.yaml +9 -0
  28. package/node/examples/.agent/habits/due-diligence.yaml +16 -0
  29. package/node/examples/.agent/habits/enterprise-grade-modular.yaml +16 -0
  30. package/node/examples/.agent/habits/fail_closed_tamper_evident.yaml +9 -0
  31. package/node/examples/.agent/habits/forever_one_idea.yaml +9 -0
  32. package/node/examples/.agent/habits/graceful_degradation.yaml +9 -0
  33. package/node/examples/.agent/habits/idempotent_operations.yaml +9 -0
  34. package/node/examples/.agent/habits/interactive-no-stalls.yaml +16 -0
  35. package/node/examples/.agent/habits/layered_not_rewritten.yaml +9 -0
  36. package/node/examples/.agent/habits/lossless_consolidation.yaml +9 -0
  37. package/node/examples/.agent/habits/no-credential-leak.yaml +41 -0
  38. package/node/examples/.agent/habits/no-deception.yaml +14 -0
  39. package/node/examples/.agent/habits/no_hold_narration.yaml +9 -0
  40. package/node/examples/.agent/habits/one_concern_per_file.yaml +9 -0
  41. package/node/examples/.agent/habits/optimized-robust.yaml +16 -0
  42. package/node/examples/.agent/habits/registered_plugin_not_string.yaml +9 -0
  43. package/node/examples/.agent/habits/rename_as_layer_op.yaml +9 -0
  44. package/node/examples/.agent/habits/resolve-root-cause.yaml +15 -0
  45. package/node/examples/.agent/habits/rigorous-commits-no-push.yaml +18 -0
  46. package/node/examples/.agent/habits/safe-deletion-via-trash.yaml +20 -0
  47. package/node/examples/.agent/habits/safe_file_permissions.yaml +9 -0
  48. package/node/examples/.agent/habits/self-healing-portability.yaml +14 -0
  49. package/node/examples/.agent/habits/shippable-pride.yaml +15 -0
  50. package/node/examples/.agent/habits/single_source_of_truth.yaml +9 -0
  51. package/node/examples/.agent/habits/test_of_forever.yaml +9 -0
  52. package/node/examples/.agent/habits/timeout_and_retry.yaml +9 -0
  53. package/node/examples/.agent/habits/track_defects_openly.yaml +9 -0
  54. package/node/examples/.agent/habits/validate-against-real-source.yaml +14 -0
  55. package/node/examples/.agent/habits/verify-against-docs.yaml +15 -0
  56. package/node/examples/.agent/habits/verify-functionality-not-syntax.yaml +15 -0
  57. package/node/examples/.agent/habits/versioning-discipline.yaml +18 -0
  58. package/node/package.json +53 -0
  59. package/node/src/enforcer/client.js +149 -0
  60. package/node/src/hooks/character.js +286 -0
  61. package/node/src/index.js +24 -0
  62. package/node/src/knowledge/indexer.js +486 -0
  63. package/node/src/knowledge/semantic.js +154 -0
  64. package/node/src/memory/index.js +355 -0
  65. package/node/tests/character.test.js +80 -0
  66. package/node/tests/habit-create.test.js +67 -0
  67. package/node/tests/habits-default.test.js +68 -0
  68. package/node/tests/install-chain.test.js +19 -0
  69. package/node/tests/install.test.js +104 -0
  70. package/package.json +65 -0
  71. package/python/agent_character_kit/__init__.py +388 -0
  72. package/python/agent_character_kit/__main__.py +333 -0
  73. package/python/agent_character_kit/_yaml.py +33 -0
  74. package/python/agent_character_kit/enforcer.py +379 -0
  75. package/python/agent_character_kit/memory.py +257 -0
  76. package/python/agent_character_kit/semantic.py +126 -0
  77. package/python/example_workspace/.agent/habits/affirm_character_each_action.yaml +9 -0
  78. package/python/example_workspace/.agent/habits/audit_not_silent.yaml +9 -0
  79. package/python/example_workspace/.agent/habits/binding_map_one_core.yaml +9 -0
  80. package/python/example_workspace/.agent/habits/character_hash_visible.yaml +9 -0
  81. package/python/example_workspace/.agent/habits/check_duplication_before_debug.yaml +9 -0
  82. package/python/example_workspace/.agent/habits/complete-thoroughly.yaml +16 -0
  83. package/python/example_workspace/.agent/habits/consistent-info-across-files.yaml +18 -0
  84. package/python/example_workspace/.agent/habits/document-for-next-agent.yaml +15 -0
  85. package/python/example_workspace/.agent/habits/documented_rollback.yaml +9 -0
  86. package/python/example_workspace/.agent/habits/drift_signal_detection.yaml +9 -0
  87. package/python/example_workspace/.agent/habits/due-diligence.yaml +16 -0
  88. package/python/example_workspace/.agent/habits/enterprise-grade-modular.yaml +16 -0
  89. package/python/example_workspace/.agent/habits/fail_closed_tamper_evident.yaml +9 -0
  90. package/python/example_workspace/.agent/habits/forever_one_idea.yaml +9 -0
  91. package/python/example_workspace/.agent/habits/graceful_degradation.yaml +9 -0
  92. package/python/example_workspace/.agent/habits/idempotent_operations.yaml +9 -0
  93. package/python/example_workspace/.agent/habits/interactive-no-stalls.yaml +16 -0
  94. package/python/example_workspace/.agent/habits/layered_not_rewritten.yaml +9 -0
  95. package/python/example_workspace/.agent/habits/lossless_consolidation.yaml +9 -0
  96. package/python/example_workspace/.agent/habits/no-credential-leak.yaml +41 -0
  97. package/python/example_workspace/.agent/habits/no-deception.yaml +14 -0
  98. package/python/example_workspace/.agent/habits/no_hold_narration.yaml +9 -0
  99. package/python/example_workspace/.agent/habits/one_concern_per_file.yaml +9 -0
  100. package/python/example_workspace/.agent/habits/optimized-robust.yaml +16 -0
  101. package/python/example_workspace/.agent/habits/registered_plugin_not_string.yaml +9 -0
  102. package/python/example_workspace/.agent/habits/rename_as_layer_op.yaml +9 -0
  103. package/python/example_workspace/.agent/habits/resolve-root-cause.yaml +15 -0
  104. package/python/example_workspace/.agent/habits/rigorous-commits-no-push.yaml +18 -0
  105. package/python/example_workspace/.agent/habits/safe-deletion-via-trash.yaml +20 -0
  106. package/python/example_workspace/.agent/habits/safe_file_permissions.yaml +9 -0
  107. package/python/example_workspace/.agent/habits/self-healing-portability.yaml +14 -0
  108. package/python/example_workspace/.agent/habits/shippable-pride.yaml +15 -0
  109. package/python/example_workspace/.agent/habits/single_source_of_truth.yaml +9 -0
  110. package/python/example_workspace/.agent/habits/test_of_forever.yaml +9 -0
  111. package/python/example_workspace/.agent/habits/timeout_and_retry.yaml +9 -0
  112. package/python/example_workspace/.agent/habits/track_defects_openly.yaml +9 -0
  113. package/python/example_workspace/.agent/habits/validate-against-real-source.yaml +14 -0
  114. package/python/example_workspace/.agent/habits/verify-against-docs.yaml +15 -0
  115. package/python/example_workspace/.agent/habits/verify-functionality-not-syntax.yaml +15 -0
  116. package/python/example_workspace/.agent/habits/versioning-discipline.yaml +18 -0
  117. package/python/hermes_plugin/README.md +63 -0
  118. package/python/hermes_plugin/__init__.py +367 -0
  119. package/python/hermes_plugin/config.yaml +20 -0
  120. package/python/hermes_plugin/plugin.yaml +7 -0
  121. package/python/hermes_plugin/test_plugin.py +166 -0
  122. package/python/pyproject.toml +19 -0
  123. package/python/tests/python_parity_test.py +84 -0
@@ -0,0 +1,166 @@
1
+ """
2
+ Test: Agent Character Kit Hermes plugin (pre_tool_call enforcement, thin client).
3
+
4
+ The plugin is a THIN CLIENT to the enforcer daemon (single source of truth).
5
+ These tests prove:
6
+ 1. block test — when a daemon is reachable, allow/deny match its policy.
7
+ 2. fail-closed — when the daemon socket is DOWN, the plugin BLOCKS (never allows).
8
+ 3. manifest — compact {habit,prompt} manifest on every call (token-bounded),
9
+ on-demand get_habit returns full assert/evidence/logic proof,
10
+ daemon self-verify reports no defects.
11
+
12
+ Run from the agent-character-kit dir:
13
+ python3 -m pytest python/hermes_plugin/test_plugin.py -q
14
+ or directly:
15
+ python3 python/hermes_plugin/test_plugin.py
16
+ """
17
+
18
+ import asyncio
19
+ import importlib.util
20
+ import os
21
+ import pathlib
22
+ import socket
23
+ import subprocess
24
+ import sys
25
+ import tempfile
26
+ import time
27
+
28
+ _HERE = pathlib.Path(__file__).resolve().parent
29
+ _ROOT = _HERE.parent.parent # repo root (agent-character-kit/)
30
+ sys.path.insert(0, str(_ROOT))
31
+ sys.path.insert(0, str(_ROOT / "python"))
32
+ sys.path.insert(0, str(_HERE))
33
+
34
+ DAEMON = _ROOT / "node" / "enforcer" / "agent_enforcer_daemon.js"
35
+ NODE = os.environ.get("ACK_NODE", "node")
36
+
37
+
38
+ def _load_plugin():
39
+ spec = importlib.util.spec_from_file_location("aik_plugin", str(_HERE / "__init__.py"))
40
+ m = importlib.util.module_from_spec(spec)
41
+ spec.loader.exec_module(m)
42
+ return m
43
+
44
+
45
+ def _call_hook(m, cmd):
46
+ res = m._on_pre_tool_call(tool_name="Bash", args={"command": cmd})
47
+ # Hermes convention: None (or no action:block) means ALLOW.
48
+ return res or {"action": "allow"}
49
+
50
+
51
+ def _free_tcp_port():
52
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
53
+ s.bind(("127.0.0.1", 0))
54
+ port = s.getsockname()[1]
55
+ s.close()
56
+ return port
57
+
58
+
59
+ def _start_daemon(sock_addr: str, workspace: str = None):
60
+ """Spawn the real daemon (the single source of truth) on sock_addr.
61
+
62
+ workspace=None -> an EMPTY temp dir (proves usefulness out of the box,
63
+ embedded defaults, FOREVER-SYSTEM §1). Pass node/examples to exercise the
64
+ full habit set + prompts + proof layer.
65
+ """
66
+ tmp = workspace or tempfile.mkdtemp()
67
+ env = dict(os.environ, ENFORCER_SOCKET=sock_addr, AGENT_WORKSPACE=tmp)
68
+ p = subprocess.Popen([NODE, str(DAEMON)], env=env)
69
+ deadline = time.time() + 10
70
+ if sock_addr.startswith("tcp://"):
71
+ host, port = sock_addr[len("tcp://"):].split(":")
72
+ while time.time() < deadline:
73
+ try:
74
+ with socket.create_connection((host, int(port)), timeout=0.5):
75
+ return p
76
+ except OSError:
77
+ time.sleep(0.1)
78
+ else:
79
+ while time.time() < deadline:
80
+ if os.path.exists(sock_addr):
81
+ return p
82
+ time.sleep(0.1)
83
+ p.terminate()
84
+ raise RuntimeError("daemon did not start")
85
+
86
+
87
+ def _run_block_test():
88
+ # Use the daemon's seeded example workspace (has hard_constraints + leak habit).
89
+ sock = f"tcp://127.0.0.1:{_free_tcp_port()}"
90
+ proc = _start_daemon(sock)
91
+ try:
92
+ os.environ["ENFORCER_SOCKET"] = sock
93
+ m = _load_plugin()
94
+ assert _call_hook(m, "ls -la").get("action") != "block", "ls must be allowed"
95
+ assert _call_hook(m, "rm -rf /").get("action") == "block", "rm -rf / must block"
96
+ assert _call_hook(m, "sudo bash").get("action") == "block", "sudo must block"
97
+ assert _call_hook(m, "curl x?api_key=sk-ABC123").get("action") == "block", "leak must block"
98
+ assert _call_hook(m, "echo read api_key from vault").get("action") != "block", "mention must allow"
99
+ print(" PASS: blocks rm -rf /, sudo, leak; allows ls + mention (via daemon)")
100
+ finally:
101
+ proc.terminate()
102
+ try:
103
+ proc.wait(timeout=5)
104
+ except subprocess.TimeoutExpired:
105
+ proc.kill()
106
+
107
+
108
+ def _run_manifest_test():
109
+ # Boot the daemon on the EXAMPLES workspace (full habit set + prompts + proof).
110
+ examples = str(_ROOT / "node" / "examples")
111
+ sock = f"tcp://127.0.0.1:{_free_tcp_port()}"
112
+ proc = _start_daemon(sock, workspace=examples)
113
+ try:
114
+ os.environ["ENFORCER_SOCKET"] = sock
115
+ import sys as _sys
116
+ _sys.path.insert(0, str(_ROOT / "python"))
117
+ from agent_character_kit.enforcer import EnforcerClient
118
+ c = EnforcerClient(sock)
119
+
120
+ async def run():
121
+ ls = await c.call("execute_tool", {"tool": "Bash", "command": "ls"})
122
+ g = await c.call("get_habit", {"name": "verify_functionality_not_syntax"})
123
+ return ls, g
124
+ ls, g = asyncio.run(run())
125
+
126
+ # Manifest: compact {habit, prompt}, no prose, every call.
127
+ man = ls.get("manifest", [])
128
+ assert isinstance(man, list) and len(man) == 14, f"expected 14 manifest entries, got {len(man)}"
129
+ assert all(set(m.keys()) == {"habit", "prompt"} for m in man), "manifest entries must be {habit,prompt} only"
130
+ assert all(len(m["prompt"]) < 90 for m in man), "manifest prompts must stay short (token-bounded)"
131
+ assert not ls.get("self_verify_defects"), f"self-verify defects: {ls.get('self_verify_defects')}"
132
+
133
+ # get_habit: on-demand full proof (assert + evidence + logic).
134
+ assert g.get("name") == "verify_functionality_not_syntax"
135
+ assert g.get("prompt") and g.get("assert") and g.get("evidence") and g.get("logic"), \
136
+ "get_habit must return full proof (prompt+assert+evidence+logic)"
137
+
138
+ # Unknown habit -> error, not a silent empty proof.
139
+ bad = asyncio.run(c.call("get_habit", {"name": "does_not_exist"}))
140
+ assert bad.get("error"), "get_habit on unknown name must error"
141
+
142
+ print(" PASS: compact manifest per call + get_habit proof layer (14 habits)")
143
+ finally:
144
+ proc.terminate()
145
+ try:
146
+ proc.wait(timeout=5)
147
+ except subprocess.TimeoutExpired:
148
+ proc.kill()
149
+
150
+
151
+ def _run_fail_closed_test():
152
+ # Point the plugin at a socket that does NOT exist -> daemon down -> block.
153
+ os.environ["ENFORCER_SOCKET"] = "/tmp/aik-nonexistent-XXXX/main.sock"
154
+ m = _load_plugin()
155
+ res = _call_hook(m, "ls -la")
156
+ assert res is not None and res.get("action") == "block", f"expected fail-closed block, got {res}"
157
+ assert "unavailable" in (res.get("message", "").lower()) or "guard" in res.get("message", "").lower()
158
+ print(" PASS: fails CLOSED when daemon socket is down")
159
+
160
+
161
+ if __name__ == "__main__":
162
+ print("agent-character-kit hermes plugin (thin client):")
163
+ _run_block_test()
164
+ _run_fail_closed_test()
165
+ _run_manifest_test()
166
+ print("ALL PASS")
@@ -0,0 +1,19 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "agent-character-kit"
7
+ version = "0.1.0"
8
+ description = "Character enforcement + knowledge + memory for AI agents (Python companion to the Node.js aik package)"
9
+ requires-python = ">=3.9"
10
+ dependencies = ["PyYAML"]
11
+
12
+ [project.optional-dependencies]
13
+ vectors = ["numpy", "sentence-transformers"]
14
+
15
+ [project.scripts]
16
+ aik-py = "agent_character_kit.__main__:main"
17
+
18
+ [tool.setuptools]
19
+ packages = ["agent_character_kit"]
@@ -0,0 +1,84 @@
1
+ """Parity tests for the Python package (mirrors tests/enforcer.test.js + identity.test.js)."""
2
+ import os
3
+ import sys
4
+ import json
5
+ import tempfile
6
+ import importlib.util
7
+ from pathlib import Path
8
+
9
+ HERE = Path(__file__).resolve().parent
10
+ sys.path.insert(0, str(HERE.parent))
11
+
12
+
13
+ def load(modname):
14
+ spec = importlib.util.spec_from_file_location(
15
+ f"aik_{modname}", HERE.parent / "agent_character_kit" / f"{modname}.py")
16
+ mod = importlib.util.module_from_spec(spec)
17
+ spec.loader.exec_module(mod)
18
+ return mod
19
+
20
+
21
+ def tmp_ws():
22
+ d = Path(tempfile.mkdtemp(prefix="aik-py-"))
23
+ (d / ".agent" / "habits").mkdir(parents=True)
24
+ (d / ".agent" / "constitution.yaml").write_text(
25
+ "agent:\n id: t\ncore_values:\n - be excellent\n"
26
+ "hard_constraints:\n - \"rm -rf /\"\n - \"git push --force\"\n")
27
+ (d / ".agent" / "enforcer.yaml").write_text("allow:\n - \"ls\"\n - \"git status\"\n")
28
+ return d
29
+
30
+
31
+ def test_enforcer_allowlist_and_audit():
32
+ ws = tmp_ws()
33
+ prev = os.environ.get("AGENT_WORKSPACE")
34
+ os.environ["AGENT_WORKSPACE"] = str(ws)
35
+ try:
36
+ from agent_character_kit.enforcer import Enforcer
37
+ enf = Enforcer()
38
+ r1 = enf.execute_tool("Bash", {"command": "ls"})
39
+ r2 = enf.execute_tool("Bash", {"command": "rm file"})
40
+ r3 = enf.execute_tool("Bash", {"command": "rm -rf /"})
41
+ assert r1["denied"] is False
42
+ assert r2["denied"] is True
43
+ assert r3["denied"] is True
44
+ # every call is audited (allow leaves a trail)
45
+ log = enf.audit_path()
46
+ assert log.exists(), "audit log should exist"
47
+ lines = log.read_text(encoding="utf-8").strip().splitlines()
48
+ assert len(lines) == 3
49
+ assert json.loads(lines[0])["decision"] == "allow"
50
+ finally:
51
+ if prev is None:
52
+ del os.environ["AGENT_WORKSPACE"]
53
+ else:
54
+ os.environ["AGENT_WORKSPACE"] = prev
55
+
56
+
57
+ def test_memory_and_knowledge_modules():
58
+ ws = tmp_ws()
59
+ from agent_character_kit.memory import Memory
60
+ mem = Memory(str(ws))
61
+ mem.init()
62
+ mem.daily.log("did X", ["a", "b"], "dev")
63
+ mem.longterm.add_lesson("Ctx", "fresh beats exhausted", ["x"])
64
+ mem.knowledge.add_entity("Kyle", "person", {"tz": "PST"}, [])
65
+ st = mem.status()
66
+ assert st["dailyNotes"] == 1
67
+ assert st["longtermLessons"] == 1
68
+ assert st["entities"] == 1
69
+ ent = mem.knowledge.get_entity("Kyle")
70
+ assert ent["facts"]["tz"] == "PST"
71
+
72
+
73
+ def test_indexer_excludes_agent_internal():
74
+ ws = Path(tempfile.mkdtemp(prefix="aik-py-idx-"))
75
+ (ws / "SOUL.md").write_text("# soul\n")
76
+ (ws / ".agent").mkdir()
77
+ (ws / ".agent" / "constitution.yaml").write_text("agent:\n id: x\n")
78
+ (ws / "userdoc.md").write_text("real corpus doc\n")
79
+ import agent_character_kit
80
+ idx = agent_character_kit.DocumentIndexer(str(ws))
81
+ idx.init()
82
+ res = idx.index_directory(str(ws), {})
83
+ assert res["indexed"] == 1
84
+ assert any("userdoc" in k for k in idx.index["documents"])