Skip to content

feat: add captureAi on the capture V1 AI endpoint - #939

Merged
turnipdabeets merged 20 commits into
feat/v4-capture-v1from
feat/v4-capture-ai
Oct 28, 2026
Merged

turnipdabeets merged 20 commits into
feat/v4-capture-v1from
feat/v4-capture-ai

Conversation

@turnipdabeets

@turnipdabeets turnipdabeets commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

💡 Motivation and Context

Adds captureAi(...), which sends AI observability events ($ai_generation, $ai_span, $ai_trace, $ai_embedding, ...) to the capture V1 AI endpoint (POST /i/v1/ai/events). That endpoint accepts events up to 8 MiB. It follows the capture_ai lanes in posthog-rs (PostHog/posthog-rs#250), posthog-go (PostHog/posthog-go#323) and posthog-python (PostHog/posthog-python#1017, #1022), and Ingestion's goal of shipping captureAi with every V1 release. Stacked on #938.

  • captureAi takes the same arguments as capture and goes through the same enrichment and beforeSend. Only the delivery route differs. capture never reroutes $ai_* events.
  • It uses the capture V1 transport from feat!: send events through capture v1 and add captureAi #938: Bearer auth, V1 headers, per-event results, retry identity, same-origin redirects and the Retry-After cap. Retry identity is now tracked per endpoint, so the two lanes don't reset each other's attempt numbers. A 404 is terminal for AI: there is no /batch fallback.
  • AI events go to their own disk queue (posthog.aiFolder). It is flushed by flush() and close(), and created with the other queues so events left on disk by a previous launch still send.
  • captureAi returns the queued event's UUID (@discardableResult), or nil when the event isn't queued: opted out, disabled, dropped by beforeSend, or over the size limit.
  • New PostHogConfig settings tune the AI lane separately from other events:
    • captureAiCompression (default .gzip): compression for AI request bodies, independent of config.compression.
    • captureAiMaxQueueSize (default 100): events kept in the AI disk queue.
    • captureAiTimeout (default 60 s): AI request timeout without network activity, instead of 10 s.
    • captureAiMaxEventBytes (default 8 MiB + 64 KiB): largest serialized AI event. It can only lower the limit: a larger value is clamped to the default with a debug warning.
    • A value of 0 or less for the numeric settings uses the default.
  • The queue batches by bytes as well as count, with a 5 MiB soft limit (new internal QueueEndpoint.maxBatchBytes). It drops an event over captureAiMaxEventBytes when it is queued (new internal QueueEndpoint.maxRecordBytes), logging only the event name and size. The queue also flushes as soon as it holds 5 MiB, so large events are sent before the disk cap evicts them, and a flush reads about one batch from disk at a time.
  • The queue keeps at most captureAiMaxQueueSize events and 50 MiB on disk, dropping the oldest first (new internal QueueEndpoint.maxQueueBytes, enforced in PostHogFileBackedQueue on add and on load). Without the byte cap, 100 events at the size limit could take ~800 MiB.
  • With debug on, each event a capture V1 2xx drops (analytics or AI) is logged with its name, UUID and the server's details, e.g. ai_event_too_big, misrouted_event or ai_byte_rate_limited. Never its properties.
  • The compliance adapter adds POST /capture_ai and advertises capture_ai_v1.

Differences from the spec and the other SDKs

The capture-ai spec only applies to server SDKs. Where iOS differs:

  • No uuid argument: iOS capture doesn't take one either. captureAi does return the UUID, like the spec.
  • Queue created at setup, not lazily: an offline mobile queue has to send events persisted by an earlier launch.
  • Queue limit is 100 events (captureAiMaxQueueSize) and 50 MiB, not 1000 events: keeps the disk use of multi-MB events down on devices.
  • Gzip by default, via captureAiCompression: independent of config.compression. The server SDKs send AI requests uncompressed by default.

💚 How did you test it?

  • New Swift Testing suite "Capture V1 AI lane", using MockPostHogServer's new /i/v1/ai/events stub:
    • routing by method, including beforeSend and options
    • a 404 is terminal, with no /batch request
    • an oversized event is dropped when queued, with a single size log line
    • captureAiMaxEventBytes above the ceiling is clamped and warns
    • requests split at the byte limit
    • captureAi returns the sent event's UUID, and nil for an event that isn't queued
    • the queue flushes once a batch's worth of bytes is queued, before flushAt
  • "Capture V1 transport": each dropped event is logged with its name, UUID and details.
  • PostHogFileBackedQueue tests for the byte cap (evicts the oldest, keeps an oversized newest record, trims on load) and for a byte-bounded read.
  • Removing the 404 path guard or the byte limit makes the matching test fail.
  • CI passes after rebasing onto main, including test-ios-simulator, test-no-traits and Public API snapshot.
  • Locally, make lint passes, and make test on the top of the stack (feat!: remove the /batch fallback from capture V1 #940) ran 1185 tests. The only failures are the 13 known group-container issues, which also fail on the base branch locally.
  • SDK compliance harness 1.14.0 in CI, capture_ai_v1 suite: 97/101. The 4 failures:
    • capture_ai_keeps_supplied_uuid: captureAi doesn't take a UUID, like capture. The test aborts before flushing, so its request lands in the next test and fails event_has_required_root_fields_batch too.
    • max_retries_respected and unset_options_omitted: the same mobile failures as in the analytics suite.
  • Not done: docs for captureAi on posthog.com / LLM analytics.

📝 Checklist

  • I reviewed the submitted code.
  • I added tests to verify the changes.
  • I updated the docs if needed.
  • No breaking change or entry added to the changelog.

If releasing new changes

  • Ran pnpm changeset to generate a changeset file

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

Built with Claude Code. The design follows the sdk-specs capture-ai spec, the V1 AI route in rust/capture/src/v1, and the posthog-rs, posthog-go and posthog-python AI lanes. Maintainer decisions: a separate AI lane with a small disk queue and no /batch fallback, PR'd separately from #938.

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

posthog-ios Compliance Report

Date: 2026-10-28T15:21:54.214992+00:00
Duration: 913677ms

⚠️ Some Tests Failed

216/219 tests passed, 3 failed


Capture_V1 Tests

⚠️ 99/101 tests passed, 2 failed

View Details
Test Status Duration
Endpoint And Method.Targets V1 Endpoint ✅ 2892ms
Endpoint And Method.Does Not Use Legacy Endpoints ✅ 412ms
Required Headers.Has Authorization Bearer Header ✅ 2841ms
Required Headers.Has Content Type Json ✅ 2835ms
Required Headers.Has Posthog Sdk Info Format ✅ 2773ms
Required Headers.Has Posthog Attempt Header ✅ 2825ms
Required Headers.Has Posthog Request Id ✅ 301ms
Required Headers.Has Posthog Request Timestamp ✅ 2797ms
Required Headers.Has User Agent ✅ 2807ms
Body Format.Body Has Created At And Batch ✅ 2797ms
Body Format.No Api Key In Body ✅ 2767ms
Body Format.No Sent At In Body ✅ 2969ms
Event Format.Event Has Required Root Fields ✅ 2747ms
Event Format.Event Uuid Is Valid ✅ 457ms
Event Format.Event Timestamp Is Rfc3339 ✅ 2793ms
Event Format.Non Utc Event Timestamp Is Converted To Utc ✅ 2753ms
Event Format.Distinct Id Is String ✅ 2673ms
Event Format.Distinct Id At Root Not Properties ✅ 2802ms
Event Format.Custom Properties Preserved ✅ 2834ms
Event Format.Set Properties Preserved ✅ 2709ms
Event Format.Set Once Properties Preserved ✅ 2813ms
Event Format.Groups Properties Preserved ✅ 419ms
Event Format.Sdk Generates Uuid If Not Provided ✅ 482ms
Event Format.Event Has Required Root Fields Batch ✅ 3312ms
Event Format.Event Uuid Is Valid Batch ✅ 2809ms
Event Format.Event Timestamp Is Rfc3339 Batch ✅ 2777ms
Event Format.Distinct Id Is String Batch ✅ 2955ms
Event Format.Distinct Id At Root Not Properties Batch ✅ 3421ms
Event Format.Custom Properties Preserved Batch ✅ 2796ms
Event Format.Set Properties Preserved Batch ✅ 2890ms
Event Format.Set Once Properties Preserved Batch ✅ 2820ms
Event Format.Groups Properties Preserved Batch ✅ 2764ms
Event Format.Sdk Generates Uuid If Not Provided Batch ✅ 2936ms
Batch Behavior.Multiple Events In Single Batch ✅ 2775ms
Batch Behavior.Batch Envelope Smoke ✅ 387ms
Batch Behavior.Flush With No Events Sends Nothing ✅ 248ms
Batch Behavior.Flush At Triggers Batch ✅ 1186ms
Batch Behavior.Created At Reflects Batch Creation Time ✅ 2780ms
Deduplication.Generates Unique Uuids ✅ 3158ms
Deduplication.Different Events Same Content Different Uuids ✅ 2906ms
Deduplication.Preserves Uuid On Retry ✅ 9165ms
Deduplication.Preserves Timestamp On Retry ✅ 9141ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry ✅ 7904ms
Deduplication.No Duplicate Events In Batch ✅ 2961ms
Header Behavior On Retry.Attempt Header Starts At One ✅ 2752ms
Header Behavior On Retry.Attempt Header Increments On Retry ✅ 16314ms
Header Behavior On Retry.Request Id Preserved On Retry ✅ 9166ms
Header Behavior On Retry.Different Requests Have Different Request Ids ✅ 4895ms
Header Behavior On Retry.Request Timestamp Changes On Retry ✅ 9267ms
Response Format Validation.Success Response Has Uuid Keyed Results ✅ 2760ms
Response Format Validation.Success Response Has Ok For Each Event ✅ 2925ms
Response Format Validation.Success No Retry After When All Ok ✅ 2842ms
Response Format Validation.Success Retry After Present When Retry Events ✅ 2956ms
Response Format Validation.Success No Retry After When Drop Only ✅ 2898ms
Response Format Validation.Response Echoes Request Id ✅ 350ms
Retry Behavior.Retries On 408 ✅ 9270ms
Retry Behavior.Retries On 500 ✅ 9164ms
Retry Behavior.Retries On 503 ✅ 11377ms
Retry Behavior.Retries On 504 ✅ 9222ms
Retry Behavior.Retryable Errors Have Retry After ✅ 6333ms
Retry Behavior.Respects Retry After On Retryable Error ✅ 10788ms
Retry Behavior.Does Not Retry On 400 ✅ 4816ms
Retry Behavior.Does Not Retry On 401 ✅ 4752ms
Retry Behavior.Does Not Retry On 402 ✅ 4779ms
Retry Behavior.Does Not Retry On 413 ✅ 4819ms
Retry Behavior.Does Not Retry On 415 ✅ 4722ms
Retry Behavior.Non Retryable Errors Have No Retry After ✅ 5131ms
Retry Behavior.Implements Backoff ✅ 15341ms
Retry Behavior.Max Retries Respected ❌ 21295ms
Partial Batch Handling.Handles 200 Full Success ✅ 4688ms
Partial Batch Handling.Handles 200 With All Ok ✅ 5902ms
Partial Batch Handling.Does Not Retry Dropped Events ✅ 5698ms
Partial Batch Handling.Does Not Retry Limited Events ✅ 5791ms
Partial Batch Handling.Prunes Ok Events On Partial Retry ✅ 7765ms
Partial Batch Handling.Prunes Dropped Events On Partial Retry ✅ 7747ms
Partial Batch Handling.Retries Only Retry Events From Partial ✅ 7808ms
Partial Batch Handling.Partial Retry Preserves Uuids ✅ 7756ms
Partial Batch Handling.Partial Retry Attempt Header Increments ✅ 7668ms
Partial Batch Handling.Partial Retry Request Id Preserved ✅ 7733ms
Partial Batch Handling.Respects Retry After On Partial ✅ 7765ms
Partial Batch Handling.Unknown Result Treated As Terminal ✅ 5798ms
Partial Batch Handling.Mixed Ok Drop Limited No Retry ✅ 5894ms
Compression.Sends Gzip Content Encoding ✅ 2675ms
Compression.No Content Encoding When Disabled ✅ 2749ms
Compression.Compressed Body Is Decompressible ✅ 314ms
Error Handling.Does Not Retry On Unknown 4Xx ✅ 2243ms
Event Options.Cookieless Mode Override ✅ 303ms
Event Options.Disable Skew Correction Override ✅ 2787ms
Event Options.Process Person Profile Override ✅ 2734ms
Event Options.Product Tour Id Override ✅ 2745ms
Event Options.Unset Options Omitted ✅ 2756ms
Event Options.Options Override In Batch ✅ 2766ms
Event Options.Unknown Option Passes Through ✅ 357ms
Event Options.Option Value Is Not Converted ✅ 2664ms
Event Options.Lenient Bool Option Values Are Not Converted ✅ 2642ms
Event Options.Option Wins Over Legacy Property ✅ 2704ms
Event Options.Legacy Property Fills Unset Option ✅ 2735ms
Event Options.Null Option Falls Back To Legacy Property ✅ 2684ms
Geoip And Historical Migration.Geoip Disable Injected Into Properties ✅ 2621ms
Geoip And Historical Migration.Historical Migration Set In Body ❌ 2773ms
Geoip And Historical Migration.Historical Migration Absent By Default ✅ 2621ms

Failures

retry_behavior.max_retries_respected

Expected 4 requests, got 5

geoip_and_historical_migration.historical_migration_set_in_body

Body missing 'historical_migration' field. Body keys: ['batch', 'created_at']

Capture_Ai_V1 Tests

⚠️ 100/101 tests passed, 1 failed

View Details
Test Status Duration
Endpoint And Method.Targets V1 Endpoint ✅ 2756ms
Endpoint And Method.Does Not Use Legacy Endpoints ✅ 393ms
Endpoint And Method.Capture Does Not Reroute Ai Named Events ✅ 2771ms
Required Headers.Has Authorization Bearer Header ✅ 2786ms
Required Headers.Has Content Type Json ✅ 2776ms
Required Headers.Has Posthog Sdk Info Format ✅ 2741ms
Required Headers.Has Posthog Attempt Header ✅ 2667ms
Required Headers.Has Posthog Request Id ✅ 390ms
Required Headers.Has Posthog Request Timestamp ✅ 2683ms
Required Headers.Has User Agent ✅ 2794ms
Body Format.Body Has Created At And Batch ✅ 287ms
Body Format.No Api Key In Body ✅ 2711ms
Body Format.No Sent At In Body ✅ 2735ms
Event Format.Event Has Required Root Fields ✅ 2731ms
Event Format.Event Uuid Is Valid ✅ 2637ms
Event Format.Event Timestamp Is Rfc3339 ✅ 2746ms
Event Format.Non Utc Event Timestamp Is Converted To Utc ✅ 362ms
Event Format.Distinct Id Is String ✅ 2711ms
Event Format.Distinct Id At Root Not Properties ✅ 2813ms
Event Format.Custom Properties Preserved ✅ 2754ms
Event Format.Set Properties Preserved ✅ 366ms
Event Format.Set Once Properties Preserved ✅ 2712ms
Event Format.Groups Properties Preserved ✅ 2625ms
Event Format.Sdk Generates Uuid If Not Provided ✅ 347ms
Event Format.Capture Ai Keeps Supplied Uuid ❌ 114ms
Event Format.Event Has Required Root Fields Batch ✅ 2816ms
Event Format.Event Uuid Is Valid Batch ✅ 2698ms
Event Format.Event Timestamp Is Rfc3339 Batch ✅ 426ms
Event Format.Distinct Id Is String Batch ✅ 2682ms
Event Format.Distinct Id At Root Not Properties Batch ✅ 2757ms
Event Format.Custom Properties Preserved Batch ✅ 334ms
Event Format.Set Properties Preserved Batch ✅ 2799ms
Event Format.Set Once Properties Preserved Batch ✅ 2833ms
Event Format.Groups Properties Preserved Batch ✅ 2768ms
Event Format.Sdk Generates Uuid If Not Provided Batch ✅ 2801ms
Batch Behavior.Multiple Events In Single Batch ✅ 2845ms
Batch Behavior.Batch Envelope Smoke ✅ 2807ms
Batch Behavior.Flush At Triggers Batch ✅ 1150ms
Batch Behavior.Created At Reflects Batch Creation Time ✅ 2628ms
Deduplication.Generates Unique Uuids ✅ 2901ms
Deduplication.Different Events Same Content Different Uuids ✅ 2775ms
Deduplication.Preserves Uuid On Retry ✅ 9299ms
Deduplication.Preserves Timestamp On Retry ✅ 5321ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry ✅ 7711ms
Deduplication.No Duplicate Events In Batch ✅ 2809ms
Header Behavior On Retry.Attempt Header Starts At One ✅ 2685ms
Header Behavior On Retry.Attempt Header Increments On Retry ✅ 16221ms
Header Behavior On Retry.Request Id Preserved On Retry ✅ 9290ms
Header Behavior On Retry.Different Requests Have Different Request Ids ✅ 7480ms
Header Behavior On Retry.Request Timestamp Changes On Retry ✅ 8769ms
Response Format Validation.Success Response Has Uuid Keyed Results ✅ 2753ms
Response Format Validation.Success Response Has Ok For Each Event ✅ 2790ms
Response Format Validation.Success No Retry After When All Ok ✅ 2833ms
Response Format Validation.Success Retry After Present When Retry Events ✅ 2722ms
Response Format Validation.Success No Retry After When Drop Only ✅ 2902ms
Response Format Validation.Response Echoes Request Id ✅ 2750ms
Retry Behavior.Retries On 408 ✅ 9267ms
Retry Behavior.Retries On 500 ✅ 9408ms
Retry Behavior.Retries On 503 ✅ 10705ms
Retry Behavior.Retries On 504 ✅ 8778ms
Retry Behavior.Retryable Errors Have Retry After ✅ 6218ms
Retry Behavior.Respects Retry After On Retryable Error ✅ 10788ms
Retry Behavior.Does Not Retry On 400 ✅ 4759ms
Retry Behavior.Does Not Retry On 401 ✅ 4721ms
Retry Behavior.Does Not Retry On 402 ✅ 4813ms
Retry Behavior.Does Not Retry On 413 ✅ 4763ms
Retry Behavior.Does Not Retry On 415 ✅ 2453ms
Retry Behavior.Non Retryable Errors Have No Retry After ✅ 4833ms
Retry Behavior.Implements Backoff ✅ 21704ms
Retry Behavior.Max Retries Respected ✅ 15350ms
Partial Batch Handling.Handles 200 Full Success ✅ 4822ms
Partial Batch Handling.Handles 200 With All Ok ✅ 5850ms
Partial Batch Handling.Does Not Retry Dropped Events ✅ 5890ms
Partial Batch Handling.Does Not Retry Limited Events ✅ 5869ms
Partial Batch Handling.Prunes Ok Events On Partial Retry ✅ 7772ms
Partial Batch Handling.Prunes Dropped Events On Partial Retry ✅ 7799ms
Partial Batch Handling.Retries Only Retry Events From Partial ✅ 7699ms
Partial Batch Handling.Partial Retry Preserves Uuids ✅ 7877ms
Partial Batch Handling.Partial Retry Attempt Header Increments ✅ 7893ms
Partial Batch Handling.Partial Retry Request Id Preserved ✅ 7870ms
Partial Batch Handling.Respects Retry After On Partial ✅ 7837ms
Partial Batch Handling.Unknown Result Treated As Terminal ✅ 5893ms
Partial Batch Handling.Mixed Ok Drop Limited No Retry ✅ 5832ms
Compression.Sends Gzip Content Encoding ✅ 2824ms
Compression.No Content Encoding When Disabled ✅ 2817ms
Compression.Compressed Body Is Decompressible ✅ 2795ms
Error Handling.Does Not Retry On Unknown 4Xx ✅ 4838ms
Event Options.Cookieless Mode Override ✅ 298ms
Event Options.Disable Skew Correction Override ✅ 2833ms
Event Options.Process Person Profile Override ✅ 2767ms
Event Options.Product Tour Id Override ✅ 2798ms
Event Options.Unset Options Omitted ✅ 2765ms
Event Options.Options Override In Batch ✅ 2835ms
Event Options.Unknown Option Passes Through ✅ 2815ms
Event Options.Option Value Is Not Converted ✅ 2705ms
Event Options.Lenient Bool Option Values Are Not Converted ✅ 277ms
Event Options.Option Wins Over Legacy Property ✅ 2629ms
Event Options.Legacy Property Fills Unset Option ✅ 2845ms
Event Options.Null Option Falls Back To Legacy Property ✅ 2755ms
Geoip And Historical Migration.Geoip Disable Injected Into Properties ✅ 2871ms
Geoip And Historical Migration.Historical Migration Absent By Default ✅ 410ms

Failures

event_format.capture_ai_keeps_supplied_uuid

Expected last action result '0198c0de-0000-7000-8000-000000000abc', got '01a18314-04a8-7582-baea-6cca2ef671d7'

Feature_Flags Tests

✅ 17/17 tests passed

View Details
Test Status Duration
Request Payload.Request With Person Properties Device Id ✅ 2858ms
Request Payload.Flags Request Uses V2 Query Param ✅ 2794ms
Request Payload.Flags Request Hits Flags Path Not Decide ✅ 2747ms
Request Payload.Flags Request Omits Authorization Header ✅ 2759ms
Request Payload.Token In Flags Body Matches Init ✅ 2846ms
Request Payload.Groups Round Trip ✅ 2893ms
Request Payload.Groups Default To Empty Object ✅ 2857ms
Request Payload.Disable Geoip False Propagates As Geoip Disable False ✅ 2735ms
Request Payload.Disable Geoip Omitted Defaults To False ✅ 2735ms
Request Payload.Flag Keys To Evaluate Contains Only Requested Key ✅ 2885ms
Request Lifecycle.No Flags Request On Init Alone ✅ 66ms
Request Lifecycle.No Flags Request On Normal Capture ✅ 2748ms
Request Lifecycle.Two Flag Calls Produce Two Remote Requests ✅ 5498ms
Request Lifecycle.Mock Response Value Is Returned To Caller ✅ 2708ms
Retry Behavior.Retries Flags On 502 ✅ 2884ms
Retry Behavior.Retries Flags On 504 ✅ 2908ms
Side Effect Events.Get Feature Flag Captures Feature Flag Called Event ✅ 2868ms

@github-actions

Copy link
Copy Markdown
Contributor

This PR hasn't seen activity in a week! Should it be merged, closed, or further worked on? If you want to keep it open, post a comment or remove the stale label – otherwise this will be closed in another week.

@posthog posthog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

PostHog Review · Standard · reviewed 86a175b: 5 Should fix.

This is the only automatic review while this pull request is a draft. Comment @posthog review to review new changes sooner. Otherwise, a review of the new changes runs when you mark it ready for review.

Comment thread PostHog/PostHogSDK.swift Resolved
Comment thread PostHog/PostHogSDK.swift Resolved
Comment thread PostHog/PostHogQueue.swift Resolved
@turnipdabeets
turnipdabeets marked this pull request as ready for review October 20, 2026 17:37
@turnipdabeets
turnipdabeets requested a review from a team as a code owner October 20, 2026 17:37
Comment thread PostHog/QueueEndpoint+Factories.swift Outdated
/// Soft request size for the AI endpoint, matching the other PostHog SDKs.
let aiMaxBatchBytes = 5 * 1024 * 1024
/// AI events can be megabytes, so keep fewer of them on disk than analytics events.
let aiMaxQueueSize = 100

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Other v1 SDKs give the AI lane its own settings, separate from analytics: compression, queue size and (in Python and Go) a request timeout of at least 30 s, since AI events can be several MB and shouldn't share analytics' limits. Here the AI lane inherits compression, flushAt, maxBatchSize and the 10 s timeout, and hardcodes 100 events / 50 MiB. Could we add AI-specific settings, or document these as fixed mobile limits if that's the better fit for iOS?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I didn't find AI-specific settings in the other SDKs: Node only has enableFullAiCapture, Python's AI lane uses the client's flush, batch, timeout and gzip settings, Go doesn't have AI capture on main yet, and the capture-ai spec only defines enable_full_ai_capture. Is that changing with the v1 work? If the other SDKs are adding AI settings, I'll add matching ones here.

For now, 3791af1 documents these as fixed mobile limits on captureAi. AI requests now time out after 60 s instead of 10 s. flushAt, maxBatchSize, flushIntervalSeconds and compression still come from the shared config. The 5 MiB request size, the send at 5 MiB queued, and the 100 events / 50 MiB disk caps are fixed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

sorry for the confusion - these are present on Rust/Go/Python (soon node) v1 branches only as that's the only place we've added capture_ai explicit interfaces and pipelines yet. The idea is to be able to tune those submit pipelines explicitly for AI events.

Check those out and then let me know what you think. since they are all backend SDKs the needs of this one may differ

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks, added in 4dd9dc5: captureAiCompression, captureAiMaxQueueSize, captureAiTimeout and captureAiMaxEventBytes on PostHogConfig, named after the backend ones. Defaults stay mobile-friendly: gzip, 100 events, 60 s, 8 MiB. captureAiMaxEventBytes can only lower the limit, and the 50 MiB disk cap stays fixed so a big queue can't fill the phone.

Comment thread PostHog/PostHogSDK.swift Resolved
Comment thread PostHog/PostHogQueue.swift Resolved
'posthog-ios': minor
---

Add `captureAi(...)` to send AI events such as `$ai_generation` to PostHog's AI endpoint (`/i/v1/ai/events`), which accepts events up to 8 MiB. It returns the event UUID.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The 4.0 guide has no section for captureAi yet. Can this PR add one covering the AI endpoint, the 8 MiB event limit, the 60-second timeout and the new 5 MiB analytics batch cap? How wrapper SDKs such as Flutter expose AI capture is still being worked out for all of them at once, so for now the guide can describe only the native iOS API.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

added in 5880463

turnipdabeets and others added 9 commits October 26, 2026 12:26
captureAi(...) takes capture's arguments, runs the same enrichment and
beforeSend, and queues the event on its own disk queue for
/i/v1/ai/events. It reuses the capture V1 transport (headers, per-event
results, retry identity per endpoint, redirects, Retry-After), with no
/batch fallback. The AI queue batches by bytes (5 MiB) as well as count,
keeps at most 100 events, and drops events over 8 MiB + 64 KiB logging
only the name and size. capture() never reroutes $ai_ events.

The compliance adapter gains /capture_ai and the capture_ai_v1 capability.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The AI queue now drops its oldest events once they take more than 50 MiB
on disk, on top of the 100-event limit. captureAi returns the queued
event's UUID, or nil when the event isn't queued.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…lose()

- close() flushes the AI queue along with the others, and aiQueue is guarded
  by setupLock like the other queues.
- The AI queue flushes once a batch's worth of bytes (5 MiB) is queued, so
  multi-MB events are sent before the 50 MiB cap evicts them.
- A flush reads about one byte-sized batch from disk instead of the whole
  window, so large AI events aren't all held in memory at once.
- The compliance adapter's /capture_ai accepts bodies up to 9 MB and passes
  the harness timestamp.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The harness reads the action result's success field, as posthog-python's
adapter returns it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Capture V1 rejects requests over 10 MiB, and analytics batches were capped
by count only. Give the analytics endpoint the same 5 MiB soft byte limit
as the AI lane (renamed to captureV1MaxBatchBytes). An event over the limit
is still sent alone, and 413 halving is unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Document the AI lane's fixed limits on captureAi.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Add captureAiCompression, captureAiMaxQueueSize, captureAiTimeout and
captureAiMaxEventBytes, matching the AI lane settings in the backend v1
SDKs. Defaults keep the current behavior: gzip, 100 events, 60 s, 8 MiB.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
turnipdabeets and others added 5 commits October 26, 2026 14:54
It capped properties but the SDK added 64 KiB on top of any value, so a
lowered limit let much larger events through. Like posthog-python's
capture_ai_max_event_bytes, it now limits the whole serialized event and
defaults to 8 MiB plus 64 KiB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Analytics events now only go to /i/v1/analytics/events. A 404 is terminal
like any other non-retriable status, matching posthog-python, posthog-go
and posthog-rs, instead of resending the batch to /batch for the session.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The SDK no longer sends to /batch, so replay doesn't need to filter it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A 404 from capture v1 now drops the events, as the changeset says.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
React Native apps record replay through posthog-ios while their JS SDK
still posts to /batch, so those requests would show up in recordings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The adapter maps enable_compression: false only to compression, so AI requests stay gzipped and the AI no_content_encoding_when_disabled harness test fails. Setting captureAiCompression = .none here too should fix it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

addressed in 949225e

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 949225e. enable_compression: false now also sets captureAiCompression = .none, and the AI no_content_encoding_when_disabled test passes.

turnipdabeets and others added 4 commits October 27, 2026 09:49
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…alse

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Log each event a 2xx drops at debug level with its name, UUID and the
  server's details (e.g. ai_event_too_big), never its properties.
- Check the AI event size limit in the queue via maxRecordBytes, so an
  oversized event logs only the size line, not "unserialisable record".
- Warn once at setup when captureAiMaxEventBytes is over the ceiling.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-remove-batch-merge

# Conflicts:
#	PostHog/PostHogApi.swift

@eli-r-ph eli-r-ph left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

still locking some decisions but we can iterate on these after the fact if needed. thanks for the updates, LGTM!

@turnipdabeets
turnipdabeets merged commit 2ed20ee into chore/v4-remove-deprecated Oct 28, 2026
23 checks passed
@turnipdabeets
turnipdabeets deleted the feat/v4-capture-ai branch October 28, 2026 15:00
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.

3 participants