Skip to main content
Glama
README.md
# goat

A personal assistant backend. It stores facts in Postgres or SQLite, fills them
from importers on a schedule, and answers questions about them over Slack, MCP,
SMS, or a terminal REPL.

Facts carry two timestamps: when the fact is *about*, and when goat learned it.
Re-running an importer with unchanged data writes nothing.

## Install

```sh
uv add "goat-butler @ git+https://github.com/phin-tech/goat"
```

Extras: `postgres`, `agent` (pydantic-ai), `cli`, `mcp`, `slack`, `sms`,
`weather`, `migrations`, and `all` for every one of them. SQLite needs none.

```sh
uv add "goat-butler[all] @ git+https://github.com/phin-tech/goat"
```

To work on goat itself:

```sh
git clone https://github.com/phin-tech/goat
cd goat
uv sync
uv run pytest
```

The suite runs against both SQLite and Postgres. Postgres comes from
`docker compose up -d`.

## Start a shed

A shed is one deployment: a directory with a `shed.py` that builds a `Shed`, and
a `.env` holding the credentials. `goat init` writes both.

```sh
uv run goat init ~/my-shed
cd ~/my-shed
uv sync
goat migrate
goat ingest
goat repl
```

Defaults to SQLite, so this runs with no database server and no API key. Set a
model provider key in `.env` for `goat brief` and the REPL's `ask`.

`.env` can hold 1Password references instead of secrets. goat resolves them at
startup via the `op` CLI, so the file is safe to read aloud or paste:

```
OPENROUTER_API_KEY=op://Private/OpenRouter/credential
```

## Commands

| | |
|---|---|
| `goat init` | scaffold a new shed |
| `goat migrate` | create or update the schema |
| `goat ingest` | run every importer |
| `goat brief` | compose a brief; `--send` delivers it |
| `goat facts`, `goat history` | inspect the store |
| `goat repl` | interactive session |
| `goat mcp` | serve the store over MCP on stdio |

## What goes where

goat holds the mechanism: the store, importers, delivery, tools, the CLI. A shed
holds the values: coordinates, model, credentials, prompt, schedule.
`OpenMeteoImporter` lives in goat because a latitude is a constructor argument.
The latitude lives in your shed.

## Docs

- [`docs/design/fact-store.md`](docs/design/fact-store.md) — schema, decisions, and the ones that were reversed
- [`docs/mcp.md`](docs/mcp.md) — MCP server and Claude Desktop config
- [`docs/slack.md`](docs/slack.md) — Slack app setup
- [`docs/weather.md`](docs/weather.md) — weather sources
- [`skills/`](skills/) — Claude Code skills for setup and daily use