Skip to content

londondan/flowotter

Repository files navigation

FlowOtter

FlowOtter gives every person on your team their own persistent AI workspace — scoped to their identity, inaccessible to anyone else. Trigger tasks from Slack, store the results, and recall them wherever you work next.

In a shared environment like Slack, anyone can see a thread — but only you can add it to your memory. No one else can write to your workspace, read from it, or pollute it. It's yours.

Bot name: Flo  ·  Phase: 1 (Slack + SSO + Storage + Retrieval)


What Flo Does

  • @Flo <instruction> — Execute any natural language task. Flo runs it through your configured LLM and stores the full output as markdown in S3, scoped to your identity.
  • @Flo what did you find about X? — Retrieve past sessions semantically. Flo searches your history and synthesises a response with source dates.
  • Proactive tasks: @Flo pull the open customer complaints for Acme and DM me 15 minutes before my 3pm call — schedule work and get results delivered at the right moment.
  • Capture from shared spaces: Spot a useful Slack discussion? @Flo add this thread to my memory — it's stored in your personal workspace, invisible to others, and retrievable later wherever you work.
  • Works in channels, threads, and DMs.
  • Each member's workspace is fully isolated — no one else can read from it, write to it, or inject into it.

Prerequisites

  • Docker + Docker Compose
  • A Slack workspace where you can install apps
  • An Anthropic or OpenAI API key (for LLM execution)
  • An OpenAI API key (always required for embeddings, even if using Anthropic for LLM)
  • A Google Workspace OAuth 2.0 app (or any OIDC provider)
  • An AWS account with an S3 bucket (or use LocalStack for fully local dev)
  • ngrok or equivalent to expose your local port to Slack (development only)

Quick Start (Local Development)

1. Clone and install

git clone https://github.com/your-org/flowotter.git
cd flowotter
npm install

2. Configure environment

cp .env.example .env

Edit .env with your credentials. See the inline comments for each variable.

Minimum required for local dev:

  • SLACK_BOT_TOKEN, SLACK_SIGNING_SECRET
  • FLOWOTTER_LLM_PROVIDER, FLOWOTTER_LLM_API_KEY
  • OPENAI_API_KEY (for embeddings)
  • FLOWOTTER_OIDC_CLIENT_ID, FLOWOTTER_OIDC_CLIENT_SECRET, FLOWOTTER_OIDC_REDIRECT_URI
  • FLOWOTTER_BASE_URL (your ngrok URL once running)

3. Create a Slack app

  1. Go to api.slack.com/appsCreate New AppFrom manifest
  2. Paste the following manifest:
display_information:
  name: Flo
  description: Your persistent AI work assistant
features:
  bot_user:
    display_name: Flo
    always_online: true
oauth_config:
  scopes:
    bot:
      - app_mentions:read
      - chat:write
      - channels:history
      - im:history
      - im:write
      - im:read
      - users:read
settings:
  event_subscriptions:
    bot_events:
      - app_mention
      - message.im
  interactivity:
    is_enabled: false
  org_deploy_enabled: false
  socket_mode_enabled: false
  1. Install the app to your workspace and copy the Bot User OAuth TokenSLACK_BOT_TOKEN
  2. From Basic Information, copy the Signing SecretSLACK_SIGNING_SECRET

4. Set up Google Workspace OIDC

  1. Go to console.cloud.google.comAPIs & ServicesCredentials
  2. Click Create CredentialsOAuth 2.0 Client IDWeb application
  3. Add http://localhost:3000/auth/callback to Authorized redirect URIs
  4. Copy the Client ID and Client Secret to .env

5. Start the stack

docker compose up

This starts:

  • FlowOtter on port 3000
  • Postgres (with pgvector) on port 5432
  • LocalStack S3 on port 4566

Database migrations run automatically on startup.

6. Expose to Slack with ngrok

ngrok http 3000

Copy the https://...ngrok.io URL and:

  1. Update FLOWOTTER_BASE_URL in .env to the ngrok URL
  2. Update FLOWOTTER_OIDC_REDIRECT_URI to https://...ngrok.io/auth/callback
  3. Update the Slack app's Event Subscriptions Request URL to https://...ngrok.io/slack/events
  4. Update the Authorized redirect URIs in Google Cloud Console

Restart docker compose after updating .env.

7. First run verification

  1. Add Flo to a Slack channel
  2. Send @Flo hello
  3. Flo should DM you with an authentication link
  4. Click the link, complete Google SSO
  5. Send @Flo hello again — Flo should respond normally

Production Deployment (AWS ECS)

Build and push

docker build -t flowotter:latest .
docker tag flowotter:latest <your-ecr-repo>/flowotter:latest
docker push <your-ecr-repo>/flowotter:latest

Environment

Set all variables from .env.example as ECS task environment variables or Secrets Manager references.

For production:

  • FLOWOTTER_STORAGE_PROVIDER=s3 with a real S3 bucket
  • FLOWOTTER_BASE_URL set to your production domain
  • DATABASE_URL pointing to an RDS Postgres instance with the pgvector extension enabled
  • Remove FLOWOTTER_S3_ENDPOINT (use native S3)
  • Use an ECS task role with s3:PutObject, s3:GetObject permissions instead of AWS_ACCESS_KEY_ID

Enabling pgvector on RDS

CREATE EXTENSION IF NOT EXISTS vector;

Run this once on your RDS instance before the first deployment. RDS for Postgres supports pgvector on Postgres 15+.


Architecture

                        ┌─────────────────────┐
                        │     Slack Event      │
                        │   (DM or @mention)   │
                        └──────────┬──────────┘
                                   │
                        ┌──────────▼──────────┐
                        │  Bolt App — ack()    │
                        └──────────┬──────────┘
                                   │
                        ┌──────────▼──────────┐
                        │  Identity Middleware  │
                        │  (lookup Slack ID)   │
                        └──────────┬──────────┘
                                   │
                    ┌──────────────┴──────────────┐
              User found?                    Not found
                    │                             │
                    │                  ┌──────────▼──────────┐
                    │                  │   Send auth DM with  │
                    │                  │   Google OIDC link   │
                    │                  └──────────┬──────────┘
                    │                             │
                    │                  ┌──────────▼──────────┐
                    │                  │  User authenticates  │
                    │                  │  OAuth callback →    │
                    │                  │  create user record  │
                    │                  └─────────────────────┘
                    │
         ┌──────────▼──────────┐
         │   classifyIntent()   │
         │     (LLM call)       │
         └──────────┬──────────┘
                    │
          ┌─────────┴──────────┐
        TASK               RETRIEVE
          │                    │
          │            ┌───────▼──────────────┐
          │            │  Acknowledge in thread │
          │            │  "Searching history..." │
          │            └───────┬──────────────┘
          │                    │
          │            ┌───────▼──────────────┐
          │            │  Generate query embed  │
          │            │  (OpenAI 3-small)      │
          │            └───────┬──────────────┘
          │                    │
          │            ┌───────▼──────────────┐
          │            │  pgvector search       │
          │            │  cosine×0.7 +          │
          │            │  recency×0.3           │
          │            └───────┬──────────────┘
          │                    │
          │          ┌─────────┴──────────────┐
          │      No results               Results found
          │          │                         │
          │   ┌──────▼──────┐        ┌─────────▼──────────┐
          │   │ Reply:       │        │ Fetch top 3         │
          │   │ nothing found│        │ previews from S3    │
          │   └─────────────┘        └─────────┬──────────┘
          │                                     │
          │                          ┌──────────▼──────────┐
          │                          │  LLM synthesise from │
          │                          │  excerpts            │
          │                          └──────────┬──────────┘
          │                                     │
          │                          ┌──────────▼──────────┐
          │                          │  Reply in thread     │
          │                          │  with source dates   │
          │                          └─────────────────────┘
          │
 ┌────────▼─────────────┐
 │  Acknowledge in thread │
 │  "On it..."            │
 └────────┬─────────────┘
          │
 ┌────────▼─────────────┐
 │  Fetch thread context  │
 │  from Slack (up to 20) │
 └────────┬─────────────┘
          │
 ┌────────▼─────────────┐
 │  Build system prompt   │
 │  + LLM complete()      │
 └────────┬─────────────┘
          │
 ┌────────▼─────────────┐
 │  Format session        │
 │  markdown              │
 └────────┬─────────────┘
          │
 ┌────────▼─────────────┐
 │  Store to S3           │
 │  (user-namespaced key) │
 └────────┬─────────────┘
          │
 ┌────────▼─────────────┐
 │  Reply in Slack thread │  ◄── user sees response here
 └────────┬─────────────┘
          │
 ┌────────▼─────────────┐
 │  Generate embedding    │
 │  (OpenAI 3-small)      │
 └────────┬─────────────┘
          │
 ┌────────▼─────────────┐
 │  Insert session row    │
 │  to PostgreSQL         │
 │  (with vector)         │
 └───────────────────────┘

Key design decisions

  • Per-user data isolation: Every S3 key and every database query is scoped to the authenticated user's ID. A user cannot access another user's data.
  • Opaque S3 prefixes: Each user is assigned a random usr_{id} prefix at registration. Email and SSO sub are never used in S3 paths.
  • Embeddings always use OpenAI: text-embedding-3-small (1536 dimensions) is used for all semantic indexing, regardless of which LLM provider handles task execution. This ensures a consistent vector space.
  • Auth state in Postgres: OAuth2 state tokens are stored in the database, not in memory. This makes the service stateless and safe to run as multiple containers.

Test Scenarios

Test Description
T-01 First-time user receives auth DM, authenticates, can use Flo
T-02 Unauthenticated user is blocked and sent auth DM
T-03 Task execution stores markdown at correct S3 path
T-04 @Flo summarise this thread uses thread context
T-05 Retrieval returns relevant past session
T-06 More recent sessions rank higher when relevance is equal
T-07 No-results retrieval offers to research instead
T-08 LLM error surfaces as DM, service stays up
T-09 User B cannot retrieve User A's sessions
T-10 DM invocation works end-to-end

Contributing

FlowOtter is open source (MIT). Issues and PRs welcome.


License

MIT — see LICENSE.

About

An open source tool to manage identity and persistence on agentic workflows across multiple interfaces

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors