Fuzzball Documentation
Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage

Connecting to Workflow Services

The fuzzball workflow connect command provides a convenient way to interact with a service running in a workflow. Depending on the service definition, it can either open the service’s public endpoint in a web browser or run a client program locally that connects to the service.

Usage

$ fuzzball workflow connect WORKFLOW_ID [flags] [-- COMMAND...]

Arguments:

ArgumentRequiredDescription
WORKFLOW_IDYesThe ID of a running workflow
COMMAND...NoArguments forwarded to the client program when the service is configured for client-script mode

Flags:

FlagDescription
--nameName of the service to connect to. If omitted and the workflow defines a single service, that service is selected automatically; otherwise the CLI prompts interactively.
--endpointName of the service endpoint to use. If omitted and the service defines a single endpoint, that endpoint is selected automatically; otherwise the CLI prompts interactively.

The command waits for the target service to become ready (its readiness probe must pass) before opening the browser or launching the client program. While waiting, container logs for the service are streamed to the terminal.

Connection modes

fuzzball workflow connect supports two modes, selected automatically based on the workflow definition:

Browser mode

If the service has no fuzzball.io/connect/<service-name> annotation declared on the workflow, the command resolves the URL of the selected endpoint and opens it in the default web browser. This is the simplest mode and is well suited to services that expose a web UI such as Jupyter or RStudio.

Client-script mode

If the workflow declares an annotation of the form fuzzball.io/connect/<service-name> whose value is a command to run inside the service container, that command is executed and its standard output is treated as a client script. The script is saved to a temporary file, made executable, and run locally. Any extra arguments supplied after -- on the fuzzball workflow connect command line are forwarded to the script.

This mode is useful when connecting to a service requires more than just a URL — for example, to fetch a generated access token or TLS configuration and then launch a local client with the right parameters.

Examples

Connect to the only service in a workflow (auto-select service and endpoint):

$ fuzzball workflow connect <workflow-id>

Connect to a specific service by name:

$ fuzzball workflow connect --name jupyter <workflow-id>

Select a specific endpoint within a service:

$ fuzzball workflow connect --name api-server --endpoint grpc <workflow-id>

Forward arguments to a client script (client-script mode):

$ fuzzball workflow connect --name code-server <workflow-id> -- --workspace /home/user/project

Connecting at workflow start

The start commands accept a --connect flag that starts the workflow and then runs workflow connect against the new workflow as soon as it is ready.

$ fuzzball workflow start --connect my-workflow.fuzz
$ fuzzball application start --connect <application>
$ fuzzball workflow-template start --connect <template>

When invoked this way, and the selected service has a fuzzball.io/connect/<service-name> annotation, the CLI prompts for arguments to pass to the client script.

Configuring a service for client-script mode

To enable client-script mode for a service, add a workflow-level annotation whose key is fuzzball.io/connect/<service-name> and whose value is the command to execute inside the service container. The command must write the client script to standard output. The script itself runs locally on the user’s machine.

Runtime variables available in the service container

When the connect command runs inside the service container, Fuzzball injects a number of environment variables that can be used to render runtime values into the generated client script. The most useful ones are:

VariableDescription
FB_WORKFLOW_IDID of the running workflow
FB_ACCOUNT_IDID of the account that owns the workflow
FB_JOB_NAMEName of the service (matches the service key in the workflow)
FB_ENDPOINT_URLPublic URL of the first endpoint of the service (e.g. https://...)
FB_ENDPOINT_HOSTHostname portion of the first endpoint URL
FB_ENDPOINT_PATHPath portion of the first endpoint URL
FB_ENDPOINT_URL_<NAME>Public URL of the endpoint named <NAME> (uppercased)
FB_ENDPOINT_HOST_<NAME>Hostname portion of the endpoint named <NAME>
FB_ENDPOINT_PATH_<NAME>Path portion of the endpoint named <NAME>

Any user-defined env entries declared on the service are also available to the connect command. Combined with files embedded via the service’s files section, this is enough to generate a fully parameterized client script without baking values into the container image.

Example: vLLM server with a generated agent client

The example below defines a vLLM service that exposes an OpenAI-compatible API on a public HTTP endpoint and ships a connect command that emits a shell script to launch a local coding agent (opencode) wired up to the running model.

The service uses two embedded files:

  • /app/agent-script.tmpl — the template of the local script, with placeholders such as __ENDPOINT_URL__ and __WORKFLOW_ID__ that will be substituted at connect time.
  • /app/generate-agent-command.sh — invoked by the fuzzball.io/connect/vllm annotation, it sed-substitutes the placeholders with values taken from the runtime environment (FB_ENDPOINT_URL, FB_WORKFLOW_ID, and the user-defined VLLM_API_KEY / VLLM_MODEL) and writes the resulting script to standard output.
version: v1

volumes:
  data:
    reference: volume://user/ephemeral

annotations:
  fuzzball.io/connect/vllm: "sh /app/generate-agent-command.sh"

services:
  vllm:
    image:
      uri: docker://vllm/vllm-openai:latest
    persist: true
    network:
      ports:
        - name: openai-api
          port: 8000
          protocol: tcp
      endpoints:
        - name: openai-vllm
          port-name: openai-api
          protocol: http
          type: subdomain
          scope: public
    mounts:
      data:
        location: /home/vllm
    resource:
      cpu:
        cores: 8
      memory:
        size: 32GB
      devices:
        nvidia.com/gpu: 1
      annotations:
        nvidia.com/gpu.model: "H100"
    env:
      - HOME=/home/vllm
      - VLLM_API_KEY=replace-me
      - VLLM_MODEL=meta-llama/Llama-3.1-8B-Instruct
    script: |
      #!/bin/sh
      cd $HOME && exec vllm serve "$VLLM_MODEL" \
        --tensor-parallel-size 1 \
        --host 0.0.0.0 \
        --port 8000
    readiness-probe:
      tcp-socket:
        port: 8000
      initial-delay-seconds: 60
      period-seconds: 30
      failure-threshold: 60
      success-threshold: 1
    files:
      /app/agent-script.tmpl: |-
        #!/usr/bin/env bash
        if [ "$#" -ne 1 ]; then
          echo "project directory path is missing"
          exit 1
        fi
        mkdir -p /tmp/__WORKFLOW_ID__
        base64 -w0 <<EOF |
        {
          "\$schema": "https://opencode.ai/config.json",
          "model": "local/__MODEL__",
          "provider": {
            "local": {
              "npm": "@ai-sdk/openai-compatible",
              "name": "LocalAI (local)",
              "options": {
                "baseURL": "__ENDPOINT_URL__v1",
                "apiKey": "__API_KEY__",
                "headers": {
                  "keep_alive": -1
                }
              },
              "models": {
                "__MODEL__": {
                  "name": "__MODEL__",
                  "options": {
                    "max_tokens": 16000
                  }
                }
              }
            }
          }
        }
        EOF
        cat > /tmp/__WORKFLOW_ID__/agent.config
        docker run --rm -it \
          -v $1:/home/agent/workspace \
          --entrypoint /bin/bash \
          docker/sandbox-templates:opencode \
          -c "mkdir -p /home/agent/.config/opencode && \
              cat >/home/agent/.config/opencode/opencode.json \
              <(echo $(cat /tmp/__WORKFLOW_ID__/agent.config) | base64 -d) && \
              exec opencode"

      /app/generate-agent-command.sh: |-
        sed -e "s|__ENDPOINT_URL__|$FB_ENDPOINT_URL|g" \
            -e "s|__API_KEY__|$VLLM_API_KEY|g" \
            -e "s|__MODEL__|$VLLM_MODEL|g" \
            -e "s|__WORKFLOW_ID__|$FB_WORKFLOW_ID|g" \
            /app/agent-script.tmpl

A user can then start the workflow and immediately drop into the local agent, passing the project directory as the only positional argument forwarded to the script:

$ fuzzball workflow start --connect vllm.fuzz
# (prompt) Enter arguments to pass to client script: /home/user/projects/myapp

Or, against an already-running workflow:

$ fuzzball workflow connect --name vllm <workflow-id> -- /home/user/projects/myapp

In both cases Fuzzball:

  1. Waits for the vllm service readiness probe to pass.
  2. Runs sh /app/generate-agent-command.sh inside the vllm container.
  3. Captures the substituted script from standard output, writes it to a temporary file with executable permissions, and runs it locally with /home/user/projects/myapp as $1.

Pattern recap

  1. Declare the annotation fuzzball.io/connect/<service-name> on the workflow.
  2. Embed any helper files (template, substitution script, certificates, etc.) under the service’s files section so they are available inside the container.
  3. The annotation command should print the final, ready-to-run client script to standard output. It must not depend on standard input, since the connect machinery does not forward a TTY to the in-container command.
  4. Use FB_* environment variables and any service-level env entries to inject runtime values into the generated script.

Services without this annotation always use browser mode. See the workflow syntax reference for the full set of services and network configuration options, and the workflow endpoints page for details on how endpoint URLs are constructed.