Skip to content

129: Give dispatched agents a live preview environment instead of only a local checkout#152

Merged
kjgbot merged 15 commits into
mainfrom
factory/129-agentworkforce-factory-fbac5605
Jul 21, 2026
Merged

129: Give dispatched agents a live preview environment instead of only a local checkout#152
kjgbot merged 15 commits into
mainfrom
factory/129-agentworkforce-factory-fbac5605

Conversation

@agent-relay-code

@agent-relay-code agent-relay-code Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Summary

Factory's implementer/reviewer/babysitter agents work in a git worktree on a fleet node (src/node/factory-node.ts, createFactoryNodeDefinition(), AgentSpec.clonePath in src/ports/fleet.ts) but there is no concept of exposing whatever the checkout runs (a dev server, a built app) as a reachable URL. A competitor (Amika, gofixpoint/amika) treats this as first-class: .amika/config.toml [services.*] blocks declare ports plus a url_scheme, and every sandbox gets a generated preview URL (amika service list). Factory has no equivalent — confirmed zero references to port publishing or preview URLs anywhere in src/ (a repo-wide search for preview-url/port-publish/service-port patterns returns nothing). This is table stakes for letting an agent (or a human, or an eventual computer-use verification step) actually see the running result of a change instead of only reading a diff.

Proposed approach

Do not build a bespoke sandboxing/port-proxy layer — evaluate and integrate an existing hosted or self-hostable preview-environment provider rather than reimplementing container orchestration and port publishing from scratch. The work here is the integration, not a new sandbox runtime.

A. Provider evaluation (spike)

Compare 2-3 concrete options against Factory's actual requirements: must run inside the same fleet-node model (local broker or hosted relay node), must be reachable long enough to survive an implementer→reviewer/babysitter handoff (a preview instance's lifetime should track the issue's in-flight lifetime, not one agent's process lifetime), and must produce a URL that can be safely posted to a Slack thread and PR comment (i.e., some access-control story, not a bare open port on the internet).

B. Node-level plumbing

Extend createFactoryNodeDefinition() (src/node/factory-node.ts) so a node can advertise the chosen provider's availability, and extend AgentSpec (src/ports/fleet.ts) with an optional preview reference (URL + expiry/lifetime) that's set once the underlying dev command starts and torn down when the issue reaches a terminal state. Reuse the existing terminal-state plumbing (pr-human-review-terminal / pr-done-terminal in src/orchestrator/factory.ts) as the teardown trigger so preview environments don't leak the way .factory-worktrees did (see issue #123 for exactly that failure mode with worktrees — the same "clean up on terminal state, sweep orphans on daemon startup" pattern applies here).

C. Surfacing the URL

Once available, the URL should reach: the task template (renderAgentTask() in src/dispatch/templates.ts, via a new optional previewUrl field on RenderAgentTaskInput, following the same additive pattern as the existing slackDispatchThread field), the Slack dispatch thread, and the PR body/description, so a human reviewing the PR can click through without pulling the branch locally.

D. Verification hook (depends on the feature/test map, #128/#131)

Once a feature's manifest entry (from #128) says the touched surface is UI-facing and needs a running instance to verify, the task template should instruct the agent to drive its own existing computer-use/browser tool against the preview URL before reporting readiness — Factory does not need to build browser automation itself; Claude Code and Codex already have this capability. Factory only needs to (1) have a URL to hand them and (2) know, from the manifest, when to ask for it. This should be wired in as an addition to #131's task-template changes once a previewUrl exists on the input, rather than duplicating verify-instruction logic here.

Acceptance criteria

  • A concrete provider decision is documented with the evaluation criteria above answered for each candidate.
  • A dispatched implementer whose checkout starts a dev server gets back a reachable URL, propagated to AgentSpec and rendered into the task template, the Slack thread, and the PR.
  • The preview instance is torn down when the issue reaches its terminal state (Human Review or Done) and a startup sweep reaps orphans the same way factory reap-orphans does for stale processes (src/orchestrator/reaper.ts) — do not reuse factory reap-orphans directly since it's process-PID-scoped, but mirror its "identity-checked, only touch what we own" guard.
  • No preview URL is ever posted to a channel without whatever access control the chosen provider offers (document the guarantee explicitly).

Notes for the implementer

Related: #128 (feature/test map this feeds verification decisions from), #131 (task-template consumption this hands the preview URL to)

Fixes #129


Summary by cubic

Adds issue-lifetime live previews via Tailscale Serve. A supervised dev command publishes a tailnet-only URL shown in agent tasks, Slack, and PRs; previews are created before dispatch, pinned to a capable node, and cleaned up on terminal states and by startup/periodic sweeps. Fixes Linear #129.

  • New Features

    • Config: preview with provider tailscale-serve and enforced access: tailnet; per-repo services (port, optional portSpan/httpsPort, startCommand), optional registryPath and tailscaleBinary; workspace+node config is merged and passed through the CLI to the fleet.
    • Node/Fleet/Orchestrator: nodes advertise preview:tailscale-serve; internal and relay fleets implement createPreview, removePreview, and reapPreviews, routing actions to the owning node with exact-match teardown; orchestrator creates previews pre-dispatch, selects a capable node, pins the team, posts the URL, rejects non-tailnet outputs, and runs startup and periodic preview sweeps; dispatch results include previews for consumers.
    • Process + Provider: PreviewProcessSupervisor runs and proves the repo’s foreground dev command, tracks identity, and reaps orphans; TailscalePreviewManager manages Serve routes with a locked file-backed registry, safe HTTPS port allocation, target-port probing, published-ready checks, and preservation of unrelated Serve/Funnel routes; fail-closed.
    • Task/Docs/Tests: task template shows the URL/command/target port and adds a reviewer hint to map PR diffs to manifest entries that require a running instance; README adds “Tailnet live previews”; provider decision documented; feature catalog updated; extensive unit tests plus scripts/verify-tailscale-preview-e2e.mjs.
  • Migration

    • Install and sign in to Tailscale on nodes. Configure preview.services in factory.node.json (and on the control plane if separate). Start Factory; preview-capable nodes advertise preview:tailscale-serve. Factory creates a tailnet-only URL per issue and repo.

Written for commit 677667b. Summary will update on new commits.

Review in cubic

@kjgbot

kjgbot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Babysitter handoff: ready for human review

PR #152 is cleanly mergeable against current main at head 2ff18f7.

Validation:

  • GitHub CI package: passed (build, feature-map check, 1,089-test suite, packed E2E lifecycle, attestation, package contents)
  • Cubic AI review: passed
  • Review threads: none open

The babysitter pass hardened the preview integration for concurrent issue ports, workspace-fenced orphan sweeping, crash-safe pending routes, Funnel rejection, remote reviewer/babysitter handoff, existing Slack threads, and teardown-before-agent-release.

Documented tradeoff: Tailscale owns the issue-lifetime route; the dispatched agent owns and must keep the repository dev server running. Happy to discuss that boundary or any of the lifecycle/security choices before approval.

@kjgbot

kjgbot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Factory PR babysitter handoff

PR #152 is ready for human review on head 3dbe6795a898d2c96cee10bfc8f626453bf44059.

  • The branch is reconciled with current main (3164bb1527c8cc792da5742fbbf40b721ffdd020) and GitHub reports it mergeable/CLEAN.
  • Tailscale Serve is documented against Cloudflare Tunnel + Access and ngrok; the node advertises the provider and Factory persists an issue-lifetime preview reference across implementer/reviewer/babysitter handoffs.
  • The tailnet-only URL reaches agent tasks, manifest-driven browser guidance, Slack, and PR bodies. Funnel/public routes are rejected before publication.
  • Terminal cleanup runs before agent release, and startup sweeping is fenced by Factory marker, workspace namespace, owner, route port, and live upstream identity.
  • Validation on this exact head: build; feature-map check (241 features / 19 categories, no advisories); full Vitest suite (54 files, 1,095 tests); packed E2E (9 checks); package dry run. Hosted package CI and Cubic both pass.
  • Review state: no review comments or unresolved threads.

I did not merge. Happy to discuss the deliberate v1 boundary where Tailscale owns the persistent route while the dispatched agent owns and keeps the repository dev server running through the handoff.

@khaliqgant

Copy link
Copy Markdown
Member

Was this feature fully proven end to end?

@kjgbot
kjgbot merged commit aeec56c into main Jul 21, 2026
2 checks passed
@kjgbot
kjgbot deleted the factory/129-agentworkforce-factory-fbac5605 branch July 21, 2026 10:13
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.

Give dispatched agents a live preview environment instead of only a local checkout

2 participants