Skip to content

Model list for the picker #3

Closed
Closed

Description

@Silthus

Part of #1

Question

What can feed the model picker on a function node, and how much does a live model list cost?

The constraint: inputs_schema choice fields carry their choices array baked into the Hog template at build time (nodejs/src/cdp/schema/cyclotron.ts). There is no dynamic or async option source for a plain choice. The only dynamic input type is integration_field, which is coupled to an OAuth integration record.

Establish as fact:

  • What each candidate source offers. GET /api/llm_proxy/models/ (products/ai_observability/backend/api/proxy.py) is session-authenticated and readable by the web frontend, but its ModelInfo carries no reasoning-effort capability. The gateway's own registry (services/llm-gateway/.../model_registry.py) does carry supported_reasoning_levels, but sits behind the privileged llm_gateway:read scope.
  • Whether reasoning effort is uniform across models or varies per model. products/desktop/packages/agent/src/adapters/reasoning-effort.ts implies it varies.
  • What a custom input renderer actually costs: registering in CUSTOM_INPUT_RENDERERS plus adding the type string to four separate unions that are already drifting. Reference implementation: posthog_assignee in products/conversations/frontend/components/Assignee/.
  • Whether the existing LemonUI ModelPicker (products/ai_observability/frontend/ModelPicker.tsx) is reusable inside a Cyclotron input, or is too coupled to its own logic.
  • How other Hog templates handle a list that changes over time, and how often a baked list would actually go stale.

Report findings with file:line references. The picker's shape is decided in the ticket it blocks.

Activity

  1. self-assigned this
    on Aug 6, 2026
  2. Silthus commented on Aug 6, 2026

    @Silthus
    OwnerAuthor

    Research: model list for the picker

    Facts only. Every claim carries a file:line. Where the repo cannot settle a question, it says so.


    1. What each candidate source offers

    GET /api/llm_proxy/models/ — session-readable, no reasoning data, stale default list.

    Registered on the root router (not project-scoped) at products/ai_observability/backend/routes.py:38, mounted at posthog/urls.py:518. The frontend already calls it at products/ai_observability/frontend/modelPickerLogic.ts:216.

    Auth, at products/ai_observability/backend/api/proxy.py:140-142:

    authentication_classes = [SessionAuthentication]
    permission_classes = [IsAuthenticated, PlaygroundAccessPermission]

    Session-only. No personal API key, no project secret key, no scopes. The viewset carries no scope_object deliberately — the docstring at proxy.py:100-101 says it "would imply personal API key support that SessionAuthentication doesn't allow." PlaygroundAccessPermission (proxy.py:94-131) requires llm_playground access at viewer and a set user.current_team (proxy.py:118-121). The models action is exempt from throttling (proxy.py:146-149).

    The payload is a bare JSON array (proxy.py:459-473) of ModelInfo, a TypedDict at products/ai_observability/backend/llm/__init__.py:15-20. Five fields, complete:

    class ModelInfo(TypedDict):
        id: str
        name: str
        provider: str
        description: str
        is_recommended: bool

    No reasoning field. The backend does know which models support thinking — SUPPORTED_MODELS_WITH_THINKING at products/ai_observability/backend/llm/__init__.py:10-12 — but it is only used to validate a completion request (proxy.py:264-266) and is never serialized into the models response. The playground UI consequently hardcodes a flat effort list with no per-model gating (products/ai_observability/frontend/playground/AIObservabilityPlaygroundScene.tsx:588-604).

    Two branches (proxy.py:430-473):

    • No provider_key_id (the default a workflow node would hit): returns get_playground_models() (proxy.py:473), a static Python list assembled from per-provider constants — products/ai_observability/backend/llm/__init__.py:44-53, providers/openai.py:77-85, providers/gemini.py:57-63, providers/anthropic.py:75-95. No cache; it is an in-process constant. It is behind the frontier: the OpenAI playground subset tops out at gpt-5-mini / o4-mini (providers/openai.py:77-85), and no claude-opus-5, claude-sonnet-5, claude-fable-5, gpt-5.5, or gpt-5.6-* appears anywhere in this catalogue.
    • With provider_key_id (BYOK): live call to the customer's own provider (proxy.py:452), cached 60 s (MODELS_CACHE_TIMEOUT_SECONDS = 60, proxy.py:58, used at :456). Requires an LLMProviderKey row on the team (proxy.py:181-188), so it is not available to a team without BYOK configured.

    There is a second, adjacent endpoint worth not confusing it with: LLMModelsViewSet at products/ai_observability/backend/api/models.py:29-34, project-scoped via routes.py:87, scope_object = "evaluation". It also returns provider-listed models with no reasoning data.

    The gateway registry does not carry reasoning levels. The ticket's premise is wrong on this point.

    services/llm-gateway/src/llm_gateway/services/model_registry.py:30-36:

    @dataclass(frozen=True)
    class ModelInfo:
        id: str
        provider: str
        context_window: int
        supports_streaming: bool = True
        supports_vision: bool = False

    No reasoning field. supported_reasoning_levels exists in exactly one line in the whole repo — services/llm-gateway/src/llm_gateway/api/models.py:44, on the API response model ModelObject, defaulted to []:

    # codex-acp required fields (codex-core ModelInfo struct)
    supported_reasoning_levels: list[str] = []

    _build_response() (api/models.py:66-80) constructs each ModelObject from only id/slug/display_name/owned_by/context_window/supports_streaming/supports_vision. supported_reasoning_levels is never populated and always serializes as []. Nothing reads it. So the gateway catalog is not a source of reasoning-effort capability today, whatever its scope.

    Two related corrections to the ticket's framing:

    • The registry is not static: it is derived from litellm's cost map, fetched over the network and cached 300 s (services/llm-gateway/src/llm_gateway/rate_limiting/model_cost_service.py:6-11, :17, :79-109), intersected with configured provider keys (model_registry.py:53-61) and a per-product allowlist (model_registry.py:118-177). The nearest fixed number is the 20-model _POSTHOG_CODE_AGENT_MODELS allowlist at services/llm-gateway/src/llm_gateway/products/config.py:70-93.
    • The models endpoints are not behind llm_gateway:read. GET /v1/models and GET /{product}/v1/models (services/llm-gateway/src/llm_gateway/api/models.py:104-126) declare no auth dependency at all, and create_app() installs no auth middleware (services/llm-gateway/src/llm_gateway/main.py:283-305). Tests call them with no Authorization header and assert 200 (services/llm-gateway/tests/test_models_api.py:149,182,192). The real barrier is network reachability of LLM_GATEWAY_URL, not a scope.

    For completeness on the scope itself, since the ticket asks: llm_gateway is declared at posthog/scopes.py:74 and classified privileged at posthog/scopes.py:232 (PRIVILEGED_SCOPES = frozenset({"llm_gateway:read", "llm_gateway:write"})). It is filtered out of self-serve OAuth registration (posthog/scopes.py:314, :540), flag-gated on personal API keys (posthog/api/personal_api_key.py:49-54) and on project secret keys (posthog/api/project_secret_api_key.py:71-105). A browser session cannot obtain it — Django session auth carries no API scopes.

    posthog/llm/gateway_client.py has no model-listing function. Verified by grep; the file's public surface is client construction. But listing is reachable through the returned OpenAI SDK client, whose base_url is f"{settings.LLM_GATEWAY_URL.rstrip('/')}/{product}/v1" (gateway_client.py:100), so .models.list() hits /{product}/v1/models. products/slack_app does exactly this at products/slack_app/backend/services/llm_models.py:47. Auth to the gateway is the shared settings.LLM_GATEWAY_API_KEY (gateway_client.py:98-105).

    The one place in the repo that already joins a live model list to per-model reasoning efforts is products/slack_app/backend/services/model_catalogue.py:75-99 — available_model_choices() returns ModelChoice(runtime_adapter, model, label, supported_efforts) (:65-72), with efforts from the tasks facade (:96). It has no HTTP surface; it renders into Slack Block Kit. Its live list is cached 30 min (products/slack_app/backend/services/llm_models.py:24).

    Other frontend-readable model lists, all static and hand-maintained:

    Source Nature
    products/posthog_ai/frontend/utils/composerModels.ts:15-19 (COMPOSER_MODELS) + :35-60 (EFFORTS_BY_MODEL) Static TS, Claude-only, comment at :33-34 says it mirrors the backend map
    products/desktop/packages/ui/src/features/loops/loopModels.ts:40-61 Static TS, explicitly a fallback; served catalog wins (:92-101)
    products/desktop/packages/ui/src/features/agent-applications/hooks/useModelCatalog.ts:8-15 Static fallback, visibly stale — no Claude 5, no gpt-5.5/gpt-5.6-*
    products/tasks/backend/temporal/process_task/utils.py:108-174 Static Python, current, and the de-facto canonical map (below)

    There is no generated TypeScript union of model names anywhere — grep -n "gpt-5\|claude-" over products/tasks/frontend/generated/api.schemas.ts returns nothing. The reasoning-effort enum is generated (api.schemas.ts:215-217); the model list is not.


    2. Reasoning effort is per-model, not uniform

    Established. It varies by model, by adapter, and by provider surface, and there is no single canonical registry — the closest thing lives in products/tasks, not in either gateway.

    products/desktop/packages/agent/src/adapters/reasoning-effort.ts is a dispatch shim, not the data. getReasoningEffortOptions(adapter, modelId) at :18-28 returns ReasoningEffortOption[] | null, where null means the model has no reasoning-effort control at all.

    The de-facto canonical table is CLAUDE_REASONING_EFFORTS_BY_MODEL at products/tasks/backend/temporal/process_task/utils.py:108-174:

    model allowed efforts line
    @cf/zai-org/glm-5.2 high, max :112-115
    moonshotai/kimi-k3 () — accepts none :116
    claude-opus-4-5 low, medium, high :117-121
    claude-opus-4-6 low, medium, high, xhigh, max :122-128
    claude-opus-4-7 / 4-8 / claude-opus-5 / claude-fable-5 / claude-sonnet-5 low, medium, high, xhigh, max, ultracode :129-168
    claude-sonnet-4-6 low, medium, high :169-173

    Codex side (utils.py:176-190): base low, medium, high; +xhigh for {"gpt-5.5"}; +xhigh, +max for {"gpt-5.6-sol","gpt-5.6-terra","gpt-5.6-luna"}.

    So the spread is real: one model accepts nothing, one accepts only high, max, others accept three or five or six levels. A single global effort dropdown would offer illegal combinations.

    The repo states this itself, at posthog/temporal/ai/slack_app/activities/classifiers.py:324-340, which deliberately leaves reasoning_effort unconstrained in an LLM schema because "which efforts are valid depends on the model the run finally lands on, not on the catalogue-wide union."

    There are at least five different effort vocabularies in the repo:

    Location Vocabulary
    products/desktop/packages/shared/src/domain-types.ts:36-43 low, medium, high, xhigh, max, ultracode
    products/tasks/.../utils.py:78-86 (ReasoningEffort) off, minimal, low, medium, high, xhigh, max, ultracode
    products/tasks/backend/presentation/serializers.py:62 (Pi) off, minimal, low, medium, high, xhigh, max — no ultracode
    products/ai_observability/frontend/playground/llmPlaygroundPromptsLogic.ts:35 minimal, low, medium, high, null — plus a separate thinking: boolean
    products/signals/backend/scout_harness/model_selection.py:108 low, medium, high, xhigh, max — comment at :105 claims it mirrors the tasks enum; it does not

    products/desktop/packages/api-client/src/generated.ts:4223 also drops ultracode while products/tasks/frontend/generated/api.zod.ts:90 — generated from the same Django schema — keeps it.

    ai_observability is an island: its vocabulary, its boolean thinking flag, and its fixed budget_tokens=4096 (products/ai_observability/backend/llm/providers/anthropic.py:43, :273-274) share nothing with the tasks/desktop model, and its supported-model lists name an older model generation entirely (providers/anthropic.py:83-94, providers/openai.py:89-99).

    Not establishable from this repo: whether the Go PostHog/ai-gateway defines per-model reasoning levels. It is not vendored here (referenced only as an external repo in AGENTS.md:188), and services/llm-gateway/PARITY.md contains zero occurrences of "reasoning", "thinking", or "effort".


    3. What a custom input renderer actually costs

    Registration itself is cheap. CUSTOM_INPUT_RENDERERS at frontend/src/lib/components/CyclotronJob/customInputRenderers.ts:17-38 is a flat Record<string, LazyExoticComponent>; adding an entry is one lazy import. The dispatch is a single default: branch at frontend/src/lib/components/CyclotronJob/CyclotronJobInputs.tsx:707, which falls through to Unknown field type "<type>" when no renderer matches. The component contract is five props (customInputRenderers.ts:8-14): schema, value, onChange, optional sampleGlobalsWithInputs.

    The reference implementation products/conversations/frontend/components/Assignee/CyclotronJobInputAssignee.tsx is 43 lines — it mounts an existing kea logic (assigneeSelectLogic), wraps an existing dropdown, and calls onChange. That is the whole renderer.

    Confirmed relevant: workflow function nodes render through this same shared component — products/workflows/frontend/Workflows/hogflows/steps/components/HogFlowFunctionConfiguration.tsx:8,155 imports lib/components/CyclotronJob/CyclotronJobInputs. So a CUSTOM_INPUT_RENDERERS entry does reach a function node.

    The cost is the type-string unions. There are five hand-maintained ones, not four.

    # File:line Values
    1 nodejs/src/cdp/schema/cyclotron.ts:14-31 (zod enum) 16
    2 nodejs/src/cdp/types.ts:441-459 (TS union) 18
    3 frontend/src/types.ts:7106-7124 (TS union) 17
    4 posthog/cdp/validation.py:304-322 (DRF ChoiceField) 17
    5 products/workflows/frontend/Workflows/hogflows/steps/types.ts:76-94 (zod enum) 17

    The ~30 further occurrences in products/workflows/frontend/Workflows/workflowLogic.ts, hogflows/hogFlowEditorLogic.tsx, steps/stepDelayLogic.ts, and steps/stepWaitUntilTimeWindowLogic.ts are kea-typegen output (alphabetized, inlined) derived from #5. They regenerate; they are not hand-edited.

    How badly they have drifted — measured:

    type cyclotron.ts nodejs types.ts frontend types.ts validation.py workflows types.ts
    push_subscription ✅ :29 ✅ :456 ❌ ❌ ❌
    non_failure_status_codes ❌ ✅ :457 ✅ :7122 ✅ :320 ✅ :91
    all 16 others ✅ ✅ ✅ ✅ ✅

    Only nodejs/src/cdp/types.ts is complete. Two of the eighteen types — 11 % — are missing from at least one union. nodejs/src/cdp/types.ts:440 carries the comment // Mostly copied from frontend types, which is the drift mechanism stated in the source.

    The drift has real consequences today:

    • push_subscription passes the Node zod schema (cyclotron.ts:29) and is handled at runtime (nodejs/src/cdp/services/hog-executor.service.ts:377,389, hog-inputs.service.ts:133), but is rejected by the Django serializer (absent from validation.py:304-322) and would render as Unknown field type in the frontend (CyclotronJobInputs.tsx:722-724). It survives only because no shipped template uses it — grep for type: 'push_subscription' under nodejs/src/cdp/templates/ returns nothing; it appears only in tests (hog-inputs.service.test.ts:306, push-notification.service.test.ts:31).
    • non_failure_status_codes is missing from the Node zod schema while being fully implemented everywhere else, including a runtime read at nodejs/src/cdp/services/hog-executor-async.service.ts:425.

    A second drift axis: the optional searchable flag exists in nodejs/src/cdp/types.ts, frontend/src/types.ts, and posthog/cdp/validation.py:328-329, but not in either zod schema (cyclotron.ts, workflows/.../steps/types.ts) — grep count is 0 in both.

    Generated artifacts that must be regenerated after touching validation.py (12 files carry the enum):

    • products/cdp/frontend/generated/api.schemas.ts, api.zod.ts
    • products/desktop/packages/api-client/src/generated.ts
    • services/mcp/src/api/generated.ts, services/mcp/src/generated/cdp_functions/api.ts, services/mcp/src/generated/error_tracking_alerts/api.ts
    • 5 MCP tool-schema snapshots under services/mcp/tests/unit/__snapshots__/tool-schemas/

    hogli build:openapi covers the generated TS; the MCP snapshots update with the MCP test suite.

    Backend value validation is a sixth touchpoint. posthog/cdp/validation.py validates the input value by type in a long elif chain (:390-436) and separately decides whether the value gets Hog-transpiled via a hardcoded list at :445-455. A new type that is a plain string needs no entry (it falls through), but one that is a dict or list does. posthog/cdp/test/test_validation.py:750-801 shows the expected test shape, including the regression note at :796-801 about the InputsItemSerializer opt-in.

    Total for a new type, floor: 5 union edits + 1 registry entry + 1 component + 1 generated-types run + validation tests. The unions are one-line edits each; the work is in knowing all five exist, which is exactly the failure mode the current 11 % drift demonstrates.


    4. Is ModelPicker reusable inside a Cyclotron input?

    Yes. It is fully presentational and takes its data as a prop.

    products/ai_observability/frontend/ModelPicker.tsx is 222 lines. Its props (:27-37) are model, selectedProviderKeyId, onSelect, groups: ProviderModelGroup[], loading, footerLink, placeholder, selectedModelName, data-attr. It mounts no kea logic — it holds two useState hooks for search and expand state (:77-78) and imports only LemonMenu/LemonButton/LemonInput/LemonSkeleton/Link, LLMProviderIcon, and two types. The only non-prop coupling is a settings link constant PROVIDER_SETTINGS_URL (:14), used solely through the optional footerLink prop — pass footerLink={null} and it drops out.

    Three existing call sites already treat it this way, each supplying its own data: evaluations/AIObservabilityEvaluation.tsx:791, playground/AIObservabilityPlaygroundScene.tsx:501, tags/AIObservabilityTag.tsx:135.

    The coupling lives in the logic, not the component. products/ai_observability/frontend/modelPickerLogic.ts:162-169 connects llmProviderKeysLogic for BYOK keys, and its loader (:170-216) branches on provider keys before falling back to /api/llm_proxy/models/. A workflows renderer would not reuse that logic; it would build ProviderModelGroup[] itself. The helper buildPlaygroundProviderModelGroups(models) at modelPickerLogic.ts:34-59 does exactly that shaping from a flat ModelOption[] and is already exported — but note it drops any model whose provider does not map through toLLMProvider (:46-48), so a gateway-sourced list with providers outside LLMProvider would silently lose entries.

    One caveat: ModelPicker has no affordance for reasoning effort. ModelOption (modelPickerLogic.ts:17-24) is id, name, provider, description, providerKeyId?, isRecommended?. A per-model effort control would be a sibling component, not a prop on this one — which is how the playground does it (AIObservabilityPlaygroundScene.tsx:588-604 renders effort separately) and how the composer does it (products/posthog_ai/frontend/components/composer/ComposerModelEffortPickers.tsx:15,65).


    5. How other Hog templates handle a drifting list, and how stale a baked list gets

    No Hog template anywhere in the repo lists LLM model names. Grepped nodejs/src/cdp/templates/ and posthog/cdp/templates/ for gpt-, claude-, gemini-, and key: 'model' — zero hits. There is no precedent to copy.

    There are 22 type: 'choice' inputs across the TS templates and 13 across the Python ones. The vendor-owned examples:

    Template Line List owner Size
    nodejs/src/cdp/templates/_destinations/whatsapp/whatsapp.template.ts:156 template_language Meta approved locales 71 baked entries, searchable: true
    posthog/cdp/templates/meta_ads/template_meta_ads.py:129 actionSource Meta Conversions API 9
    nodejs/src/cdp/templates/_destinations/openai_ads/openai.template.ts:58 eventType OpenAI Ads standard events 13
    nodejs/src/cdp/templates/_destinations/snapchat_ads/snapchat.template.ts:36 actionSource Snap Conversions API 3
    nodejs/src/cdp/templates/_destinations/google_sheets/google_sheets.template.ts:79 data_format Google Sheets ValueInputOption 2

    All literal arrays. No runtime resolution of any kind.

    The update path is: code edit → deploy → a startup task re-syncs the template row. Collection at posthog/management/commands/sync_hog_function_templates.py:74-121, write via sync_template_to_db (posthog/cdp/templates/hog_function_template.py:79-90), queued once per deploy behind a Redis setnx lock (products/cdp/backend/tasks/hog_functions.py:124-137, from posthog/apps.py:152-157). The frontend reads inputs_schema from the DB at request time via PublicHogFunctionTemplateViewSet (products/cdp/backend/api/hog_function_template.py:88-98, routed at products/cdp/backend/routes.py:64-69), called from frontend/src/lib/api.ts:705.

    Template versioning exists but is vestigial. products/cdp/backend/models/hog_function_template.py:22-23,63 has template_id + sha with unique_together, and _generate_sha_from_content (:74-97) hashes inputs_schema. But sync_template_to_db (hog_function_template.py:82-88) passes the existing latest row as the serializer instance, so the sync mutates in place rather than appending a version. get_template accepts a sha (:99-128) but nothing passes one. The Node manager carries the comment "Currently this essentially loads the 'latest' template each time. We may need to swap this to using a specific version" (nodejs/src/cdp/services/managers/hog-function-template-manager.service.ts:41). No -v2-style template ids exist.

    Already-configured functions are pinned and never receive updated choices. HogFunction has its own inputs_schema JSON column (products/cdp/backend/models/hog_functions/hog_function.py:129), copied from the template once at creation:

    # products/cdp/backend/api/hog_function.py:513
    data["inputs_schema"] = data.get("inputs_schema") or template.inputs_schema

    Nothing refreshes it. refresh_hog_functions.py only recompiles filters; resave_hog_functions.py only touches integration inputs; no code path re-reads template.inputs_schema into an existing row. The frontend confirms the pinning — the template loader short-circuits to null when editing by id (frontend/src/scenes/hog-functions/configuration/hogFunctionConfigurationLogic.tsx:980-982), and case 'choice' feeds the stored schema.choices ?? [] into the select (frontend/src/lib/components/CyclotronJob/CyclotronJobInputs.tsx:617-637).

    The repo's actual remediation mechanism is a hand-written string-replace management command: posthog/management/commands/update_hog_function_code.py, a hardcoded replaceOptions dict of one-off migrations (LinkedIn 202409 → 202508 at :47-54, Meta v21.0 → v25.0 at :55-61, Google Ads v19–v23 → v24 at :71-77). Its own comment states the problem: "Destinations carry whatever version the template pinned at creation" (:64-70) and "functions created earlier keep their frozen code and reject the new URL" (:80-82). It rewrites hog code only, not inputs_schema — there is no precedent for backfilling a stale choices array into existing functions at all.

    There is no dynamic choice. choices is a static z.array({value, label}) at nodejs/src/cdp/schema/cyclotron.ts:33-42, mirrored at frontend/src/types.ts:7112 and posthog/cdp/validation.py:328-329. No URL, no resolver, no callback.

    integration_field escapes the choice system rather than extending it: the frontend resolves it through a hardcoded if-chain mapping field names to bespoke pickers (frontend/src/lib/components/CyclotronJobInputIntegrationField.tsx:101-180), each hitting a dedicated @action on the integration viewset (posthog/api/integration.py:1244, :1337, :1478, :1603, and ~20 more). It requires a credentialed integration row — it bails with "Configure X to continue" when none is selected (CyclotronJobInputIntegrationField.tsx:92-97). Nothing else piggybacks on it. CUSTOM_INPUT_RENDERERS is the only precedent for a dynamically-populated picker that needs no OAuth integration.

    How stale a baked list actually gets — measured both ways.

    Vendor choice arrays in this repo are written once and effectively never maintained:

    • posthog/cdp/templates/meta_ads/template_meta_ads.py — 18 commits to the file. git log -L 127,172 over the actionSource choices returns exactly one: 3208def7ab1, 2024-10-14. Untouched for ~22 months while the file was edited 17 more times.
    • nodejs/src/cdp/templates/_destinations/whatsapp/whatsapp.template.ts — 1 commit ever (c7840acc2d0, 2026-05-27). The 71-entry locale list has never been revised.
    • nodejs/src/cdp/templates/_destinations/snapchat_ads/snapchat.template.ts — 8 commits, none touching the choices.

    Against that, PostHog's own model-and-effort list churns roughly once every ten days. git log -L 108,174 over CLAUDE_REASONING_EFFORTS_BY_MODEL in products/tasks/backend/temporal/process_task/utils.py returns 11 commits between 2026-04-15 and 2026-07-29:

    2026-07-29 bd1eada9ae9 fix(tasks): register Kimi without effort levels (#74627)
    2026-07-27 4e3773eb965 feat(tasks): Accept context window and fast mode on runs (#73803)
    2026-07-24 5d8b336b652 feat(llm-gateway): add claude opus 5 (#73618)
    2026-07-21 c82cafa4dab fix(tasks): allow GLM reasoning levels (#72549)
    2026-07-14 a41c01928bd feat(self-driving): ReviewHog (#64651)
    2026-07-02 465f56e0b00 fix(tasks): support claude-sonnet-5 reasoning effort (#67681)
    2026-06-11 5a88ba43c04 fix(tasks): allow reasoning effort for claude-fable-5 runs (#62949)
    2026-05-28 81bb14740e1 feat(llma): add claude-opus-4-8 to ai gateway (#60507)
    2026-04-28 c39208134f2 feat(tasks): add xhigh reasoning effort for opus 4.6 and 4.7 (#56767)
    2026-04-17 04b03a066be feat: add claude opus 4.7 reasoning efforts (#55129)
    2026-04-15 dece1e9bd9d feat(cloud-agent): codex support (#54462)
    

    That is the quantified answer: a baked model list would need ~11 template edits a year, and each edit would reach only newly-created nodes. Every workflow already configured keeps the list it was born with, permanently, with no backfill mechanism in the repo.


    Facts that could not be established from this repo

    • Whether the Go PostHog/ai-gateway defines a per-model reasoning-level list, under what field name. Not vendored; PARITY.md does not mention reasoning at all.
    • The auth, scope, and payload of GET /api/projects/:team_id/agent_applications/models/. The agent_platform Django app is not in this repo — products/desktop/packages/api-client/src/posthog-client.ts:5610-5612 says so explicitly.
    • Whether the sha column on HogFunctionTemplate was ever used for real multi-version pinning historically. The model supports it; no caller passes one today.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions