Installing & Running Agent Nodes

August 4, 2026 · View on GitHub

af install and af run turn a published agent-node repository into a locally running agent that connects to your control plane. Nodes are described by an agentfield-package.yaml manifest, their dependencies (Python packages and other agent nodes) are resolved automatically, and the secrets they need are stored encrypted and injected only at runtime.

# Install a node straight from GitHub (pulls in its node dependencies too)
af install https://github.com/Agent-Field/pr-af

# Start it — you'll be prompted once for any missing required secrets
af run pr-af

Nodes can be written in Python (venv + pip) or Go (compiled binary). af install/af run pick the right toolchain per node; the port, health check, secret injection, and control-plane wiring are identical either way. See Language: Python or Go for the Go specifics.

Everything lives under ~/.agentfield/ (override with AGENTFIELD_HOME):

~/.agentfield/
├── installed.yaml          # registry of installed nodes + runtime state
├── packages/<node>/        # the installed node + its Python venv or built Go binary
├── logs/<node>.log         # process logs (af logs <node>)
├── keyring/master.key      # 0600 — local key that decrypts your secrets
└── secrets/
    ├── global.enc          # secrets shared across all nodes (encrypted)
    └── <node>.enc          # secrets scoped to one node (encrypted)

The manifest: agentfield-package.yaml

Every installable node has this file at its repo root. Only name, version, and a way to start (an entrypoint.start, a top-level main.py for a Python node, or a go.mod for a Go node) are required; everything else is optional.

config_version: v1            # manifest *schema* version (see below). Omit = v0 (legacy).
name: pr-af
version: 0.1.0                # the node's own release version — unrelated to config_version
description: Opens draft PRs from a task description
author: Agent-Field

# How to launch the node. The first token is run inside the node's venv when it
# is "python"/"python3". Omit this only if the repo has a top-level main.py.
entrypoint:
  start: python -m pr_af.app
  healthcheck: /health        # polled after launch to confirm readiness

agent_node:
  node_id: pr-af
  default_port: 8004

dependencies:
  python:                     # extra pip installs (requirements.txt is also honored)
    - httpx>=0.27
  nodes:                      # other agent nodes this one calls — installed recursively
    - af://registry/swe-planner

# Variables the node needs. Required ones are prompted for on first run and
# remembered (encrypted). type: secret hides the input and stores it encrypted.
user_environment:
  required:                     # every one of these must be set
    - name: GH_TOKEN
      description: GitHub token
      type: secret
      scope: global           # global (default) = shared across nodes; node = this node only
  require_one_of:               # each group needs AT LEAST ONE option set
    - id: llm_provider
      description: an LLM provider key
      options:
        - name: ANTHROPIC_API_KEY
          description: Anthropic key (Claude)
          type: secret
        - name: OPENROUTER_API_KEY
          description: OpenRouter key (DeepSeek/Qwen/Llama/…)
          type: secret
  optional:
    - name: PR_AF_MODEL
      description: Override the default model
      default: openrouter/moonshotai/kimi-k2

A published, real-world manifest to copy from: Agent-Field/SWE-AF agentfield-package.yaml.

Manifest schema version (config_version)

config_version declares which version of the manifest format your file was written against, so the control plane knows how to read it as the format evolves — you're never locked into whatever shape shipped the day you authored the file.

  • It is not the same as version. version is your node's own release (semver of the agent); config_version is the schema version of this file.
  • Omitting it means v0 — the original, pre-versioning format. Existing manifests keep working untouched.
  • The current version is v1. New manifests should set config_version: v1.
  • The v prefix is optional and case-insensitive (v1, V1, and 1 are equal). A value the control plane doesn't recognize (a typo, or a version newer than your af binary understands) fails the install with a clear message rather than being silently mis-read — upgrade af to install a node authored for a newer schema.

When does config_version get bumped? Only for breaking changes to the format — a field renamed or removed, or its shape/meaning changed such that an old reader would mis-handle a new file (or vice-versa). Adding a new optional field is not breaking and does not bump the version: unknown keys are ignored by older readers, and newer readers fall back to defaults. So most format growth needs no bump at all; you only stamp a new config_version when the structure of an existing config actually changes.

config_versionReader behavior
absent / v0Legacy format, read leniently. Every field below is optional except the manifest basics.
v1Same fields as v0, now explicitly versioned. Current default for new manifests. Later additive keys (e.g. language, entrypoint.build for Go nodes) live here too — they did not bump the version.

require_one_of — "at least one of these"

Some nodes accept alternatives — e.g. either an Anthropic key or an OpenRouter key. List them under require_one_of as a group of options. A group is satisfied as soon as one option resolves (from the process environment, the secret store, or a manifest default).

On af run, if no option of a group is set, you're asked to fill in one and leave the rest blank — the value you enter is validated and stored encrypted, exactly like a required secret. In a non-interactive session an unsatisfied group is a clean error listing the alternatives (at least one of [ANTHROPIC_API_KEY | OPENROUTER_API_KEY] is required) instead of a runtime failure inside the node.

required (all must be set), require_one_of (at least one per group), and optional (falls back to default) can all be used together in one manifest.

Python dependencies

On install, a node's Python dependencies are installed into a per-node virtual environment under ~/.agentfield/packages/<node>/venv. Sources are honored in order: requirements.txt, then pip install . for a pyproject.toml/setup.py project, then any packages listed under dependencies.python in the manifest. af run uses this venv automatically.

The venv is built with an interpreter that satisfies the node's requires-python (e.g. >=3.11). af looks for one in order: the python3/python on your PATH, then a uv-provisioned interpreter (uv downloads a standalone build if needed), then a matching pyenv version. Only if none of those yields a compatible interpreter does install fail, naming exactly how to get one.

Language: Python or Go

A node's implementation language is set by the optional top-level language field: python (the default) or go. When language is omitted, af detects a Go node by the presence of a go.mod at the package root; anything else is treated as Python. Existing Python manifests need no changes.

language: go
entrypoint:
  build: ./cmd/swe-planner   # Go package to compile at install time
  start: bin/swe-planner     # resulting binary, launched by `af run`
  healthcheck: /health

At install time a Go node is compiled, not pip-installed:

  • The go toolchain is resolved the same way a Python interpreter is: af uses the go on your PATH when it can build the module, and otherwise provisions one for you — downloading the official toolchain from go.dev/dl, verifying its published SHA256 before unpacking it, and caching it under ~/.agentfield/toolchains/<version>/ so later installs reuse it. You do not need Go installed to install a Go node. Set AGENTFIELD_DISABLE_GO_PROVISIONING=1 to turn that off, in environments that must not fetch binaries; install then fails with the same actionable "install Go" message as before.
  • A go on PATH that is older than the module's go.mod directive is not refused: since Go 1.21 the toolchain downloads and switches to the requested version itself (GOTOOLCHAIN=auto, the default), so af lets it. Only a go older than 1.21, or one pinned with GOTOOLCHAIN=local, falls through to provisioning.
  • With entrypoint.build set, af runs go build -o <start> <build>, leaving a runnable binary at the entrypoint.start path. af run launches that binary directly — same PORT, health check, secrets, and control-plane env as a Python node.
  • Alternatively, use a go run entrypoint (start: go run ./cmd/swe-planner) or omit entrypoint.start entirely (defaults to go run .). Install then only compile-checks (go build ./...) and the binary is built on launch. This is simpler but recompiles each start, so a prebuilt binary is preferred for large nodes.

A Go node reads the same runtime environment as a Python node — PORT, AGENTFIELD_SERVER, and any declared user_environment secrets are injected into the process identically, and readiness is confirmed by polling entrypoint.healthcheck.

replace directives and vendoring

Go modules that use a local replace directive pointing outside the package (e.g. replace example.com/sdk => ../../other/sdk, as the SWE-AF Go port does for the AgentField Go SDK) will not build after install, because the node is copied into ~/.agentfield/packages/<node>/ and the relative path no longer resolves. af install detects this and refuses with guidance rather than a confusing raw build failure. Fix it one of these ways:

  • Vendor the module (recommended): run go mod vendor in the node repo and commit the vendor/ directory. It ships with the package, so the build is hermetic (go build -mod=vendor) regardless of replace targets.
  • Publish/tag the replaced module and use a versioned require instead of a local replace.
  • Override at install time with AGENTFIELD_GO_REPLACE — a comma-separated list of go mod edit -replace specs applied before building, e.g. AGENTFIELD_GO_REPLACE="example.com/sdk=/abs/path/to/sdk" af install <src>.

In-tree replaces (pointing inside the package) and module-version replaces are always fine.

Node dependencies

dependencies.nodes lets one node declare that it needs others. Each entry is an installable reference:

ReferenceResolves to
af://registry/<name>https://github.com/Agent-Field/<name>
af://registry/<name>@<version>same (version constraint is recorded, not yet enforced)
https://github.com/<org>/<repo>used as-is

af install <node> installs the node and any declared node dependencies it doesn't already have, recursively. Already-installed nodes are skipped, which also breaks dependency cycles. af run <node> starts a node's installed dependencies first (in dependency order) before the node itself.

One repo, many nodes: --path

By default af install <src> looks for the agentfield-package.yaml at the root of the source (a git repo or a local directory). When a single repository ships more than one installable node — for example SWE-AF, whose advertised install is the Go node under go/, alongside a Python node that also lives at the repo root — use --path to select the subdirectory to install:

# Install the node whose manifest lives at go/agentfield-package.yaml
af install https://github.com/Agent-Field/SWE-AF --path go

# Composes with an @ref pin on the URL
af install https://github.com/Agent-Field/SWE-AF@v1.2.3 --path go

# Also works for a local source
af install ./SWE-AF --path go

The subdirectory must contain its own agentfield-package.yaml; that subtree becomes the package root that is copied to ~/.agentfield/packages/<name> and installed (a Go node builds relative to it). Registry entries are keyed by the manifest name, so a root node and a --path node from the same repo coexist as separate installs when their names differ — and replace one another when they do not. --path is a path relative to the source root: absolute paths and paths that escape the root with .. are rejected, and a missing manifest is reported with the full expected path. A bare af install <src> (no --path) is unchanged — the root manifest is always what you get by default, unless it redirects.

Retiring or renaming a node: superseded_by

A manifest can declare that it has been replaced by another package. Installing it then installs that other package instead:

name: my-node
superseded_by: https://github.com/me/my-repo//v2

The value is any source af install accepts — including a //subdir selector and an @ref. This lets you move, rename, or reimplement your node without anyone having to learn a new install command, and without AgentField holding a list of who redirects where: the redirect lives in your manifest.

What happens on install:

  • The redirect is resolved before anything is copied, so a redirected install never leaves the superseded package half-installed.
  • If the superseded package is currently installed, the user is warned that it will be replaced, and the successor is installed first — a failure leaves what they had exactly as it was.
  • Node-scoped secrets follow: when the successor takes a different name they are copied across before the old package is uninstalled (which would delete that scope); values already set on the successor win. When the successor takes the same name — a node renaming itself in place — they are already in the right scope and stay put.
  • Retiring the old package never fails the install. If it cannot be removed you are told how to remove it by hand.
  • Chains are bounded at three hops, so two manifests pointing at each other fail with a clear error instead of looping.

The redirect applies to git installs. A local-path install ignores it, which is how you install a superseded package deliberately.

Previewing requirements before installing: af show-requirements

To see what environment a node needs before committing to an install, run:

af show-requirements ./my-agent
af show-requirements https://github.com/Agent-Field/pr-af
af show-requirements https://github.com/Agent-Field/SWE-AF//go   # subdir selector
af show-requirements ./my-agent -o json                          # machine-readable

It resolves the source without installing anything — a local path is parsed in place; a Git URL (with an optional @ref and //subdir selector) is shallow-cloned into a temporary directory that is removed afterwards, so nothing is written under ~/.agentfield. The output lists the node's required variables, optional variables with their defaults, and require_one_of groups, and pairs each required variable with the exact af secrets set <VAR> --node <name> command that supplies it. -o json emits the same information as a machine-readable object for scripting.

Secrets: encrypted, shared, runtime-only

Secrets are never written to disk in plaintext and never baked into the package. They are encrypted with AES-256-GCM under a random 32-byte key kept in ~/.agentfield/keyring/master.key (mode 0600). At start time they are decrypted straight into the child process' environment — nowhere else.

When af run needs a required variable, it resolves it in this order:

  1. Process environment — if OPENROUTER_API_KEY is already exported, it's used as-is and not persisted.
  2. Node-scoped store (secrets/<node>.enc), then global store (secrets/global.enc).
  3. Manifest default.
  4. Prompt (required variables only, when attached to a terminal). The value is validated against the manifest validation regex, then saved encrypted to the variable's scope.

Because most provider keys are scope: global, you enter OPENROUTER_API_KEY once and every node reuses it. A scope: node variable is stored per-node and overrides the global value for that node.

Managing secrets directly

af secrets set OPENROUTER_API_KEY            # prompts, hidden input, stored global
af secrets set GH_TOKEN ghp_xxx              # value inline
af secrets set PR_AF_MODEL ... --node pr-af  # node-scoped override
af secrets ls                                # keys + scope only (values never shown)
af secrets rm GH_TOKEN                        # remove from global
af secrets rm PR_AF_MODEL --node pr-af        # remove a node-scoped secret

In a non-interactive session (CI, no TTY), missing required secrets are reported as an error instead of hanging on a prompt. The error names the node and pairs each unset variable (and each option of an unsatisfied require_one_of group) with the exact command that fixes it — af secrets set <VAR> --node <name> — so you can copy-paste the fix. Set them ahead of time with af secrets set or by exporting them.

af config — configure an installed node

af config <node> is a convenience wrapper for configuring an installed node. af config <node> --set KEY=VALUE (and the interactive af config <node>) write through to both the node-scoped encrypted secret store — the same one af secrets set KEY --node <node> writes to, and the one af run reads at start time — and the package's .env file (which af dev and the web UI env editor read). af config <node> --unset KEY removes the value from both. Because the value lands in the secret store, af run <node> picks it up immediately:

af config my-agent --set OPENROUTER_API_KEY=sk-or-...  # secret store + .env
af config my-agent --list                              # show configured values
af config my-agent --unset OPENROUTER_API_KEY          # remove from both

Use af secrets when you want a global secret shared across every node, or to configure a node before it is installed; use af config when you want to configure a single installed node and keep its .env in sync for af dev.

Control-plane connection

af run exports AGENTFIELD_SERVER (the variable the SDK reads) and the legacy AGENTFIELD_SERVER_URL, plus the assigned PORT, into the node process. The server URL is resolved from your local configuration.

Lifecycle reference

CommandDoes
af show-requirements <src>Print the environment a node needs, without installing it
af install <src>Install from a local path, git URL, or registry name + node deps
af install <src> --path <subdir>Install the node in <subdir> (one repo shipping multiple nodes)
af run <node>Start a node (and its node deps) in the background
af listShow installed nodes and runtime state
af logs <node>Tail a node's process log
af stop <node>Stop a running node
af uninstall <node>Stop and remove a node
af config <node>Configure an installed node (writes secret store + .env)
af secrets set/ls/rmManage the encrypted secret store