Integrations, 2026-09-01/02: sub-agents, GitHub (per-user MCP), Google Drive¶
Process and results of the two-day integrations push on mit.nonlocally.org. Everything here was measured (a command, a probe, a live click) unless marked unverified. Owner decisions are recorded where they shaped the outcome. Tracking issue: #442.
TL;DR¶
| capability | state | how a user gets it | evidence |
|---|---|---|---|
| Tool calling for every model | fixed | nothing to do | 17/32 models TOOL CALL under FORCED tool_choice (was 13); grok-4.6 and workspace-agent flipped from prose |
Sub-agents (delegate_task) |
live on code-agent, code-reviewer only |
pick one of those presets | two parallel delegations returned 697 and 4096, attributed to sub-agents |
| GitHub (issues, PRs, repo files) as the acting user | live, opt-in per chat | in a chat: integrations icon → Tools → GitHub on; authorize once | get_me → the user's own login; add_issue_comment posted as the user; PRs listed from the real chat UI |
| Google Drive picker | wired; Google shows an unverified-app screen | + → Google Drive → Advanced → continue | client id + API key live, JS origin added; blocked by OpenWebUI's drive.readonly scope (upstream issue filed) |
Stays within OpenWebUI's own features: every change is reachable from the admin UI. The
repo's models.yaml + scripts/sync_*.py are the declarative mirror of the same endpoints.
Process (what was actually done, in order)¶
- Probe before believing. A FORCED-
tool_choiceprobe (scripts/probe_tool_calling.py, run inside the litellm pod) measured every offered model. Only the forced form is deterministic against this proxy;autoflip-flops on unchanged config. - Root cause, in our own code. grok-4.6 "could not call tools" because
k8s/litellm/tool_policy.py, our LiteLLM pre-call hook, strippedtoolsfor any alias whose name did not start with a listed prefix. Not upstream LiteLLM, not the model. Fixed config-driven (#461 → #462): capability now comes frommodel_info.supports_function_callingor the upstream provider, and every strip is logged. Deployed withscripts/deploy_litellm.sh. Record:tests/data/tool_calling_probe.json, pinned to the hook's sha (tests/test_goal_20260901.py). - Sub-agents, safely. OpenWebUI v0.11.2 gates
delegate_taskper model withmeta.builtinTools.subagents, default true, AND a global toggle. So the toggle alone would have enabled delegation on all 37 visible presets (it briefly did; turned off again). Now:models.yamlbuiltin_tools:(defaults deny,codingbundle grants),sync_models.pyrenders/drift-checks it, andscripts/sync_subagents.pyrefuses to flip the toggle while any visible record-backed model lacks the opt-out (#463, #464). E2E verified in the UI. - GitHub as a standard connector. Options researched (shared PAT, login-token pass-through,
community Workspace tools, mcpo + archived stdio server, repo-as-context) and rejected in
favour of GitHub's official remote MCP server (
https://api.githubcopilot.com/mcp/) as an admin External Tool Server with OAuth 2.1 (Static) backed by the org's GitHub Appmit-nonlocally-openwebui(App 4804367): each user authorizes once and acts as themselves; no third-party code in the pod; no tokens pasted into chat. Verified live from the UI and API. Runbook and options memo: #442 comments. - Google Drive. First-party integration:
GOOGLE_DRIVE_CLIENT_ID/GOOGLE_DRIVE_API_KEYvia ExternalSecret (#467), toggle in Admin › Documents, and the OAuth client's missing JavaScript originhttps://mit.nonlocally.orgadded in the Cloud console. - Spec as tests.
tests/test_goal_20260901.pyandtests/test_goal_20260902.pyhold the criteria; still-red items arexfail(strict=True)with the measured reason, never deleted.
Decisions (owner)¶
- Auth pane unchanged: merge-accounts-by-email off, auto-redirect off, LDAP off, keep Google + GitHub login.
- Sub-agents: coding presets only, background mode on.
- GitHub: new GitHub App with per-user OAuth, toolsets issues + pull_requests + repos, and opt-in per chat, not a preset default (a default auto-redirects a user's first new chat to GitHub consent). Coding prompts tell the model how to ask the user to enable it.
- Prefer official/vendor integrations over community plugins or custom code.
How to operate¶
# declared vs live, then apply (all read-only until --apply)
python3 scripts/sync_models.py # presets, builtinTools opt-outs, prompts
python3 scripts/sync_tool_servers.py # external tool servers (github: OAuth material carried from live)
python3 scripts/sync_subagents.py # global toggle; refuses to enable while a visible model could leak
python3 scripts/probe_tool_calling.py --list # the offered-model set the probe covers
sync_models.py --apply (opt-outs) before
sync_subagents.py --apply. GitHub's OAuth client blob is created only by Register Client in
Admin › Integrations; the sync never renders it and fails closed if the server is not registered.
Production detection is scheduled by k8s/model-served-base-check-cronjob.yaml; it runs
sync_models.py in plan-only mode and alerts through the existing CronJob-failure channel only when
newly unresolvable declaration ids appear vs the previous snapshot.
Secrets (all in GCP Secret Manager, never in the repo): openweb-marimo-github-mcp-client-id,
-client-secret, -private-key, openweb-marimo-google-drive-api-key.
Gotchas we hit¶
- OpenWebUI's per-model builtin-tool default is true; a global toggle is never enough.
sync_tool_servers.py --applyreplaces the whole connection list; an undeclared live connection is retired. Declare before you apply.- A LiteLLM fallback can answer for a pinned model:
gpt-5TOOL CALLs were served byclaude-sonnet-4-6while OpenAI had no credits. The probe recordsserved_model. - Base-model records can be deactivated (happened 2026-09-01 20:40Z, unattributed): presets on
them return "Model not found".
sync_models.pyreports them as "not served by any connection". Recovery runbook (admin token):POST /api/v1/models/model/toggle?id=<MODEL_ID>to re-activate each flipped base record (for exampleclaude-sonnet-4-6,claude-haiku-4-5,[local] Qwen3 235B). - Reconciler attribution lines are tagged
[reconciler-audit]: reconciler-made restores/hides areactor=model-health-reconciler, and externally observed flips include OpenWebUIupdated_by*actor hints in the event detail. - Google's Drive picker needs the OAuth client's JavaScript origin, not just redirect URIs;
OpenWebUI requests
drive.readonly(restricted) so an unverified app shows Google's warning and is capped at 100 users. Upstream: open-webui/open-webui#29537. - The Claude Code in-app browser could not render OpenWebUI while its IndexedDB backend was
wedged (
(app)/+layout.svelteawaitsopenDB('Chats')); a desktop-app restart fixed it. Upstream: open-webui/open-webui#29538. tests/goal/spawns a real Kubernetes Job; it is not a local regression suite.
Open¶
- OpenAI account has no credits (6
openai/*routes 429); two strict xfails flip when fixed. #468glm has no backend;#470model_health_reconciler still opens the dead sqlite file.- Google Drive: Google verification of
drive.readonly, or upstream scope change. - PR #469: goal-state 2026-09-02 + the GitHub declaration in
models.yaml.