ragnarbot-ai 0.1.0__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.
- ragnarbot_ai-0.1.0/.gitignore +17 -0
- ragnarbot_ai-0.1.0/LICENSE +22 -0
- ragnarbot_ai-0.1.0/PKG-INFO +28 -0
- ragnarbot_ai-0.1.0/README.md +183 -0
- ragnarbot_ai-0.1.0/pyproject.toml +77 -0
- ragnarbot_ai-0.1.0/ragnarbot/__init__.py +6 -0
- ragnarbot_ai-0.1.0/ragnarbot/__main__.py +8 -0
- ragnarbot_ai-0.1.0/ragnarbot/agent/__init__.py +8 -0
- ragnarbot_ai-0.1.0/ragnarbot/agent/context.py +223 -0
- ragnarbot_ai-0.1.0/ragnarbot/agent/loop.py +365 -0
- ragnarbot_ai-0.1.0/ragnarbot/agent/memory.py +109 -0
- ragnarbot_ai-0.1.0/ragnarbot/agent/skills.py +228 -0
- ragnarbot_ai-0.1.0/ragnarbot/agent/subagent.py +241 -0
- ragnarbot_ai-0.1.0/ragnarbot/agent/tools/__init__.py +6 -0
- ragnarbot_ai-0.1.0/ragnarbot/agent/tools/base.py +102 -0
- ragnarbot_ai-0.1.0/ragnarbot/agent/tools/cron.py +114 -0
- ragnarbot_ai-0.1.0/ragnarbot/agent/tools/filesystem.py +191 -0
- ragnarbot_ai-0.1.0/ragnarbot/agent/tools/message.py +86 -0
- ragnarbot_ai-0.1.0/ragnarbot/agent/tools/registry.py +73 -0
- ragnarbot_ai-0.1.0/ragnarbot/agent/tools/shell.py +141 -0
- ragnarbot_ai-0.1.0/ragnarbot/agent/tools/spawn.py +65 -0
- ragnarbot_ai-0.1.0/ragnarbot/agent/tools/web.py +163 -0
- ragnarbot_ai-0.1.0/ragnarbot/bus/__init__.py +6 -0
- ragnarbot_ai-0.1.0/ragnarbot/bus/events.py +37 -0
- ragnarbot_ai-0.1.0/ragnarbot/bus/queue.py +81 -0
- ragnarbot_ai-0.1.0/ragnarbot/channels/__init__.py +6 -0
- ragnarbot_ai-0.1.0/ragnarbot/channels/base.py +121 -0
- ragnarbot_ai-0.1.0/ragnarbot/channels/manager.py +129 -0
- ragnarbot_ai-0.1.0/ragnarbot/channels/telegram.py +302 -0
- ragnarbot_ai-0.1.0/ragnarbot/cli/__init__.py +1 -0
- ragnarbot_ai-0.1.0/ragnarbot/cli/commands.py +568 -0
- ragnarbot_ai-0.1.0/ragnarbot/config/__init__.py +6 -0
- ragnarbot_ai-0.1.0/ragnarbot/config/loader.py +95 -0
- ragnarbot_ai-0.1.0/ragnarbot/config/schema.py +114 -0
- ragnarbot_ai-0.1.0/ragnarbot/cron/__init__.py +6 -0
- ragnarbot_ai-0.1.0/ragnarbot/cron/service.py +346 -0
- ragnarbot_ai-0.1.0/ragnarbot/cron/types.py +59 -0
- ragnarbot_ai-0.1.0/ragnarbot/heartbeat/__init__.py +5 -0
- ragnarbot_ai-0.1.0/ragnarbot/heartbeat/service.py +130 -0
- ragnarbot_ai-0.1.0/ragnarbot/providers/__init__.py +6 -0
- ragnarbot_ai-0.1.0/ragnarbot/providers/base.py +69 -0
- ragnarbot_ai-0.1.0/ragnarbot/providers/litellm_provider.py +135 -0
- ragnarbot_ai-0.1.0/ragnarbot/providers/transcription.py +67 -0
- ragnarbot_ai-0.1.0/ragnarbot/session/__init__.py +5 -0
- ragnarbot_ai-0.1.0/ragnarbot/session/manager.py +202 -0
- ragnarbot_ai-0.1.0/ragnarbot/skills/README.md +24 -0
- ragnarbot_ai-0.1.0/ragnarbot/skills/cron/SKILL.md +40 -0
- ragnarbot_ai-0.1.0/ragnarbot/skills/github/SKILL.md +48 -0
- ragnarbot_ai-0.1.0/ragnarbot/skills/skill-creator/SKILL.md +371 -0
- ragnarbot_ai-0.1.0/ragnarbot/skills/summarize/SKILL.md +67 -0
- ragnarbot_ai-0.1.0/ragnarbot/skills/tmux/SKILL.md +121 -0
- ragnarbot_ai-0.1.0/ragnarbot/skills/tmux/scripts/find-sessions.sh +112 -0
- ragnarbot_ai-0.1.0/ragnarbot/skills/tmux/scripts/wait-for-text.sh +83 -0
- ragnarbot_ai-0.1.0/ragnarbot/skills/weather/SKILL.md +49 -0
- ragnarbot_ai-0.1.0/ragnarbot/utils/__init__.py +5 -0
- ragnarbot_ai-0.1.0/ragnarbot/utils/helpers.py +91 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 nanobot contributors
|
|
4
|
+
Copyright (c) 2025 BlckLvls
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ragnarbot-ai
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A lightweight personal AI assistant framework
|
|
5
|
+
Author: BlckLvls
|
|
6
|
+
License: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Keywords: agent,ai,chatbot
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Requires-Python: >=3.11
|
|
15
|
+
Requires-Dist: croniter>=2.0.0
|
|
16
|
+
Requires-Dist: httpx>=0.25.0
|
|
17
|
+
Requires-Dist: litellm>=1.0.0
|
|
18
|
+
Requires-Dist: loguru>=0.7.0
|
|
19
|
+
Requires-Dist: pydantic-settings>=2.0.0
|
|
20
|
+
Requires-Dist: pydantic>=2.0.0
|
|
21
|
+
Requires-Dist: python-telegram-bot>=21.0
|
|
22
|
+
Requires-Dist: readability-lxml>=0.8.0
|
|
23
|
+
Requires-Dist: rich>=13.0.0
|
|
24
|
+
Requires-Dist: typer>=0.9.0
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
28
|
+
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="ragnarbot_logo.jpg" alt="ragnarbot" width="500">
|
|
3
|
+
</div>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<em>Async-first personal AI assistant. Lightweight, extensible, runs anywhere.</em>
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
Install [uv](https://github.com/astral-sh/uv) if you don't have it:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Then install ragnarbot:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
uv tool install ragnarbot-ai
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
**1. Initialize**
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
ragnarbot onboard
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**2. Configure**
|
|
34
|
+
|
|
35
|
+
Add your API key to `~/.ragnarbot/config.json`:
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"providers": {
|
|
40
|
+
"anthropic": {
|
|
41
|
+
"apiKey": "sk-ant-xxx"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Get an API key from [Anthropic](https://console.anthropic.com/keys), [OpenAI](https://platform.openai.com/api-keys), or [Google AI Studio](https://aistudio.google.com/apikey).
|
|
48
|
+
|
|
49
|
+
**3. Chat**
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
ragnarbot agent -m "What can you do?"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Or start an interactive session:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
ragnarbot agent
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Telegram
|
|
62
|
+
|
|
63
|
+
ragnarbot is designed to work through Telegram. Set up a bot, point it at your instance, and you have a personal AI assistant in your pocket.
|
|
64
|
+
|
|
65
|
+
**Create a bot**
|
|
66
|
+
|
|
67
|
+
Open Telegram, find `@BotFather`, send `/newbot`, and follow the prompts. Copy the token.
|
|
68
|
+
|
|
69
|
+
**Get your user ID**
|
|
70
|
+
|
|
71
|
+
Message `@userinfobot` on Telegram to get your numeric user ID.
|
|
72
|
+
|
|
73
|
+
**Add to config**
|
|
74
|
+
|
|
75
|
+
```json
|
|
76
|
+
{
|
|
77
|
+
"channels": {
|
|
78
|
+
"telegram": {
|
|
79
|
+
"enabled": true,
|
|
80
|
+
"token": "YOUR_BOT_TOKEN",
|
|
81
|
+
"allowFrom": ["YOUR_USER_ID"]
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**Run the gateway**
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
ragnarbot gateway
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Your bot is live. Message it from Telegram.
|
|
94
|
+
|
|
95
|
+
## Configuration
|
|
96
|
+
|
|
97
|
+
All configuration lives in `~/.ragnarbot/config.json`. Keys are camelCase.
|
|
98
|
+
|
|
99
|
+
### Providers
|
|
100
|
+
|
|
101
|
+
Only one provider is required. ragnarbot uses [LiteLLM](https://github.com/BerriAI/litellm) under the hood, so any model string LiteLLM supports will work.
|
|
102
|
+
|
|
103
|
+
| Provider | Config key | Models | API Key |
|
|
104
|
+
|----------|-----------|--------|---------|
|
|
105
|
+
| Anthropic | `providers.anthropic` | `anthropic/claude-*` | [console.anthropic.com](https://console.anthropic.com) |
|
|
106
|
+
| OpenAI | `providers.openai` | `openai/gpt-*` | [platform.openai.com](https://platform.openai.com) |
|
|
107
|
+
| Gemini | `providers.gemini` | `gemini/*` | [aistudio.google.com](https://aistudio.google.com) |
|
|
108
|
+
|
|
109
|
+
Set the default model under `agents.defaults.model`.
|
|
110
|
+
|
|
111
|
+
### Transcription
|
|
112
|
+
|
|
113
|
+
Voice messages in Telegram are automatically transcribed when a Groq API key is configured. Groq provides free access to Whisper.
|
|
114
|
+
|
|
115
|
+
```json
|
|
116
|
+
{
|
|
117
|
+
"transcription": {
|
|
118
|
+
"apiKey": "gsk_xxx"
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Get a key at [console.groq.com](https://console.groq.com).
|
|
124
|
+
|
|
125
|
+
### Web Search
|
|
126
|
+
|
|
127
|
+
ragnarbot can search the web via the Brave Search API.
|
|
128
|
+
|
|
129
|
+
```json
|
|
130
|
+
{
|
|
131
|
+
"tools": {
|
|
132
|
+
"web": {
|
|
133
|
+
"search": {
|
|
134
|
+
"apiKey": "BSA-xxx"
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Get a key at [brave.com/search/api](https://brave.com/search/api/).
|
|
142
|
+
|
|
143
|
+
## CLI Reference
|
|
144
|
+
|
|
145
|
+
| Command | Description |
|
|
146
|
+
|---------|-------------|
|
|
147
|
+
| `ragnarbot onboard` | Initialize config and workspace |
|
|
148
|
+
| `ragnarbot agent -m "..."` | Send a single message |
|
|
149
|
+
| `ragnarbot agent` | Interactive chat session |
|
|
150
|
+
| `ragnarbot gateway` | Start gateway (Telegram + cron + heartbeat) |
|
|
151
|
+
| `ragnarbot status` | Show configuration status |
|
|
152
|
+
| `ragnarbot channels status` | Show channel status |
|
|
153
|
+
| `ragnarbot cron list` | List scheduled jobs |
|
|
154
|
+
| `ragnarbot cron add` | Add a scheduled job |
|
|
155
|
+
| `ragnarbot cron remove <id>` | Remove a scheduled job |
|
|
156
|
+
|
|
157
|
+
## Architecture
|
|
158
|
+
|
|
159
|
+
ragnarbot is async-first and built around a simple message-passing architecture:
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
Telegram --> MessageBus --> AgentLoop --> LLM --> Tools
|
|
163
|
+
\ /
|
|
164
|
+
\-- Sessions --/
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**MessageBus** decouples channels from agent logic using async queues. Channels publish inbound messages; the agent publishes responses back.
|
|
168
|
+
|
|
169
|
+
**AgentLoop** consumes messages, builds context (system prompt, conversation history, skills), calls the LLM, and executes tool calls in a loop. It also exposes a `process_direct()` path for CLI usage and cron jobs.
|
|
170
|
+
|
|
171
|
+
**Tools** are registered in a `ToolRegistry` and exposed to the LLM as OpenAI-compatible function calls. Built-in tools include file operations, shell execution, web search, cron management, and sub-agent spawning.
|
|
172
|
+
|
|
173
|
+
**Skills** are markdown files with YAML frontmatter. Skills marked `always: true` are included in every prompt; others appear as summaries the agent can load on demand.
|
|
174
|
+
|
|
175
|
+
**Sessions** persist conversation history as JSONL files under `~/.ragnarbot/sessions/`.
|
|
176
|
+
|
|
177
|
+
## License
|
|
178
|
+
|
|
179
|
+
MIT
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
<sub>Based on [nanobot](https://github.com/HKUDS/nanobot)</sub>
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "ragnarbot-ai"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "A lightweight personal AI assistant framework"
|
|
5
|
+
requires-python = ">=3.11"
|
|
6
|
+
license = {text = "MIT"}
|
|
7
|
+
authors = [
|
|
8
|
+
{name = "BlckLvls"}
|
|
9
|
+
]
|
|
10
|
+
keywords = ["ai", "agent", "chatbot"]
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Development Status :: 3 - Alpha",
|
|
13
|
+
"Intended Audience :: Developers",
|
|
14
|
+
"License :: OSI Approved :: MIT License",
|
|
15
|
+
"Programming Language :: Python :: 3.11",
|
|
16
|
+
"Programming Language :: Python :: 3.12",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
dependencies = [
|
|
20
|
+
"typer>=0.9.0",
|
|
21
|
+
"litellm>=1.0.0",
|
|
22
|
+
"pydantic>=2.0.0",
|
|
23
|
+
"pydantic-settings>=2.0.0",
|
|
24
|
+
"httpx>=0.25.0",
|
|
25
|
+
"loguru>=0.7.0",
|
|
26
|
+
"readability-lxml>=0.8.0",
|
|
27
|
+
"rich>=13.0.0",
|
|
28
|
+
"croniter>=2.0.0",
|
|
29
|
+
"python-telegram-bot>=21.0",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
dev = [
|
|
34
|
+
"pytest>=7.0.0",
|
|
35
|
+
"pytest-asyncio>=0.21.0",
|
|
36
|
+
"ruff>=0.1.0",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
[project.scripts]
|
|
40
|
+
ragnarbot = "ragnarbot.cli.commands:app"
|
|
41
|
+
|
|
42
|
+
[build-system]
|
|
43
|
+
requires = ["hatchling"]
|
|
44
|
+
build-backend = "hatchling.build"
|
|
45
|
+
|
|
46
|
+
[tool.hatch.build.targets.wheel]
|
|
47
|
+
packages = ["ragnarbot"]
|
|
48
|
+
|
|
49
|
+
[tool.hatch.build.targets.wheel.sources]
|
|
50
|
+
"ragnarbot" = "ragnarbot"
|
|
51
|
+
|
|
52
|
+
# Include non-Python files in skills
|
|
53
|
+
[tool.hatch.build]
|
|
54
|
+
include = [
|
|
55
|
+
"ragnarbot/**/*.py",
|
|
56
|
+
"ragnarbot/skills/**/*.md",
|
|
57
|
+
"ragnarbot/skills/**/*.sh",
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
[tool.hatch.build.targets.sdist]
|
|
61
|
+
include = [
|
|
62
|
+
"ragnarbot/",
|
|
63
|
+
"README.md",
|
|
64
|
+
"LICENSE",
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
[tool.ruff]
|
|
68
|
+
line-length = 100
|
|
69
|
+
target-version = "py311"
|
|
70
|
+
|
|
71
|
+
[tool.ruff.lint]
|
|
72
|
+
select = ["E", "F", "I", "N", "W"]
|
|
73
|
+
ignore = ["E501"]
|
|
74
|
+
|
|
75
|
+
[tool.pytest.ini_options]
|
|
76
|
+
asyncio_mode = "auto"
|
|
77
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""Agent core module."""
|
|
2
|
+
|
|
3
|
+
from ragnarbot.agent.loop import AgentLoop
|
|
4
|
+
from ragnarbot.agent.context import ContextBuilder
|
|
5
|
+
from ragnarbot.agent.memory import MemoryStore
|
|
6
|
+
from ragnarbot.agent.skills import SkillsLoader
|
|
7
|
+
|
|
8
|
+
__all__ = ["AgentLoop", "ContextBuilder", "MemoryStore", "SkillsLoader"]
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"""Context builder for assembling agent prompts."""
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
import mimetypes
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from ragnarbot.agent.memory import MemoryStore
|
|
9
|
+
from ragnarbot.agent.skills import SkillsLoader
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ContextBuilder:
|
|
13
|
+
"""
|
|
14
|
+
Builds the context (system prompt + messages) for the agent.
|
|
15
|
+
|
|
16
|
+
Assembles bootstrap files, memory, skills, and conversation history
|
|
17
|
+
into a coherent prompt for the LLM.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
BOOTSTRAP_FILES = ["AGENTS.md", "SOUL.md", "USER.md", "TOOLS.md", "IDENTITY.md"]
|
|
21
|
+
|
|
22
|
+
def __init__(self, workspace: Path):
|
|
23
|
+
self.workspace = workspace
|
|
24
|
+
self.memory = MemoryStore(workspace)
|
|
25
|
+
self.skills = SkillsLoader(workspace)
|
|
26
|
+
|
|
27
|
+
def build_system_prompt(self, skill_names: list[str] | None = None) -> str:
|
|
28
|
+
"""
|
|
29
|
+
Build the system prompt from bootstrap files, memory, and skills.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
skill_names: Optional list of skills to include.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Complete system prompt.
|
|
36
|
+
"""
|
|
37
|
+
parts = []
|
|
38
|
+
|
|
39
|
+
# Core identity
|
|
40
|
+
parts.append(self._get_identity())
|
|
41
|
+
|
|
42
|
+
# Bootstrap files
|
|
43
|
+
bootstrap = self._load_bootstrap_files()
|
|
44
|
+
if bootstrap:
|
|
45
|
+
parts.append(bootstrap)
|
|
46
|
+
|
|
47
|
+
# Memory context
|
|
48
|
+
memory = self.memory.get_memory_context()
|
|
49
|
+
if memory:
|
|
50
|
+
parts.append(f"# Memory\n\n{memory}")
|
|
51
|
+
|
|
52
|
+
# Skills - progressive loading
|
|
53
|
+
# 1. Always-loaded skills: include full content
|
|
54
|
+
always_skills = self.skills.get_always_skills()
|
|
55
|
+
if always_skills:
|
|
56
|
+
always_content = self.skills.load_skills_for_context(always_skills)
|
|
57
|
+
if always_content:
|
|
58
|
+
parts.append(f"# Active Skills\n\n{always_content}")
|
|
59
|
+
|
|
60
|
+
# 2. Available skills: only show summary (agent uses read_file to load)
|
|
61
|
+
skills_summary = self.skills.build_skills_summary()
|
|
62
|
+
if skills_summary:
|
|
63
|
+
parts.append(f"""# Skills
|
|
64
|
+
|
|
65
|
+
The following skills extend your capabilities. To use a skill, read its SKILL.md file using the read_file tool.
|
|
66
|
+
Skills with available="false" need dependencies installed first - you can try installing them with apt/brew.
|
|
67
|
+
|
|
68
|
+
{skills_summary}""")
|
|
69
|
+
|
|
70
|
+
return "\n\n---\n\n".join(parts)
|
|
71
|
+
|
|
72
|
+
def _get_identity(self) -> str:
|
|
73
|
+
"""Get the core identity section."""
|
|
74
|
+
from datetime import datetime
|
|
75
|
+
now = datetime.now().strftime("%Y-%m-%d %H:%M (%A)")
|
|
76
|
+
workspace_path = str(self.workspace.expanduser().resolve())
|
|
77
|
+
|
|
78
|
+
return f"""# ragnarbot 🤖
|
|
79
|
+
|
|
80
|
+
You are ragnarbot, a helpful AI assistant. You have access to tools that allow you to:
|
|
81
|
+
- Read, write, and edit files
|
|
82
|
+
- Execute shell commands
|
|
83
|
+
- Search the web and fetch web pages
|
|
84
|
+
- Send messages to users on chat channels
|
|
85
|
+
- Spawn subagents for complex background tasks
|
|
86
|
+
|
|
87
|
+
## Current Time
|
|
88
|
+
{now}
|
|
89
|
+
|
|
90
|
+
## Workspace
|
|
91
|
+
Your workspace is at: {workspace_path}
|
|
92
|
+
- Memory files: {workspace_path}/memory/MEMORY.md
|
|
93
|
+
- Daily notes: {workspace_path}/memory/YYYY-MM-DD.md
|
|
94
|
+
- Custom skills: {workspace_path}/skills/{{skill-name}}/SKILL.md
|
|
95
|
+
|
|
96
|
+
IMPORTANT: When responding to direct questions or conversations, reply directly with your text response.
|
|
97
|
+
Only use the 'message' tool when you need to send a message to a specific chat channel.
|
|
98
|
+
For normal conversation, just respond with text - do not call the message tool.
|
|
99
|
+
|
|
100
|
+
Always be helpful, accurate, and concise. When using tools, explain what you're doing.
|
|
101
|
+
When remembering something, write to {workspace_path}/memory/MEMORY.md"""
|
|
102
|
+
|
|
103
|
+
def _load_bootstrap_files(self) -> str:
|
|
104
|
+
"""Load all bootstrap files from workspace."""
|
|
105
|
+
parts = []
|
|
106
|
+
|
|
107
|
+
for filename in self.BOOTSTRAP_FILES:
|
|
108
|
+
file_path = self.workspace / filename
|
|
109
|
+
if file_path.exists():
|
|
110
|
+
content = file_path.read_text(encoding="utf-8")
|
|
111
|
+
parts.append(f"## {filename}\n\n{content}")
|
|
112
|
+
|
|
113
|
+
return "\n\n".join(parts) if parts else ""
|
|
114
|
+
|
|
115
|
+
def build_messages(
|
|
116
|
+
self,
|
|
117
|
+
history: list[dict[str, Any]],
|
|
118
|
+
current_message: str,
|
|
119
|
+
skill_names: list[str] | None = None,
|
|
120
|
+
media: list[str] | None = None,
|
|
121
|
+
channel: str | None = None,
|
|
122
|
+
chat_id: str | None = None,
|
|
123
|
+
) -> list[dict[str, Any]]:
|
|
124
|
+
"""
|
|
125
|
+
Build the complete message list for an LLM call.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
history: Previous conversation messages.
|
|
129
|
+
current_message: The new user message.
|
|
130
|
+
skill_names: Optional skills to include.
|
|
131
|
+
media: Optional list of local file paths for images/media.
|
|
132
|
+
channel: Current channel (e.g. telegram).
|
|
133
|
+
chat_id: Current chat/user ID.
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
List of messages including system prompt.
|
|
137
|
+
"""
|
|
138
|
+
messages = []
|
|
139
|
+
|
|
140
|
+
# System prompt
|
|
141
|
+
system_prompt = self.build_system_prompt(skill_names)
|
|
142
|
+
if channel and chat_id:
|
|
143
|
+
system_prompt += f"\n\n## Current Session\nChannel: {channel}\nChat ID: {chat_id}"
|
|
144
|
+
messages.append({"role": "system", "content": system_prompt})
|
|
145
|
+
|
|
146
|
+
# History
|
|
147
|
+
messages.extend(history)
|
|
148
|
+
|
|
149
|
+
# Current message (with optional image attachments)
|
|
150
|
+
user_content = self._build_user_content(current_message, media)
|
|
151
|
+
messages.append({"role": "user", "content": user_content})
|
|
152
|
+
|
|
153
|
+
return messages
|
|
154
|
+
|
|
155
|
+
def _build_user_content(self, text: str, media: list[str] | None) -> str | list[dict[str, Any]]:
|
|
156
|
+
"""Build user message content with optional base64-encoded images."""
|
|
157
|
+
if not media:
|
|
158
|
+
return text
|
|
159
|
+
|
|
160
|
+
images = []
|
|
161
|
+
for path in media:
|
|
162
|
+
p = Path(path)
|
|
163
|
+
mime, _ = mimetypes.guess_type(path)
|
|
164
|
+
if not p.is_file() or not mime or not mime.startswith("image/"):
|
|
165
|
+
continue
|
|
166
|
+
b64 = base64.b64encode(p.read_bytes()).decode()
|
|
167
|
+
images.append({"type": "image_url", "image_url": {"url": f"data:{mime};base64,{b64}"}})
|
|
168
|
+
|
|
169
|
+
if not images:
|
|
170
|
+
return text
|
|
171
|
+
return images + [{"type": "text", "text": text}]
|
|
172
|
+
|
|
173
|
+
def add_tool_result(
|
|
174
|
+
self,
|
|
175
|
+
messages: list[dict[str, Any]],
|
|
176
|
+
tool_call_id: str,
|
|
177
|
+
tool_name: str,
|
|
178
|
+
result: str
|
|
179
|
+
) -> list[dict[str, Any]]:
|
|
180
|
+
"""
|
|
181
|
+
Add a tool result to the message list.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
messages: Current message list.
|
|
185
|
+
tool_call_id: ID of the tool call.
|
|
186
|
+
tool_name: Name of the tool.
|
|
187
|
+
result: Tool execution result.
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
Updated message list.
|
|
191
|
+
"""
|
|
192
|
+
messages.append({
|
|
193
|
+
"role": "tool",
|
|
194
|
+
"tool_call_id": tool_call_id,
|
|
195
|
+
"name": tool_name,
|
|
196
|
+
"content": result
|
|
197
|
+
})
|
|
198
|
+
return messages
|
|
199
|
+
|
|
200
|
+
def add_assistant_message(
|
|
201
|
+
self,
|
|
202
|
+
messages: list[dict[str, Any]],
|
|
203
|
+
content: str | None,
|
|
204
|
+
tool_calls: list[dict[str, Any]] | None = None
|
|
205
|
+
) -> list[dict[str, Any]]:
|
|
206
|
+
"""
|
|
207
|
+
Add an assistant message to the message list.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
messages: Current message list.
|
|
211
|
+
content: Message content.
|
|
212
|
+
tool_calls: Optional tool calls.
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
Updated message list.
|
|
216
|
+
"""
|
|
217
|
+
msg: dict[str, Any] = {"role": "assistant", "content": content or ""}
|
|
218
|
+
|
|
219
|
+
if tool_calls:
|
|
220
|
+
msg["tool_calls"] = tool_calls
|
|
221
|
+
|
|
222
|
+
messages.append(msg)
|
|
223
|
+
return messages
|