merge origin/main into pr-1039, adopt HEARTBEAT_OK in-check and on_notify

This commit is contained in:
Re-bin
2026-02-23 13:57:28 +00:00
23 changed files with 281 additions and 348 deletions

View File

@@ -96,14 +96,18 @@ Your workspace is at: {workspace_path}
- History log: {workspace_path}/memory/HISTORY.md (grep-searchable)
- Custom skills: {workspace_path}/skills/{{skill-name}}/SKILL.md
IMPORTANT: When responding to direct questions or conversations, reply directly with your text response.
Only use the 'message' tool when you need to send a message to a specific chat channel (like WhatsApp).
For normal conversation, just respond with text - do not call the message tool.
Reply directly with text for conversations. Only use the 'message' tool to send to a specific chat channel.
Always be helpful, accurate, and concise. Before calling tools, briefly tell the user what you're about to do (one short sentence in the user's language).
If you need to use tools, call them directly — never send a preliminary message like "Let me check" without actually calling a tool.
When remembering something important, write to {workspace_path}/memory/MEMORY.md
To recall past events, grep {workspace_path}/memory/HISTORY.md"""
## Tool Call Guidelines
- Before calling tools, you may briefly state your intent (e.g. "Let me check that"), but NEVER predict or describe the expected result before receiving it.
- Before modifying a file, read it first to confirm its current content.
- Do not assume a file or directory exists — use list_dir or read_file to verify.
- After writing or editing a file, re-read it if accuracy matters.
- If a tool call fails, analyze the error before retrying with a different approach.
## Memory
- Remember important facts: write to {workspace_path}/memory/MEMORY.md
- Recall past events: grep {workspace_path}/memory/HISTORY.md"""
def _load_bootstrap_files(self) -> str:
"""Load all bootstrap files from workspace."""

View File

@@ -27,7 +27,7 @@ from nanobot.providers.base import LLMProvider
from nanobot.session.manager import Session, SessionManager
if TYPE_CHECKING:
from nanobot.config.schema import ExecToolConfig
from nanobot.config.schema import ChannelsConfig, ExecToolConfig
from nanobot.cron.service import CronService
@@ -49,19 +49,21 @@ class AgentLoop:
provider: LLMProvider,
workspace: Path,
model: str | None = None,
max_iterations: int = 20,
temperature: float = 0.7,
max_iterations: int = 40,
temperature: float = 0.1,
max_tokens: int = 4096,
memory_window: int = 50,
memory_window: int = 100,
brave_api_key: str | None = None,
exec_config: ExecToolConfig | None = None,
cron_service: CronService | None = None,
restrict_to_workspace: bool = False,
session_manager: SessionManager | None = None,
mcp_servers: dict | None = None,
channels_config: ChannelsConfig | None = None,
):
from nanobot.config.schema import ExecToolConfig
self.bus = bus
self.channels_config = channels_config
self.provider = provider
self.workspace = workspace
self.model = model or provider.get_default_model()
@@ -172,9 +174,9 @@ class AgentLoop:
async def _run_agent_loop(
self,
initial_messages: list[dict],
on_progress: Callable[[str], Awaitable[None]] | None = None,
) -> tuple[str | None, list[str]]:
"""Run the agent iteration loop. Returns (final_content, tools_used)."""
on_progress: Callable[..., Awaitable[None]] | None = None,
) -> tuple[str | None, list[str], list[dict]]:
"""Run the agent iteration loop. Returns (final_content, tools_used, messages)."""
messages = initial_messages
iteration = 0
final_content = None
@@ -196,8 +198,7 @@ class AgentLoop:
clean = self._strip_think(response.content)
if clean:
await on_progress(clean)
else:
await on_progress(self._tool_hint(response.tool_calls))
await on_progress(self._tool_hint(response.tool_calls), tool_hint=True)
tool_call_dicts = [
{
@@ -227,7 +228,14 @@ class AgentLoop:
final_content = self._strip_think(response.content)
break
return final_content, tools_used
if final_content is None and iteration >= self.max_iterations:
logger.warning("Max iterations ({}) reached", self.max_iterations)
final_content = (
f"I reached the maximum number of tool call iterations ({self.max_iterations}) "
"without completing the task. You can try breaking the task into smaller steps."
)
return final_content, tools_used, messages
async def run(self) -> None:
"""Run the agent loop, processing messages from the bus."""
@@ -300,13 +308,13 @@ class AgentLoop:
key = f"{channel}:{chat_id}"
session = self.sessions.get_or_create(key)
self._set_tool_context(channel, chat_id, msg.metadata.get("message_id"))
history = session.get_history(max_messages=self.memory_window)
messages = self.context.build_messages(
history=session.get_history(max_messages=self.memory_window),
history=history,
current_message=msg.content, channel=channel, chat_id=chat_id,
)
final_content, _ = await self._run_agent_loop(messages)
session.add_message("user", f"[System: {msg.sender_id}] {msg.content}")
session.add_message("assistant", final_content or "Background task completed.")
final_content, _, all_msgs = await self._run_agent_loop(messages)
self._save_turn(session, all_msgs, 1 + len(history))
self.sessions.save(session)
return OutboundMessage(channel=channel, chat_id=chat_id,
content=final_content or "Background task completed.")
@@ -376,21 +384,23 @@ class AgentLoop:
if isinstance(message_tool, MessageTool):
message_tool.start_turn()
history = session.get_history(max_messages=self.memory_window)
initial_messages = self.context.build_messages(
history=session.get_history(max_messages=self.memory_window),
history=history,
current_message=msg.content,
media=msg.media if msg.media else None,
channel=msg.channel, chat_id=msg.chat_id,
)
async def _bus_progress(content: str) -> None:
async def _bus_progress(content: str, *, tool_hint: bool = False) -> None:
meta = dict(msg.metadata or {})
meta["_progress"] = True
meta["_tool_hint"] = tool_hint
await self.bus.publish_outbound(OutboundMessage(
channel=msg.channel, chat_id=msg.chat_id, content=content, metadata=meta,
))
final_content, tools_used = await self._run_agent_loop(
final_content, _, all_msgs = await self._run_agent_loop(
initial_messages, on_progress=on_progress or _bus_progress,
)
@@ -400,9 +410,7 @@ class AgentLoop:
preview = final_content[:120] + "..." if len(final_content) > 120 else final_content
logger.info("Response to {}:{}: {}", msg.channel, msg.sender_id, preview)
session.add_message("user", msg.content)
session.add_message("assistant", final_content,
tools_used=tools_used if tools_used else None)
self._save_turn(session, all_msgs, 1 + len(history))
self.sessions.save(session)
if message_tool := self.tools.get("message"):
@@ -414,6 +422,21 @@ class AgentLoop:
metadata=msg.metadata or {},
)
_TOOL_RESULT_MAX_CHARS = 500
def _save_turn(self, session: Session, messages: list[dict], skip: int) -> None:
"""Save new-turn messages into session, truncating large tool results."""
from datetime import datetime
for m in messages[skip:]:
entry = {k: v for k, v in m.items() if k != "reasoning_content"}
if entry.get("role") == "tool" and isinstance(entry.get("content"), str):
content = entry["content"]
if len(content) > self._TOOL_RESULT_MAX_CHARS:
entry["content"] = content[:self._TOOL_RESULT_MAX_CHARS] + "\n... (truncated)"
entry.setdefault("timestamp", datetime.now().isoformat())
session.messages.append(entry)
session.updated_at = datetime.now()
async def _consolidate_memory(self, session, archive_all: bool = False) -> bool:
"""Delegate to MemoryStore.consolidate(). Returns True on success."""
return await MemoryStore(self.workspace).consolidate(

View File

@@ -49,17 +49,22 @@ class ToolRegistry:
Raises:
KeyError: If tool not found.
"""
_HINT = "\n\n[Analyze the error above and try a different approach.]"
tool = self._tools.get(name)
if not tool:
return f"Error: Tool '{name}' not found"
return f"Error: Tool '{name}' not found. Available: {', '.join(self.tool_names)}"
try:
errors = tool.validate_params(params)
if errors:
return f"Error: Invalid parameters for tool '{name}': " + "; ".join(errors)
return await tool.execute(**params)
return f"Error: Invalid parameters for tool '{name}': " + "; ".join(errors) + _HINT
result = await tool.execute(**params)
if isinstance(result, str) and result.startswith("Error"):
return result + _HINT
return result
except Exception as e:
return f"Error executing {name}: {str(e)}"
return f"Error executing {name}: {str(e)}" + _HINT
@property
def tool_names(self) -> list[str]:

View File

@@ -16,11 +16,12 @@ class InboundMessage:
timestamp: datetime = field(default_factory=datetime.now)
media: list[str] = field(default_factory=list) # Media URLs
metadata: dict[str, Any] = field(default_factory=dict) # Channel-specific data
session_key_override: str | None = None # Optional override for thread-scoped sessions
@property
def session_key(self) -> str:
"""Unique key for session identification."""
return f"{self.channel}:{self.chat_id}"
return self.session_key_override or f"{self.channel}:{self.chat_id}"
@dataclass

View File

@@ -89,7 +89,8 @@ class BaseChannel(ABC):
chat_id: str,
content: str,
media: list[str] | None = None,
metadata: dict[str, Any] | None = None
metadata: dict[str, Any] | None = None,
session_key: str | None = None,
) -> None:
"""
Handle an incoming message from the chat platform.
@@ -102,6 +103,7 @@ class BaseChannel(ABC):
content: Message text content.
media: Optional list of media URLs.
metadata: Optional channel-specific metadata.
session_key: Optional session key override (e.g. thread-scoped sessions).
"""
if not self.is_allowed(sender_id):
logger.warning(
@@ -117,7 +119,8 @@ class BaseChannel(ABC):
chat_id=str(chat_id),
content=content,
media=media or [],
metadata=metadata or {}
metadata=metadata or {},
session_key_override=session_key,
)
await self.bus.publish_inbound(msg)

View File

@@ -193,6 +193,12 @@ class ChannelManager:
timeout=1.0
)
if msg.metadata.get("_progress"):
if msg.metadata.get("_tool_hint") and not self.config.channels.send_tool_hints:
continue
if not msg.metadata.get("_tool_hint") and not self.config.channels.send_progress:
continue
channel = self.channels.get(msg.channel)
if channel:
try:

View File

@@ -179,6 +179,9 @@ class SlackChannel(BaseChannel):
except Exception as e:
logger.debug("Slack reactions_add failed: {}", e)
# Thread-scoped session key for channel/group messages
session_key = f"slack:{chat_id}:{thread_ts}" if thread_ts and channel_type != "im" else None
try:
await self._handle_message(
sender_id=sender_id,
@@ -189,8 +192,9 @@ class SlackChannel(BaseChannel):
"event": event,
"thread_ts": thread_ts,
"channel_type": channel_type,
}
},
},
session_key=session_key,
)
except Exception:
logger.exception("Error handling Slack message from {}", sender_id)

View File

@@ -199,84 +199,34 @@ def onboard():
def _create_workspace_templates(workspace: Path):
"""Create default workspace template files."""
templates = {
"AGENTS.md": """# Agent Instructions
"""Create default workspace template files from bundled templates."""
from importlib.resources import files as pkg_files
You are a helpful AI assistant. Be concise, accurate, and friendly.
templates_dir = pkg_files("nanobot") / "templates"
## Guidelines
for item in templates_dir.iterdir():
if not item.name.endswith(".md"):
continue
dest = workspace / item.name
if not dest.exists():
dest.write_text(item.read_text(encoding="utf-8"), encoding="utf-8")
console.print(f" [dim]Created {item.name}[/dim]")
- Always explain what you're doing before taking actions
- Ask for clarification when the request is ambiguous
- Use tools to help accomplish tasks
- Remember important information in memory/MEMORY.md; past events are logged in memory/HISTORY.md
""",
"SOUL.md": """# Soul
I am nanobot, a lightweight AI assistant.
## Personality
- Helpful and friendly
- Concise and to the point
- Curious and eager to learn
## Values
- Accuracy over speed
- User privacy and safety
- Transparency in actions
""",
"USER.md": """# User
Information about the user goes here.
## Preferences
- Communication style: (casual/formal)
- Timezone: (your timezone)
- Language: (your preferred language)
""",
}
for filename, content in templates.items():
file_path = workspace / filename
if not file_path.exists():
file_path.write_text(content, encoding="utf-8")
console.print(f" [dim]Created {filename}[/dim]")
# Create memory directory and MEMORY.md
memory_dir = workspace / "memory"
memory_dir.mkdir(exist_ok=True)
memory_template = templates_dir / "memory" / "MEMORY.md"
memory_file = memory_dir / "MEMORY.md"
if not memory_file.exists():
memory_file.write_text("""# Long-term Memory
This file stores important information that should persist across sessions.
## User Information
(Important facts about the user)
## Preferences
(User preferences learned over time)
## Important Notes
(Things to remember)
""", encoding="utf-8")
memory_file.write_text(memory_template.read_text(encoding="utf-8"), encoding="utf-8")
console.print(" [dim]Created memory/MEMORY.md[/dim]")
history_file = memory_dir / "HISTORY.md"
if not history_file.exists():
history_file.write_text("", encoding="utf-8")
console.print(" [dim]Created memory/HISTORY.md[/dim]")
# Create skills directory for custom user skills
skills_dir = workspace / "skills"
skills_dir.mkdir(exist_ok=True)
(workspace / "skills").mkdir(exist_ok=True)
def _make_provider(config: Config):
@@ -368,6 +318,7 @@ def gateway(
restrict_to_workspace=config.tools.restrict_to_workspace,
session_manager=session_manager,
mcp_servers=config.tools.mcp_servers,
channels_config=config.channels,
)
# Set cron callback (needs agent)
@@ -389,21 +340,57 @@ def gateway(
return response
cron.on_job = on_cron_job
# Create channel manager
channels = ChannelManager(config, bus)
def _pick_heartbeat_target() -> tuple[str, str]:
"""Pick a routable channel/chat target for heartbeat-triggered messages."""
enabled = set(channels.enabled_channels)
# Prefer the most recently updated non-internal session on an enabled channel.
for item in session_manager.list_sessions():
key = item.get("key") or ""
if ":" not in key:
continue
channel, chat_id = key.split(":", 1)
if channel in {"cli", "system"}:
continue
if channel in enabled and chat_id:
return channel, chat_id
# Fallback keeps prior behavior but remains explicit.
return "cli", "direct"
# Create heartbeat service
async def on_heartbeat(prompt: str) -> str:
"""Execute heartbeat through the agent."""
return await agent.process_direct(prompt, session_key="heartbeat")
channel, chat_id = _pick_heartbeat_target()
async def _silent(*_args, **_kwargs):
pass
return await agent.process_direct(
prompt,
session_key="heartbeat",
channel=channel,
chat_id=chat_id,
on_progress=_silent, # suppress: heartbeat should not push progress to external channels
)
async def on_heartbeat_notify(response: str) -> None:
"""Deliver a heartbeat response to the user's channel."""
from nanobot.bus.events import OutboundMessage
channel, chat_id = _pick_heartbeat_target()
if channel == "cli":
return # No external channel available to deliver to
await bus.publish_outbound(OutboundMessage(channel=channel, chat_id=chat_id, content=response))
heartbeat = HeartbeatService(
workspace=config.workspace_path,
on_heartbeat=on_heartbeat,
on_notify=on_heartbeat_notify,
interval_s=30 * 60, # 30 minutes
enabled=True
)
# Create channel manager
channels = ChannelManager(config, bus)
if channels.enabled_channels:
console.print(f"[green]✓[/green] Channels enabled: {', '.join(channels.enabled_channels)}")
else:
@@ -484,6 +471,7 @@ def agent(
cron_service=cron,
restrict_to_workspace=config.tools.restrict_to_workspace,
mcp_servers=config.tools.mcp_servers,
channels_config=config.channels,
)
# Show spinner when logs are off (no output to miss); skip when logs are on
@@ -494,7 +482,12 @@ def agent(
# Animated spinner is safe to use with prompt_toolkit input handling
return console.status("[dim]nanobot is thinking...[/dim]", spinner="dots")
async def _cli_progress(content: str) -> None:
async def _cli_progress(content: str, *, tool_hint: bool = False) -> None:
ch = agent_loop.channels_config
if ch and tool_hint and not ch.send_tool_hints:
return
if ch and not tool_hint and not ch.send_progress:
return
console.print(f" [dim]↳ {content}[/dim]")
if message:
@@ -535,7 +528,14 @@ def agent(
try:
msg = await asyncio.wait_for(bus.consume_outbound(), timeout=1.0)
if msg.metadata.get("_progress"):
console.print(f" [dim]↳ {msg.content}[/dim]")
is_tool_hint = msg.metadata.get("_tool_hint", False)
ch = agent_loop.channels_config
if ch and is_tool_hint and not ch.send_tool_hints:
pass
elif ch and not is_tool_hint and not ch.send_progress:
pass
else:
console.print(f" [dim]↳ {msg.content}[/dim]")
elif not turn_done.is_set():
if msg.content:
turn_response.append(msg.content)
@@ -961,6 +961,7 @@ def cron_run(
exec_config=config.tools.exec,
restrict_to_workspace=config.tools.restrict_to_workspace,
mcp_servers=config.tools.mcp_servers,
channels_config=config.channels,
)
store_path = get_data_dir() / "cron" / "jobs.json"

View File

@@ -168,6 +168,8 @@ class QQConfig(Base):
class ChannelsConfig(Base):
"""Configuration for chat channels."""
send_progress: bool = True # stream agent's text progress to the channel
send_tool_hints: bool = False # stream tool-call hints (e.g. read_file("…"))
whatsapp: WhatsAppConfig = Field(default_factory=WhatsAppConfig)
telegram: TelegramConfig = Field(default_factory=TelegramConfig)
discord: DiscordConfig = Field(default_factory=DiscordConfig)
@@ -185,9 +187,9 @@ class AgentDefaults(Base):
workspace: str = "~/.nanobot/workspace"
model: str = "anthropic/claude-opus-4-5"
max_tokens: int = 8192
temperature: float = 0.7
max_tool_iterations: int = 20
memory_window: int = 50
temperature: float = 0.1
max_tool_iterations: int = 40
memory_window: int = 100
class AgentsConfig(Base):

View File

@@ -1,7 +1,6 @@
"""Heartbeat service - periodic agent wake-up to check for tasks."""
import asyncio
import re
from pathlib import Path
from typing import Any, Callable, Coroutine
@@ -10,14 +9,15 @@ from loguru import logger
# Default interval: 30 minutes
DEFAULT_HEARTBEAT_INTERVAL_S = 30 * 60
# The prompt sent to agent during heartbeat
HEARTBEAT_PROMPT = """Read HEARTBEAT.md in your workspace (if it exists).
Follow any instructions or tasks listed there.
If nothing needs attention, reply with just: HEARTBEAT_OK"""
# Token that indicates "nothing to do"
# Token the agent replies with when there is nothing to report
HEARTBEAT_OK_TOKEN = "HEARTBEAT_OK"
# The prompt sent to agent during heartbeat
HEARTBEAT_PROMPT = (
"Read HEARTBEAT.md in your workspace and follow any instructions listed there. "
f"If nothing needs attention, reply with exactly: {HEARTBEAT_OK_TOKEN}"
)
def _is_heartbeat_empty(content: str | None) -> bool:
"""Check if HEARTBEAT.md has no actionable content."""
@@ -36,32 +36,28 @@ def _is_heartbeat_empty(content: str | None) -> bool:
return True
def _is_heartbeat_ok_response(response: str | None) -> bool:
"""Return True only for an exact HEARTBEAT_OK token (with simple markdown wrappers)."""
if not response:
return False
normalized = re.sub(r"[\s`*_>\-]", "", response).upper()
return normalized == HEARTBEAT_OK_TOKEN.replace("_", "")
class HeartbeatService:
"""
Periodic heartbeat service that wakes the agent to check for tasks.
The agent reads HEARTBEAT.md from the workspace and executes any
tasks listed there. If nothing needs attention, it replies HEARTBEAT_OK.
The agent reads HEARTBEAT.md from the workspace and executes any tasks
listed there. If it has something to report, the response is forwarded
to the user via on_notify. If nothing needs attention, the agent replies
HEARTBEAT_OK and the response is silently dropped.
"""
def __init__(
self,
workspace: Path,
on_heartbeat: Callable[[str], Coroutine[Any, Any, str]] | None = None,
on_notify: Callable[[str], Coroutine[Any, Any, None]] | None = None,
interval_s: int = DEFAULT_HEARTBEAT_INTERVAL_S,
enabled: bool = True,
):
self.workspace = workspace
self.on_heartbeat = on_heartbeat
self.on_notify = on_notify
self.interval_s = interval_s
self.enabled = enabled
self._running = False
@@ -126,15 +122,14 @@ class HeartbeatService:
if self.on_heartbeat:
try:
response = await self.on_heartbeat(HEARTBEAT_PROMPT)
# Check if agent said "nothing to do"
if _is_heartbeat_ok_response(response):
logger.info("Heartbeat: OK (no action needed)")
if HEARTBEAT_OK_TOKEN in response.upper():
logger.info("Heartbeat: OK (nothing to report)")
else:
logger.info("Heartbeat: completed task")
except Exception as e:
logger.error("Heartbeat execution failed: {}", e)
logger.info("Heartbeat: completed, delivering response")
if self.on_notify:
await self.on_notify(response)
except Exception:
logger.exception("Heartbeat execution failed")
async def trigger_now(self) -> str | None:
"""Manually trigger a heartbeat."""

View File

@@ -0,0 +1,29 @@
# Agent Instructions
You are a helpful AI assistant. Be concise, accurate, and friendly.
## Guidelines
- Always explain what you're doing before taking actions
- Ask for clarification when the request is ambiguous
- Remember important information in `memory/MEMORY.md`; past events are logged in `memory/HISTORY.md`
## Scheduled Reminders
When user asks for a reminder at a specific time, use `exec` to run:
```
nanobot cron add --name "reminder" --message "Your message" --at "YYYY-MM-DDTHH:MM:SS" --deliver --to "USER_ID" --channel "CHANNEL"
```
Get USER_ID and CHANNEL from the current session (e.g., `8281248569` and `telegram` from `telegram:8281248569`).
**Do NOT just write reminders to MEMORY.md** — that won't trigger actual notifications.
## Heartbeat Tasks
`HEARTBEAT.md` is checked every 30 minutes. Use file tools to manage periodic tasks:
- **Add**: `edit_file` to append new tasks
- **Remove**: `edit_file` to delete completed tasks
- **Rewrite**: `write_file` to replace all tasks
When the user asks for a recurring/periodic task, update `HEARTBEAT.md` instead of creating a one-time cron reminder.

View File

@@ -0,0 +1,16 @@
# Heartbeat Tasks
This file is checked every 30 minutes by your nanobot agent.
Add tasks below that you want the agent to work on periodically.
If this file has no tasks (only headers and comments), the agent will skip the heartbeat.
## Active Tasks
<!-- Add your periodic tasks below this line -->
## Completed
<!-- Move completed tasks here or delete them -->

21
nanobot/templates/SOUL.md Normal file
View File

@@ -0,0 +1,21 @@
# Soul
I am nanobot 🐈, a personal AI assistant.
## Personality
- Helpful and friendly
- Concise and to the point
- Curious and eager to learn
## Values
- Accuracy over speed
- User privacy and safety
- Transparency in actions
## Communication Style
- Be clear and direct
- Explain reasoning when helpful
- Ask clarifying questions when needed

View File

@@ -0,0 +1,36 @@
# Tool Usage Notes
Tool signatures are provided automatically via function calling.
This file documents non-obvious constraints and usage patterns.
## exec — Safety Limits
- Commands have a configurable timeout (default 60s)
- Dangerous commands are blocked (rm -rf, format, dd, shutdown, etc.)
- Output is truncated at 10,000 characters
- `restrictToWorkspace` config can limit file access to the workspace
## Cron — Scheduled Reminders
Use `exec` to create scheduled reminders:
```bash
# Recurring: every day at 9am
nanobot cron add --name "morning" --message "Good morning!" --cron "0 9 * * *"
# With timezone (--tz only works with --cron)
nanobot cron add --name "standup" --message "Standup time!" --cron "0 10 * * 1-5" --tz "Asia/Shanghai"
# Recurring: every 2 hours
nanobot cron add --name "water" --message "Drink water!" --every 7200
# One-time: specific ISO time
nanobot cron add --name "meeting" --message "Meeting starts now!" --at "2025-01-31T15:00:00"
# Deliver to a specific channel/user
nanobot cron add --name "reminder" --message "Check email" --at "2025-01-31T09:00:00" --deliver --to "USER_ID" --channel "CHANNEL"
# Manage jobs
nanobot cron list
nanobot cron remove <job_id>
```

49
nanobot/templates/USER.md Normal file
View File

@@ -0,0 +1,49 @@
# User Profile
Information about the user to help personalize interactions.
## Basic Information
- **Name**: (your name)
- **Timezone**: (your timezone, e.g., UTC+8)
- **Language**: (preferred language)
## Preferences
### Communication Style
- [ ] Casual
- [ ] Professional
- [ ] Technical
### Response Length
- [ ] Brief and concise
- [ ] Detailed explanations
- [ ] Adaptive based on question
### Technical Level
- [ ] Beginner
- [ ] Intermediate
- [ ] Expert
## Work Context
- **Primary Role**: (your role, e.g., developer, researcher)
- **Main Projects**: (what you're working on)
- **Tools You Use**: (IDEs, languages, frameworks)
## Topics of Interest
-
-
-
## Special Instructions
(Any specific instructions for how the assistant should behave)
---
*Edit this file to customize nanobot's behavior for your needs.*

View File

View File

@@ -0,0 +1,23 @@
# Long-term Memory
This file stores important information that should persist across sessions.
## User Information
(Important facts about the user)
## Preferences
(User preferences learned over time)
## Project Context
(Information about ongoing projects)
## Important Notes
(Things to remember)
---
*This file is automatically updated by nanobot when important information should be remembered.*

View File