Files
zopu-code/docs/DOGFOOD_V0.md
-Puter a11ed988fb feat(dogfood): integrate and document complete v0 loop
Extend the smoke harness with Orb execution-plane preflight probes
(Docker, OpenCode, Gitea reachability/creds/PR-creation, repository
writable) that report BLOCKED with exact reasons when dependencies are
absent. Add scripts/orb-project-run.ts as the missing connection point
between OrbProjectManager and the Orb/Git lifecycle. Write
docs/DOGFOOD_V0.md covering architecture, startup order, env vars,
local/server flows, demo procedure, health checks, failure recovery,
cleanup, known limitations, and next milestones.

The smoke harness now runs 18 preflight checks across control-plane and
Orb execution-plane layers, all with stable markers and sanitized JSON
reports. The orb:run script (ORB_RUN=1) drives one issue through the
full Orb project-manager lifecycle.
2026-07-25 02:15:41 +05:30

19 KiB

Zopu Dogfood v0 — Complete Loop

Integration and operations document for the first end-to-end dogfood loop. Grounded in the merged code on dogfood/v0. Read alongside docs/PRODUCT.md, docs/DESIGN.md, docs/TECH.md, and docs/SMOKE.md.

1. What this loop proves

A user sends a message in the web Work OS. Zopu (the global planning agent) extracts a Signal, routes it to a ProjectIssue, and the user starts work. The issue runs inside an isolated execution environment (Orb: Docker + AgentOS + OpenCode), produces a verified change on a work branch, opens a Gitea pull request, and reports the outcome back into durable state. A GLM planner/reviewer independently approves the result.

The loop touches every layer: control plane (Convex), agent service (Flue), execution plane (Orb runtime), model gateway, Git/Gitea, and the web UI.

2. Architecture summary

┌──────────────────────────────────────────────────────────────────┐
│  Web Work OS (apps/web)                                          │
│  Conversation composer → Zopu agent → Signals → Work Units       │
│  Start button → projectIssues.begin + Flue project-manager send  │
└──────────────┬───────────────────────────────────────────────────┘
               │
    ┌──────────▼──────────┐
    │ Convex (control plane) │
    │ projects, projectIssues, │
    │ signals, events, artifacts│
    └──────────┬──────────┘
               │
    ┌──────────▼──────────────────────┐
    │ Flue agent service (:3583)       │
    │ ┌─────────────────────────────┐  │
    │ │ zopu agent                  │  │
    │ │ (global conversation →      │  │
    │ │  Signal → issue routing)    │  │
    │ └─────────────────────────────┘  │
    │ ┌─────────────────────────────┐  │
    │ │ project-manager agent       │  │
    │ │ (issue-scoped work,          │  │
    │ │  AgentOS sandbox,            │  │
    │ │  finalize_gitea_lifecycle)   │  │
    │ └─────────────────────────────┘  │
    └──────────┬──────────────────────┘
               │
    ┌──────────▼──────────────────────────────────────┐
    │ Orb Runtime (Docker + AgentOS + OpenCode)        │
    │ OrbProjectManager orchestrates:                  │
    │   createOrb → prepareRepository → openSession    │
    │   → sendTask (context pack) → model turn          │
    │   → WORK_COMPLETE / NEEDS_INPUT detection         │
    │   → Git lifecycle (commit, push, Gitea PR)        │
    │ Scripts: scripts/orb-proof.ts (3-stage proof)     │
    │          scripts/orb-project-run.ts (full issue)  │
    └──────────┬──────────────────────────────────────┘
               │
    ┌──────────▼──────────┐    ┌───────────────────┐
    │ Model gateway (CPA)  │    │ Gitea (self-hosted)│
    │ OpenAI-compatible    │    │ PRs, branches      │
    │ minimax-m3 (worker)  │    │                    │
    │ glm-5.2 (planner)    │    │                    │
    └──────────────────────┘    └───────────────────┘

Two execution paths

The codebase has two ways to run issue-scoped work, both merged and functional:

  1. Flue project-manager path (the web start button). The web UI calls projectIssues.begin (Convex mutation) then sends a message to the project-manager Flue agent (apps/web/src/hooks/use-project-workspace.ts). The agent works inside an in-process AgentOS sandbox and calls the finalize_gitea_lifecycle action (packages/agents/src/actions/finalize-gitea-lifecycle.ts) to open a Gitea PR.

  2. Orb project-manager path (Docker-isolated). The OrbProjectManager (packages/agents/src/orb/orb-project-manager.ts) orchestrates a Docker sandbox + AgentOS VM + OpenCode session per issue. It projects raw OrbEvents into durable ProjectRunEvents, detects WORK_COMPLETE: / NEEDS_INPUT: markers, and drives the Git lifecycle via git-adapter.ts. The entry point is scripts/orb-project-run.ts.

The Orb path provides stronger isolation (full Docker container vs. in-process VM) and is the forward path for the dedicated server. The Flue path is the current local MacBook flow.

Key files

Component File
Zopu agent (global routing) packages/agents/src/agents/zopu.ts
Project-manager agent (Flue) packages/agents/src/agents/project-manager.ts
Signal routing tools packages/agents/src/tools/signals.ts
Orb runtime packages/agents/src/orb/runtime.ts
Orb project-manager packages/agents/src/orb/orb-project-manager.ts
Orb adapter packages/agents/src/orb/orb-adapter.ts
Git adapter (Gitea lifecycle) packages/agents/src/orb/git-adapter.ts
Docker sandbox packages/agents/src/orb/docker-sandbox.ts
OpenCode config injection packages/agents/src/orb/opencode-config.ts
Context pack assembly packages/agents/src/orb/context-pack.ts
Event projection packages/agents/src/orb/project-events.ts
finalize_gitea_lifecycle action packages/agents/src/actions/finalize-gitea-lifecycle.ts
Smoke harness scripts/zopu-smoke.ts
Orb proof fixture scripts/orb-proof.ts
Orb issue driver scripts/orb-project-run.ts
Web start hook apps/web/src/hooks/use-project-workspace.ts
Convex projectIssues packages/backend/convex/projectIssues.ts
Convex signals packages/backend/convex/signals.ts
Daemon runtime apps/daemon/src/runtime.ts

3. Service startup order

Services must start in dependency order. The daemon owns the in-process RivetKit engine that the agent service depends on.

Local MacBook development

# 1. Convex dev server (control plane + generated API)
bun run dev:server

# 2. Agent daemon (Effect daemon + RivetKit engine + AgentOS)
bun run dev:daemon

# 3. Flue agent service (loads zopu + project-manager agents on :3583)
bun run dev:agents

# 4. Web app (Vite on :5173)
bun run dev:web

# 5. (Optional) Docker Desktop — required for the Orb path
open -a Docker

Dedicated server

See deploy/zopu-runtime/README.md. The systemd order is:

systemctl start zopu-daemon   # starts RivetKit engine on :6420
sleep 3
systemctl start zopu-agent    # starts Flue on :3583

The daemon must be active before the agent service because registry.start() boots the in-process RivetKit engine that the agent's createClient(RIVET_ENDPOINT) connects back to.

4. Required environment variables

All groups are documented in deploy/zopu-runtime/.env.template. Parsed by packages/env/src/agent.ts (parseAgentEnv) and packages/env/src/server.ts.

Convex (control plane)

Variable Required Description
CONVEX_URL Yes Convex deployment URL
CONVEX_SITE_URL Yes Convex site URL (for actions)
SITE_URL Yes Web app origin (CORS / redirects)

Self-hosted Git / Gitea

Variable Required Description
GITEA_URL For PR lifecycle Gitea base URL (default: https://git.openputer.com)
GITEA_TOKEN For PR lifecycle Gitea API token for branch push + PR creation

Model gateway (CPA)

Variable Required Description
AGENT_MODEL_PROVIDER Yes Provider identifier (e.g. cheaptricks)
AGENT_MODEL_NAME Yes Model name (e.g. glm-5.2)
AGENT_MODEL_API Yes Must be openai-completions
AGENT_MODEL_BASE_URL Yes OpenAI-compatible /v1 endpoint
AGENT_MODEL_API_KEY Yes Gateway API key
AGENT_MODEL_CONTEXT_WINDOW Yes Context window in tokens
AGENT_MODEL_MAX_TOKENS Yes Max output tokens

The smoke worker uses minimax-m3; the planner/reviewer uses glm-5.2. Both must be in the CPA model catalog.

AgentOS / RivetKit

Variable Required Description
RIVET_ENDPOINT No RivetKit engine endpoint (default: http://localhost:6420)

registry.start() boots an in-process engine (envoy mode) backed by a native Rust sidecar. No separate Rivet Engine process is required for single-node operation.

Zopu agent service (Flue)

Variable Required Description
FLUE_DB_TOKEN Yes Flue persistence adapter token
PORT Yes Flue HTTP listen port (default: 3583)

Daemon identity

Variable Required Description
DAEMON_ID Yes Unique daemon identifier
DAEMON_NAME Yes Human-readable name
DAEMON_VERSION Yes Daemon version string
DAEMON_HEARTBEAT_MS Yes Heartbeat interval (default: 15000)
DAEMON_COMMAND_LEASE_MS Yes Command lease duration (default: 60000)

Docker sandbox (Orb path)

Variable Required Description
ORB_DOCKER_IMAGE No Docker image override (default: rivetdev/sandbox-agent:0.5.0-rc.2-full)
ORB_DOCKER_WORKSPACE No Host workspace root (default: /tmp/orb-workspaces)

Docker socket access is via group membership on the dedicated server.

Service authentication

Variable Required Description
AUTH_SECRET If needed Better Auth / Convex JWT secret

5. Local MacBook development flow

# Clone and install
git clone <repo> zopu && cd zopu
git checkout dogfood/v0
bun install

# Start services in order (see startup order above)
bun run dev:server   # Convex
bun run dev:daemon   # daemon + RivetKit engine
bun run dev:agents   # Flue agents
bun run dev:web      # web UI

# Verify the loop is wired
bun run smoke:zopu

# Run the Orb proof (requires Docker + gateway)
ORB_PROOF=1 \
ORB_GATEWAY_API_KEY=... \
ORB_GATEWAY_BASE_URL=https://ai.example.com/v1 \
ORB_GATEWAY_MODEL=glm-5.2 \
ORB_GATEWAY_PROVIDER=cheaptricks \
bun run orb:proof

# Drive one issue through the Orb project-manager (requires Docker + gateway)
ORB_RUN=1 \
ORB_GATEWAY_API_KEY=... \
ORB_GATEWAY_BASE_URL=https://ai.example.com/v1 \
ORB_GATEWAY_MODEL=glm-5.2 \
ORB_GATEWAY_PROVIDER=cheaptricks \
bun run orb:run

6. Dedicated-server runtime flow

The dedicated server runs only the execution plane. Convex is already deployed as the control plane.

# SSH into the fresh Debian 12 server as root
export ZOPU_REPO_URL="ssh://git@git.openputer.com:2222/puter/zopu-code.git"
export ZOPU_REPO_BRANCH="dogfood/v0"
bash deploy/zopu-runtime/bootstrap.sh

# Edit .env with real values
nano /opt/zopu/.env

# Start services
systemctl start zopu-daemon
sleep 3
systemctl start zopu-agent

# Enable timers
systemctl enable --now zopu-health.timer zopu-docker-cleanup.timer

# Verify
/opt/zopu/deploy/zopu-runtime/scripts/health-check.sh

See deploy/zopu-runtime/README.md for update, rollback, Docker cleanup, disk monitoring, firewall, and Tailscale details.

7. Demo procedure

Prerequisites: all services running, a disposable test project with a connected repository source, and the issue-scoped artifact set.

  1. Open the web app (http://localhost:5173 or the Tailscale hostname).
  2. Select the test project in the Work OS.
  3. Type an actionable message in the composer (e.g. "Add a tiny status indicator to the dashboard"). Zopu creates a Signal and routes it to a ProjectIssue.
  4. The Work Unit card appears. Click Start work.
  5. The web UI calls projectIssues.begin then sends the issue to the project-manager Flue agent. The agent works inside its sandbox.
  6. Watch the Work Unit timeline for run.session_opened, run.agent_message, and run.command_executed events.
  7. When the agent emits WORK_COMPLETE:, the Git lifecycle runs (commit, push, Gitea PR).
  8. The Work Unit card shows the PR link. Click Review changes to open the Gitea PR.
  9. Merge remains a manual action.

For the Orb Docker path, replace step 4-5 with:

ORB_RUN=1 \
ORB_GATEWAY_API_KEY=... \
ORB_GATEWAY_BASE_URL=... \
ORB_GATEWAY_MODEL=glm-5.2 \
ORB_GATEWAY_PROVIDER=cheaptricks \
GITEA_URL=... GITEA_TOKEN=... \
ORB_RUN_REPOSITORY_URL=https://git.example.com/owner/repo.git \
ORB_RUN_REPOSITORY_PATH=owner/repo \
bun run orb:run

8. Health checks

Local

bun run smoke:zopu                    # full preflight (all layers)
curl -s http://localhost:5173/         # web
curl -s http://localhost:3583/         # Flue (no health endpoint by design)

Dedicated server

/opt/zopu/deploy/zopu-runtime/scripts/health-check.sh
# Probes: systemd units active, RivetKit :6420 TCP, Flue :3583 TCP, docker info

9. Logs

Local

Flue agent logs go to stdout in the bun run dev:agents terminal. Daemon logs go to the bun run dev:daemon terminal.

Dedicated server

journalctl -u zopu-daemon -f      # daemon (live)
journalctl -u zopu-agent -f       # agent (live)
journalctl -u zopu-daemon -n 100  # last 100 lines
journalctl -u zopu-health.service -n 50
journalctl -t zopu-daemon -t zopu-agent --since "1 hour ago"

10. Failure recovery

Symptom Diagnosis Recovery
ZOPU_SMOKE_CONTRACT_BLOCKED with convex failed Convex dev server not running bun run dev:server
ZOPU_SMOKE_CONTRACT_BLOCKED with flue failed Flue agent service not running bun run dev:agents
ZOPU_SMOKE_CONTRACT_BLOCKED with docker-daemon failed Docker not running Start Docker Desktop / systemctl start docker
ZOPU_SMOKE_CONTRACT_BLOCKED with gitea-creds-valid failed Missing or invalid GITEA_TOKEN Set GITEA_URL + GITEA_TOKEN in env
ORB_PROOF_BLOCKED at stage 2 AgentOS VM creation failed Check Docker daemon + rivetdev/sandbox-agent image
ORB_PROOF_BLOCKED at stage 3 Model gateway unreachable Check ORB_GATEWAY_* env and gateway connectivity
Work Unit stuck in needs-input Agent emitted NEEDS_INPUT: Resolve the question and send a follow-up message
PR not created after WORK_COMPLETE Gitea creds or repo path missing Verify GITEA_URL, GITEA_TOKEN, repository path
Daemon offline in Convex Daemon process crashed systemctl restart zopu-daemon

11. Cleanup

Local Orb artifacts

docker container prune -f --filter "label=orb"
rm -rf /tmp/orb-* /tmp/orb-proof-*

Dedicated server

/opt/zopu/deploy/zopu-runtime/scripts/docker-cleanup.sh  # prune stopped containers/images

Named volumes and running containers are never removed.

12. Known limitations

  1. Two parallel execution paths. The Flue project-manager agent (in-process AgentOS sandbox) and the OrbProjectManager (Docker + AgentOS + OpenCode) both work but are not yet unified behind a single dispatcher. The web start button uses the Flue path; the Orb path is driven by scripts/orb-project-run.ts. Unifying these behind the Work Unit start button is a forward milestone.

  2. No auto-merge. The Git lifecycle opens a pull request and stops. Merge is always a manual human action, by design.

  3. Docker not wired on the dedicated server. Docker Engine is installed and the zopu user is in the docker group, but the daemon currently uses the in-process AgentOS VM sandbox. The Orb Docker path is proven locally via scripts/orb-proof.ts but not yet the default on the server.

  4. OpenCode ACP gateway wiring. The model gateway works via OPENCODE_CONFIG_CONTENT injected through the package manifest env + OpenAI seed model remapping (see packages/agents/src/orb/opencode-config.ts and runtime.ts). The three-stage orb proof passes. This is a known working boundary, not a general-purpose provider configuration.

  5. Pre-existing check failures. The root bun run check has a pre-existing unrelated failure: formatting under repos/effect and dual Hono patch versions (4.12.30 vs 4.12.31). These are not related to the dogfood loop and should not be fixed in this lane.

  6. Single-node only. The dedicated server runs one node with an in-process RivetKit engine. Multi-node coordination is out of scope.

  7. Web app not deployed on the server. The server runs the execution plane only; the web frontend runs locally or is deployed separately.

  8. No production generated-app hosting. Only the execution plane runs on the dedicated server.

13. Next three incremental milestones

  1. Unify the execution dispatcher. Route the web Work Unit start button through a single dispatcher that selects the Orb Docker path (when Docker is available) or the Flue in-process path (fallback). This eliminates the two-parallel-paths limitation and makes the Orb path the default on the dedicated server. Touch point: apps/web/src/hooks/use-project-workspace.ts start action + a new Convex action that invokes OrbProjectManager.

  2. Stream Orb events into Convex ProjectEvents. The OrbProjectManager.onProjectEvent callback currently writes to an in-memory array. Wire it to persist ProjectRunEvents into the Convex projectEvents table so the web Work Unit timeline shows live Orb execution progress. Touch point: packages/agents/src/orb/orb-project-manager.ts + a new Convex mutation mirroring agentWorkspace.recordGiteaLifecycle.

  3. Durable run resume after daemon restart. OrbProjectManager holds active runs in memory. Add a Convex-backed run ledger so an active Orb run can resume after a daemon restart instead of being lost. This moves the run-state machine from in-memory to durable, matching the OrbState / RunState transitions in packages/agents/src/orb/domain.ts.