PostHog / PostHog/posthog

Destinations: draft, publish, and revisions lifecycle for agent-driven management

Open
#74,237 0 comments 0 reactions 1 assignee View on GitHub

@mayteio is already working on this.

Since Jul 28, 2026.

Dominant language
Python
Stars
39.9k
Forks
3.4k
Avg merge
6h 51m
Merged PRs (30d)
232

Description

Background

Workflows now has a full agent-first management lifecycle (#66380): content edits to active workflows stage as drafts instead of mutating live config, agents test drafts before going live, publishing requires an impact preview with a signed confirm token, and every live-content change lands in an append-only revision history with restore-to-draft rollback.

Destinations (hog functions) have none of this. The cdp-functions-* MCP tools see heavy real-world use, including a steady stream of cdp-functions-partial-update calls, and every one of those against an enabled destination is a hot edit: the serializer validates, filters bytecode recompiles, and the post_save signal reloads workers immediately. The change is live on the next matching event with no staging, no preview, and no rollback beyond hand-reverting from the activity log.

This issue brings destinations the core of that lifecycle - drafts, publish with a confirm gate, revisions with restore - plus the remediation surface agents need to monitor and fix destinations (cross-function stats, invocation history, rerun, sample events). It is deliberately smaller than the workflows effort: destinations are stateless per event, so none of the follow-live hardening (parked runs, skip-forward, reschedule sweeps) that dominated that project applies here. Publish impact is entirely "future traffic behaves differently from worker reload onward".

Current state

Already in place:

  • POST /hog_functions/{id}/invocations test-invokes with mocked async functions and accepts a full unsaved configuration (MCP-exposed). This is the natural "test the draft" primitive.
  • metrics, metrics/totals, and logs via the shared AppMetricsMixin / LogEntryMixin.
  • 11 MCP tools in products/cdp/mcp/ with secret redaction and sensible exclude_params.
  • Activity log records full config diffs for hog, inputs, and filters.
  • Validation layers (posthog/cdp/validation.py, posthog/cdp/filters.py) covering inputs schema, hog/liquid templating, bytecode compilation, and cohort inlining.

Missing: any draft, version, or revision concept; publish/discard/restore endpoints; cross-function stats; a REST invocation-history endpoint (posthog/api/hog_invocation_results.py is wired only into the workflows viewset); an API-reachable sample-event source for test payloads.

Design decisions

  • Draft lives on the model, revisions are a rollback log. Same shape as the shipped HogFlow pattern: draft columns on HogFunction, an append-only HogFunctionRevision table that is never the source of truth. Revision-rows-as-source-of-truth was explored for workflows and deliberately not taken; destinations are the strongest case for the shipped pattern since they are pure follow-live.
  • Hog functions keep their own lifecycle rather than becoming one-node workflows. The execution engine is already shared (workflows embed hog functions as action nodes), a stateless per-event invocation is far cheaper than a workflow run, and the installed base of standalone destinations and cdp-functions-* agent usage needs safety now. Convergence of the management surface is a possible future, not a prerequisite.
  • No tool renames. The existing cdp-functions-* names have a large installed base. New tools extend the same prefix; existing tools gain draft-routing behavior behind the flag.
  • Draft routing is agent-only. Content edits from agent event sources (MCP, posthog-code, wizard, CLI) against an enabled destination stage into a draft. Web UI, headless API keys, and internal programmatic writers keep direct edits. Disabled destinations always edit directly. Event-source detection fails closed to direct-edit.
  • Type scope: destination only for v1. Transformations add execution-order semantics, site_* types add transpiled-JS concerns, and internal_destination rows are written by other products' code rather than agents.
  • Content vs live fields. hog, inputs, inputs_schema, filters, mappings, masking are draftable content. name, description, enabled always apply live.
  • Enable with an open draft refuses. enabled=true on a destination with an open draft returns an error directing the caller to publish or discard first. No implicit publish, no silently enabling stale config.
  • One confirm token closes both races. The publish preview mints a TimestampSigner token over {id}:{draft_updated_at}:{live_updated_at} (15 min expiry). Either the draft changing or the live config changing between preview and publish invalidates the token and forces a re-preview. This is both the "you saw the preview for exactly this draft" guarantee and the live-clobber protection.
  • Preview is a slim field-level diff in v1. Which of filters/inputs/code/mappings/masking changed, computed from comparing draft to live. Consequence analysis (filters widening/narrowing direction, function health, matched-event-volume estimates) is a fast-follow, not a v1 blocker.
  • Token gate only when enabled. Publishing a draft on a disabled destination has no live impact and skips the confirm requirement.
  • Revision bumps compare user content only. Derived fields (bytecode, transpiled, filters.bytecode) are stripped before comparison. Background re-saves (e.g. refresh_affected_hog_functions recompiling filters when an action or cohort changes) must never create revisions or touch drafts - enforced by a test that fails loudly if the content-comparison set widens, not by convention.
  • Revisions are append-only, forever. No pruning in v1; snapshots are small JSON. No backfill from the activity log: the first tracked write also snapshots the pre-change content, matching workflows.
  • Feature flag destinations-revisions, fail-closed. Flag off (or any error evaluating it) means today's behavior: direct live edits. The kill switch is always safe.

Plan

Track 1: draft, publish, revisions (behind destinations-revisions)

PR 1: model + migration (deployed ahead of the code PR)
HogFunction gains draft (JSON content snapshot), draft_updated_at, draft_encrypted_inputs, version. New append-only HogFunctionRevision (team, hog_function, version, content, created_by; unique on function+version). Secrets partitioned out of draft and revision snapshots. post_save skips worker reload when only draft columns changed.

PR 2: draft cycle + revisions API
Agent-source content edits to enabled destinations route to _write_draft (draft = live snapshot merged with existing draft merged with this edit, so publish is a plain copy). publish revalidates through the normal serializer (recompiles filters and hog bytecode), bumps the revision, nulls draft columns, then reloads workers. discard_draft is idempotent. invocations gains use_draft=true with secret rehydration. base_updated_at optimistic concurrency on both draft and live writes. Preview + confirm token per design decisions. revisions list, revisions/<version> detail, revisions/<version>/restore (revision content becomes the draft; 409 on an existing draft unless overwrite=true). Rollback is just another publish, so preview and confirm apply unchanged.

PR 3: MCP tools
cdp-functions-publish, cdp-functions-discard-draft, cdp-functions-list-revisions, cdp-functions-get-revision, cdp-functions-restore-revision, cdp-functions-enable / cdp-functions-disable (status-only; mixed status+content updates rejected, matching workflows). Guardrails: no create-as-enabled over MCP; summary serializer for MCP list responses. Tool descriptions and responses spell out the staging behavior ("draft staged; not live until cdp-functions-publish") so agents discover the cycle mid-conversation.

Rollout: destinations-revisions internal, then selected teams, then 100%. Flag off is always today's behavior.

Track 2: remediation surface (independent of track 1, no flag)

The monitor-and-fix loop: a scout checks overall destination health, digs into a failing destination, fixes it or notifies the user, and replays failed events. None of this depends on drafts, so it ships independently and in any order.

  • Enable the existing rerun and metrics-totals operations for MCP (config only).
  • metrics/global endpoint + cdp-functions-global-stats (most-failing-first across the team's destinations).
  • Wire hog_invocation_results into the hog function viewset + cdp-functions-list-invocations / cdp-functions-get-invocation.
  • cdp-functions-sample-event: server-side fetch of a recent event matching given filters, accepted in the request body so unsaved configs can use it; removes the need for agents to hand-construct globals.

Follow-ups (cut from v1, additive on the same primitives)

  • Preview consequence analysis: filters widening/narrowing direction (widening means more events flow to the third party from reload onward; narrowing means events silently stop being delivered), function health in the preview, matched-event-volume estimates.
  • Builder awareness: emit resource_edited on HogFunction saves and listen in the configuration scene - a "this destination was changed elsewhere" reload/keep-mine banner, plus a visible indicator when an agent has staged a draft.
  • A building-destinations skill (template selection, patch/test/publish loop, filters and inputs gotchas) and an error-copy papercut pass - after observing real agent usage of the cycle. Known hotspots from MCP analytics: template retrieval failing on guessed ids, test invocations failing on hand-rolled globals (largely addressed by the sample-event tool), and delete errors.
  • Revision backfill from the activity log, and revision pruning.
  • Dedicated signing key for the publish confirm token (flagged by review: TimestampSigner with no key= implicitly falls back to SECRET_KEY, against .agents/security.md). Fix jointly with the identical HogFlow confirm tokens the pattern was copied from; needs a fail-closed env-provisioned key alongside the code change.

Out of scope

  • Transformations, site_* types, and internal_destination in the draft cycle.
  • Web UI adoption of the draft/publish cycle (agent surfaces only, matching workflows).
  • Template upgrade flow (migrating a function pinned to an old template sha via the draft cycle) - a natural follow-up built on these primitives, tracked separately.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.