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.
380 lines
19 KiB
Markdown
380 lines
19 KiB
Markdown
# 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](./PRODUCT.md), [docs/DESIGN.md](./DESIGN.md), [docs/TECH.md](./TECH.md), and [docs/SMOKE.md](./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 `ProjectRunEvent`s, 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
|
|
|
|
```bash
|
|
# 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](../deploy/zopu-runtime/README.md). The systemd order is:
|
|
|
|
```bash
|
|
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](../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
|
|
|
|
```bash
|
|
# 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.
|
|
|
|
```bash
|
|
# 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](../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:
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
/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
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
docker container prune -f --filter "label=orb"
|
|
rm -rf /tmp/orb-* /tmp/orb-proof-*
|
|
```
|
|
|
|
### Dedicated server
|
|
|
|
```bash
|
|
/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 `ProjectRunEvent`s 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`.
|