Commit Graph

317 Commits

Author SHA1 Message Date
Ryan Chen af33463f98 Put today's date (ET) in system prompt; stop hardcoding Simba's age
The model had no idea of the current date unless it remembered to call
the get_current_date tool, so relative/yearless dates (e.g. 'October')
got filled in from training data. build_system_prompt_with_memories now
prepends today's date (America/New_York) to every prompt, covering all
channels (web chat, iMessage, WhatsApp) and all date-related questions.

Simba's age is now computed from his birth date instead of a stale
hardcoded 'approximately 9 years 5 months'.
2026-10-12 18:25:32 -04:00
ryan 417d4061d7 Merge pull request 'Mirror YNAB budget into Postgres with delta sync and change log' (#55) from feat/ynab-mirror into main 2026-10-12 18:07:32 -04:00
Ryan Chen 0fa30edb82 Merge origin/main into feat/ynab-mirror
Resolve config/db.py (artifacts + ynab models) and regenerate the YNAB
migration with aerich as 18 on top of main's 17_add_artifacts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 18:07:26 -04:00
Ryan Chen 10f3f1bcab Merge origin/main into feat/ynab-mirror
Resolve the Makefile .PHONY conflict (desktop + ynab targets) and
regenerate the YNAB migration with aerich as 17 on top of main's
16_add_oidc_states, replacing the colliding 16_add_ynab_mirror.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 18:06:47 -04:00
ryan 3dbd320fe9 Merge pull request 'Cowork A: SSE v2 tool events, artifacts table + API' (#60) from feat/cowork-stream into main 2026-10-12 18:06:02 -04:00
Ryan Chen d1c3b7693e Fix second-round YNAB review findings
- Failure backoff is re-checked under the sync lock, so parallel tool
  calls share one failed YNAB request instead of retrying in turn.
- Budget id: YNAB_BUDGET_ID aliases ("last-used", "default") count as
  unset; state is always keyed by the real budget id, a mismatched
  response errors instead of silently skipping every sync, and more
  than one stored budget is an error rather than an arbitrary pick.
- Change log renames only id fields that resolve to names (account,
  payee, category, group); subtransaction transaction_id stays as is.
- ynab_recent_changes: correct singular for "categories", multi-word
  types, and the "month" type.
- The SQL wrap test now parses the wrapped query.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 18:03:48 -04:00
Ryan Chen 9249f92db8 Fix YNAB mirror review findings
- Full resyncs mark mirror rows missing from the download as deleted;
  YNAB omits deleted entities from non-delta responses, so deletions
  made just before a full sync never reached the mirror.
- YNAB tools return errors as text instead of raising, since ToolNode
  re-raises and would fail the whole chat turn. ynab_sql catches all
  sqlglot errors (e.g. an unbalanced quote) and runs the SQL regenerated
  from the validated tree, which also drops comments that broke the
  LIMIT wrapper. Results keep duplicate column names.
- Sync fetches from YNAB before opening the DB transaction, discards the
  payload if the other container synced meanwhile, re-checks freshness
  under the lock so parallel tool calls share one sync, and backs off
  for 60s after a failure.
- The mirror is pinned to one budget; a changed YNAB_BUDGET_ID errors
  instead of mixing budgets, and an unset id stays on the budget the
  first sync resolved from "last-used".
- Change log: no entries for empty month rollover rows, and id fields
  are logged as names (category, payee, account). ynab_recent_changes
  accepts the names it prints ("assignment") and plurals.
- Outputs sized to the 4000-char tool result cap; ynab_sql reuses the
  shared admin message.
- YNAB API client moves back to utils/ynab_service.py, and the frontend
  tool status labels list the new tools.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 17:58:14 -04:00
Ryan Chen 1cdc380870 Add artifacts table migration
Generated with aerich against a fresh Postgres after applying 1-16.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 17:56:26 -04:00
Ryan Chen 1c1ae9f158 Validate artifact ids before querying; match artifacts by str id
asyncpg raises OperationalError (not ValueError) on malformed UUIDs, so
bad artifact ids now 404 and a bad conversation_id 400s before any query.
get_conversation keys artifacts by str(message_id) so matching doesn't
depend on the driver's UUID types.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 17:56:26 -04:00
Ryan Chen c678bf55bc Stream v2 tool events, save artifacts, enrich conversation payload
stream_query emits the new tool_start/tool_end payloads, maps
on_tool_error to tool_end status=error, and saves an artifact linked to
the tool message. GET /api/conversation/<id> adds tool, label and
artifact to tool messages.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 17:53:22 -04:00
Ryan Chen 0cf6229441 Add Artifact model and owner-scoped artifacts API
GET /api/artifacts?conversation_id= lists ArtifactRefs without data;
GET /api/artifacts/<id> returns the full artifact. Both 404 for other
users. create_artifact_from_output persists ToolArtifact dicts and drops
ones over 256 KB serialised.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 17:53:22 -04:00
Ryan Chen d2f21a6d23 Add tool label registry and SSE v2 tool event shaping
Pure functions build tool_start/tool_end payloads with run_id, friendly
label, truncated input (config dropped), status, duration and artifact ref.
Labels cover every agent tool, both old and #55 YNAB names, and the
homebox_/bookstack_ MCP prefixes, falling back to "Using <tool>…".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 17:53:22 -04:00
ryan 1a73e26f23 Merge pull request 'Frontend: send access token, refresh once on 401' (#59) from fix/access-token-auth into main 2026-10-12 17:49:18 -04:00
ryan 86e1912871 Merge pull request 'Cowork UI (phase 2) plan' (#58) from plan/cowork-ui into main 2026-10-12 17:47:52 -04:00
Ryan Chen abf9415696 Add Cowork UI (phase 2) plan and drop quick-ask window
Defines the shared contract (SSE v2 tool events, artifact kinds and
storage, artifacts API, uploads), five parallel tasks with file
ownership, merge order and verification, and removes the quick-ask
window from the desktop plan.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 17:47:43 -04:00
ryan b77e21474a Merge pull request 'Accept access tokens on protected routes; persist OIDC state' (#57) from fix/access-token-auth-backend into main 2026-10-12 17:44:56 -04:00
Ryan Chen 004a9bc85d Frontend: send access token, refresh once on 401
Collapse fetchWithRefreshToken/fetchWithAuth into a single fetchWithAuth
that sends the access token, refreshes up front when none is stored, and
on a 401 refreshes once (deduplicated across concurrent calls) and retries.
The refresh token is now only sent to /api/user/refresh.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 17:44:34 -04:00
Ryan Chen bc3e3d2797 Accept access tokens on protected routes; persist OIDC state
- Add transitional @jwt_access_required (accepts access tokens, and for now
  also refresh tokens) and use it on every protected route and inside
  admin_required. /api/user/refresh stays refresh-only.
- Move pending OIDC state/PKCE verifiers from an in-process dict to an
  oidc_states table (10 min TTL, single-use) so logins survive a blue/green
  flip. Drop the unused redirect_after_login.
- Unit tests for the decorator and the state store.

Deploy this commit on its own, before the access-token frontend: the old
backend answers access tokens with 422, so the new frontend only works once
this backend is live on both colors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 17:44:34 -04:00
ryan fde8208afe Merge pull request 'Desktop client: exploration doc + Tauri shell spike' (#56) from explore/desktop-client into main 2026-10-12 17:42:25 -04:00
Ryan Chen 6a03f3f9f0 Add Tauri desktop shell spike
desktop/ is a thin Tauri v2 app: a bundled connect page probes
SIMBA_URL/api/health and hands the window to the deployed web app.
Adds a menu-bar icon (Show / New Chat / Reconnect / Quit), Alt+Space
toggle, close-to-hide, single-instance and Dock reopen. The remote
origin gets no IPC. Rust 1.90 is pinned via rust-toolchain.toml.
New make targets: desktop-dev, desktop.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 17:40:50 -04:00
Ryan Chen eea96d9010 Add desktop client exploration doc
Compares PWA, Tauri v2 and Electron shells, recommends a Tauri shell
loading the deployed origin, and lays out backend changes (structured
SSE events, artifacts table, uploads, notifications, local tool bridge,
access-token auth) with a phased plan.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 17:40:49 -04:00
Ryan Chen 8f02318542 Mirror YNAB budget into Postgres with delta sync and change log
YNAB has no event feed, so keep a local copy in ynab_* tables synced via
server_knowledge delta requests: full download on first run, deltas every
YNAB_SYNC_INTERVAL seconds and on demand before each tool call, full
resync daily. Each sync diffs against the mirror and appends real edits
(transactions, assignments, renames) to ynab_changes, the edit history
YNAB doesn't keep.

Agent YNAB tools now answer from the mirror: budget summary (spending
computed from transactions, since YNAB's month activity nets in credit
card payment categories), account balances, transaction search with
splits expanded, spending reports by category/payee/account/month,
category budget, and recent changes. Adds an admin-only read-only
ynab_sql tool guarded by a sqlglot allowlist (scope-resolved tables, no
dangerous functions), a READ ONLY transaction, a timeout, and a row cap.

Replaces utils/ynab_service.py and drops the unused ynab SDK.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 17:32:38 -04:00
ryan 3e2a237eb6 Merge pull request 'Expose Homebox set_item edit tool to admin agent' (#54) from feat/homebox-set-item into main 2026-10-12 17:02:27 -04:00
Ryan Chen b1e210de5d Expose Homebox set_item edit tool to admin agent
Allowlist homebox-mcp's general item editor so the agent can rename items
and edit description, notes, quantity, purchase info, flags and custom
fields. Only passed args change; the rest of the item is preserved.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 17:02:17 -04:00
ryan cdd067fb8e Merge pull request 'Add admin-only BookStack MCP connector' (#53) from feat/bookstack-mcp into main 2026-10-12 16:54:35 -04:00
Ryan Chen 0b8fe7b2df Add admin-only BookStack MCP connector
Load an allowlisted subset of bookstack-mcp tools (search/read plus
create/update books, chapters, pages and comments; no deletes) over stdio
and add them to the admin agent alongside Homebox. Extract the shared
allowlist + is_admin wrapping into utils/mcp_tools.py.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 16:54:01 -04:00
ryan e03c10cde5 Merge pull request 'Add admin-only Homebox MCP connector' (#52) from feat/homebox-mcp into main
Reviewed-on: #52
2026-10-12 16:45:32 -04:00
Ryan Chen 9858e06b73 Add admin-only Homebox MCP connector
Load an allowlisted subset of homebox-mcp tools (read + safe writes, no
deletes) over stdio and compile them into a separate admin agent, so
non-admins never see them. Each tool also re-checks is_admin per call.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 11:01:19 -04:00
Ryan Chen ccc5126ae0 Sample least-recently-fetched friends instead of random
Replaces random.sample with a staleness-ordered pick: friends never
fetched sort first (epoch timestamp), then oldest fetched_at. Ensures
full following list coverage over successive nightly runs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-18 07:15:35 -04:00
Ryan Chen 4d29bb3ca4 Pin llama-server reasoning_effort to low to fix Jinja template error
LangChain was sending reasoning_effort=thinking which Qwen3's chat
template rejects (only xhigh/medium/low are valid). low disables
thinking mode, which is appropriate for agentic tool use and saves tokens.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-18 07:09:11 -04:00
Ryan Chen 64b20754a1 Trim all tool docstrings to reduce token count per request
Removed verbose "Use this tool when the user asks about" bullet lists
from all 31 tools. Each tool now has a 1-2 line description plus compact
arg docs. Saves ~3-5k tokens per request on a 32k context budget.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-18 07:04:26 -04:00
Ryan Chen 6fb9bade08 Trim conversation history to last 12 messages to prevent context overflow
The LangGraph checkpointer replays full history on every request. Without
trimming, long conversations compound past the 32k llama-server slot limit.
12 messages covers ~4-6 turns including tool call/result pairs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-18 06:56:12 -04:00
Ryan Chen 70475865cc Sample up to 3 top picks per friend for friend restaurant queries
Previously ordered by friend_beli_user_id first, returning 15 results
from whoever sorts first. Now fetches top-ranked rows globally and
caps at 3 per friend, giving a diverse cross-friend sample.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-17 22:49:34 -04:00
Ryan Chen 98b4b46b11 Add friends_only filter to beli_search_restaurants tool
Replaces include_friends with friends_only so the LLM can return
exclusively friend-sourced rows when user asks what their friends like,
rather than mixing in own rankings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-17 22:44:28 -04:00
Ryan Chen 834c2c5bfe Drop beli result cap to 15 to stay under 32k context
30 still overflowed when combined with system prompt and conversation history.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-17 22:39:31 -04:00
Ryan Chen b7ac24f2ff Cap beli_search_restaurants results at 30 to prevent context overflow
50k+ token requests were blowing past the 32k llama-server context window
when returning the full ranked list.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-17 22:36:25 -04:00
Ryan Chen 69bf5edefe Add manual friend refresh endpoint and fix category filter guidance
POST /api/beli/profiles/<id>/refresh-friends triggers friend ranking
sampling on demand without waiting for the nightly cron. Also tightens
the beli_search_restaurants docstring so the LLM passes category=restaurant
when the user asks for restaurants, preventing bars/bakeries from appearing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-17 22:29:52 -04:00
Ryan Chen 4c0d161f10 Fix Beli update_or_create ambiguity and friend sampling scope
- Add friend_beli_user_id="" to own ranking lookups to prevent
  MultipleObjectsReturned when friend rows exist for same restaurant
- Move friend sampling out of refresh_profile into _refresh_after_delay
  so manual refreshes stay fast; nightly cron still samples friends

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-17 22:20:08 -04:00
Ryan Chen c24a44618f Demote friend ranking 405s to debug — private profiles are expected
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-17 22:13:02 -04:00
Ryan Chen c183a13db2 Add migration for beli_category column
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-17 22:09:46 -04:00
Ryan Chen c682d2b320 Add beli_category (RES/DES/BAR/BAK) to restaurant records
Store Beli's category code on each ranking so the agent can filter by type
(restaurant/bar/cafe/bakery). Category label shown in tool output.

Requires migration for beli_category column + updated unique constraint.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-17 22:09:09 -04:00
Ryan Chen aa61ec54b9 Add migration for Beli friend ranking columns
Adds friend_beli_user_id (default '') and friend_name to beli_restaurants,
updates unique constraint to include friend_beli_user_id.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-17 20:55:10 -04:00
Ryan Chen b79229a5b0 Add random friend ranking refresh to Beli integration
Each nightly profile refresh now also picks FRIEND_SAMPLE_SIZE (5) random
followed users and fetches their rankings, building up a picture of friends'
lists over time. Rows are tagged with friend_beli_user_id/friend_name so the
agent tool can surface attribution ("via Alex").

Requires migration to add friend_beli_user_id/friend_name columns and update
unique_together on beli_restaurants.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-17 20:52:14 -04:00
Ryan Chen 7d1a89475a Fix Beli API client against actual spec
- Category codes: RES/DES/BAR/BAK (was full English words)
- score field: use 'value' from Score model (was 'score', which doesn't exist)
- rank_index: pass array position (no rank field in response)
- fetch_bookmarks: no query params (user param caused 405)
- notes: correctly None (stored separately, not in ranking results)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-17 20:23:52 -04:00
Ryan Chen 69b8697389 Fix Beli category codes: use RES/DES/BAR/BAK not full words
The API uses 3-letter codes, not full English words. Remove debug logging.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-17 20:21:14 -04:00
Ryan Chen 0367dac3cd Log full JWT claims on auth and refresh to find correct user UUID
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-17 20:15:04 -04:00
Ryan Chen 828fc7d16a Log full Beli ranking response for debugging
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-17 20:12:25 -04:00
Ryan Chen fcadec8bf6 Log raw response keys from Beli ranking endpoint
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-17 20:09:56 -04:00
Ryan Chen 2674a49574 Add debug logging to Beli refresh to inspect API response shape
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-17 20:06:13 -04:00
Ryan Chen 4c55168d52 Make Beli bookmark fetch non-fatal, pass user_id param
Bookmarks returning 400 was aborting the whole refresh and discarding
successfully-fetched ranking data. Now bookmarks log a warning and
continue. Also pass user= to the bookmark endpoint in case it's required.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-17 20:02:13 -04:00