Skip to content

feat(tasks): add durable Pi task sessions - #72928

Merged
trunk-io[bot] merged 24 commits into
masterfrom
feat/task-sessions
Jul 29, 2026
Merged

feat(tasks): add durable Pi task sessions#72928
trunk-io[bot] merged 24 commits into
masterfrom
feat/task-sessions

Conversation

@jonathanlab

@jonathanlab jonathanlab commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add durable TaskSession storage for native Pi JSONL sessions, with immutable revisions and active-session linkage on task runs
  • add read/write session APIs with scoped authorization, revision CAS, stale-sandbox fencing, size limits, JSONL validation, and object cleanup
  • enable Pi task runs through the existing cloud workflow and relay native Pi events/readiness without translating them into ACP envelopes
  • pass runtime and sandbox identity into the sandbox agent environment, support Docker and Modal, and clear stale sandbox routing before cloud resume
  • update local sandbox packaging to build the local agent from a minimal pnpm workspace without manifest rewriting
  • accept the generic pi/rpc tunnel while leaving native command validation to Pi

Durability model

TaskSession object storage is authoritative. Sandboxes upload staged JSONL, the backend validates and promotes immutable revisions, and replacement sandboxes restore the latest promoted revision. Snapshots remain an optimization rather than the source of truth.

Testing

  • Python lint, formatting, and type checks via pre-commit
  • Docker sandbox unit suite
  • Modal sandbox focused tests
  • migration consistency: makemigrations tasks --check --dry-run
  • serializer/API and TaskSession storage tests
  • real Electron + local Temporal/Docker E2E:
    • fresh cloud Pi task and follow-up
    • replacement-sandbox restore from TaskSession
    • app restart and conversation restore
    • post-restore follow-up
    • durable Pi event projection and promoted TaskSession revisions

Rollout

Deploy this backend and its migrations before the corresponding PostHog Code client and production sandbox agent image.

PostHog Code client: PostHog/code#3719

@github-actions

Copy link
Copy Markdown
Contributor

Hey @jonathanlab! 👋

It looks like your git author email on this PR isn't your @posthog.com address (jonathanmieloo@gmail.com). Since you're on the PostHog team, it's worth pointing your local git author email at your @posthog.com address. Why it matters:

  • Consistent work identity in git history — internal tooling that attributes commits to team members keys off your @posthog.com address.
  • Keeps team contributions easy to tell apart from external community ones when scanning history.

You can fix it for this repo with:

git config user.email "you@posthog.com"

Or set it globally with git config --global user.email "you@posthog.com". No need to redo this PR — just a nudge for next time. 🙂

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

🦔 Hogbox preview · ✅ ready

▶ Open the preview

🔑 Login test@posthog.com / 12345678 (demo data)
🧩 Running this PR's backend and frontend, on the PostHog :master base
🔗 Link stable across rebuilds — a re-push swaps the box underneath, the URL stays
🔒 Access tailnet only (PostHog VPN)
🛠️ Admin inspect & debug state in hogland
💤 Idle sleeps after ~30 min idle (snapshot to S3, zero node cost) and wakes on your next visit in ~30s, behind a brief "waking up" screen

commit 68826c4 · box box-3c83ac8443ca · ready in 791s (push → usable) · build log · rebuilds on every push, torn down on close

@greptile-apps

greptile-apps Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Comments Outside Diff (1)

  1. products/tasks/backend/temporal/process_task/activities/relay_sandbox_events.py, line 817-823 (link)

    P1 Pi Completion Depends On Disconnect

    A Pi run emits turn_completed, but this terminal check recognizes only ACP completion and error notifications. If the sandbox exits or the connection fails after turn_completed without a clean SSE close, the Redis stream remains incomplete and consumers can wait indefinitely for a run that already finished.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: products/tasks/backend/temporal/process_task/activities/relay_sandbox_events.py
    Line: 817-823
    
    Comment:
    **Pi Completion Depends On Disconnect**
    
    A Pi run emits `turn_completed`, but this terminal check recognizes only ACP completion and error notifications. If the sandbox exits or the connection fails after `turn_completed` without a clean SSE close, the Redis stream remains incomplete and consumers can wait indefinitely for a run that already finished.
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
Fix the following 3 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 3
products/tasks/backend/facade/api.py:2205-2220
**Superseded Upload Can Reappear**

When a second prepare supersedes the first, this callback deletes the old key only once. The first presigned POST remains valid and can upload after that deletion; its finalize is then rejected by the new `pending_sync_id`, but no path removes the recreated object, so repeated prepares can leave abandoned session uploads.

### Issue 2 of 3
products/tasks/backend/temporal/process_task/activities/relay_sandbox_events.py:817-823
**Pi Completion Depends On Disconnect**

A Pi run emits `turn_completed`, but this terminal check recognizes only ACP completion and error notifications. If the sandbox exits or the connection fails after `turn_completed` without a clean SSE close, the Redis stream remains incomplete and consumers can wait indefinitely for a run that already finished.

### Issue 3 of 3
products/tasks/backend/presentation/views/api.py:1600-1606
**ACP Commands Reach Pi Servers**

This guard rejects `pi/*` methods on ACP tasks but permits every existing ACP command on Pi tasks. A client that sends `user_message`, `permission_response`, or `mcp_response` to a Pi run now forwards an ACP-shaped request to an agent server started with `POSTHOG_AGENT_PROTOCOL=pi`, which can reject or misinterpret the command instead of returning the previous clear unsupported-runtime response.

Reviews (1): Last reviewed commit: "feat(tasks): add durable Pi task session..." | Re-trigger Greptile

Comment thread products/tasks/backend/facade/api.py Outdated
Comment thread products/tasks/backend/presentation/views/api.py
@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

🤖 CI report

Bundle size — no change

Uncompressed size of every built .js bundle, compared against the base branch.

Total: 65.20 MiB · no change

No file changed by more than 1000 B.

Posted automatically by build-bundle-size-report · uncompressed bytes from dist-report

Eager graph — within budget

How much code each root ships on the eager path — downloaded and parsed before the surface is interactive. Measured from the esbuild output chunks (post-tree-shake, static imports only); lazy import() / React.lazy chunks are not counted.

Root Eager (shipped) Δ vs base Budget
entry (logged-out pages, app bootstrap)
src/index.tsx
1.24 MiB · 22 files no change ███░░░░░░░ 27.6% of 4.51 MiB
authenticated shell (every logged-in page)
src/scenes/AuthenticatedShell.tsx
8.09 MiB · 3,015 files 🟢 -5 B (-0.0%) ████████░░ 83.3% of 9.71 MiB

🟢 node_modules/monaco-editor/ stays out of src/index.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/index.tsx
🟢 [object Object] stays out of src/index.tsx
🟢 [object Object] stays out of src/index.tsx
🟢 node_modules/monaco-editor/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx

Largest files eagerly shipped from src/index.tsx
Size File
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
24.6 KiB ../node_modules/.pnpm/buffer@6.0.3/node_modules/buffer/index.js
6.3 KiB ../node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react.production.min.js
4.5 KiB ../node_modules/.pnpm/@jspm+core@2.1.0/node_modules/@jspm/core/nodelibs/browser/process.js
3.9 KiB ../node_modules/.pnpm/scheduler@0.23.2/node_modules/scheduler/cjs/scheduler.production.min.js
1.4 KiB ../node_modules/.pnpm/base64-js@1.5.1/node_modules/base64-js/index.js
1.3 KiB src/RootErrorBoundary.tsx
912 B ../node_modules/.pnpm/ieee754@1.2.1/node_modules/ieee754/index.js
789 B src/scenes/ChunkLoadErrorBoundary.tsx
762 B src/index.tsx
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx
Size File
281.6 KiB ../node_modules/.pnpm/posthog-js@1.407.6/node_modules/posthog-js/dist/rrweb.js
267.7 KiB ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
235.5 KiB src/taxonomy/core-filter-definitions-by-group.json
226.5 KiB ../node_modules/.pnpm/posthog-js@1.407.6/node_modules/posthog-js/dist/module.js
154.3 KiB ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
105.2 KiB src/lib/api.ts
94.0 KiB ../packages/quill/packages/quill/dist/index.js
93.3 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
90.6 KiB ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js

Posted automatically by check-eager-graph · sizes are eager output bytes (shipped, post-tree-shake) from the esbuild metafile · part of #32479

Toolbar bundle — eager 2.18 MiB within budget

What the toolbar ships to customer pages, measured from the esbuild output (minified, post-tree-shake). The eager set is the entry plus everything statically imported from it — fetched before any feature runs; deferred chunks load lazily. The eager guardrail is 5.72 MiB. Each output file must also stay below 10 MB, where CloudFront stops compressing it. The module boundary is enforced separately by check-toolbar-graph.

Metric Size Δ vs base Budget
Eager (shipped)
entry + static imports
2.18 MiB · 17 files no change ████░░░░░░ 38.2% of 5.72 MiB
Deferred (lazy) 2.07 MiB · 33 files no change n/a — loads on demand
Loader dist/toolbar.js 1.1 KiB no change █░░░░░░░░░ 5.8% of 19.5 KiB
Largest eagerly-shipped chunks
Size File
717.2 KiB dist/toolbar/toolbar-app-JFACJRNE.css
546.1 KiB dist/toolbar/chunk-chunk-B274S4W6.js
484.3 KiB dist/toolbar/chunk-chunk-CQF6YMXB.js
133.6 KiB dist/toolbar/chunk-chunk-TJQEEBAA.js
131.8 KiB dist/toolbar/chunk-chunk-T5KY5WYR.js
71.0 KiB dist/toolbar/toolbar-app-FEADCRM6.js
69.0 KiB dist/toolbar/chunk-chunk-27JL52RE.js
35.6 KiB dist/toolbar/chunk-chunk-DSLM75PZ.js
20.9 KiB dist/toolbar/chunk-chunk-R7I63UQX.js
12.2 KiB dist/toolbar/chunk-chunk-PIK3PADE.js

Posted automatically by check-toolbar-size · sizes are toolbar output bytes (shipped, post-tree-shake) from the esbuild metafile

Dist folder size — 🔺 +6.8 KiB (+0.0%)

Total size of the built frontend/dist folder (all assets), compared against the base branch.

Total: 1362.68 MiB · 🔺 +6.8 KiB (+0.0%)

ℹ️ MCP UI apps size — 32 app(s), 17067.9 KB JS

Built size of each MCP UI app (main.js + styles.css).

App JS CSS
debug 599.5 KB 187.7 KB
action 457.8 KB 187.7 KB
action-list 564.3 KB 187.7 KB
cohort 456.8 KB 187.7 KB
cohort-list 563.3 KB 187.7 KB
email-template 456.6 KB 187.7 KB
error-details 472.4 KB 187.7 KB
error-issue 457.5 KB 187.7 KB
error-issue-list 564.2 KB 187.7 KB
experiment 561.5 KB 187.7 KB
experiment-list 565.1 KB 187.7 KB
experiment-results 563.2 KB 187.7 KB
feature-flag 567.1 KB 187.7 KB
feature-flag-list 570.9 KB 187.7 KB
feature-flag-testing 461.0 KB 187.7 KB
insight-actors 562.1 KB 187.7 KB
invite-email-preview 456.0 KB 187.7 KB
llm-costs 559.5 KB 187.7 KB
session-recording 458.6 KB 187.7 KB
session-summary 463.9 KB 187.7 KB
survey 458.4 KB 187.7 KB
survey-global-stats 562.2 KB 187.7 KB
survey-list 565.0 KB 187.7 KB
survey-stats 562.2 KB 187.7 KB
trace-span 457.2 KB 187.7 KB
trace-span-list 564.2 KB 187.7 KB
workflow 457.1 KB 187.7 KB
workflow-list 563.7 KB 187.7 KB
loops-review 461.2 KB 187.7 KB
query-results 746.7 KB 187.7 KB
render-ui 827.3 KB 187.7 KB
visual-review-snapshots 461.6 KB 187.7 KB
Playwright — all passed

All tests passed.

View test results →

⚠️ Backend coverage — 74.0% of changed backend lines covered — 218 uncovered

🧪 Backend test coverage

Patch coverage — changed backend lines (products + core): ███████████████░░░░░ 74.0% (649 / 867)

File Patch Uncovered changed lines
products/tasks/backend/temporal/process_task/activities/provision_sandbox.py 0.0% 630, 638–639, 641
products/tasks/backend/temporal/process_task/activities/tests/test_start_agent_server.py 0.0% 180
products/tasks/backend/temporal/process_task/activities/get_sandbox_for_repository.py 0.0% 383–384, 389–392, 399–401, 403–404
products/tasks/backend/temporal/process_task/activities/tests/test_relay_sandbox_events.py 0.0% 158–159
products/tasks/backend/temporal/process_task/activities/tests/test_get_task_processing_context.py 0.0% 363–364, 367–370, 372, 376, 378–379, 381–385, 391, 393, 395–396, 1032–1035, 1040–1049
products/tasks/backend/temporal/process_task/tests/test_workflow.py 0.0% 380–384, 386–387, 389, 391–395, 397–399, 401–402, 404–408, 410–411, 413
products/tasks/backend/temporal/process_task/tests/test_send_followup_to_sandbox.py 0.0% 712–713, 717–718, 727–729
products/tasks/backend/temporal/process_task/activities/relay_sandbox_events.py 10.0% 596–599, 604–606, 695–701, 758–761
products/tasks/backend/temporal/process_task/workflow.py 14.8% 116–118, 301–302, 746–749, 1146–1147, 1151, 1157, 2033–2041, 2126
products/tasks/backend/temporal/process_task/activities/start_agent_server.py 18.2% 225–226, 228, 235–238, 454, 492
products/tasks/backend/temporal/process_task/activities/get_task_processing_context.py 22.2% 683–684, 804–807, 809
products/tasks/backend/temporal/process_task/activities/send_followup_to_sandbox.py 50.0% 294
products/tasks/backend/models.py 70.4% 912–914, 918, 929–930, 2778–2781, 2783–2787, 2794
products/tasks/backend/facade/api.py 75.4% 2242–2245, 2247–2250, 2260, 2264–2267, 2292–2293, 2318–2319, 2341, 2344, 2358, 2360, 2364–2365, 2375, 3653, 3785–3787, 4932–4933
products/tasks/backend/logic/stream/redis_stream.py 76.9% 574–576
products/tasks/backend/logic/services/docker_sandbox.py 80.0% 188, 326–328
products/tasks/backend/presentation/serializers.py 81.0% 2521, 2524, 2527, 2529
products/tasks/backend/presentation/views/api.py 83.3% 135, 138–139, 145, 983, 1020, 1225, 1282, 1294, 1314, 2038
products/tasks/backend/logic/services/modal_sandbox.py 84.0% 331, 333, 337, 1184
products/tasks/backend/logic/services/connection_token.py 84.6% 261, 281
products/tasks/backend/logic/services/sandbox_usage.py 94.4% 93
products/tasks/backend/tests/test_api.py 99.5% 6868

🤖 Agents: add a test covering the lines above, or note why under "How did you test this code?". Machine-readable gap list: the patch-coverage artifact on this run (gh run download 30450444338 -n patch-coverage), or the coverage-data block at the end of this comment.

Per-product line coverage (touched products)
Product Coverage Lines
platform_features ██░░░░░░░░░░░░░░░░░░ 12.1% 7 / 58
batch_exports ████████░░░░░░░░░░░░ 39.5% 8,593 / 21,741
demo ███████████░░░░░░░░░ 56.2% 1,497 / 2,663
warehouse_sources_queue ████████████░░░░░░░░ 59.2% 148 / 250
tasks ██████████████░░░░░░ 69.8% 32,022 / 45,892
data_tools ██████████████░░░░░░ 70.0% 63 / 90
ai_gateway ███████████████░░░░░ 75.0% 9 / 12
cdp ████████████████░░░░ 81.0% 3,146 / 3,883
signals ████████████████░░░░ 81.1% 23,840 / 29,387
wizard █████████████████░░░ 84.8% 935 / 1,102
data_modeling █████████████████░░░ 85.3% 7,343 / 8,611
notebooks █████████████████░░░ 86.0% 7,794 / 9,060
actions █████████████████░░░ 86.6% 717 / 828
cohorts █████████████████░░░ 86.9% 5,648 / 6,496
product_tours ██████████████████░░ 87.9% 1,303 / 1,482
data_warehouse ██████████████████░░ 88.3% 12,036 / 13,635
exports ██████████████████░░ 88.4% 6,950 / 7,862
business_knowledge ██████████████████░░ 89.0% 4,391 / 4,936
dashboards ██████████████████░░ 89.4% 5,983 / 6,693
engineering_analytics ██████████████████░░ 89.4% 6,441 / 7,202
visual_review ██████████████████░░ 89.5% 5,837 / 6,522
conversations ██████████████████░░ 89.6% 17,234 / 19,241
alerts ██████████████████░░ 90.0% 4,342 / 4,827
mcp_analytics ██████████████████░░ 90.2% 2,883 / 3,198
links ██████████████████░░ 90.6% 183 / 202
streamlit_apps ██████████████████░░ 90.7% 2,630 / 2,901
error_tracking ██████████████████░░ 91.0% 10,925 / 12,004
slack_app ██████████████████░░ 91.1% 9,554 / 10,492
stamphog ██████████████████░░ 91.1% 4,056 / 4,450
marketing_analytics ██████████████████░░ 91.2% 12,058 / 13,222
mcp_store ██████████████████░░ 91.9% 4,257 / 4,634
product_analytics ███████████████████░ 92.5% 5,849 / 6,321
managed_migrations ███████████████████░ 92.6% 1,556 / 1,681
early_access_features ███████████████████░ 92.6% 1,287 / 1,390
notifications ███████████████████░ 92.6% 1,017 / 1,098
ai_observability ███████████████████░ 92.8% 15,328 / 16,517
surveys ███████████████████░ 93.1% 5,771 / 6,197
web_analytics ███████████████████░ 93.2% 14,826 / 15,906
posthog_ai ███████████████████░ 93.2% 1,326 / 1,422
approvals ███████████████████░ 93.3% 3,437 / 3,682
reminders ███████████████████░ 93.4% 468 / 501
legal_documents ███████████████████░ 93.8% 1,628 / 1,736
workflows ███████████████████░ 93.8% 6,943 / 7,399
endpoints ███████████████████░ 94.2% 8,655 / 9,192
tracing ███████████████████░ 94.5% 2,670 / 2,826
skills ███████████████████░ 94.6% 3,158 / 3,337
review_hog ███████████████████░ 94.6% 6,912 / 7,303
messaging ███████████████████░ 94.7% 2,885 / 3,048
experiments ███████████████████░ 95.4% 25,415 / 26,637
logs ███████████████████░ 95.5% 10,435 / 10,928
growth ███████████████████░ 96.1% 3,245 / 3,376
annotations ███████████████████░ 96.2% 732 / 761
revenue_analytics ███████████████████░ 96.3% 1,887 / 1,960
feature_flags ███████████████████░ 96.4% 17,371 / 18,023
replay_vision ███████████████████░ 96.4% 15,861 / 16,455
user_interviews ███████████████████░ 96.5% 2,638 / 2,734
access_control ███████████████████░ 96.9% 870 / 898
customer_analytics ███████████████████░ 97.1% 9,798 / 10,086
warehouse_sources ███████████████████░ 97.2% 346,751 / 356,648
data_catalog ████████████████████ 97.7% 2,555 / 2,615
analytics_platform ████████████████████ 98.0% 2,153 / 2,197
metrics ████████████████████ 98.2% 2,491 / 2,536
pulse ████████████████████ 98.4% 2,017 / 2,049
live_debugger ████████████████████ 99.2% 613 / 618
field_notes ████████████████████ 99.4% 158 / 159

Report-only. Patch coverage = changed backend lines covered vs origin/master. Sorted lowest first.
Known gaps: lines covered only by Temporal tests show as uncovered; core line numbers may drift if master changed the same file.

⚠️ Django migration SQL — 1 new migration to review

We've detected new migrations on this PR. Review the SQL output for each migration:

products/tasks/backend/migrations/0074_task_session.py

BEGIN;
--
-- Create model TaskSession
--
CREATE TABLE "posthog_task_session" ("id" uuid NOT NULL PRIMARY KEY, "object_storage_key" varchar(512) NULL UNIQUE, "content_sha256" varchar(64) NULL, "size" integer NOT NULL CHECK ("size" >= 0), "created_at" timestamp with time zone NOT NULL, "updated_at" timestamp with time zone NOT NULL, "organization_id" uuid NOT NULL, "team_id" integer NOT NULL, "task_id" uuid NOT NULL);
--
-- Add field active_task_session to taskrun
--
ALTER TABLE "posthog_task_run" ADD COLUMN "active_task_session_id" uuid NULL CONSTRAINT "posthog_task_run_active_task_session__739b7e8b_fk_posthog_t" REFERENCES "posthog_task_session"("id") DEFERRABLE INITIALLY DEFERRED; SET CONSTRAINTS "posthog_task_run_active_task_session__739b7e8b_fk_posthog_t" IMMEDIATE;
--
-- Create index task_session_org_updated_idx on field(s) organization, -updated_at of model tasksession
--
CREATE INDEX "task_session_org_updated_idx" ON "posthog_task_session" ("organization_id", "updated_at" DESC);
--
-- Create index task_session_team_updated_idx on field(s) team, -updated_at of model tasksession
--
CREATE INDEX "task_session_team_updated_idx" ON "posthog_task_session" ("team_id", "updated_at" DESC);
--
-- Create index task_session_task_updated_idx on field(s) task, -updated_at of model tasksession
--
CREATE INDEX "task_session_task_updated_idx" ON "posthog_task_session" ("task_id", "updated_at" DESC);
ALTER TABLE "posthog_task_session" ADD CONSTRAINT "posthog_task_session_task_id_a9a76dc4_fk_posthog_task_id" FOREIGN KEY ("task_id") REFERENCES "posthog_task" ("id") DEFERRABLE INITIALLY DEFERRED;
CREATE INDEX "posthog_task_session_object_storage_key_68a9145c_like" ON "posthog_task_session" ("object_storage_key" varchar_pattern_ops);
CREATE INDEX "posthog_task_run_active_task_session_id_739b7e8b" ON "posthog_task_run" ("active_task_session_id");
COMMIT;

Last updated: 2026-07-29 12:13 UTC (68826c4)

Django migration risk — migration analysis complete

We've analyzed your migrations for potential risks.

Summary: 10 Safe | 4 Needs Review | 0 Blocked

⚠️ Needs Review

May have performance impact

agent_platform.0002_alter_agentapplication_slug
  └─ #1 ⚠️ AlterField
     Field alteration may cause table locks or data loss (check if changing type or constraints)
     model: agentapplication, field: slug, field_type: SlugField
agent_platform.0003_agentapplication_global_slug_unique
  └─ #1 ⚠️ RemoveConstraint: Unscored Django operation: RemoveConstraint (needs manual review)
  └─ #2 ⚠️ AddConstraint
     Adding constraint may lock table (use NOT VALID pattern)
     model: agentapplication
agent_platform.0005_copy_encrypted_env_to_revisions
  └─ #1 ⚠️ RunPython: RunPython data migration needs review for performance
agent_platform.0011_agentrevision_skill_refs
  └─ #1 ⚠️ AddField
     Adding NOT NULL field with callable default (list) - verify it's stable
     model: agentrevision, field: skill_refs, default: list

✅ Safe

Brief or no lock, backwards compatible

agent_platform.0001_initial
  └─ #1 ✅ CreateModel
     Creating new table is safe
     model: AgentApplication
  └─ #2 ✅ CreateModel
     Creating new table is safe
     model: AgentRevision
  └─ #3 ✅ AddField
     Adding nullable field requires brief lock
     model: agentapplication, field: live_revision
  └─ #4 ✅ CreateModel
     Creating new table is safe
     model: AgentSandboxInstance
  └─ #5 ✅ CreateModel
     Creating new table is safe
     model: AgentSession
  └─ #6 ✅ CreateModel
     Creating new table is safe
     model: AgentSessionCredential
  └─ #7 ✅ CreateModel
     Creating new table is safe
     model: AgentToolApprovalRequest
  └─ #8 ✅ CreateModel
     Creating new table is safe
     model: AgentUser
  │
  └──> ℹ️  INFO:
       ℹ️  Skipped operations on newly created tables (empty tables
       don't cause lock contention).
agent_platform.0004_agentrevision_encrypted_env
  └─ #1 ✅ AddField
     Adding nullable field requires brief lock
     model: agentrevision, field: encrypted_env
agent_platform.0006_remove_agentapplication_encrypted_env
  └─ #1 ✅ SeparateDatabaseAndState: Only state operations (no database changes)
agent_platform.0007_agent_identity_linking
  └─ #1 ✅ CreateModel
     Creating new table is safe
     model: AgentIdentityCredential
  └─ #2 ✅ CreateModel
     Creating new table is safe
     model: AgentIdentityLinkState
  │
  └──> ℹ️  INFO:
       ℹ️  Skipped operations on newly created tables (empty tables
       don't cause lock contention).
agent_platform.0008_agent_identity_subject
  └─ #1 ✅ SeparateDatabaseAndState: Only state operations (no database changes)
  └─ #2 ✅ AddField
     Adding nullable field requires brief lock
     model: agentidentitycredential, field: subject
agent_platform.0009_agentsession_agenttoolapprovalrequest_is_preview
  └─ #1 ✅ AddField
     Adding NOT NULL field with constant default (safe in PG11+)
     model: agentsession, field: is_preview
  └─ #2 ✅ AddField
     Adding NOT NULL field with constant default (safe in PG11+)
     model: agenttoolapprovalrequest, field: is_preview
agent_platform.0010_remove_is_preview_state_only
  └─ #1 ✅ SeparateDatabaseAndState: Only state operations (no database changes)
agent_platform.0012_agentsession_search_text_turn_count
  └─ #1 ✅ AddField
     Adding nullable field requires brief lock
     model: agentsession, field: search_text
  └─ #2 ✅ AddField
     Adding NOT NULL field with constant default (safe in PG11+)
     model: agentsession, field: turn_count
agent_platform.0013_agent_transport_binding
  └─ #1 ✅ CreateModel
     Creating new table is safe
     model: AgentTransportBinding
  │
  └──> ℹ️  INFO:
       ℹ️  Skipped operations on newly created tables (empty tables
       don't cause lock contention).
tasks.0074_task_session
  └─ #1 ✅ CreateModel
     Creating new table is safe
     model: TaskSession
  └─ #2 ✅ AddField
     Adding nullable field requires brief lock
     model: taskrun, field: active_task_session
  │
  └──> ℹ️  INFO:
       ℹ️  Skipped operations on newly created tables (empty tables
       don't cause lock contention).

📚 How to Deploy These Changes Safely

AddConstraint:

Add constraints in 2 phases without locking, using the PostHog helpers:

  1. AddConstraintNotValid (instant, validates new rows only, no table scan)

  2. ValidateConstraint in a separate migration (scans table with non-blocking lock)

    from posthog.migration_helpers import AddConstraintNotValid, ValidateConstraint

See the migration safety guide

AddField:

This operation acquires a brief lock but doesn't rewrite the table.

Deployment uses lock timeouts with automatic retries, so lock contention will cause retries rather than connection pile-up.

RunPython:

Use batching for large data migrations:

  • Use .iterator() to avoid loading all rows into memory
  • Use .bulk_update() instead of saving individual objects
  • Batch size: 1,000-10,000 rows per batch
  • Add pauses between batches
  • Consider background jobs for very large updates (millions of rows)

See the migration safety guide

Last updated: 2026-07-29 12:13 UTC (68826c4)

@jonathanlab

Copy link
Copy Markdown
Contributor Author

Addressed the latest Greptile review in f35832e. One finding was intentionally not changed: Pi turn_completed is an interactive turn boundary, not a terminal run event, so marking the Redis stream complete there would prevent follow-up turns. The relay keeps the stream open across turns; an actual terminal event or clean SSE close completes it, and exhausted transport retries mark it errored rather than leaving consumers waiting indefinitely.

@posthog-bot-comment-resolver

posthog-bot-comment-resolver Bot commented Jul 23, 2026

Copy link
Copy Markdown

🔀 Tried to auto-resolve conflicts with master but this one needs a human.

I won't retry until the branch or master moves.

@tatoalo

tatoalo commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Stress-tested this a bit in cloud runs (replicating this also on the code PR):

  • seeing runtime_adapter required upon creation, so we need to specify that, otherwise a task row can be created but not TaskRun spawned in reality
  • had to tunnel OBJECT_STORAGE_ENDPOINT as well, so basically now to have real E2E capabilities we are tunnelling three ports, we need something easier DX-wise (unrelated to this exact PR, just spitting out)
  • TaskSession has no team_id which I think we want
  • metadata, copy, download, and JSON validation occur inside the transaction holding TaskRun and TaskSession row locks inside a single atomic() transaction

Can we ff-gate the in the backend as well? We can have afail-closed flag checked when creating/starting/resuming Pi cloud runs, plus sandbox-image capability/version validation

Comment thread products/tasks/backend/temporal/process_task/workflow.py Outdated
@veria-ai

veria-ai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

PR overview

All previously flagged issues have been addressed. No open security concerns remain on this pull request.

Security review

No open security issues remain on this pull request.

Fixed/addressed: 4 · PR risk: 0/10

@tatoalo
tatoalo force-pushed the feat/task-sessions branch from 3cb9e6b to 9a48648 Compare July 27, 2026 10:30
@github-actions
github-actions Bot requested a deployment to preview-pr-72928 July 27, 2026 10:30 In progress
Comment thread products/tasks/backend/temporal/process_task/workflow.py Outdated
@jonathanlab

Copy link
Copy Markdown
Contributor Author

Addressed the remaining backend review items in 2a67537: actor-scoped bounded message dedupe; TaskSession UUIDv7 + team-scoped fail-closed manager/IDOR coverage; backend fail-closed pi-harness checks for cloud create/start/resume; and sandbox image capability probing before Pi launch. The earlier runtime-adapter and transaction/I/O concerns are already addressed on the branch. The multi-port local DX note is unrelated to this PR.

@github-actions
github-actions Bot requested a deployment to preview-pr-72928 July 27, 2026 12:05 In progress
Comment thread products/tasks/backend/presentation/views/api.py
Comment thread products/tasks/backend/presentation/views/api.py Outdated
@jonathanlab

Copy link
Copy Markdown
Contributor Author

/trunk merge

jonathanlab and others added 24 commits July 29, 2026 14:09
# Conflicts:
#	products/tasks/backend/facade/api.py
#	products/tasks/backend/migrations/max_migration.txt
@trunk-io
trunk-io Bot merged commit 7e50462 into master Jul 29, 2026
258 checks passed
@trunk-io
trunk-io Bot deleted the feat/task-sessions branch July 29, 2026 13:02
@deployment-status-posthog

deployment-status-posthog Bot commented Jul 29, 2026

Copy link
Copy Markdown

Deploy status

Environment Status Deployed At Workflow
dev ✅ Deployed 2026-07-29 13:39 UTC Run
prod-us ✅ Deployed 2026-07-29 13:54 UTC Run
prod-eu ✅ Deployed 2026-07-29 13:57 UTC Run

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.

2 participants