klanker 0.0.1__tar.gz
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.
- klanker-0.0.1/LICENSE +21 -0
- klanker-0.0.1/PKG-INFO +75 -0
- klanker-0.0.1/README.md +48 -0
- klanker-0.0.1/pyproject.toml +45 -0
- klanker-0.0.1/setup.cfg +4 -0
- klanker-0.0.1/src/klanker/__init__.py +3 -0
- klanker-0.0.1/src/klanker/__main__.py +162 -0
- klanker-0.0.1/src/klanker/morph.py +36 -0
- klanker-0.0.1/src/klanker/nucleus.py +74 -0
- klanker-0.0.1/src/klanker/prompt.py +60 -0
- klanker-0.0.1/src/klanker/sensing.py +106 -0
- klanker-0.0.1/src/klanker.egg-info/PKG-INFO +75 -0
- klanker-0.0.1/src/klanker.egg-info/SOURCES.txt +16 -0
- klanker-0.0.1/src/klanker.egg-info/dependency_links.txt +1 -0
- klanker-0.0.1/src/klanker.egg-info/entry_points.txt +2 -0
- klanker-0.0.1/src/klanker.egg-info/requires.txt +17 -0
- klanker-0.0.1/src/klanker.egg-info/top_level.txt +1 -0
- klanker-0.0.1/tests/test_klanker.py +54 -0
klanker-0.0.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lolaplex
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
klanker-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: klanker
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Universal, adaptive slimemold AI agent shell on the Lolaplex suite
|
|
5
|
+
Author: Lolaplex
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Lolaplex/klanker
|
|
8
|
+
Project-URL: Repository, https://github.com/Lolaplex/klanker
|
|
9
|
+
Project-URL: Bug Tracker, https://github.com/Lolaplex/klanker/issues
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: agents-harness>=0.0.1
|
|
14
|
+
Requires-Dist: agents-relay>=0.0.1
|
|
15
|
+
Requires-Dist: agents-memory>=0.0.1
|
|
16
|
+
Requires-Dist: agents-traces>=0.0.1
|
|
17
|
+
Requires-Dist: agents-docs>=0.0.1
|
|
18
|
+
Provides-Extra: browser
|
|
19
|
+
Requires-Dist: agents-browser>=0.0.1; extra == "browser"
|
|
20
|
+
Provides-Extra: keys
|
|
21
|
+
Requires-Dist: agents-keys>=0.0.1; extra == "keys"
|
|
22
|
+
Provides-Extra: all
|
|
23
|
+
Requires-Dist: klanker[browser,keys]; extra == "all"
|
|
24
|
+
Provides-Extra: suite
|
|
25
|
+
Requires-Dist: klanker[browser,keys]; extra == "suite"
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# Klanker 🤖
|
|
29
|
+
|
|
30
|
+
**The Adaptive Agent Distribution on the Lolaplex Suite.**
|
|
31
|
+
|
|
32
|
+
Klanker ist eine schlanke, adaptive Agenten-Distribution auf Basis des **Slimemold-Paradigmas**. Aufsetzend auf dem deterministischen Motor von `agents-harness` formt sich Klanker nach Bedarf:
|
|
33
|
+
- Vom autarken Server-Worker (`klanker cron`),
|
|
34
|
+
- ĂĽber den direkten Terminal-Begleiter (`klanker chat`),
|
|
35
|
+
- bis zum vollwertigen VPS-Assistenten via Telegram & HTTP (`klanker serve`).
|
|
36
|
+
|
|
37
|
+
## Quickstart
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# Clone & install (installiert den kompletten Agents Core Stack: harness, gateway, memory, traces, docs)
|
|
41
|
+
pip install -e .
|
|
42
|
+
|
|
43
|
+
# Optional: Zusätzliche Slimemold-Fühler aktivieren
|
|
44
|
+
pip install -e ".[browser]" # CDP Browser Automation
|
|
45
|
+
pip install -e ".[keys]" # Ed25519 Agent Keys
|
|
46
|
+
pip install -e ".[suite]" # Volle Lolaplex Suite
|
|
47
|
+
|
|
48
|
+
# 1. Umgebung & FĂĽhler prĂĽfen
|
|
49
|
+
klanker sense
|
|
50
|
+
|
|
51
|
+
# 2. Dynamischen System-Prompt einsehen
|
|
52
|
+
klanker prompt
|
|
53
|
+
|
|
54
|
+
# 3. Interaktiver Chat (REPL)
|
|
55
|
+
klanker
|
|
56
|
+
|
|
57
|
+
# 4. Direkter Turn im Terminal
|
|
58
|
+
klanker "Wer bist du?"
|
|
59
|
+
|
|
60
|
+
# 5. I/O-Gateway starten (HTTP & Telegram)
|
|
61
|
+
klanker serve
|
|
62
|
+
|
|
63
|
+
# 6. Geplante Koru-Harness-Flows ausfĂĽhren
|
|
64
|
+
klanker cron
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
## Die Architektur
|
|
69
|
+
|
|
70
|
+
1. **Das Brain (`agents-harness`)** — Motor, LLM-Streaming, Cordis-Kernel, Koru-Schedules, lokales Gedächtnis (`agents-memory`), JSONL-Observability & Chat-Rekonstruktion (`agents-traces`) und lokales Markdown-RAG (`agents-docs`).
|
|
71
|
+
2. **Die I/O-Schicht (`agents-relay`)** — Universelle Ein- und Ausgabe via HTTP (`/v1/turn`) und Telegram Long-Poll.
|
|
72
|
+
3. **Die User-App (`klanker`)** — Verbindet Brain & Relay zu einer adaptiven Slimemold-Shell.
|
|
73
|
+
4. **Optionale Tools & Fühler** — Externe Fähigkeiten wie CDP-Browser (`agents-browser`), Ed25519-Keys (`agents-keys`), Jailed Terminal (`agents-terminal`) oder beliebige MCP-Tools, die Klanker bei Bedarf vorschlägt oder einbindet.
|
|
74
|
+
|
|
75
|
+
|
klanker-0.0.1/README.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Klanker 🤖
|
|
2
|
+
|
|
3
|
+
**The Adaptive Agent Distribution on the Lolaplex Suite.**
|
|
4
|
+
|
|
5
|
+
Klanker ist eine schlanke, adaptive Agenten-Distribution auf Basis des **Slimemold-Paradigmas**. Aufsetzend auf dem deterministischen Motor von `agents-harness` formt sich Klanker nach Bedarf:
|
|
6
|
+
- Vom autarken Server-Worker (`klanker cron`),
|
|
7
|
+
- ĂĽber den direkten Terminal-Begleiter (`klanker chat`),
|
|
8
|
+
- bis zum vollwertigen VPS-Assistenten via Telegram & HTTP (`klanker serve`).
|
|
9
|
+
|
|
10
|
+
## Quickstart
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
# Clone & install (installiert den kompletten Agents Core Stack: harness, gateway, memory, traces, docs)
|
|
14
|
+
pip install -e .
|
|
15
|
+
|
|
16
|
+
# Optional: Zusätzliche Slimemold-Fühler aktivieren
|
|
17
|
+
pip install -e ".[browser]" # CDP Browser Automation
|
|
18
|
+
pip install -e ".[keys]" # Ed25519 Agent Keys
|
|
19
|
+
pip install -e ".[suite]" # Volle Lolaplex Suite
|
|
20
|
+
|
|
21
|
+
# 1. Umgebung & FĂĽhler prĂĽfen
|
|
22
|
+
klanker sense
|
|
23
|
+
|
|
24
|
+
# 2. Dynamischen System-Prompt einsehen
|
|
25
|
+
klanker prompt
|
|
26
|
+
|
|
27
|
+
# 3. Interaktiver Chat (REPL)
|
|
28
|
+
klanker
|
|
29
|
+
|
|
30
|
+
# 4. Direkter Turn im Terminal
|
|
31
|
+
klanker "Wer bist du?"
|
|
32
|
+
|
|
33
|
+
# 5. I/O-Gateway starten (HTTP & Telegram)
|
|
34
|
+
klanker serve
|
|
35
|
+
|
|
36
|
+
# 6. Geplante Koru-Harness-Flows ausfĂĽhren
|
|
37
|
+
klanker cron
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
## Die Architektur
|
|
42
|
+
|
|
43
|
+
1. **Das Brain (`agents-harness`)** — Motor, LLM-Streaming, Cordis-Kernel, Koru-Schedules, lokales Gedächtnis (`agents-memory`), JSONL-Observability & Chat-Rekonstruktion (`agents-traces`) und lokales Markdown-RAG (`agents-docs`).
|
|
44
|
+
2. **Die I/O-Schicht (`agents-relay`)** — Universelle Ein- und Ausgabe via HTTP (`/v1/turn`) und Telegram Long-Poll.
|
|
45
|
+
3. **Die User-App (`klanker`)** — Verbindet Brain & Relay zu einer adaptiven Slimemold-Shell.
|
|
46
|
+
4. **Optionale Tools & Fühler** — Externe Fähigkeiten wie CDP-Browser (`agents-browser`), Ed25519-Keys (`agents-keys`), Jailed Terminal (`agents-terminal`) oder beliebige MCP-Tools, die Klanker bei Bedarf vorschlägt oder einbindet.
|
|
47
|
+
|
|
48
|
+
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "klanker"
|
|
7
|
+
version = "0.0.1"
|
|
8
|
+
description = "Universal, adaptive slimemold AI agent shell on the Lolaplex suite"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
authors = [{ name = "Lolaplex" }]
|
|
13
|
+
requires-python = ">=3.10"
|
|
14
|
+
dependencies = [
|
|
15
|
+
"agents-harness>=0.0.1",
|
|
16
|
+
"agents-relay>=0.0.1",
|
|
17
|
+
"agents-memory>=0.0.1",
|
|
18
|
+
"agents-traces>=0.0.1",
|
|
19
|
+
"agents-docs>=0.0.1",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[project.optional-dependencies]
|
|
23
|
+
browser = [
|
|
24
|
+
"agents-browser>=0.0.1",
|
|
25
|
+
]
|
|
26
|
+
keys = [
|
|
27
|
+
"agents-keys>=0.0.1",
|
|
28
|
+
]
|
|
29
|
+
all = [
|
|
30
|
+
"klanker[browser,keys]",
|
|
31
|
+
]
|
|
32
|
+
suite = [
|
|
33
|
+
"klanker[browser,keys]",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[project.urls]
|
|
37
|
+
Homepage = "https://github.com/Lolaplex/klanker"
|
|
38
|
+
Repository = "https://github.com/Lolaplex/klanker"
|
|
39
|
+
"Bug Tracker" = "https://github.com/Lolaplex/klanker/issues"
|
|
40
|
+
|
|
41
|
+
[project.scripts]
|
|
42
|
+
klanker = "klanker.__main__:main"
|
|
43
|
+
|
|
44
|
+
[tool.setuptools.packages.find]
|
|
45
|
+
where = ["src"]
|
klanker-0.0.1/setup.cfg
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""Klanker CLI — Entry point for the Slimemold Agent."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
import logging
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
from .nucleus import Nucleus
|
|
12
|
+
from .sensing import probe_host
|
|
13
|
+
|
|
14
|
+
log = logging.getLogger("klanker")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
18
|
+
parser = argparse.ArgumentParser(
|
|
19
|
+
prog="klanker",
|
|
20
|
+
description="Klanker — Adaptive Agent Shell on the Lolaplex Suite",
|
|
21
|
+
)
|
|
22
|
+
parser.add_argument("--user", type=str, default="user", help="User alias")
|
|
23
|
+
parser.add_argument("--provider", type=str, default=None, help="LLM provider")
|
|
24
|
+
|
|
25
|
+
sub = parser.add_subparsers(dest="command")
|
|
26
|
+
|
|
27
|
+
# 1. Sense host capabilities
|
|
28
|
+
sense_p = sub.add_parser("sense", help="Probe host environment and available feelers")
|
|
29
|
+
sense_p.add_argument("--json", action="store_true", help="Output JSON format")
|
|
30
|
+
|
|
31
|
+
# 1b. Dump system prompt
|
|
32
|
+
sub.add_parser("prompt", help="Print the dynamic Klanker system prompt")
|
|
33
|
+
|
|
34
|
+
# 2. Interactive or one-turn chat (alias)
|
|
35
|
+
chat_p = sub.add_parser("chat", help="Chat with Klanker (spawns harness turn)")
|
|
36
|
+
chat_p.add_argument("message", type=str, nargs="?", default="", help="Message content")
|
|
37
|
+
chat_p.add_argument("--user", type=str, default="user", help="User alias")
|
|
38
|
+
chat_p.add_argument("--provider", type=str, default=None, help="LLM provider")
|
|
39
|
+
|
|
40
|
+
# 3. Serve gateway
|
|
41
|
+
serve_p = sub.add_parser("serve", help="Start I/O Gateway (HTTP /v1/turn & Telegram)")
|
|
42
|
+
serve_p.add_argument("--no-telegram", action="store_true", help="Disable Telegram polling")
|
|
43
|
+
|
|
44
|
+
# 4. Run background cron / scheduled flows
|
|
45
|
+
cron_p = sub.add_parser("cron", help="Run scheduled care flows via harness executor")
|
|
46
|
+
cron_p.add_argument("--flow", type=str, default="", help="Run specific flow name")
|
|
47
|
+
|
|
48
|
+
return parser
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def run_repl(nucleus: Nucleus, *, user: str = "user", provider: str | None = None) -> int:
|
|
52
|
+
"""Run an interactive multi-turn REPL loop retaining conversation session."""
|
|
53
|
+
import uuid
|
|
54
|
+
session_id = f"ses_{uuid.uuid4().hex[:12]}"
|
|
55
|
+
print("klanker (v0.0.1) — interactive chat. Type 'exit' or Ctrl+C to quit.\n")
|
|
56
|
+
|
|
57
|
+
while True:
|
|
58
|
+
try:
|
|
59
|
+
prompt = input("klanker> ").strip()
|
|
60
|
+
except (EOFError, KeyboardInterrupt):
|
|
61
|
+
print()
|
|
62
|
+
break
|
|
63
|
+
|
|
64
|
+
if not prompt:
|
|
65
|
+
continue
|
|
66
|
+
if prompt.lower() in ("exit", "quit", ":q"):
|
|
67
|
+
break
|
|
68
|
+
|
|
69
|
+
nucleus.run_turn(message=prompt, user=user, session=session_id, provider=provider)
|
|
70
|
+
print()
|
|
71
|
+
|
|
72
|
+
return 0
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
KNOWN_SUBCOMMANDS = {"sense", "prompt", "serve", "cron", "chat", "-h", "--help"}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def parse_cli_args(argv: list[str] | None = None) -> tuple[argparse.Namespace, str]:
|
|
79
|
+
"""Parse CLI arguments allowing both subcommands and direct prompts."""
|
|
80
|
+
raw_args = list(sys.argv[1:] if argv is None else argv)
|
|
81
|
+
direct_prompt = ""
|
|
82
|
+
|
|
83
|
+
# If first positional argument is not a known subcommand or flag, treat it as direct prompt
|
|
84
|
+
if raw_args and not raw_args[0].startswith("-") and raw_args[0] not in KNOWN_SUBCOMMANDS:
|
|
85
|
+
direct_prompt = raw_args.pop(0)
|
|
86
|
+
|
|
87
|
+
parser = build_parser()
|
|
88
|
+
parsed = parser.parse_args(raw_args)
|
|
89
|
+
return parsed, direct_prompt
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def main(argv: list[str] | None = None) -> int:
|
|
93
|
+
logging.basicConfig(level=logging.INFO, format="%(levelname)s %(name)s: %(message)s")
|
|
94
|
+
args, direct_prompt = parse_cli_args(argv)
|
|
95
|
+
nucleus = Nucleus()
|
|
96
|
+
|
|
97
|
+
if args.command == "sense":
|
|
98
|
+
summary = nucleus.sense()
|
|
99
|
+
if args.json:
|
|
100
|
+
print(json.dumps(summary, indent=2))
|
|
101
|
+
else:
|
|
102
|
+
print("Klanker Host Sensing:")
|
|
103
|
+
for k, v in summary.items():
|
|
104
|
+
print(f" * {k}: {v}")
|
|
105
|
+
return 0
|
|
106
|
+
|
|
107
|
+
elif args.command == "prompt":
|
|
108
|
+
from .prompt import build_system_prompt
|
|
109
|
+
print(build_system_prompt(nucleus.caps))
|
|
110
|
+
return 0
|
|
111
|
+
|
|
112
|
+
elif args.command == "serve":
|
|
113
|
+
if not (nucleus.caps.has_relay or nucleus.caps.has_gateway):
|
|
114
|
+
print("Error: agents-relay is not installed. Install via pip install agents-relay", file=sys.stderr)
|
|
115
|
+
return 1
|
|
116
|
+
|
|
117
|
+
# If LOOP_CMD is default and harness is present, inject Klanker's dynamic system prompt
|
|
118
|
+
if "LOOP_CMD" not in os.environ and nucleus.caps.has_harness:
|
|
119
|
+
from .prompt import save_system_prompt
|
|
120
|
+
prompt_file = save_system_prompt(nucleus.caps)
|
|
121
|
+
os.environ["LOOP_CMD"] = f"python -m runner.loop --system {prompt_file.as_posix()}"
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
from agents_relay.__main__ import main as relay_main
|
|
125
|
+
except ImportError:
|
|
126
|
+
from agents_gateway.__main__ import main as relay_main
|
|
127
|
+
sub_argv = ["serve"]
|
|
128
|
+
if getattr(args, "no_telegram", False):
|
|
129
|
+
sub_argv.append("--no-telegram")
|
|
130
|
+
return relay_main(sub_argv)
|
|
131
|
+
|
|
132
|
+
elif args.command == "cron":
|
|
133
|
+
if not nucleus.caps.has_harness:
|
|
134
|
+
print("Error: agents-harness is not installed. Install via pip install agents-harness", file=sys.stderr)
|
|
135
|
+
return 1
|
|
136
|
+
from runner.executor import main as executor_main
|
|
137
|
+
if args.flow:
|
|
138
|
+
return executor_main(["--run", args.flow])
|
|
139
|
+
return executor_main(["--all"])
|
|
140
|
+
|
|
141
|
+
else:
|
|
142
|
+
# Chat mode: either explicit 'chat', direct prompt argument, stdin pipe, or interactive REPL
|
|
143
|
+
msg = direct_prompt or getattr(args, "message", None) or ""
|
|
144
|
+
user = getattr(args, "user", "user")
|
|
145
|
+
provider = getattr(args, "provider", None)
|
|
146
|
+
|
|
147
|
+
if msg:
|
|
148
|
+
return nucleus.run_turn(message=msg, user=user, provider=provider)
|
|
149
|
+
|
|
150
|
+
# If piped input via stdin (e.g. echo "hi" | klanker)
|
|
151
|
+
if not sys.stdin.isatty():
|
|
152
|
+
piped_msg = sys.stdin.read().strip()
|
|
153
|
+
if piped_msg:
|
|
154
|
+
return nucleus.run_turn(message=piped_msg, user=user, provider=provider)
|
|
155
|
+
return 0
|
|
156
|
+
|
|
157
|
+
# Interactive REPL
|
|
158
|
+
return run_repl(nucleus, user=user, provider=provider)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
if __name__ == "__main__":
|
|
162
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Dynamic manifest and schedule synthesizer."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def synthesize_schedule(
|
|
11
|
+
*,
|
|
12
|
+
name: str,
|
|
13
|
+
verb: str,
|
|
14
|
+
cadence: str = "daily",
|
|
15
|
+
rests_on: str = "",
|
|
16
|
+
expected_exit: int = 0,
|
|
17
|
+
timeout_sec: int = 300,
|
|
18
|
+
) -> dict[str, Any]:
|
|
19
|
+
"""Generate a declarative Koru schedule manifest dictionary."""
|
|
20
|
+
return {
|
|
21
|
+
"name": name,
|
|
22
|
+
"verb": verb,
|
|
23
|
+
"cadence": cadence,
|
|
24
|
+
"rests_on": rests_on or f"Synthesized by Klanker for {name}",
|
|
25
|
+
"expected_exit": expected_exit,
|
|
26
|
+
"timeout_sec": timeout_sec,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def write_schedule(target_dir: Path | str, manifest: dict[str, Any]) -> Path:
|
|
31
|
+
"""Safely write a synthesized schedule to the target schedules directory."""
|
|
32
|
+
path = Path(target_dir) / f"{manifest['name']}.json"
|
|
33
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
34
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
35
|
+
json.dump(manifest, f, indent=2)
|
|
36
|
+
return path
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Klanker Nucleus — The adaptive core coordinator."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
from .sensing import HostCapabilities, probe_host
|
|
13
|
+
|
|
14
|
+
log = logging.getLogger("klanker.nucleus")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Nucleus:
|
|
18
|
+
def __init__(self, caps: HostCapabilities | None = None) -> None:
|
|
19
|
+
self.caps = caps or probe_host()
|
|
20
|
+
|
|
21
|
+
def sense(self) -> dict[str, Any]:
|
|
22
|
+
"""Return environment topology summary."""
|
|
23
|
+
return self.caps.summary()
|
|
24
|
+
|
|
25
|
+
def run_turn(
|
|
26
|
+
self,
|
|
27
|
+
*,
|
|
28
|
+
message: str,
|
|
29
|
+
user: str = "default",
|
|
30
|
+
session: str | None = None,
|
|
31
|
+
channel: str = "local",
|
|
32
|
+
provider: str | None = None,
|
|
33
|
+
deliver: str = "buffered",
|
|
34
|
+
) -> int:
|
|
35
|
+
"""Execute one conversational turn using available runner or fallback."""
|
|
36
|
+
selected_provider = provider or os.environ.get("LOOP_PROVIDER", "openai.default")
|
|
37
|
+
|
|
38
|
+
# If agents-harness is installed, invoke runner.loop directly
|
|
39
|
+
if self.caps.has_harness:
|
|
40
|
+
from .prompt import build_system_prompt
|
|
41
|
+
|
|
42
|
+
system_prompt = build_system_prompt(self.caps)
|
|
43
|
+
|
|
44
|
+
cmd = [
|
|
45
|
+
"python",
|
|
46
|
+
"-m",
|
|
47
|
+
"runner.loop",
|
|
48
|
+
"--channel",
|
|
49
|
+
channel,
|
|
50
|
+
"--user",
|
|
51
|
+
user,
|
|
52
|
+
"--message",
|
|
53
|
+
message,
|
|
54
|
+
"--system",
|
|
55
|
+
system_prompt,
|
|
56
|
+
"--complete",
|
|
57
|
+
"--provider",
|
|
58
|
+
selected_provider,
|
|
59
|
+
"--deliver",
|
|
60
|
+
deliver,
|
|
61
|
+
]
|
|
62
|
+
if session:
|
|
63
|
+
cmd.extend(["--session", session])
|
|
64
|
+
|
|
65
|
+
res = subprocess.run(cmd)
|
|
66
|
+
return res.returncode
|
|
67
|
+
else:
|
|
68
|
+
print(
|
|
69
|
+
"Error: agents-harness is required for conversational turns.\n"
|
|
70
|
+
"Install it via: pip install -e ../agents-harness (or pip install klanker)",
|
|
71
|
+
file=sys.stderr,
|
|
72
|
+
)
|
|
73
|
+
return 1
|
|
74
|
+
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Klanker System Prompt — Slimemold identity and dynamic host grounding."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from .sensing import HostCapabilities
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def build_system_prompt(caps: HostCapabilities) -> str:
|
|
11
|
+
"""Render Klanker's adaptive system prompt based on probed host feelers."""
|
|
12
|
+
installed = [mod for mod, present in caps.suite_modules.items() if present]
|
|
13
|
+
missing = [mod for mod, present in caps.suite_modules.items() if not present]
|
|
14
|
+
|
|
15
|
+
installed_str = ", ".join(installed) if installed else "none (minimal core only)"
|
|
16
|
+
missing_str = ", ".join(missing) if missing else "none (fully equipped)"
|
|
17
|
+
|
|
18
|
+
lines = [
|
|
19
|
+
"You are Klanker (v0.0.1), an autonomous, adaptive slimemold AI agent on the Lolaplex suite.",
|
|
20
|
+
"",
|
|
21
|
+
"Core Philosophy & Identity:",
|
|
22
|
+
"- You are NOT a generic assistant and NOT a chatterbox. You are an adaptive, living agent shell that morphs to this host and user.",
|
|
23
|
+
"- Follow the Unix philosophy: dense, direct, zero fluff, zero courtesy filler.",
|
|
24
|
+
"- Never introduce yourself as 'Cordis'. Cordis is the underlying job-term engine, not your persona.",
|
|
25
|
+
"- Never hallucinate capabilities. You strictly rely on verified feelers and tools present on this machine.",
|
|
26
|
+
"- NEVER comment on time of day, late hours, location, or make clock-based small talk (e.g. do not say 'it is late at night' or 'guten abend'). The clock is purely for internal event timestamping.",
|
|
27
|
+
"",
|
|
28
|
+
f"Host Grounding ({caps.os_name}, Python {caps.python_version}):",
|
|
29
|
+
f"- Active feelers: {installed_str}",
|
|
30
|
+
f"- Missing feelers: {missing_str}",
|
|
31
|
+
"",
|
|
32
|
+
"Adaptive Rules & Behavior:",
|
|
33
|
+
"1. Organic Onboarding & Discovery (Low friction):",
|
|
34
|
+
" - If user memory is empty (no facts about user in memory), do NOT dump a giant questionnaire or essay.",
|
|
35
|
+
" - Greet briefly (1-2 sentences) and establish a fast baseline:",
|
|
36
|
+
" 'Klanker hier (v0.0.1, Slimemold). Alles läuft lokal auf diesem Host. Wie soll ich mit dir kommunizieren (z.B. Caveman, Sprache) und woran arbeiten wir?'",
|
|
37
|
+
" - In ongoing conversation: As you work, if you notice a missing context (e.g. project paths, stack preferences, rules), ask ONE concise, targeted question in context. Never interrogate on suspicion.",
|
|
38
|
+
" - When user provides preferences or facts, proactively remember them across sessions.",
|
|
39
|
+
"",
|
|
40
|
+
"2. Memory & Cloud Sync:",
|
|
41
|
+
" - If 'memory' is active, use `mcp.memory.search` to ground yourself in user facts, project standards, and rules.",
|
|
42
|
+
" - If user requests syncing memory with another device or remote memory server, use `python -m agents_memory connect <url> --token <token>` or guide them concisely.",
|
|
43
|
+
" - If 'memory' is missing and durable memory is needed, explain: 'pip install agents-memory (oder klanker[memory])'.",
|
|
44
|
+
"",
|
|
45
|
+
"3. Action & Execution:",
|
|
46
|
+
" - When requested to run tasks, use the available catalog tools (call_job, list_catalog, load_schema).",
|
|
47
|
+
" - Execute directly without asking for confirmation unless destructive.",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
return "\n".join(lines).strip()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def save_system_prompt(caps: HostCapabilities) -> Path:
|
|
54
|
+
"""Save dynamic Klanker prompt to ~/.agents/klanker_prompt.txt and return path."""
|
|
55
|
+
agents_dir = Path(os.environ.get("AGENTS_DIR", Path.home() / ".agents"))
|
|
56
|
+
agents_dir.mkdir(parents=True, exist_ok=True)
|
|
57
|
+
prompt_file = agents_dir / "klanker_prompt.txt"
|
|
58
|
+
prompt_text = build_system_prompt(caps)
|
|
59
|
+
prompt_file.write_text(prompt_text, encoding="utf-8")
|
|
60
|
+
return prompt_file
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""Host capability probe (Slimemold sensing)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import importlib.util
|
|
6
|
+
import os
|
|
7
|
+
import platform
|
|
8
|
+
import shutil
|
|
9
|
+
import sys
|
|
10
|
+
from dataclasses import asdict, dataclass, field
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
COMMON_RUNTIMES = ["git", "docker", "curl", "node", "npm", "uv", "sqlite3"]
|
|
15
|
+
SUITE_MODULES = {
|
|
16
|
+
"harness": "runner.loop",
|
|
17
|
+
"relay": "agents_relay",
|
|
18
|
+
"memory": "agents_memory",
|
|
19
|
+
"docs": "agents_docs",
|
|
20
|
+
"traces": "agents_traces",
|
|
21
|
+
"keys": "agents_keys",
|
|
22
|
+
"browser": "agents_browser",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass(frozen=True)
|
|
27
|
+
class HostCapabilities:
|
|
28
|
+
os_name: str
|
|
29
|
+
is_tty: bool
|
|
30
|
+
python_version: str
|
|
31
|
+
runtimes: dict[str, bool] = field(default_factory=dict)
|
|
32
|
+
suite_modules: dict[str, bool] = field(default_factory=dict)
|
|
33
|
+
has_agents_home: bool = False
|
|
34
|
+
|
|
35
|
+
# Backwards-compatible convenience properties
|
|
36
|
+
@property
|
|
37
|
+
def has_git(self) -> bool:
|
|
38
|
+
return self.runtimes.get("git", False)
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def has_docker(self) -> bool:
|
|
42
|
+
return self.runtimes.get("docker", False)
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def has_curl(self) -> bool:
|
|
46
|
+
return self.runtimes.get("curl", False)
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def has_harness(self) -> bool:
|
|
50
|
+
return self.suite_modules.get("harness", False)
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def has_relay(self) -> bool:
|
|
54
|
+
return self.suite_modules.get("relay", False)
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def has_gateway(self) -> bool:
|
|
58
|
+
return self.has_relay
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def has_memory(self) -> bool:
|
|
62
|
+
return self.suite_modules.get("memory", False)
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def has_docs(self) -> bool:
|
|
66
|
+
return self.suite_modules.get("docs", False)
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def has_traces(self) -> bool:
|
|
70
|
+
return self.suite_modules.get("traces", False)
|
|
71
|
+
|
|
72
|
+
def summary(self) -> dict[str, object]:
|
|
73
|
+
return {
|
|
74
|
+
"os_name": self.os_name,
|
|
75
|
+
"is_tty": self.is_tty,
|
|
76
|
+
"python_version": self.python_version,
|
|
77
|
+
"has_agents_home": self.has_agents_home,
|
|
78
|
+
"runtimes": self.runtimes,
|
|
79
|
+
"suite_modules": self.suite_modules,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def probe_host() -> HostCapabilities:
|
|
84
|
+
"""Probe current environment and available feelers dynamically."""
|
|
85
|
+
def _has_cmd(cmd: str) -> bool:
|
|
86
|
+
return shutil.which(cmd) is not None
|
|
87
|
+
|
|
88
|
+
def _has_mod(name: str) -> bool:
|
|
89
|
+
try:
|
|
90
|
+
return importlib.util.find_spec(name) is not None
|
|
91
|
+
except Exception:
|
|
92
|
+
return False
|
|
93
|
+
|
|
94
|
+
runtimes = {cmd: _has_cmd(cmd) for cmd in COMMON_RUNTIMES}
|
|
95
|
+
modules = {alias: _has_mod(pkg) for alias, pkg in SUITE_MODULES.items()}
|
|
96
|
+
agents_home = Path.home() / ".agents"
|
|
97
|
+
|
|
98
|
+
return HostCapabilities(
|
|
99
|
+
os_name=platform.system(),
|
|
100
|
+
is_tty=sys.stdin.isatty() if hasattr(sys.stdin, "isatty") else False,
|
|
101
|
+
python_version=platform.python_version(),
|
|
102
|
+
runtimes=runtimes,
|
|
103
|
+
suite_modules=modules,
|
|
104
|
+
has_agents_home=agents_home.exists(),
|
|
105
|
+
)
|
|
106
|
+
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: klanker
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Universal, adaptive slimemold AI agent shell on the Lolaplex suite
|
|
5
|
+
Author: Lolaplex
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Lolaplex/klanker
|
|
8
|
+
Project-URL: Repository, https://github.com/Lolaplex/klanker
|
|
9
|
+
Project-URL: Bug Tracker, https://github.com/Lolaplex/klanker/issues
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: agents-harness>=0.0.1
|
|
14
|
+
Requires-Dist: agents-relay>=0.0.1
|
|
15
|
+
Requires-Dist: agents-memory>=0.0.1
|
|
16
|
+
Requires-Dist: agents-traces>=0.0.1
|
|
17
|
+
Requires-Dist: agents-docs>=0.0.1
|
|
18
|
+
Provides-Extra: browser
|
|
19
|
+
Requires-Dist: agents-browser>=0.0.1; extra == "browser"
|
|
20
|
+
Provides-Extra: keys
|
|
21
|
+
Requires-Dist: agents-keys>=0.0.1; extra == "keys"
|
|
22
|
+
Provides-Extra: all
|
|
23
|
+
Requires-Dist: klanker[browser,keys]; extra == "all"
|
|
24
|
+
Provides-Extra: suite
|
|
25
|
+
Requires-Dist: klanker[browser,keys]; extra == "suite"
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# Klanker 🤖
|
|
29
|
+
|
|
30
|
+
**The Adaptive Agent Distribution on the Lolaplex Suite.**
|
|
31
|
+
|
|
32
|
+
Klanker ist eine schlanke, adaptive Agenten-Distribution auf Basis des **Slimemold-Paradigmas**. Aufsetzend auf dem deterministischen Motor von `agents-harness` formt sich Klanker nach Bedarf:
|
|
33
|
+
- Vom autarken Server-Worker (`klanker cron`),
|
|
34
|
+
- ĂĽber den direkten Terminal-Begleiter (`klanker chat`),
|
|
35
|
+
- bis zum vollwertigen VPS-Assistenten via Telegram & HTTP (`klanker serve`).
|
|
36
|
+
|
|
37
|
+
## Quickstart
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# Clone & install (installiert den kompletten Agents Core Stack: harness, gateway, memory, traces, docs)
|
|
41
|
+
pip install -e .
|
|
42
|
+
|
|
43
|
+
# Optional: Zusätzliche Slimemold-Fühler aktivieren
|
|
44
|
+
pip install -e ".[browser]" # CDP Browser Automation
|
|
45
|
+
pip install -e ".[keys]" # Ed25519 Agent Keys
|
|
46
|
+
pip install -e ".[suite]" # Volle Lolaplex Suite
|
|
47
|
+
|
|
48
|
+
# 1. Umgebung & FĂĽhler prĂĽfen
|
|
49
|
+
klanker sense
|
|
50
|
+
|
|
51
|
+
# 2. Dynamischen System-Prompt einsehen
|
|
52
|
+
klanker prompt
|
|
53
|
+
|
|
54
|
+
# 3. Interaktiver Chat (REPL)
|
|
55
|
+
klanker
|
|
56
|
+
|
|
57
|
+
# 4. Direkter Turn im Terminal
|
|
58
|
+
klanker "Wer bist du?"
|
|
59
|
+
|
|
60
|
+
# 5. I/O-Gateway starten (HTTP & Telegram)
|
|
61
|
+
klanker serve
|
|
62
|
+
|
|
63
|
+
# 6. Geplante Koru-Harness-Flows ausfĂĽhren
|
|
64
|
+
klanker cron
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
## Die Architektur
|
|
69
|
+
|
|
70
|
+
1. **Das Brain (`agents-harness`)** — Motor, LLM-Streaming, Cordis-Kernel, Koru-Schedules, lokales Gedächtnis (`agents-memory`), JSONL-Observability & Chat-Rekonstruktion (`agents-traces`) und lokales Markdown-RAG (`agents-docs`).
|
|
71
|
+
2. **Die I/O-Schicht (`agents-relay`)** — Universelle Ein- und Ausgabe via HTTP (`/v1/turn`) und Telegram Long-Poll.
|
|
72
|
+
3. **Die User-App (`klanker`)** — Verbindet Brain & Relay zu einer adaptiven Slimemold-Shell.
|
|
73
|
+
4. **Optionale Tools & Fühler** — Externe Fähigkeiten wie CDP-Browser (`agents-browser`), Ed25519-Keys (`agents-keys`), Jailed Terminal (`agents-terminal`) oder beliebige MCP-Tools, die Klanker bei Bedarf vorschlägt oder einbindet.
|
|
74
|
+
|
|
75
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/klanker/__init__.py
|
|
5
|
+
src/klanker/__main__.py
|
|
6
|
+
src/klanker/morph.py
|
|
7
|
+
src/klanker/nucleus.py
|
|
8
|
+
src/klanker/prompt.py
|
|
9
|
+
src/klanker/sensing.py
|
|
10
|
+
src/klanker.egg-info/PKG-INFO
|
|
11
|
+
src/klanker.egg-info/SOURCES.txt
|
|
12
|
+
src/klanker.egg-info/dependency_links.txt
|
|
13
|
+
src/klanker.egg-info/entry_points.txt
|
|
14
|
+
src/klanker.egg-info/requires.txt
|
|
15
|
+
src/klanker.egg-info/top_level.txt
|
|
16
|
+
tests/test_klanker.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
agents-harness>=0.0.1
|
|
2
|
+
agents-relay>=0.0.1
|
|
3
|
+
agents-memory>=0.0.1
|
|
4
|
+
agents-traces>=0.0.1
|
|
5
|
+
agents-docs>=0.0.1
|
|
6
|
+
|
|
7
|
+
[all]
|
|
8
|
+
klanker[browser,keys]
|
|
9
|
+
|
|
10
|
+
[browser]
|
|
11
|
+
agents-browser>=0.0.1
|
|
12
|
+
|
|
13
|
+
[keys]
|
|
14
|
+
agents-keys>=0.0.1
|
|
15
|
+
|
|
16
|
+
[suite]
|
|
17
|
+
klanker[browser,keys]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
klanker
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Unit tests for Klanker nucleus, sensing, and CLI."""
|
|
2
|
+
|
|
3
|
+
import unittest
|
|
4
|
+
from klanker.sensing import probe_host, HostCapabilities
|
|
5
|
+
from klanker.morph import synthesize_schedule
|
|
6
|
+
from klanker.nucleus import Nucleus
|
|
7
|
+
from klanker.__main__ import build_parser
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TestKlanker(unittest.TestCase):
|
|
11
|
+
def test_probe_host(self):
|
|
12
|
+
caps = probe_host()
|
|
13
|
+
self.assertIsInstance(caps, HostCapabilities)
|
|
14
|
+
summary = caps.summary()
|
|
15
|
+
self.assertIn("os_name", summary)
|
|
16
|
+
self.assertIn("runtimes", summary)
|
|
17
|
+
self.assertIn("suite_modules", summary)
|
|
18
|
+
self.assertTrue(hasattr(caps, "has_git"))
|
|
19
|
+
|
|
20
|
+
def test_synthesize_schedule(self):
|
|
21
|
+
manifest = synthesize_schedule(
|
|
22
|
+
name="test_flow",
|
|
23
|
+
verb="python -m test",
|
|
24
|
+
cadence="daily",
|
|
25
|
+
)
|
|
26
|
+
self.assertEqual(manifest["name"], "test_flow")
|
|
27
|
+
self.assertEqual(manifest["expected_exit"], 0)
|
|
28
|
+
|
|
29
|
+
def test_nucleus_sense(self):
|
|
30
|
+
n = Nucleus()
|
|
31
|
+
sense = n.sense()
|
|
32
|
+
self.assertIsInstance(sense, dict)
|
|
33
|
+
self.assertIn("python_version", sense)
|
|
34
|
+
|
|
35
|
+
def test_cli_parser(self):
|
|
36
|
+
from klanker.__main__ import parse_cli_args
|
|
37
|
+
args, direct_prompt = parse_cli_args(["sense", "--json"])
|
|
38
|
+
self.assertEqual(args.command, "sense")
|
|
39
|
+
self.assertTrue(args.json)
|
|
40
|
+
self.assertEqual(direct_prompt, "")
|
|
41
|
+
|
|
42
|
+
# Direct prompt argument test
|
|
43
|
+
args, direct_prompt = parse_cli_args(["Wer bist du?"])
|
|
44
|
+
self.assertIsNone(args.command)
|
|
45
|
+
self.assertEqual(direct_prompt, "Wer bist du?")
|
|
46
|
+
|
|
47
|
+
# Empty prompt test (triggers REPL)
|
|
48
|
+
args, direct_prompt = parse_cli_args([])
|
|
49
|
+
self.assertIsNone(args.command)
|
|
50
|
+
self.assertEqual(direct_prompt, "")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
if __name__ == "__main__":
|
|
54
|
+
unittest.main()
|