Skip to content

jdlien/goldfish

Repository files navigation

Goldfish

AI agent runtime — a Claude Code-native Slack bot with persistent memory.

Goldfish allows you to use Claude Code from Slack — but it's also much more than that. Think of it like the best parts of the OpenClaw AI assistant harness but dramatically simpler, polished for a specific use case. It's your agent that knows you.

This gives you:

  • Full tool access: Bash, file read/write, web search
  • Thread-based sessions: Multiple simultaneous Slack threads across different channels
  • Persistent memory: Your agent remembers the most important details of your conversations over time and gets to know you personally, details about your life, and what you're working on
  • Proactive outreach: Morning briefings, hourly heartbeat checks, and optional daily exploration sessions via scheduled tasks and reminders
  • Zero API cost: Conversations run through Claude Code on a Max subscription

Why This Exists

OpenClaw is great, but it has some issues:

  • It is complex to set up and keep working
  • It had many bugs in its Slack integration

And crucially:

  • Anthropic stopped allowing SSO auth with third-party harnesses like OpenClaw, so it became enormously expensive
  • Claude Code "just works" with a Claude Max subscription; OpenClaw had reliability issues with Claude models

While OpenClaw could technically work via ACP bridges to Claude Code, there were many problems including zombie claude sessions and it still required significant API usage (at full API costs) for certain features. This made it unusable in practice.

If you only want to use Claude over Slack, Goldfish does 90% of what OpenClaw did with 10% of the complexity:

Feature OpenClaw Goldfish
Conversations ACP bridge (fragile) Claude Code CLI (solid)
Session continuity ACP session management --resume flag
Memory Built-in indexer + embeddings FTS5 + semantic vectors + scheduled synthesis
Channels Slack, Telegram, Signal Slack
Cost API Cost Max Plan

Goldfish is compatible with OpenClaw agent workspaces. It can use the same identity files (SOUL.md, IDENTITY.md, USER.md, FOCUS.md, etc.), memory directory structure, and tools. The key difference is the entry point: OpenClaw reads AGENTS.md as its primary instruction file, while Goldfish uses Claude Code's native CLAUDE.md. An OpenClaw workspace needs a CLAUDE.md that mirrors its AGENTS.md bootstrap sequence to work with Goldfish.

Architecture

Slack message  → Goldfish daemon → spawns claude CLI → reads agent config → responds
                                                     → saves transcript to JSONL

<workspace>/schedule.yaml  → schedule run (every minute)
               → morning / heartbeat / exploration / weekly  → Claude → Slack
               → daily-synthesis (1 AM)                      → Claude summarizes → memory/YYYY-MM-DD.md
               → index-memory (1:15 AM)                      → FTS5 + vectors    → memory/search.sqlite

Two launchd agents. One config file. That's the whole thing. See docs/deployment-macos.md for the full setup.

Quick Start

Prerequisites: Node.js 22+, Claude Code CLI, a Slack app with Socket Mode enabled.

This project uses pnpm. If you don't have it, enable it via Node's built-in Corepack:

corepack enable

Then:

# Install dependencies
pnpm install

# Copy .env and fill in Slack tokens
cp .env.example .env

# Create an agent workspace (interactive — sets up identity, memory, prompts, schedule)
pnpm cli init

# Test connection
pnpm cli auth test

# Start the bot
pnpm cli start

Configuration

Environment variables (in .env):

Variable Description
SLACK_APP_TOKEN Slack Socket Mode app-level token (Required)
SLACK_BOT_TOKEN Slack bot OAuth token (Required)
GOLDFISH_WORKSPACE Path to agent workspace (default: ~/goldfish-workspace)
GOLDFISH_CHANNELS Comma-separated Slack channel IDs to listen on (in addition to DMs)
GOLDFISH_DM_CHANNEL_ID Default DM channel for proactive outreach
GOLDFISH_MAX_TURNS Max Claude Code turns per message (default: 50)
GOLDFISH_TIMEOUT_MS Claude Code timeout in ms (default: 300000)
GOLDFISH_SESSION_EXPIRY_MS Session expiry in ms (default: 86400000 / 24h)
GOLDFISH_SHOW_THINKING Show "Thinking..." indicator (default: true)
GOLDFISH_EFFORT Default Claude thinking effort for all channels (see below)
GOLDFISH_EFFORT_BY_CHANNEL Per-channel effort overrides as a JSON map (see below)

Per-channel thinking effort

Claude Code accepts a --effort level per session: low, medium, high, xhigh, or max. Lower effort means faster, shallower replies; higher effort means slower, more deliberate reasoning. Goldfish lets you pick a level per Slack channel — keep a casual channel snappy on low while a work channel stays sharp on high.

  • GOLDFISH_EFFORT sets a default applied to every channel.
  • GOLDFISH_EFFORT_BY_CHANNEL is a JSON object mapping channel ID → level; it overrides the default.
  • A channel with no override (and no default) uses Claude Code's own default effort.
  • An unrecognized level or malformed JSON is ignored, falling back to the CLI default — it never errors.
# Default everything to medium, but run one casual channel fast:
GOLDFISH_EFFORT=medium
GOLDFISH_EFFORT_BY_CHANNEL='{"C0A7VB1U6EA":"low","C0WORKCHAN1":"high"}'

⚠️ Quote the JSON with single quotes. The launchd wrapper (launchd/goldfish-env.sh) loads .env by source-ing it in bash, which strips double quotes out of the value — turning {"C123":"low"} into invalid {C123:low} that silently parses to an empty map. Wrapping the whole value in single quotes preserves the inner double quotes through both bash sourcing and dotenv. (This bites any JSON-valued variable in .env, not just this one.)

After editing .env, restart the daemon so the new environment is picked up: launchctl kickstart -k gui/$(id -u)/com.jdlien.goldfish.daemon (run npm run build first if you also changed source — the daemon runs the compiled dist/, not src/).

Agent Identity

Goldfish is agent-agnostic. The agent's identity comes from the workspace it points at, not from Goldfish itself. Claude Code reads CLAUDE.md in the workspace root, which bootstraps whatever identity files, tools, and context you configure.

This means you can use Goldfish as:

  • A personal assistant with memory, opinions, and persistent personality
  • A project-specific agent with domain context
  • A team bot with shared knowledge
  • A drop-in replacement for an OpenClaw agent (same workspace, simpler runtime)
  • Anything else you can define in a CLAUDE.md

The workspace pattern (identity as markdown files, memory as a searchable archive, personality that evolves through conversation) is the core of what makes a persistent agent feel persistent. See docs/agent-identity.md for the full design philosophy, workspace anatomy, and migration guide from OpenClaw.

Commands

goldfish start                          # Start the bot daemon
goldfish auth status                    # Check token configuration
goldfish auth test                      # Test Slack API connection
goldfish send -m "Hello" -c <channel>   # Send a message manually
goldfish upload -f report.pdf -c <ch>   # Upload a file
goldfish initiate -t morning            # Trigger a morning briefing
goldfish initiate -t weekly             # Trigger a weekly review
goldfish initiate -t heartbeat          # Silent urgency check (pings only if actionable)
goldfish initiate -t exploration        # Agent picks a topic and goes deep
goldfish initiate --reminder "Call back about the service agreement"
goldfish schedule list                  # Show all scheduled tasks
goldfish schedule run                   # Run any tasks due now
goldfish schedule run --dry-run         # Preview what would run

Scheduling

Goldfish uses a single schedule.yaml in your workspace (default: ~/goldfish-workspace/schedule.yaml) to define scheduled tasks: briefings, heartbeats, maintenance, whatever you need. A launchd agent fires every 60 seconds and runs any due tasks:

tasks:
  - type: morning
    at: "8:30am"
    channel: C0ABC123DEF

  - type: heartbeat
    every: hour
    between: "10am-5pm"
    days: weekdays
    channel: C0ABC123DEF

  - type: daily-synthesis
    at: "1:00am"

  - type: index-memory
    at: "1:15am"

See docs/scheduling.md for the full reference: all fields, task types, timing syntax, locking, and configuration options.

Memory System

Goldfish maintains memory through three layers:

  1. In-session: The agent writes to memory files during conversations (daily notes, project files, etc)
  2. Post-session transcripts: Every message exchange is appended to memory/sessions/YYYY-MM-DD.jsonl
  3. Daily synthesis: A scheduled task consolidates the day's transcripts into a narrative daily log

Search memory from within a Claude session — keyword, semantic, or both (fused):

goldfish search "what you're looking for"        # hybrid: keyword ∪ semantic (default)
goldfish search "exact tokens" --mode fts        # keyword only, no model load
goldfish search "a conceptual question" --json   # stable JSON for tools

# Semantic search needs a one-time model download:
goldfish embeddings setup

The index is a plain SQLite DB, so raw FTS5 still works as a fallback:

sqlite3 memory/search.sqlite \
  "SELECT path, snippet(chunks_fts, 0, '>>>', '<<<', '...', 40) \
   FROM chunks_fts WHERE chunks_fts MATCH 'search terms' \
   ORDER BY rank LIMIT 10;"

The Name

Goldfish are commonly thought to have 3-second memories. It turns out that's a myth — they can remember for months. But an LLM-based AI-agent is kind of like that. Almost no short-term memory, but we create a long-term memory by writing everything down obsessively.

License

MIT

About

AI agent runtime — Claude Code-native Slack bot with persistent memory

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages