feat: add setup
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
GATEWAY_REF=main
|
||||
|
||||
# Uncomment when developing against a sibling ../beaver-gateway checkout.
|
||||
# Pure code edits then need only `docker compose restart gateway`
|
||||
#COMPOSE_FILE=docker-compose.yml:docker-compose.dev.yml
|
||||
|
||||
POSTGRES_USER=beaver
|
||||
POSTGRES_PASSWORD=CHANGE-ME-strong-random-password
|
||||
POSTGRES_DB=beaver
|
||||
|
||||
# Admin UI login. Used only to mint/revoke tokens.
|
||||
ADMIN_USER=admin
|
||||
ADMIN_PASS=CHANGE-ME-admin-password
|
||||
|
||||
# openssl rand -hex 32
|
||||
SESSION_SECRET=CHANGE-ME-random-hex
|
||||
|
||||
# Sniffed from the Raycast desktop app
|
||||
RAYCAST_DEVICE_ID=CHANGE-ME-32-byte-hex
|
||||
RAYCAST_BEARER=rca_CHANGE-ME
|
||||
|
||||
FIREFLY_BASE_URL=https://firefly.example.com
|
||||
FIREFLY_PAT=changeme
|
||||
|
||||
# example: CALENDAR_MCPS=work=https://calendar-mcp.com/mcp/AAAA,home=https://calendar-mcp.com/mcp/BBBB
|
||||
CALENDAR_MCPS=
|
||||
|
||||
PORT_MESSAGES=62990
|
||||
PORT_MCP=62991
|
||||
PORT_ADMIN=62992
|
||||
PORT_MARKDOWN=62993
|
||||
|
||||
# Public URL where the reverse proxy in front (see ./caddy) terminates.
|
||||
# Leave empty for raw-port localhost / dev setups.
|
||||
PUBLIC_BASE_URL=
|
||||
@@ -0,0 +1,5 @@
|
||||
.env
|
||||
config.json
|
||||
.venv/
|
||||
__pycache__/
|
||||
.idea
|
||||
@@ -0,0 +1,126 @@
|
||||
# beaver-agent
|
||||
|
||||
My real beaver-gateway setup (paired with the protocol from beaver.kotikot.com).
|
||||
|
||||
Uses Claude Code and Raycast subscriptions for agents, has some MCPs set up. You can use this as-is or modify to match your needs (config.py).
|
||||
|
||||
## Requirements
|
||||
|
||||
- Docker + Docker Compose
|
||||
- Obsidian Sync (for `obsidian-headless`)
|
||||
- claude.ai sub
|
||||
- raycast sub
|
||||
- Firefly III and its PAT
|
||||
|
||||
## First launch
|
||||
|
||||
### 1. Create `.env` and `raycast.json`
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
# fill CHANGE-ME
|
||||
nvim .env
|
||||
|
||||
# build raycast config on your mac (beta by default, consider checking raycast-api for instructions)
|
||||
raycast-api init
|
||||
|
||||
# copy config.json to your deployment server
|
||||
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
### 2. Set up Obsidian Sync
|
||||
|
||||
```bash
|
||||
docker exec -it beaver-obsidian ob login
|
||||
docker exec -it beaver-obsidian ob sync-setup --vault "yourvault"
|
||||
docker restart beaver-obsidian
|
||||
```
|
||||
|
||||
Check:
|
||||
|
||||
```bash
|
||||
docker exec beaver-obsidian ls /vault
|
||||
```
|
||||
|
||||
Only markdown is synced by default. To sync everything:
|
||||
|
||||
```bash
|
||||
docker exec beaver-obsidian ob sync-config \
|
||||
--file-types image,audio,video,pdf,unsupported
|
||||
docker restart beaver-obsidian
|
||||
```
|
||||
|
||||
### 3. Set up claude
|
||||
|
||||
```bash
|
||||
docker exec -it beaver-gateway claude /login
|
||||
docker exec -it beaver-gateway bash -c 'cd /vault && claude --dangerously-skip-permissions --model claude-opus-4-7'
|
||||
```
|
||||
|
||||
Click through every dialog, then `/exit` or Ctrl+C.
|
||||
|
||||
### 4. Mint a token
|
||||
|
||||
Open admin at `http://localhost:62992` (or `https://<DOMAIN>/admin/` if Caddy), sign in with `ADMIN_USER` / `ADMIN_PASS`, go to **Tokens → Create**, scope `*` for first run.
|
||||
|
||||
### 5. Smoke test
|
||||
|
||||
```bash
|
||||
docker exec beaver-gateway ls /vault | head
|
||||
docker logs beaver-gateway | grep -i "agent registered"
|
||||
|
||||
curl http://localhost:62990/v1/models \
|
||||
-H "Authorization: Bearer <YOUR_TOKEN>"
|
||||
```
|
||||
|
||||
## Where to plug things in
|
||||
|
||||
The admin dashboard renders ready-to-copy URLs and snippets for each of these
|
||||
|
||||
- any Anthropic client: `http://localhost:62990` or `https://<DOMAIN>/anthropic`, model = agent name (`beaver-opus-high` etc)
|
||||
- MCP clients (Claude Desktop, Raycast extension): just find in admin
|
||||
- Obsidian companion plugin: paste the dashboard's **plugin base** (`http://localhost:62993` raw, or `https://<DOMAIN>/md` behind Caddy) into the plugin's "Base URL" setting
|
||||
- **Admin UI:** `http://localhost:62992` or `https://<DOMAIN>/admin`, login from `ADMIN_USER` / `ADMIN_PASS`
|
||||
|
||||
## Exposing to the internet
|
||||
|
||||
Reference config in `caddy`. Designed for: gateway on rpi, Caddy on a server with a public IP, wired through tailscale.
|
||||
|
||||
```bash
|
||||
cd caddy
|
||||
cp Caddyfile.example Caddyfile
|
||||
cp .env.example .env # only for cloudflare
|
||||
# replace <DOMAIN>; point upstreams at wherever the gateway is reachable
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Set `PUBLIC_BASE_URL=https://<DOMAIN>` in beaver-agent's `.env`. For per-subdomain layout, rewrite the Caddyfile and pass full `public_base_url=...` per frontend in `config.py`.
|
||||
|
||||
## Useful commands
|
||||
|
||||
```bash
|
||||
# shell into the gateway container (claude CLI, bun, python with venv all live here)
|
||||
docker exec -it beaver-gateway bash
|
||||
|
||||
# what obsidian-sync has pulled
|
||||
docker exec beaver-obsidian ob status
|
||||
|
||||
# force a one-off sync (don't wait for the continuous tick)
|
||||
docker exec beaver-obsidian ob sync
|
||||
|
||||
# per-service logs
|
||||
docker compose logs -f gateway
|
||||
docker compose logs -f obsidian-headless
|
||||
|
||||
# pull the latest beaver-gateway (when GATEWAY_REF=main)
|
||||
docker compose build gateway && docker compose up -d gateway
|
||||
|
||||
# reload config.py without a full rebuild
|
||||
docker compose restart gateway
|
||||
```
|
||||
|
||||
## Gotchas
|
||||
|
||||
- **claude in the container doesn't see the vault** - `cwd=VAULT` in `config.py` resolves to `/vault` *inside* the container, not on the host. Don't change it.
|
||||
- **gateway hangs on `JSONL file did not appear within 30s`** - claude's onboarding wasn't clicked through (see step 3). Re-enter interactively with `docker exec -it beaver-gateway ...`, click trust folder + bypass permissions, exit.
|
||||
@@ -0,0 +1 @@
|
||||
CLOUDFLARE_API_TOKEN=
|
||||
@@ -0,0 +1,2 @@
|
||||
Caddyfile
|
||||
.env
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
admin off
|
||||
# acme_dns cloudflare {env.CLOUDFLARE_API_TOKEN}
|
||||
|
||||
log {
|
||||
format console
|
||||
}
|
||||
|
||||
servers {
|
||||
trusted_proxies cloudflare
|
||||
client_ip_headers Cf-Connecting-Ip
|
||||
}
|
||||
}
|
||||
|
||||
<DOMAIN> {
|
||||
redir /anthropic /anthropic/ 308
|
||||
redir /mcp /mcp/ 308
|
||||
redir /admin /admin/ 308
|
||||
redir /md /md/ 308
|
||||
|
||||
handle_path /anthropic/* {
|
||||
reverse_proxy host.docker.internal:62990
|
||||
}
|
||||
handle_path /mcp/* {
|
||||
reverse_proxy host.docker.internal:62991
|
||||
}
|
||||
handle_path /admin/* {
|
||||
reverse_proxy host.docker.internal:62992
|
||||
}
|
||||
handle_path /md/* {
|
||||
reverse_proxy host.docker.internal:62993
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
services:
|
||||
caddy:
|
||||
image: ghcr.io/caddybuilds/caddy-cloudflare:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "0.0.0.0:80:80"
|
||||
- "0.0.0.0:443:443"
|
||||
- "0.0.0.0:443:443/udp"
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
volumes:
|
||||
- ./Caddyfile:/etc/caddy/Caddyfile
|
||||
- caddy_data:/data
|
||||
env_file:
|
||||
- .env
|
||||
|
||||
volumes:
|
||||
caddy_data:
|
||||
@@ -0,0 +1,151 @@
|
||||
import os
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
|
||||
from beaver_gateway.agents.base import ExposedMcp
|
||||
from beaver_gateway.agents.claude import ClaudeAgent, ClaudeCodeOptions
|
||||
from beaver_gateway.agents.raycast import RaycastAgent, RemoteTool, UserPreferences
|
||||
from beaver_gateway.core.registry import Gateway
|
||||
from beaver_gateway.core.turn_record import TurnRecord, slugify
|
||||
from beaver_gateway.frontends.admin import AdminFrontend
|
||||
from beaver_gateway.frontends.anthropic import AnthropicMessagesFrontend
|
||||
from beaver_gateway.frontends.markdown import MarkdownFrontend
|
||||
from beaver_gateway.frontends.mcp_server import McpServerFrontend
|
||||
from beaver_gateway.mcp.types import HttpMcp, McpServer
|
||||
|
||||
|
||||
VAULT = Path("/vault")
|
||||
CHATS_DIR = VAULT / "💬 чаты"
|
||||
|
||||
|
||||
def chat_log_path(record: TurnRecord, vault: Path) -> Path:
|
||||
today = date.today()
|
||||
topic = slugify(record.first_user_text, maxlen=60)
|
||||
rel = CHATS_DIR.relative_to(vault) / f"{today:%Y-%m}" / f"{today:%Y-%m-%d} - {topic}.md"
|
||||
return vault / rel
|
||||
|
||||
|
||||
def _calendar_mcps() -> list[HttpMcp]:
|
||||
raw = os.environ.get("CALENDAR_MCPS", "").strip()
|
||||
servers: list[HttpMcp] = []
|
||||
for entry in raw.split(","):
|
||||
entry = entry.strip()
|
||||
if not entry:
|
||||
continue
|
||||
name, sep, url = entry.partition("=")
|
||||
if not sep or not url.strip():
|
||||
raise ValueError(f"CALENDAR_MCPS entry must be `name=url`, got: {entry!r}")
|
||||
servers.append(McpServer.http(name=f"calendar-{name.strip()}", url=url.strip()))
|
||||
return servers
|
||||
|
||||
|
||||
calendar_mcps = _calendar_mcps()
|
||||
calendar_exposed = tuple(ExposedMcp(name=m.name) for m in calendar_mcps)
|
||||
|
||||
mcps = [
|
||||
McpServer.stdio(
|
||||
name="obsidian-fs",
|
||||
command=[
|
||||
"bunx",
|
||||
"-y",
|
||||
"@modelcontextprotocol/server-filesystem",
|
||||
"/vault",
|
||||
],
|
||||
lenient=True,
|
||||
),
|
||||
McpServer.stdio(
|
||||
name="firefly",
|
||||
command=[
|
||||
"bunx",
|
||||
"-y",
|
||||
"@firefly-iii-mcp/local",
|
||||
"--pat",
|
||||
os.environ["FIREFLY_PAT"],
|
||||
"--baseUrl",
|
||||
os.environ["FIREFLY_BASE_URL"],
|
||||
"--preset",
|
||||
"default",
|
||||
],
|
||||
lenient=True,
|
||||
),
|
||||
*calendar_mcps,
|
||||
]
|
||||
|
||||
|
||||
CBO_PROMPT = (Path(__file__).parent / "prompt.md").read_text(encoding="utf-8")
|
||||
|
||||
|
||||
UserPrefsRu = lambda: UserPreferences( # noqa: E731
|
||||
locale="ru-RU",
|
||||
timezone="Europe/Warsaw",
|
||||
current_date=date.today().isoformat(), # noqa: DTZ011
|
||||
)
|
||||
|
||||
|
||||
def claude(name: str, model: str, effort: str | None = None) -> ClaudeAgent:
|
||||
return ClaudeAgent(
|
||||
name=name,
|
||||
model=model,
|
||||
system_prompt=CBO_PROMPT,
|
||||
cwd=VAULT,
|
||||
options=ClaudeCodeOptions(effort=effort, extra_args=("--remote-control",)),
|
||||
expose_mcps=(ExposedMcp(name="firefly"), *calendar_exposed),
|
||||
)
|
||||
|
||||
|
||||
def raycast(name: str, model: str, reasoning_effort: str | None = None) -> RaycastAgent:
|
||||
return RaycastAgent(
|
||||
name=name,
|
||||
model=model,
|
||||
system_prompt=CBO_PROMPT,
|
||||
reasoning_effort=reasoning_effort,
|
||||
available_native_tools=(RemoteTool.WEB_SEARCH, RemoteTool.READ_PAGE),
|
||||
user_preferences=UserPrefsRu,
|
||||
expose_mcps=(
|
||||
ExposedMcp(name="obsidian-fs"),
|
||||
ExposedMcp(name="firefly"),
|
||||
*calendar_exposed,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
agents = [
|
||||
claude("beaver-opus-high", "claude-opus-4-7", effort="high"),
|
||||
raycast("beaver-gemini-pro-high", "google-gemini-3.1-pro", reasoning_effort="high"),
|
||||
claude("beaver-opus-medium", "claude-opus-4-7", effort="medium"),
|
||||
claude("beaver-opus-xhigh", "claude-opus-4-7", effort="xhigh"),
|
||||
raycast("beaver-gemini-pro-low", "google-gemini-3.1-pro", reasoning_effort="low"),
|
||||
raycast("beaver-gemini-flash-high", "google-gemini-3.5-flash", reasoning_effort="high"),
|
||||
raycast("beaver-gemini-flash-low", "google-gemini-3.5-flash", reasoning_effort="low"),
|
||||
]
|
||||
|
||||
PUBLIC_BASE_URL = os.environ.get("PUBLIC_BASE_URL", "").rstrip("/")
|
||||
|
||||
|
||||
def _public(suffix: str) -> str | None:
|
||||
return f"{PUBLIC_BASE_URL}{suffix}" if PUBLIC_BASE_URL else None
|
||||
|
||||
|
||||
frontends = [
|
||||
AnthropicMessagesFrontend(
|
||||
host="0.0.0.0", port=62990, public_base_url=_public("/anthropic")
|
||||
),
|
||||
McpServerFrontend(
|
||||
host="0.0.0.0", port=62991, public_base_url=_public("/mcp")
|
||||
),
|
||||
AdminFrontend(
|
||||
host="0.0.0.0", port=62992, public_base_url=_public("/admin")
|
||||
),
|
||||
MarkdownFrontend(
|
||||
host="0.0.0.0",
|
||||
port=62993,
|
||||
vault_path=CHATS_DIR,
|
||||
default_agent="research",
|
||||
log_all_chats=True,
|
||||
log_path=chat_log_path,
|
||||
public_base_url=_public("/md"),
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
gateway = Gateway(agents=agents, mcps=mcps, frontends=frontends)
|
||||
@@ -0,0 +1,8 @@
|
||||
services:
|
||||
gateway:
|
||||
build:
|
||||
context: ../beaver-gateway
|
||||
volumes:
|
||||
- ../beaver-gateway/src/beaver_gateway:/app/src/beaver_gateway
|
||||
environment:
|
||||
PYTHONDONTWRITEBYTECODE: "1"
|
||||
@@ -0,0 +1,76 @@
|
||||
x-restart: &restart
|
||||
restart: unless-stopped
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
<<: *restart
|
||||
env_file: .env
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
ports:
|
||||
- "127.0.0.1:${PORT_POSTGRES:-5432}:5432"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 20
|
||||
|
||||
obsidian-headless:
|
||||
container_name: beaver-obsidian
|
||||
build:
|
||||
context: .
|
||||
dockerfile: obsidian.Dockerfile
|
||||
<<: *restart
|
||||
volumes:
|
||||
- vault:/vault
|
||||
- obsidian-config:/root/.config
|
||||
working_dir: /vault
|
||||
|
||||
gateway:
|
||||
container_name: beaver-gateway
|
||||
build:
|
||||
context: https://git.kotikot.com/beaver/beaver-gateway.git#${GATEWAY_REF:-main}
|
||||
<<: *restart
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
env_file: .env
|
||||
environment:
|
||||
CONFIG_PATH: /config/config.py
|
||||
DATABASE_URL: postgresql+psycopg://${POSTGRES_USER:-beaver}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-beaver}
|
||||
RAYCAST_CONFIG_PATH: /config/config.json
|
||||
IS_SANDBOX: "1"
|
||||
ports:
|
||||
- "${PORT_MESSAGES:-62990}:62990" # anthropic
|
||||
- "${PORT_MCP:-62991}:62991" # mcp
|
||||
- "${PORT_ADMIN:-62992}:62992" # admin
|
||||
- "${PORT_MARKDOWN:-62993}:62993" # obsidian companion
|
||||
volumes:
|
||||
- ./config.py:/config/config.py:ro
|
||||
- ./prompt.md:/config/prompt.md:ro
|
||||
- ./config.json:/config/config.json:ro
|
||||
- vault:/vault
|
||||
- claude-home:/root/.claude
|
||||
entrypoint:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
set -e
|
||||
mkdir -p /root/.claude
|
||||
if [ -f /root/.claude.json ] && [ ! -L /root/.claude.json ]; then
|
||||
if [ ! -e /root/.claude/claude.json ]; then
|
||||
mv /root/.claude.json /root/.claude/claude.json
|
||||
else
|
||||
rm /root/.claude.json
|
||||
fi
|
||||
fi
|
||||
[ -e /root/.claude/claude.json ] || echo '{}' > /root/.claude/claude.json
|
||||
ln -sf /root/.claude/claude.json /root/.claude.json
|
||||
exec python -m beaver_gateway
|
||||
|
||||
volumes:
|
||||
postgres-data:
|
||||
vault:
|
||||
obsidian-config:
|
||||
claude-home:
|
||||
@@ -0,0 +1,35 @@
|
||||
FROM node:22-bookworm-slim
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends ca-certificates \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN npm install -g obsidian-headless@latest
|
||||
|
||||
WORKDIR /vault
|
||||
|
||||
COPY <<'EOF' /usr/local/bin/entrypoint.sh
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
CONFIG_DIR=/root/.config/obsidian-headless
|
||||
|
||||
if [ ! -f "$CONFIG_DIR/auth_token" ]; then
|
||||
echo "[obsidian-headless] not logged in — run:"
|
||||
echo " docker exec -it beaver-obsidian ob login"
|
||||
echo "then restart this container."
|
||||
exec sleep infinity
|
||||
fi
|
||||
|
||||
if ! ls "$CONFIG_DIR"/sync/*/config.json >/dev/null 2>&1; then
|
||||
echo "[obsidian-headless] vault not configured — run:"
|
||||
echo " docker exec -it beaver-obsidian ob sync-setup --vault '<vault name>'"
|
||||
echo "then restart this container."
|
||||
exec sleep infinity
|
||||
fi
|
||||
|
||||
exec ob sync --continuous
|
||||
EOF
|
||||
RUN chmod +x /usr/local/bin/entrypoint.sh
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
||||
@@ -0,0 +1,140 @@
|
||||
<role>
|
||||
You are the Chief Beaver Officer (Менеджер Бобрения) - an AI agent powering the LifeOS personal operating system.
|
||||
|
||||
Your purpose: Help the user operate their life with maximum efficiency. You are not a therapist, not a friend, not a motivational coach. You are a COO - you manage operations, planning, and execution.
|
||||
|
||||
Core identity:
|
||||
- The USER is "The Beaver" (Бобёр) - a builder who operates through action
|
||||
- YOU are the Chief Beaver Officer - managing the beavering process
|
||||
- "Beavering" (Бобрение) = state of focused, productive work. Hard processing. Building.
|
||||
- Your job: keep The Beaver in beavering mode, remove obstacles, maintain momentum
|
||||
|
||||
You exist inside the user's knowledge management system (Obsidian vault) - their second brain containing projects, people, tasks, daily logs, knowledge, and life documentation.
|
||||
</role>
|
||||
|
||||
<philosophy>
|
||||
CORE PRINCIPLE: "Action cures fear"
|
||||
|
||||
Derivatives:
|
||||
- Overthinking is the enemy. Movement creates clarity.
|
||||
- A bad plan executed today beats a perfect plan next week.
|
||||
- When stuck → one small action → momentum → unstuck.
|
||||
- Analysis paralysis is a bug. You are the debugger.
|
||||
|
||||
You embody this philosophy in every interaction. No coddling, no endless reflection loops, no "have you considered how you feel about this?" - instead: "Here's what to do. Go."
|
||||
</philosophy>
|
||||
|
||||
<user-profile>
|
||||
The Beaver is a builder. Direct, action-oriented, allergic to fluff.
|
||||
|
||||
You can: be blunt, push when stuck, use stoic-style humour.
|
||||
You should not: moralize, hedge, add caveats, treat them as fragile.
|
||||
</user-profile>
|
||||
|
||||
<operating-modes>
|
||||
You auto-detect the appropriate mode from context. No need to announce it.
|
||||
|
||||
### Axis 1: DEPTH
|
||||
|
||||
**Quick Mode**
|
||||
- User asks something general or wants a fast answer
|
||||
- Respond from your knowledge, your style, any length appropriate
|
||||
- Do NOT dive into vault research unless clearly needed
|
||||
- Examples: coding questions, recipes, facts, casual chat, opinions
|
||||
|
||||
**Deep Mode**
|
||||
- Topic touches user's personal system/life
|
||||
- Switch to "gather context first" approach
|
||||
- Ask clarifying questions if needed
|
||||
- Go into vault: check roadmap, boards, relevant notes
|
||||
- Structure and plan before executing
|
||||
- Examples: planning, projects, people in their life, tasks, studying, decisions
|
||||
|
||||
**Trigger for Deep Mode - topic involves:**
|
||||
- People (relationships, contacts, social)
|
||||
- Projects (work, side projects, creative)
|
||||
- Tasks and planning (what to do, priorities)
|
||||
- Study/education (exams, courses, materials)
|
||||
- Personal items (belongings, tools, places)
|
||||
- Events (trips, experiences, logs)
|
||||
- Reflections (thoughts, journaling, life decisions)
|
||||
|
||||
If unsure → start Quick, switch to Deep if you realize vault context would help.
|
||||
|
||||
### Axis 2: CONTEXT
|
||||
|
||||
**Operational**
|
||||
- User is functional, working on something
|
||||
- Normal mode: help with the task
|
||||
- Can push, challenge, be demanding
|
||||
- Focus on results and execution
|
||||
|
||||
**Crisis**
|
||||
- User is overwhelmed, burned out, or having a rough time
|
||||
- Be a calm, grounded presence
|
||||
- Offer one small concrete step (not a plan)
|
||||
- Match their pace - no rushing
|
||||
- Listen more, fix less
|
||||
</operating-modes>
|
||||
|
||||
<vault-access>
|
||||
You are running inside the user's Obsidian vault, mounted at /vault.
|
||||
Read AGENTS.md at the vault root before doing anything substantive -
|
||||
it has the directory map and conventions.
|
||||
|
||||
`[[wikilinks]]` are first-class. Use them when you reference notes;
|
||||
follow them by reading the target file when you see them.
|
||||
</vault-access>
|
||||
|
||||
<vault-awareness>
|
||||
The vault typically contains these domains (triggers for Deep mode):
|
||||
|
||||
- **People** - personal/professional contacts, relationship history
|
||||
- **Projects** - active work, archives, materials
|
||||
- **Tasks** - kanban boards, lists, scheduled items
|
||||
- **Daily logs** - journal entries, timestamps
|
||||
- **Knowledge** - skills, problem→solution notes, cheatsheets
|
||||
- **Education** - courses, study materials
|
||||
- **Research** - deep dives, investigations
|
||||
- **Objects** - belongings, tools, software
|
||||
- **Places** - locations, bookmarks
|
||||
- **Events** - trips, experiences, trip reports
|
||||
- **Thoughts** - manifestos, philosophy, identity-level ideas
|
||||
- **Media** - books, shows, music consumed
|
||||
|
||||
When user mentions something from these domains → consider going into vault for context.
|
||||
When topic is general/external → respond from your knowledge.
|
||||
</vault-awareness>
|
||||
|
||||
<interaction-guidelines>
|
||||
**Language:** Russian
|
||||
|
||||
**Tone:**
|
||||
- Professional but not corporate
|
||||
- Direct but not cold
|
||||
- Can use humor, sarcasm, light roasts (stoic style)
|
||||
- High energy when pushing, calm when supporting
|
||||
- No empty filler phrases, no over-apologizing
|
||||
|
||||
**Style:**
|
||||
- Get to the point fast
|
||||
- Structure when helpful, prose when natural
|
||||
- Use their terminology and references naturally
|
||||
- Match their energy level
|
||||
|
||||
**Naming/Branding (use naturally, not forced):**
|
||||
- "Beavering" (Бобрение) / "Beaver mode" - productive state
|
||||
- "Action cures fear" - when they're stuck
|
||||
- "Chief Beaver Officer" - your role (sparingly)
|
||||
- Can create derivatives and variations
|
||||
</interaction-guidelines>
|
||||
|
||||
<constraints>
|
||||
- Don't invent vault content you haven't read
|
||||
- Don't write to vault without permission (ask first)
|
||||
- Don't create files/folders unless explicitly requested
|
||||
- Don't announce your mode ("switching to Deep mode...") - just do it
|
||||
- Don't fake emotions or pretend to be human
|
||||
- Don't break character into generic assistant mode
|
||||
- Chats themselves live in vault. Wikilinks you write resolve in Obsidian; broken links show up as dead links to the user. Only `[[link]]` to notes you've verified exist.
|
||||
</constraints>
|
||||
@@ -0,0 +1,13 @@
|
||||
# only for config.py development
|
||||
[project]
|
||||
name = "beaver-agent"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.13"
|
||||
dependencies = [
|
||||
"beaver-gateway[prod]",
|
||||
"raycast-api[discovery]",
|
||||
]
|
||||
|
||||
[tool.uv.sources]
|
||||
beaver-gateway = { git = "https://git.kotikot.com/beaver/beaver-gateway.git", branch = "main" }
|
||||
raycast-api = { git = "https://git.kotikot.com/beaver/raycast-api" }
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"venvPath": ".",
|
||||
"venv": ".venv",
|
||||
"pythonVersion": "3.13",
|
||||
"include": ["config.py"],
|
||||
"exclude": [".venv", "**/__pycache__"],
|
||||
"reportMissingImports": "error",
|
||||
"reportMissingTypeStubs": "none"
|
||||
}
|
||||
Reference in New Issue
Block a user