Pasture Protocol - your private AI companion
Runs on your computer. Connects to WhatsApp and Telegram. Uses a local or cloud LLM of your choice. No external routing - your chats stay on your machine.
- What It Does
- Requirements
- Installation
- WhatsApp Auth Setup (Baileys)
- Telegram Setup
- Configuration Reference
- Environment Variables
- Command Reference
- Skills Reference
- GitHub Integration
- Google Integration (Gmail & Calendar)
- Multi-Agent (Agent Team)
- Dashboard Guide
- Cron / Reminder Store (SQLite-free JSON store)
- Memory Store (SQLite + vector search)
- Tide (follow-up after silence)
- File and Directory Layout
- Running as a Daemon
| Category | Capability |
|---|---|
| Chat | Conversational AI with full context window. Supports private chats, group chats, images, and voice notes. |
| Reminders | Natural-language scheduling. Recurring or one-shot. Time-zone aware. |
| Web search | Brave Search integration. Returns summarized results. |
| Browser automation | Playwright-powered browser. Navigate, click, fill forms, screenshot. |
| Vision | Describe images, webcam frames, or full web pages. |
| Memory | Semantic vector memory. Recall facts, notes, and decisions from past conversations. |
| File ops | Read, write, and edit files inside your workspace directory. |
| Voice | Transcribe voice notes (speech-to-text). Optionally reply in audio (text-to-speech). |
| Home Assistant | Integration with a local Home Assistant instance. |
| SSH inspect | Inspect remote servers over SSH. |
| Tide | Sends one AI-composed follow-up message after a configurable silence window. |
| GitHub | Read repos, issues, and PRs. Create branches, open PRs, post comments. |
| Gmail | List, read, search, send, archive, and summarize emails. |
| Calendar | List events, create meetings, check availability, find free slots. |
| Multi-channel | WhatsApp (Baileys) and Telegram simultaneously. |
| Multi-agent | Multiple agent personas, each with its own skills, identity, and memory. Route chats to specialists. |
- Node.js 18+ (LTS recommended)
- pnpm 9 (
npm install -g pnpm@9) - Local LLM (recommended for privacy):
- Or a cloud API key: OpenAI, Anthropic, Grok (xAI), Together AI, or DeepSeek
- Playwright browsers (for the
browseandvisionskills): installed automatically on first use, or runnpx playwright install chromium
Linux / macOS:
curl -fsSL https://raw.githubusercontent.com/bishwashere/pastureprotocol/master/install.sh | bashWindows (PowerShell):
iwr -useb https://raw.githubusercontent.com/bishwashere/pastureprotocol/master/install.ps1 | iexThis installs Pasture Protocol, registers the pasture CLI command, and puts runtime data in ~/.pasture.
After install:
pasture start # start the bot
pasture dashboard # open the local web dashboard
pasture logs # tail daemon logs
pasture update # pull the latest version
pasture uninstall # remove Pasture ProtocolWindows notes: State and config live in %USERPROFILE%\.pasture. The daemon uses pm2 (pasture logs or pm2 logs pasture).
If PowerShell blocks the install script (execution policy), run:
powershell -NoProfile -ExecutionPolicy Bypass -Command "iwr -useb https://raw.githubusercontent.com/bishwashere/pastureprotocol/master/install.ps1 | iex"On Windows with Git Bash, you can use the Linux/macOS one-liner instead (install.sh).
git clone https://github.com/bishwashere/pastureprotocol.git
cd pastureprotocol
pnpm install
node setup.js # or: pasture setup — interactive onboarding / re-configureUpgrading from Pasture? Run pasture update (or pasture update — the old command forwards to pasture). State migrates from ~/.pasture to ~/.pasture automatically.
Pasture Protocol uses Baileys, a WhatsApp Web reverse-engineered client. Authentication uses WhatsApp's multi-device protocol and is stored in ~/.pasture/auth_info/.
# Using the installed CLI:
pasture auth
# Or directly from the repo:
node index.js --auth-onlyA QR code appears in your terminal. Open WhatsApp on your phone:
- Go to Settings → Linked Devices → Link a Device.
- Scan the QR code.
- Wait for "Connection Successful" in the terminal.
- Press
Ctrl+C- auth files are saved and reused on every subsequent start.
If you cannot scan a QR code (e.g., headless server):
node index.js --auth-only --pair <your-phone-number>
# Example: node index.js --auth-only --pair +12025550123WhatsApp will send a pairing code to that number. Enter it in Settings → Linked Devices → Link with phone number.
~/.pasture/auth_info/
├── creds.json # identity keys and registration info
└── *.json # session, pre-keys, sender-key records
These files are your WhatsApp session. Back them up. Do not commit them to git (the .gitignore excludes auth_info/ at the repo root).
If your session expires or you get logged out, delete the auth files and run --auth-only again:
rm -rf ~/.pasture/auth_info/
pasture auth- Message @BotFather on Telegram and create a bot. Copy the token.
- Add the token to your config:
// ~/.pasture/config.json
{
"channels": {
"telegram": {
"enabled": true,
"botToken": "YOUR_TELEGRAM_BOT_TOKEN"
}
}
}- Start Pasture Protocol. The bot will begin polling for Telegram messages.
For group chats, add the bot to a Telegram group. The bot only responds to messages from the configured owner or groups it has been explicitly added to.
All configuration lives in ~/.pasture/config.json. The full structure:
When multiple models are configured, Pasture Protocol selects in this order:
- Any model with
"priority": true- used first if the provider is reachable. - Local providers (
lmstudio,ollama) - used next as a privacy-preserving fallback. - Other cloud models - used in order of appearance as further fallbacks.
| Provider | "provider" value |
Notes |
|---|---|---|
| LM Studio | lmstudio |
Local; default baseUrl: http://127.0.0.1:1234/v1 |
| Ollama | ollama |
Local; default baseUrl: http://127.0.0.1:11434/v1 |
| OpenAI | openai |
Cloud; default model: gpt-5.2 |
| Anthropic | anthropic |
Cloud; default model: claude-sonnet-4-5-20250929 |
| Grok / xAI | grok or xai |
Cloud; default model: grok-4-1-fast-reasoning |
| Together AI | together |
Cloud; default model: Llama-3.3-70B-Instruct-Turbo |
| DeepSeek | deepseek |
Cloud; default model: deepseek-chat |
Environment variables live in ~/.pasture/.env. Keys referenced in config.json (e.g. "apiKey": "LLM_1_API_KEY") are resolved from this file automatically.
# LLM API keys (referenced by name in config.json)
LLM_1_API_KEY=sk-... # OpenAI API key
LLM_2_API_KEY=xai-... # Grok/xAI API key
LLM_3_API_KEY=sk-ant-... # Anthropic API key
# Search
BRAVE_API_KEY=BSA... # Brave Search API key (for the search skill)
# Telegram
TELEGRAM_BOT_TOKEN=123:ABC... # Telegram bot token (referenced in config.json)
# Optional overrides
PASTURE_STATE_DIR= # Override state dir (default: ~/.pasture)
OPENAI_MODEL=gpt-4o # Override default model for a provider
GROK_MODEL=grok-3 # Override default Grok model
ANTHROPIC_MODEL=claude-3-haiku-20240307pasture start # Start the bot in the background
pasture stop # Stop the background bot process
pasture restart # Restart the bot
pasture status # Show whether the bot is running
pasture logs # Tail the daemon log
pasture auth # WhatsApp QR/pairing auth (stops bot first)
pasture dashboard # Open the local web dashboard
pasture update # Pull and install the latest version
pasture uninstall # Remove Pasture Protocol and the CLI command
# Tide checklist - see [Tide checklist](#tide-checklist-maintenance)
pasture tide checklist list|add|remove|run|on|off|triggers|enable|disable
# Agents, skills, servers, memory index
pasture create agent <name> # New agent persona
pasture delete agent <name> [--yes]
pasture add <skill-id> # Install + enable a skill
pasture index [--source memory|filesystem] [--root <path>]
pasture server add|use|list|remove # SSH inspect targets| Message | Effect |
|---|---|
remind me in 5 minutes |
One-shot reminder in 5 minutes |
remind me tomorrow at 9am |
One-shot reminder next day at 9:00 |
every Monday at 8am remind me to standup |
Recurring weekly cron reminder |
every day at 7pm remind me to log my hours |
Recurring daily reminder |
list my reminders |
Lists all active reminders with their IDs |
cancel reminder 2 |
Cancels the reminder with id 2 |
what's scheduled? |
Shows upcoming reminders |
| Message | Effect |
|---|---|
search for AI trends |
Brave Search, returns a summary |
what's the weather in London? |
Live search + summary |
find news about X |
News search |
| Message | Effect |
|---|---|
open example.com and tell me what's there |
Navigates and describes |
go to that URL, click the login button |
Clicks an element |
fill the form and submit |
Form automation |
screenshot the page |
Returns a screenshot |
scroll down and find the pricing section |
Scroll + locate |
| Message | Effect |
|---|---|
describe this image |
Analyzes an attached image |
what's on that page? |
Describes a screenshot or URL |
what do you see? (with webcam) |
Captures and describes webcam frame |
| Message | Effect |
|---|---|
remember that the API key is XYZ |
Saves to semantic memory |
what did I note about the project? |
Semantic recall |
what did we decide yesterday? |
Recall by recency |
summarize my notes |
Summarizes workspace MEMORY.md |
read main.py |
Reads a file in the workspace |
save this to notes.md |
Creates or appends to a file |
list files in my workspace |
Lists the workspace directory |
in config.json replace "debug": false with "debug": true |
Patch-edits a file |
| Message | Effect |
|---|---|
list open issues |
Lists open issues in the default repo |
show me PR #12 |
Reads PR #12 with all comments |
create branch feat/login from main |
Creates a new branch (confirms first) |
open a PR from feat/login titled "Add login" |
Opens a pull request (confirms first) |
post a comment on issue #5: "Fixed in #9" |
Posts a comment (confirms first) |
merge PR #10 |
Merges the PR (shows details, confirms first) |
| Message | Effect |
|---|---|
what's in my inbox? |
Lists recent inbox messages |
show unread emails |
Filters to unread |
search for emails from alice@co.com |
Gmail search |
summarize my inbox |
Sender/subject breakdown |
send an email to bob@co.com about the deadline |
Composes and confirms before sending |
archive all emails older than 30 days |
Bulk archive (confirms first) |
clear my inbox |
Archives all inbox messages (confirms first) |
| Message | Effect |
|---|---|
what's on my calendar today? |
Lists today's events |
book a 30 min meeting with john@co.com next Tuesday 2pm |
Creates event (confirms first) |
am I free Friday at 3pm? |
Checks free/busy |
find a free 1-hour slot this week |
Finds next open block |
delete the standup tomorrow |
Deletes event (confirms first) |
Skills are modular capabilities. They are listed in config.json under skills.enabled. Disabling a skill removes it from the agent's tool set.
| Skill ID | What it does | Key dependency |
|---|---|---|
cron |
Natural-language reminder scheduling (recurring + one-shot) | croner |
search |
Web search via Brave Search API | BRAVE_API_KEY |
browse |
Headless browser automation | Playwright / Chromium |
vision |
Image and webcam analysis | LLM with vision support, or vision fallback model |
memory |
Semantic memory indexing and recall | sqlite-vec (vector extension) |
read |
Read files from the workspace | - |
write |
Create or overwrite workspace files | - |
edit |
Patch-edit workspace files (targeted find/replace) | - |
apply-patch |
Apply unified diffs to workspace files | - |
gog |
Google Workspace CLI (Gmail, Calendar, Drive, etc.) | gog CLI |
gmail |
Gmail - list, read, search, send, archive, summarize | gog CLI |
calendar |
Google Calendar - list, create, delete, check availability | gog CLI |
github |
GitHub - repos, issues, PRs, branches, comments | GITHUB_TOKEN |
me |
Self-reflective memory about the agent's identity | - |
go-read |
Read files from arbitrary paths (outside workspace) | - |
go-write |
Write files to arbitrary paths (outside workspace) | - |
home-assistant |
Control Home Assistant entities | Home Assistant instance |
ssh-inspect |
Inspect and query remote servers over SSH | SSH access |
speech |
Voice transcription and synthesis | Speech provider config |
Skill files live in skills/<id>/SKILL.md and define the prompts and executor logic for each skill.
Connect Pasture Protocol to GitHub so the agent can read repositories, manage issues and PRs, create branches, post comments, and more - all through natural conversation.
Go to GitHub → Settings → Developer settings → Personal access tokens.
| Use case | Recommended scopes |
|---|---|
| Read-only (public repos) | public_repo |
| Read/write (private repos + PRs) | repo |
| Issues and PR comments | repo → Issues + Pull requests |
| Fine-grained token (recommended) | Select the specific repository, grant Issues (R/W) + Pull requests (R/W) + Contents (R) |
Never grant admin:org, delete_repo, or workflow unless you specifically need them.
Option A - secrets.json (recommended, gitignored):
// ~/.pasture/secrets.json
{ "github": { "token": "ghp_your_token_here" } }Option B - .env file:
# ~/.pasture/.env
GITHUB_TOKEN=ghp_your_token_here// ~/.pasture/config.json → skills section
"github": {
"token": "GITHUB_TOKEN",
"defaultRepo": "owner/repo"
}When defaultRepo is set, you can say "list issues" without repeating the repo name every time.
"skills": {
"enabled": ["github", "...other skills"]
}Or toggle it on the Skills page in the dashboard. The badge next to the skill shows configured (green), needs setup (red), or token in config (yellow - move it to secrets.json).
| Message | What happens |
|---|---|
list open issues in myorg/myrepo |
Lists open issues |
show me PR #42 |
Reads the PR with full comment thread |
what PRs are open? |
Lists open pull requests (uses defaultRepo) |
read the README |
Reads README.md from the default branch |
create branch feat/webhooks from main |
Creates a branch (asks to confirm) |
open a PR from feat/webhooks titled "Add webhooks" |
Opens a PR (asks to confirm) |
post a comment on issue #5 saying "Fixed in #8" |
Posts a comment (asks to confirm) |
merge PR #10 with squash |
Merges the PR (shows details, asks to confirm) |
search for "executeGithub" in my repo |
Searches code on GitHub |
All write operations (create branch, post comment, create PR, merge PR) show you exactly what will happen and ask for confirmation before proceeding.
Gmail and Calendar use the gog CLI - a Google Workspace command-line tool that handles OAuth. Pasture Protocol calls gog behind the scenes, so you only need to authenticate once.
brew install gog # macOS
# or: pip install gog # Python-based installgog auth
# Follow the browser OAuth flow. Grants Gmail + Calendar scopes.// ~/.pasture/config.json → skills section
"gog": {
"account": "you@gmail.com"
}Omit this if you only have one Google account. When set, all gmail and calendar tool calls use that account automatically.
"skills": {
"enabled": ["gmail", "calendar", "..."]
}| Message | What happens |
|---|---|
what's in my inbox? |
Lists recent inbox messages |
show me unread emails |
Filters to unread |
search for emails from boss@company.com |
Searches inbox |
summarize my inbox |
Returns sender/subject breakdown for recent messages |
read that email |
Reads the full body of a selected message |
send an email to alice@co.com about the report |
Composes + asks to confirm before sending |
reply to that thread saying "Done, see PR #8" |
Replies to the thread (confirms first) |
archive all emails older than 30 days |
Bulk archive (confirms first) |
mark all unread as read |
Marks all unread messages as read |
clear my inbox |
Archives everything in inbox (confirms first) |
Send and reply actions always require explicit confirmation before executing.
| Message | What happens |
|---|---|
what's on my calendar this week? |
Lists events for the next 7 days |
do I have anything tomorrow? |
Lists tomorrow's events |
book a 30-minute meeting with john@co.com next Tuesday at 2pm |
Creates event (shows details, confirms) |
schedule a 1-hour team sync every Monday at 10am |
Creates recurring event (confirms) |
am I free Friday at 3pm? |
Checks free/busy for that slot |
find a free 1-hour slot this week |
Finds the next available block |
move the 3pm standup to 4pm |
Updates the event time (confirms) |
delete the standup tomorrow |
Deletes the event (confirms) |
create an all-day event "Offsite" on June 15 |
Creates an all-day event |
Create, update, and delete actions always require explicit confirmation. For natural-language times ("next Tuesday 2pm"), the agent converts to the correct ISO timestamp using your local timezone before calling the API.
Pasture Protocol supports multiple agent personas. Each agent has its own identity files, skill set, and optionally its own LLM config. You can route different conversations to different specialists - a coding agent, a writing agent, a personal assistant, etc.
| Term | Meaning |
|---|---|
| Agent | A named persona with its own skills, identity (WhoAmI, MyHuman), and optional LLM. |
main |
The default agent. Always exists, cannot be deleted. |
| Agent space (Team) | Central workspace for agent roster + missions. Includes cards, tree view, live context, inbox, stats, and activity feed. |
| Agent messaging | One agent can invoke another via the agent-send skill. Controlled by an allow-list. |
| Groups | WhatsApp/Telegram groups are assigned to a specific agent. Group members chat with that agent only. |
From the dashboard (easiest):
- Open the dashboard home page.
- Click + Agent (top-right of the Agent team card, or in the chat toolbar).
- Type a name (e.g. "Backend Bot", "Writer"). The internal id is auto-generated (
backend-bot,writer). - Choose Copy settings from - defaults to the most recently used non-main agent. This copies the LLM config, skills (minus sensitive defaults), and identity files.
- Click Create. The new agent appears in the tree immediately and is linked with every visible agent by default, including
main.
From the CLI:
pasture create agent backend-botClick the ✎ button on any agent card to edit:
- Title - display name shown in the UI and chat toolbar
- Skills - toggle which skills this agent has access to (independent of the main agent)
- Agent messaging - manage which other agents this one can invoke. New agents start linked to the whole visible team; remove links here when a pair should not delegate to each other.
The Team page is the current agent space. It has:
- Top-level tabs:
RosterandMissions - Roster views:
Cards(default) andTree - Filters:
View Active Only(shows only working/waiting/error agents) - Per-agent panel:
Active Context(default),Inbox,Stats - Live activity feed: recent team events (delegation, skills, turns)
Each card shows:
- agent name + state (
Idle,Working,Waiting,Error) - current step from Active Context
- tasks completed today
- quick edit button (
✎)
Tree view shows team structure and link arrows:
[main]
/ \
[writer] [backend-bot]
mainis always the root - it sits at the top.- Other agents branch below it.
- Solid lines = tree structure (hierarchy).
- Dashed arrows = message-passing permission (agent A can invoke agent B).
- Click any node/card to inspect that agent in the lower panel.
Missions are first-class entities with:
- title, owner agent, status (
active|paused|completed|blocked) - objective, plan steps, progress/evidence
- last run and next run timestamps
- context snapshot + memory anchors
From Team -> Missions you can:
- create a new mission (title/objective/owner)
- view mission cards with status + progress
- run a mission immediately (
Run now) - pause/resume/activate missions
Private chats (WhatsApp/Telegram DMs): You select which agent handles the conversation from the chat toolbar dropdown on the dashboard, or by changing selectedChatAgentId in the chat UI.
Group chats: Assign a group to a specific agent on the Groups page. Every message in that group is handled by the assigned agent with its specific skills.
Agent-to-agent messaging: If agent-send is enabled and an agent has an allow-list configured, one agent can delegate tasks to another mid-conversation:
User: "Write a PR description and then ask the backend agent to open it"
main agent → invokes writer agent to draft → invokes backend-bot to create_pr
Each agent's identity lives in ~/.pasture/agents/<id>/workspace/:
| File | Purpose |
|---|---|
SOUL.md |
Core personality - tone, style, rules |
WhoAmI.md |
Agent's self-description |
MyHuman.md |
What this agent knows about the user |
Edit these from the Agents page in the dashboard (select an agent → Identity files).
On the Groups page, select a group and assign it to an agent. You can also add a skills deny list for that group (e.g., disable go-write in a shared group).
Open the dashboard with:
pasture dashboard
# Opens http://127.0.0.1:3847| Page | What it's for |
|---|---|
| Home | Chat with any agent. Status overview and quick agent controls. |
| Team | Agent space: roster cards/tree, Active Context, Inbox, Stats, activity feed, and Missions. |
| Memory | Browse and edit all memory: Today, Yesterday, Long-term (MEMORY.md), History, Notes. |
| Crons | View, add, and delete scheduled reminders. |
| Skills | Enable/disable skills. View credential status badges. Edit SKILL.md files inline. |
| Agents | Create and configure agent personas. Edit identity files. |
| Groups | Assign WhatsApp/Telegram groups to agents. Set per-group skill restrictions. |
| LLM | Configure language models, providers, API keys, and fallback priority. |
| Tide | Enable/disable Tide follow-ups. Manage the maintenance checklist. |
| Config | Full raw JSON config editor with live preview. |
| Test | Run built-in skill tests (search, browser, memory, etc.) directly from the UI. |
| Projects | Visual project tracker with branched updates. |
The home page has two panels:
Left - Overview & Identity:
- Live status (daemon up/down, active model, skill count, timezone)
- Identity tiles: click Who am I, My human, or Group rules to open an inline editor for that file
Right - Agent controls:
- Quick agent visualization and edit controls
- ✎ opens the edit modal
- + Agent opens the create-agent dialog
- Full team coordination is on the Team page
Below - Chat:
- Full in-browser chat with the selected agent
- Agent selector dropdown + + Agent button in the toolbar
- New starts a fresh session; History browses past conversations
The Memory page has 5 tiles across the top. Click any tile to switch the view:
| Tile | Contents |
|---|---|
| Today 📅 | Today's conversation log (read-only). Auto-loads on open. |
| Yesterday 🗓 | Yesterday's conversation log (read-only). |
| Long-term 🧠 | MEMORY.md - the agent's persistent notes about you. Editable with a Save button. |
| History 💬 | All past chat days, newest first. Click a day to read the full log. |
| Notes 📝 | Custom memory files (e.g. preferences.md). Editable. |
Each skill shows:
- Name + description
- Credential badge: green
configured/ redneeds setup/ yellowtoken in config(move tosecrets.json) / greygog auth - Enable/disable toggle
- Click a skill to expand its
SKILL.mdinline and edit the agent instructions
- Go to Home or Agents.
- Click + Agent.
- Enter a name - e.g. "Research Bot". The id
research-botis auto-generated. - Copy settings from: defaults to the most recently used non-main agent. Copies skills and identity files.
- Click Create. The agent appears in the tree on the home page.
- Click ✎ on the new agent node to:
- Change the title
- Adjust which skills it has
- Enable agent messaging and set who it can invoke
- Go to Agents → Identity files to customize
WhoAmI.mdandMyHuman.mdfor this persona.
On the Skills page, github shows a credential badge:
- configured - token found in
secrets.jsonorGITHUB_TOKENenv var. Ready to use. - needs setup - no token found. Click the skill to expand and read the setup instructions.
- token in config - token found in
config.json. Works, but move it tosecrets.jsonfor better security.
gmail and calendar show a gog auth badge - they rely on the gog CLI's OAuth session. If you see errors, run gog auth in a terminal to re-authenticate.
Reminders are stored in plain JSON at ~/.pasture/cron/jobs.json (no SQLite dependency for the scheduler). The store is human-readable and can be edited manually.
{
"version": 1,
"jobs": [
{
"id": "abc123", // Unique job ID (UUID)
"name": "standup", // Human-readable name
"enabled": true,
"schedule": {
// Recurring: cron expression
"kind": "cron",
"expr": "0 8 * * 1", // Every Monday at 08:00
"tz": "America/New_York"
},
// OR one-shot:
// "schedule": { "kind": "at", "at": "2026-06-01T09:00:00Z" }
"message": "Remind me to run the standup", // Sent to LLM as the prompt
"jid": "1234567890@s.whatsapp.net", // Reply channel (WhatsApp JID or Telegram chat id)
"createdAtMs": 1716000000000,
"updatedAtMs": 1716000000000,
"sentAtMs": null // Set when a one-shot has been delivered (prevents duplicates after restart)
}
]
}Cron expressions use standard 5-field format: minute hour day month weekday. The tz field accepts any IANA timezone string.
Semantic memory is stored in a SQLite database with the sqlite-vec extension for vector similarity search.
Location: ~/.pasture/memory/ (or inside workspace/memory/ depending on configuration)
- When memory is enabled, each message exchange is indexed into SQLite from chat-log (
chat-log/YYYY-MM-DD.jsonlandchat-log/private/*.jsonl). Long-term notes in MEMORY.md (and optional custommemory/*.mdfiles likepreferences.md) are indexed too. - Legacy date-stamped
memory/YYYY-MM-DD.mdfiles are not indexed - daily history lives in chat-log only. - On each new message, the bot runs a similarity search against stored memories and prepends relevant past context to the system prompt.
- Explicit save commands (
"remember that...","save this to notes") append toMEMORY.md.
| File | Contents |
|---|---|
memory.db |
SQLite database with sqlite-vec vector index |
MEMORY.md |
Human-readable notes file (read/written by the write and memory skills) |
preferences.md |
Persistent user preferences (referenced by the me skill) |
Chat history is written to plain text files in ~/.pasture/workspace/ so you can search them with any text tool. Private and group chats are stored separately.
LLM context is scoped to a session per chat (owner log, per-DM jid, or group). Full logs still append to the same JSONL files with a sessionId field.
- Daily reset - New session at 03:00 in
agents.defaults.userTimezone(same timezone as reminders;"auto"uses the host TZ). Override hour withagents.defaults.sessionResetHour(0–23). - Manual reset - Say e.g.
start a new session,new session, or/new-session. Context clears and the bot replies briefly (e.g. "New session started."). - State file -
~/.pasture/chat-sessions/state.json - Bootstrap (not in session history) - On each new session, daemon restart, and every Tide follow-up, the model receives
MEMORY.mdplus today and yesterday’s chat logs (chat-log/YYYY-MM-DD.jsonland, for private chats,chat-log/private/<jid>.jsonl). Chat session history stays scoped to the current session only.
Tide sends a single AI-composed follow-up message when a conversation goes quiet. It reads the recent conversation context and generates a short, relevant nudge ("Tests passed - what's next?" / "Still no reply on that, should I follow up?").
// ~/.pasture/config.json
{
"tide": {
"enabled": true,
"silenceCooldownMinutes": 60, // minimum silence before a follow-up is sent
"healthCheckMinutes": 2, // polling watchdog + follow-up scheduler interval (default 2)
"inactiveStart": "23:00", // quiet hours start - no Tide during this window
"inactiveEnd": "06:00", // quiet hours end
"jid": "", // leave empty for auto-detection
"nudge": {
"enabled": true, // set false to disable history nudge independently of silence follow-up
"intervalMinutes": 120, // how often to send a history nudge (default 2 h, minimum 30 min)
"lookbackDays": 7, // how many days back to scan for topics (default 7)
"maxHistoryItems": 20 // max exchanges sampled from that window (evenly spread, default 20)
}
}
}Tide never sends more than one follow-up per silence period, and never during the configured quiet hours. Each cycle also runs the Telegram polling watchdog (self-healing heartbeat), independent of whether a follow-up is sent.
Completely separate from the silence-based follow-up. Every 2 hours (configurable) Tide scans the past 7 days of conversation, picks one interesting topic, and sends a casual proactive message:
"Hey, last week you were looking at [X]. Want to pick that back up, or take a different angle?"
"We never finished [Y]. Still relevant, or off the table?"
The nudge is evenly sampled across the lookback window so it surfaces threads from earlier in the week, not just the most recent exchanges. It respects quiet hours and the daily LLM limit. tide.nudge.enabled can be set to false to disable it independently of the silence follow-up.
Tide can run a configurable checklist of prompts. Each item is one agent turn (same skills and bootstrap context as chat)-executed one by one in order. Prior item results are passed as context to the next. Results are logged only (~/.pasture/tide-checklist-last.json), not sent to the user.
Item schema: id, label, prompt, enabled. The agent should end with OK: or FAIL:; anything else is treated as pass unless it starts with FAIL.
Automatic runs need tide.enabled + checklist.enabled + the trigger on + outside quiet hours. Manual runs (pasture tide checklist run or dashboard Run now) ignore those flags.
| Trigger | When it runs |
|---|---|
onRestart |
Daemon starts |
onCycle |
Each Tide health-check interval |
onFollowUp |
Before a follow-up message is sent for a chat |
"checklist": {
"enabled": true,
"triggers": { "onRestart": true, "onCycle": true, "onFollowUp": false },
"items": [
{
"id": "time-check",
"label": "Local time",
"prompt": "What is the current local time? Report OK or FAIL.",
"enabled": true
}
]
}Fresh installs get a default Telegram polling health item (disabled until you enable the checklist).
CLI
pasture tide checklist list
pasture tide checklist add "Local time" --prompt "What is the current local time?"
pasture tide checklist remove <id>
pasture tide checklist enable|disable <id>
pasture tide checklist on|off
pasture tide checklist run [--id <id>]
pasture tide checklist triggers [--on-restart|--no-on-restart] [--on-cycle|--no-on-cycle] [--on-follow-up|--no-on-follow-up]Dashboard: Tide page - toggle Tide/checklist, edit triggers and items, run manually, view last results. Legacy shell/http/builtin config items are auto-converted to prompts on load.
~/.pasture/
├── config.json # Main configuration
├── secrets.json # Sensitive credentials (gitignored) - GitHub token, etc.
├── .env # API keys and env var overrides (gitignored)
├── daemon.log # Bot daemon stdout log
├── daemon.err # Bot daemon stderr log
├── auth_info/ # WhatsApp session files (Baileys)
│ ├── creds.json
│ └── *.json
├── chat-sessions/
│ └── state.json # Per-chat session IDs (daily reset)
├── cron/
│ └── jobs.json # Reminder/cron job store
├── tide-checklist-last.json # Last Tide checklist run summary
├── projects.db # Dashboard Projects tracker (SQLite)
├── workspace/ # Default workspace for file operations
│ ├── MEMORY.md # User notes (read/written by skills)
│ ├── memory/ # Vector memory store + custom .md files
│ ├── chat-log/ # Chat history JSONL files (daily + private)
│ └── ... # User files
├── agents/ # Per-agent config (id, skills, workspace/)
│ └── <agent-id>/
│ └── workspace/
│ ├── SOUL.md
│ ├── WhoAmI.md
│ └── MyHuman.md
└── groups/ # Per-group config (agent assignment, skill deny list)
pastureprotocol/
├── index.js # Entry point - connects WhatsApp + Telegram, routes messages
├── llm.js # LLM provider loader and multi-model fallback logic
├── cli.js # CLI command dispatcher
├── setup.js # Interactive first-run setup wizard
├── lib/
│ ├── agent.js # Agent turn runner (tool call loop)
│ ├── intent-planner.js # Classifies intent → selects skills
│ ├── system-prompt.js # Builds the system prompt (one-on-one)
│ ├── group-prompt.js # Builds the system prompt for groups
│ ├── telegram.js # Telegram bot adapter (polling)
│ ├── whatsapp.js # WhatsApp utility functions
│ ├── memory-index.js # Memory embedding + vector search
│ ├── session-bootstrap.js # MEMORY.md bootstrap for new sessions / Tide
│ ├── chat-log.js # Append/read conversation logs
│ ├── paths.js # Resolves all state directory paths
│ ├── speech-client.js # Speech-to-text / text-to-speech client
│ ├── timezone.js # Time-zone utilities
│ ├── owner-config.js # Owner/admin identity resolution
│ ├── tide-checklist.js # Tide maintenance checklist (agent turns)
│ └── executors/ # Skill execution engines (browse, vision, github, gmail, calendar, etc.)
├── skills/
│ ├── browse/ # Playwright browser skill
│ ├── cron/ # Reminder scheduling skill
│ ├── github/ # GitHub skill (repos, issues, PRs)
│ ├── gmail/ # Gmail skill (list, read, send, archive)
│ ├── calendar/ # Google Calendar skill (events, availability)
│ ├── memory/ # Semantic memory skill
│ ├── search/ # Web search skill (Brave)
│ ├── vision/ # Image/webcam vision skill
│ ├── read/ write/ edit/ # File operation skills
│ └── loader.js # Skill registry loader
├── cron/
│ ├── runner.js # Cron job runner (croner-based scheduler)
│ ├── store.js # Read/write jobs.json
│ └── cli.js # Cron management CLI
├── dashboard/
│ └── server.js # Local Express web dashboard
└── workspace-default/ # Default workspace template files
├── SOUL.md # Agent personality template
├── WhoAmI.md # Agent identity template
└── MyHuman.md # User profile template
pasture start launches the bot as a background daemon using the platform's process manager so it survives terminal sessions.
Logs are written to ~/.pasture/daemon.log (stderr: daemon.err). Tail them with:
pasture logs
# or:
tail -f ~/.pasture/daemon.log- Runs entirely on your machine.
- WhatsApp and Telegram connect directly - no external proxy.
- Config, auth, and chats live in
~/.pasture- not in the code directory. - Local models (LM Studio, Ollama) mean zero data leaves your device.
- Cloud LLMs send only the current conversation context to the provider's API - no call history is ever sent unless it is in the active context window.
git pushon this repo never uploads your chats, auth files, or API keys. The.gitignoreexcludes all common state layout names.

{ "agents": { "defaults": { "userTimezone": "auto", // "auto" detects from system, or use IANA tz e.g. "America/New_York" "timeFormat": "auto" // "auto", "12h", or "24h" } }, "llm": { "maxTokens": 2048, // max tokens per LLM response "models": [ // Local model (LM Studio) - used by default { "provider": "lmstudio", "baseUrl": "http://127.0.0.1:1234/v1", "model": "local", "apiKey": "not-needed" }, // Cloud model with priority flag - used first if available { "provider": "openai", "apiKey": "LLM_1_API_KEY", // env var name or literal key "model": "gpt-4o", "priority": true }, // Other cloud providers - used as fallback { "provider": "grok", "apiKey": "LLM_2_API_KEY" }, { "provider": "anthropic", "apiKey": "LLM_3_API_KEY", "model": "claude-3-5-sonnet-20241022" } ] }, "skills": { "enabled": ["cron", "search", "browse", "vision", "memory"], "available": ["cron", "search", "browse", "vision", "memory", "gog", "read", "write", "edit", "apply-patch"], "search": { "provider": "brave", // only "brave" is currently supported "count": 8 // number of search results to return }, "github": { "token": "GITHUB_TOKEN", // env var name or literal PAT (repo scope for private repos) "defaultRepo": "owner/repo" // optional default repo used when agent omits repo }, "gog": { "account": "you@gmail.com" // default Google account for gmail + calendar skills } }, "channels": { "whatsapp": { "enabled": true }, "telegram": { "enabled": false, "botToken": "TELEGRAM_BOT_TOKEN" // env var name or literal token } }, "owner": { "whatsappJid": "1234567890@s.whatsapp.net", // optional: your WhatsApp JID "telegramUserId": 123456789 // optional: your Telegram user ID }, "tide": { "enabled": false, "silenceCooldownMinutes": 30, // minutes of silence before sending a follow-up "healthCheckMinutes": 2, // how often Tide wakes for polling watchdog + due follow-ups (≤ cooldown) "jid": "", // target JID/chat ID; auto-detected if empty "inactiveStart": "23:00", // quiet hours start (local time) "inactiveEnd": "06:00", // quiet hours end (local time) "checklist": { "enabled": false, "triggers": { "onRestart": true, "onCycle": true, "onFollowUp": false }, "items": [] } } }