# Dependency Patterns (depends_on)

How to express startup ordering and runtime dependencies between processes.

## The Four Conditions

| Condition | Meaning | Best for |
|---|---|---|
| `process_started` | Dependency has spawned (PID exists, may not be ready) | Coarse ordering when readiness doesn't matter |
| `process_healthy` | Dependency's `readiness_probe` passes | Runtime services that must be queryable |
| `process_completed` | Dependency exited (any code) | One-shot tasks that may fail |
| `process_completed_successfully` | Dependency exited with code 0 | One-shot init that must succeed |

## Pattern 1 — Web app + companion daemon

A common pattern: a web service + a worker daemon that talks to the same DB or queue. Daemon should start AFTER the web app has its DB connection pool warm.

```yaml
processes:
  webapp:
    command: "uv run python manage.py serve --port 8000"
    working_dir: "X:/Forge/MyApp"
    readiness_probe:
      http_get: { host: localhost, port: 8000, path: / }
      initial_delay_seconds: 10
    availability: { restart: always }

  worker:
    command: "uv run python -m myapp.worker"
    working_dir: "X:/Forge/MyApp"
    depends_on:
      webapp:
        condition: process_healthy
    availability: { restart: always }
```

Result: `worker` doesn't start until `webapp`'s readiness probe passes. If `webapp` restarts, `worker` keeps running (depends_on is a startup ordering rule, not a runtime tether).

## Pattern 2 — Three-tier chain

Web app + background daemon + audit watcher (Axiom pattern):

```yaml
processes:
  app:
    command: "..."
    readiness_probe: { ... }

  app-daemon:
    command: "..."
    depends_on:
      app:
        condition: process_healthy

  app-feedback:
    command: "..."
    depends_on:
      app:
        condition: process_started   # weaker — just needs app's pid to exist
```

## Pattern 3 — Database before app

Postgres in the same PC stack, app depends on it:

```yaml
processes:
  postgres:
    command: "postgres -D /var/lib/pg"
    readiness_probe:
      tcp_socket: { host: localhost, port: 5432 }
      initial_delay_seconds: 3

  migrate:
    command: "alembic upgrade head"
    working_dir: "X:/MyApp"
    depends_on:
      postgres:
        condition: process_healthy
    availability:
      restart: exit_on_failure   # one-shot; if it fails, the whole stack fails

  app:
    command: "uvicorn main:app"
    working_dir: "X:/MyApp"
    depends_on:
      migrate:
        condition: process_completed_successfully
      postgres:
        condition: process_healthy
    availability: { restart: always }
```

`migrate` runs once, must succeed. `app` waits for both `migrate` (success) and `postgres` (healthy).

## Pattern 4 — Tunnel that depends on the service it tunnels

E.g. Cloudflare tunnel exposing a local service:

```yaml
processes:
  mcp-server:
    command: "fastmcp serve --port 8000"
    readiness_probe:
      http_get: { host: localhost, port: 8000, path: / }
      initial_delay_seconds: 5

  mcp-tunnel:
    command: '"C:/Program Files/cloudflared/cloudflared.exe" tunnel run my-tunnel'
    depends_on:
      mcp-server:
        condition: process_healthy   # don't open tunnel until server is ready
    availability:
      restart: always
      backoff_seconds: 5
      max_restarts: 50               # tunnels can disconnect, allow many retries
```

## Pattern 5 — Static (one-time) setup task

```yaml
processes:
  fetch-secrets:
    command: "python scripts/fetch_secrets.py"
    availability:
      restart: exit_on_failure   # must complete; stop the project if it fails
    # No readiness_probe — task either completes or doesn't

  app:
    command: "..."
    depends_on:
      fetch-secrets:
        condition: process_completed_successfully
```

## Cycle Detection

PC detects cycles at startup. This fails immediately:

```yaml
processes:
  a: { depends_on: { b: { condition: process_started } } }
  b: { depends_on: { a: { condition: process_started } } }
# Error: dependency cycle detected: a -> b -> a
```

## What `depends_on` Does NOT Do

- **Does not** restart dependents when a dependency restarts. If `webapp` crashes and recovers, `worker` doesn't automatically restart.
- **Does not** stop a dependent when the dependency stops. You'll need to model this with `restart: exit_on_failure` and probes.
- **Does not** enforce shutdown order (PC shuts down in any order unless `--ordered-shutdown` flag is used).

For runtime coupling, the dependent process needs application-level reconnect/retry logic.

## Shutdown Ordering

By default PC shuts processes down in any order. For services with stateful deps, use:

```bash
process-compose down --ordered-shutdown
# Stops in reverse dependency order: dependents first, then dependencies
```

## See Also

- `probe-patterns.md` for crafting good `readiness_probe`s (without these, `process_healthy` is useless)
- `schema-reference.md` for full availability/shutdown field semantics
