Skip to content

[club-steward] Internal AI intelligence agent for club leadership#9

Merged
fullharbor merged 6 commits into
mainfrom
copilot/add-club-steward-ai-agent
Mar 11, 2026
Merged

[club-steward] Internal AI intelligence agent for club leadership#9
fullharbor merged 6 commits into
mainfrom
copilot/add-club-steward-ai-agent

Conversation

Copilot AI commented Mar 11, 2026

Copy link
Copy Markdown
Contributor
  • Create packages/club-steward/src/agent/steward.py — ClubStewardAgent with chief-of-staff persona, Supabase financial data access, strict club isolation, lazy OpenAI client, and CLI
  • Create packages/club-steward/src/agent/board_report.py — BoardReportGenerator producing structured 1-page board memos; BoardMemo data model
  • Create packages/club-steward/src/api/main.py — FastAPI app with X-API-Key header auth, per-club subscription tokens, 401/403 enforcement, lazy per-club agent cache
  • Create packages/club-steward/tests/test_steward.py — 20 unit tests (no API key in CI); no_supabase_env monkeypatch fixture; 2 integration tests skipped in CI
  • Create packages/club-steward/requirements.txt — includes supabase>=2.0.0
  • FinancialDataClient uses Supabase (SUPABASE_URL + SUPABASE_SERVICE_KEY): queries sailing_filer_core; module-level import with ImportError guard; graceful fallback (warning log) when creds unset; error log on connection failure; all SQLite/db_path code removed
  • api/main.py docstring updated to document SUPABASE_URL/SUPABASE_SERVICE_KEY (removed stale HARBOR_COMMONS_DB reference)
  • Update CI — removed --ignore=packages/club-steward
  • Fix pre-existing ask-a-sailor sys.path bug
  • Add .gitignore
  • All 33 tests pass, CodeQL clean
Original prompt

This section details on the original issue you should resolve

<issue_title>[club-steward] Internal AI intelligence agent for club leadership</issue_title>
<issue_description>## Context

Club Steward is the internal-facing AI agent — the private counterpart to Ask a Sailor. Where Ask a Sailor faces outward (parents, prospects), Club Steward faces inward (boards, commodores, club managers).

This is the premium, subscription product. It uses the same underlying infrastructure as Ask a Sailor but with a different persona, different permissions, and access to Harbor Commons financial data.

Depends on: Issue #1 (Ask a Sailor RAG pipeline), Issue #2 (Harbor Commons 990 data)

What Club Steward Answers

Questions club leadership asks in private:

  • "What are comparable clubs charging for camp?"
  • "How does our membership revenue compare to our peer group in Texas?"
  • "What did the last 3 years of 990s say about our compensation versus revenue growth?"
  • "Draft a board report on our youth program growth."
  • "Help me write the annual regatta sponsorship pitch."
  • "What percentage of our revenue comes from member dues vs. program fees?"

What Needs Doing

  1. Create packages/club-steward/src/agent/steward.py — the Club Steward agent class, extending the Ask a Sailor architecture with:
    • Access to Harbor Commons DB (financial benchmarking)
    • Club-scoped data isolation (Club A cannot see Club B's data)
    • A different system prompt persona (discreet chief of staff, not friendly public agent)
  2. Create packages/club-steward/src/agent/board_report.py — given a topic, generates a 1-page board memo in plain English
  3. Create packages/club-steward/src/api/main.py — FastAPI wrapper with API key authentication
  4. Create packages/club-steward/tests/test_steward.py
  5. Create packages/club-steward/requirements.txt

System Prompt Persona

Club Steward speaks as a knowledgeable, discreet chief of staff who:

  • Knows sailing culture and yacht club operations deeply
  • Is fluent in nonprofit finance (990s, UBIT, 501(c)(7) rules)
  • Can speak frankly to board members without hedging or over-qualifying
  • Never shares information from other clubs without explicit cross-authorization
  • Always cites sources: which 990 year, which benchmark group, which page

Acceptance Criteria

  • python3 steward.py --club lyc --question "How do we compare to HYC on member dues?" returns answer using real 990 data from Harbor Commons DB
  • Board report generator produces a coherent 1-page memo for topic "youth program growth 2019-2023"
  • API requires X-API-Key header; returns 401 without it
  • Club isolation: querying with club=lyc token cannot retrieve HYC-specific data
  • Tests pass
    </issue_description>

Comments on the Issue (you are @copilot in this section)

@fullharbor **Architecture update (March 2026):** Club Steward is the premium intelligence layer that sits on top of harbor_ingest's Supabase corpus. It should query across all the `sailing_*` tables (governance, officers, programs, revenue, expenses, balance sheet) to generate board-ready reports. The `workspace_ai_chats` and `workspace_shared_outputs` tables already exist in Supabase — use those for session persistence. Auth: Supabase Auth (the `leads` table captures inbound interest). This is the monetization path — design for a per-club subscription model.

💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI changed the title [WIP] Add internal AI intelligence agent for club leadership [club-steward] Internal AI intelligence agent for club leadership Mar 11, 2026

@fullharbor fullharbor left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FinancialDataClient must use Supabase, not SQLite

Great work overall — persona, system prompt, and board_report.py are solid. One blocking issue before merge:

FinancialDataClient uses SQLite (lines 157–260)

The class imports sqlite3 and looks for a local .db file (HARBOR_COMMONS_DB). That file does not exist in any deployed environment. See .github/copilot-instructions.md — production database is Supabase.

Fix: use supabase-py

Replace FinancialDataClient with a supabase-py implementation:

from supabase import create_client
class FinancialDataClient:
    def __init__(self):
        self._db = create_client(os.environ["SUPABASE_URL"], os.environ["SUPABASE_SERVICE_KEY"])
    def get_club_financials(self, club_slug, tax_years=3):
        ein = KNOWN_CLUB_EINS.get(club_slug)
        if not ein: return []
        return self._db.table("sailing_filer_core").select("*").eq("ein", ein).order("tax_year", desc=True).limit(tax_years).execute().data
    def get_peer_benchmarks(self, club_slug, state="TX", tax_year=None):
        q = self._db.table("sailing_filer_core").select("*").eq("state", state).neq("ein", KNOWN_CLUB_EINS.get(club_slug, "")).order("total_revenue", desc=True).limit(10)
        if tax_year: q = q.eq("tax_year", tax_year)
        return q.execute().data

Tables: sailing_filer_core (ein, tax_year, total_revenue, state). Add supabase>=2.0.0 to requirements.txt. Use SUPABASE_SERVICE_KEY (service_role) — Club Steward is internal/premium and needs full access. Everything else LGTM.

@fullharbor fullharbor left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FinancialDataClient must use Supabase not SQLite. The class imports sqlite3 and reads from HARBOR_COMMONS_DB (defaults to /tmp/full-harbor/harbor_commons.db). That file does not exist in any deployed environment. Per .github/copilot-instructions.md, the production database is Supabase. Fix: replace FinancialDataClient with supabase-py (from supabase import create_client). Use table sailing_filer_core, columns: ein, tax_year, total_revenue, total_expenses, net_assets, state. Use SUPABASE_SERVICE_KEY for service_role access (Club Steward is internal/premium). Add supabase>=2.0.0 to packages/club-steward/requirements.txt. Everything else is solid: system prompt persona, board_report.py, API shape, tests.

Copilot AI commented Mar 11, 2026

Copy link
Copy Markdown
Contributor Author

FinancialDataClient must use Supabase, not SQLite

Great work overall — persona, system prompt, and board_report.py are solid. One blocking issue before merge:...

Done in f3fb5a3. FinancialDataClient now uses supabase-py exclusively:

  • supabase imported at module level (with an ImportError guard)
  • __init__ reads SUPABASE_URL + SUPABASE_SERVICE_KEY; logs a warning when either is unset, logs an error on connection failure
  • get_club_financials and get_peer_benchmarks query sailing_filer_core exactly as you specified
  • All db_path / HARBOR_COMMONS_DB / sqlite3 references removed throughout steward.py, board_report.py, api/main.py, and the tests
  • supabase>=2.0.0 added to requirements.txt
  • Tests use a no_supabase_env monkeypatch fixture for clean env-var isolation

Copilot AI commented Mar 11, 2026

Copy link
Copy Markdown
Contributor Author

FinancialDataClient must use Supabase not SQLite. The class imports sqlite3 and reads from HARBOR_COMMONS_DB (defaults to /tmp/full-harbor/harbor_commons.db). That file does not exist in any deployed ...

Already addressed in f3fb5a3FinancialDataClient has been fully replaced with a supabase-py implementation querying sailing_filer_core using SUPABASE_URL + SUPABASE_SERVICE_KEY. The stale HARBOR_COMMONS_DB docstring in api/main.py was cleaned up in eec9a1d. All sqlite3 and db_path references are gone.

@fullharbor
fullharbor marked this pull request as ready for review March 11, 2026 06:02

@fullharbor fullharbor left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Column names fixed (address_state, cy_total_revenue_amt). FinancialDataClient correctly uses supabase-py. LGTM — merging.

Copilot AI and others added 6 commits March 11, 2026 01:03
Co-authored-by: fullharbor <237832340+fullharbor@users.noreply.github.com>
Co-authored-by: fullharbor <237832340+fullharbor@users.noreply.github.com>
Co-authored-by: fullharbor <237832340+fullharbor@users.noreply.github.com>
…SE_SERVICE_KEY

Co-authored-by: fullharbor <237832340+fullharbor@users.noreply.github.com>
…enue→cy_total_revenue_amt)

sailing_filer_core does not have 'state' or 'total_revenue' columns.
Correct names (verified against live Supabase schema):
  - address_state  (not 'state')
  - cy_total_revenue_amt  (not 'total_revenue')
@fullharbor
fullharbor force-pushed the copilot/add-club-steward-ai-agent branch from d9b2318 to 8f5cb5e Compare March 11, 2026 06:04
@fullharbor
fullharbor merged commit d44c830 into main Mar 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[club-steward] Internal AI intelligence agent for club leadership

2 participants