Skip to content

INTEGRATIONS

Mike Crowe edited this page Jul 31, 2026 · 5 revisions

External Integrations

Analysis Date: 2026-07-31

Container Runtime — Podman

  • What: All container build and launch operations.
  • How connected: subprocess calls in src/harnessed/launcher.py. The runtime binary is resolved via shutil.which("podman") (with CONTAINER_RUNTIME env override). No daemon socket; rootless podman only.
  • Key operations: podman build (base + derived images), podman pod create/start/kill, podman run (install scripts in volume-write containers), podman exec (setup scripts post-start), podman volume create/inspect/rm.
  • Build secrets: Corporate proxy CA injected via --secret id=corp_proxy_ca,src=<cert> (never baked into image layers). Resolved by _corp_proxy_ca_secret_args() in launcher.py.
  • Egress firewall: catalog/base/egress-firewall.sh runs inside the container after setup scripts complete; applies iptables rules to close outbound network.

MCP Hub — hatago

  • Package: @drmikecrowe/hatago-mcp-hub@0.1.2 (maintained fork of upstream @himorishige/hatago-mcp-hub, which stopped at 0.0.16).
  • Where installed: Baked into catalog/base/Dockerfile.harnessed-base via pnpm add -g "@drmikecrowe/hatago-mcp-hub@0.1.2". All harness images inherit it — there is no separate hatago image.
  • Role: In-container MCP bus (hatago-consolidation). Runs as a process inside the agent container (started by harnessed-start). Aggregates all recipe MCP servers (stdio children baked into the image) behind a single Streamable-HTTP endpoint — the harness's .mcp.json points only at this one URL.
  • Endpoint: paths.hatago_endpoint() (default port from paths.hatago_port(), overridden by HATAGO_PORT). Probed by capability.py at HATAGO_ENDPOINT for the capability test.
  • Config: Assembled per-stack as hatago.config.json in the profile dir by emit.write_hatago_config(), called from src/harnessed/assemble.py.
  • Transport constraint: Streamable-HTTP only. SSE is deprecated and rejected.

Secret Resolution — varlock

  • What: Resolves op:// 1Password references in .env.schema files into plain env vars, injected into the container via --env-file (never written to the repo or a profile).
  • Client code: _varlock_resolve() and _varlock_resolve_env_file() in src/harnessed/launcher.py. Also used host-side by _resolve_host_launch_secrets().
  • DSL: varlock's .env.schema format with @plugin(@varlock/1password-plugin@1.2.0) and @initOp(allowAppAuth=true). See .env.schema.example at the repo root.
  • Layering: _resolve_launch_secrets() stacks (1) ~/.config/harnessed/.env.schema (global) then (2) <project>/.env.schema or <project>/.env (project). Podman's --env-file is last-wins, so project values override global.
  • Fallback: If varlock is not on PATH, harnessed reads ~/.config/harnessed/.env literally (no resolution).
  • Security: Resolved env-files are written to temp paths (mode 0600) and unlinked immediately after podman run / os.execvp. They are never committed, never baked, and never written to a profile.

Credentials Store — 1Password

  • CLI: op (1Password CLI). Best-effort install in catalog/base/Dockerfile.harnessed-base from downloads.1password.com. A failed download warns and continues — the only capability lost is 1Password-based SSH commit signing (op-ssh-sign).
  • SSH agent socket: launcher._op_agent_socket() returns ~/.1password/agent.sock (Linux) or the macOS Group Containers path. Forwarded into the container via --volume <sock>:<ctr_sock>:ro when the socket exists. Controlled by _ssh_agent_args().
  • Shim: catalog/base/op is a shim placed at /usr/local/bin/op (ahead of /usr/bin/op on PATH). Removed from the image if the real op did not install.
  • Secret references in varlock DSL: op://Private/SNYK_API_TOKEN/credential, etc. See .env.schema.example.

Pin Update — External Registries

src/harnessed/update.py implements harnessed update, which queries live registries to detect stale pins in the catalog. Three registry backends, each called via urllib.request.urlopen (no third-party HTTP client):

Backend prefix Registry URL queried What is resolved
npm: https://registry.npmjs.org/{name} npm package release list and dates
pipx: https://pypi.org/pypi/{name}/json PyPI package release list and dates
github: / aqua https://api.github.com/repos/{repo}/releases?per_page=100 GitHub release list and dates
  • mise registry: mise registry is shelled out to resolve bare tool names (e.g. pulumi) to their aqua/ubi/github backing repo, then the GitHub API handles versioning. Used in .github/workflows/pin-check.yml which installs mise via jdx/mise-action@v2.
  • Minimum release age: Default 7 days (DEFAULT_MINIMUM_RELEASE_AGE_MINUTES = 10080). A release younger than this window is not offered — the newest version that IS old enough is offered instead (modelled on pnpm's minimumReleaseAge).
  • Held pins: install.hold in recipe.yaml or hold: on a tools: entry marks a pin manual-only. Held pins are listed in the report but never enter the bump set and never fail --check.

Supply-Chain Scanners

Four scanners are baked into harnessed-base and invoked by catalog/base/harnessed-scan as the final layer of every assembled stack image (emit.write_derived_dockerfile). They run in two tiers:

Credential-free (every build):

  • osv-scanner — checks installed packages against the OSV vulnerability database. Installed via mise use -g osv-scanner.
  • pip-audit — audits Python packages via pip-audit. Installed via uv tool install pip-audit (pinned to pip-audit==2.10.1 in pyproject.toml).

Token-gated (only in harnessed rescan, never during harnessed build):

  • snyk — deep supply-chain scan. Token: SNYK_TOKEN (from 1Password via varlock, key op://Private/SNYK_API_TOKEN/credential). Installed via pnpm add -g snyk.
  • socket @1.1.143 — socket.dev supply-chain scan. Token: SOCKET_SECURITY_API_KEY. Installed via pnpm add -g socket@1.1.143. Pinned to v1.

harnessed rescan <image> resolves scanner tokens on the host from ~/.config/harnessed/.env{,.schema}, injects them as container env, and writes its report to <profile>/scan-report.json. The build's own scan layer is deliberately credential-free.

Beads Issue Tracker — Dolt

  • What: A per-project dolt sql-server instance, serving MySQL protocol on a Unix socket. Backs the Beads (bd) issue tracker.
  • Service definition: catalog/services/beads-server/service.yamlscope: project, socket-backed.
  • Image: catalog/services/beads-server/Dockerfile — built FROM ubuntu:24.04, installs dolt and bd via mise use -g "github:dolthub/dolt@${DOLT_VERSION}" "github:gastownhall/beads@${BEADS_VERSION}".
  • Data dir: /data inside the container, bind-mounted from the project's .beads/ directory. Socket: <project>/.beads/run/mysql.sock (or equivalent per placement).
  • Client env: HARNESSED_BEADS_SERVER_SOCKET — exported by the launcher with the agent-side socket path.
  • Sync: harnessed svc sync beads-server execs bd dolt push inside the beads-server container to push refs/dolt/data to the git remote. bd must be baked into the service image (not the agent image) because dolt sql-server holds an exclusive lock on the data directory.

Agent Harness Credentials

Each supported harness has its own credential mechanism.

Harness Auth mechanism Mount strategy
claude Primary: CLAUDE_CODE_OAUTH_TOKEN, forwarded as a bare -e from the host env or an env-file (_claude_oauth_token_args, launcher.py:2073). Legacy fallback: ~/.claude/.credentials.json, used only when no token is configured With a token, no credential file is mounted at all. Without one, _claude_creds_seed_mount (launcher.py:2119) seeds a per-instance copy under $XDG_STATE_HOME/harnessed/<inst>/ and mounts it rw, re-seeding when the copy has expired. Its own docstring calls this an acknowledged anti-pattern
omp ~/.omp/agent/ dir (auth + usage + sessions together, no separately-mountable credential file) Host dir bind-mounted read-write; --profile is never used (it creates an isolated empty store)
opencode Config baked into harness image; project dir bind-mounted Per catalog/base/Dockerfile.harnessed-opencode
codex Config baked; MCP pointed at hatago hub Per catalog/base/Dockerfile.harnessed-codex
antigravity agy binary + baked config Per catalog/base/Dockerfile.harnessed-antigravity

CLAUDE_CONFIG_DIR (claude) and PI_CODING_AGENT_DIR (omp) move config and credentials together on the host backend, so the per-stack host home holds skill/rule/command content while sessions/, memories/, and history are symlinked back to the shared store.

CI — GitHub Actions

Three workflows in .github/workflows/:

Workflow Trigger What it does
test.yml PR + push to main Runs hermetic pytest suite via uv run --extra dev pytest. No podman on runner; HARNESSED_PODMAN-gated integration tests skip. Python 3.12 via astral-sh/setup-uv@v7.
pin-check.yml Scheduled (Mondays 06:00 UTC) + workflow_dispatch harnessed update --check over the catalog. Resolves live npm/PyPI/GitHub registries. Not a PR gate (a third-party release would fail unrelated PRs). Installs mise via jdx/mise-action@v2 for aqua-backend resolution.
deploy-web.yml (separate trigger) Deploys the web/ directory.

Corporate Proxy CA

  • What: An enterprise SSL-intercepting proxy CA certificate that must be trusted inside the container so curl/pip/pnpm/etc. downloads succeed.
  • How injected: Detected by _corp_proxy_ca_secret_args() in launcher.py. Passed to podman build as --secret id=corp_proxy_ca,src=<cert> — the Dockerfile.harnessed-base receives it via RUN --mount=type=secret,id=corp_proxy_ca,... and adds it to the system trust store without baking the cert into a layer.
  • Service Dockerfiles: A standard CA block is injected into service Dockerfiles automatically (see CA_BLOCK in launcher.py).

Clone this wiki locally