{"id":51229648,"url":"https://github.com/tonylnng/gateforge-loom","last_synced_at":"2026-06-28T15:01:01.834Z","repository":{"id":366949731,"uuid":"1232519150","full_name":"tonylnng/gateforge-loom","owner":"tonylnng","description":"Weave intelligent agents into workflows. A composable multi-agent orchestration stack — Brain (Claude) · Hands (OpenClaw) · Memory (Hermes) — wired via n8n on Docker.","archived":false,"fork":false,"pushed_at":"2026-06-24T02:02:51.000Z","size":545,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-24T03:04:31.021Z","etag":null,"topics":["ai-agents","claude","docker-compose","fastapi","hermes","multi-agent","n8n","openclaw","orchestration","pgvector"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tonylnng.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-08T02:31:07.000Z","updated_at":"2026-06-24T01:35:53.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/tonylnng/gateforge-loom","commit_stats":null,"previous_names":["tonylnng/gateforge-loom"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/tonylnng/gateforge-loom","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tonylnng%2Fgateforge-loom","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tonylnng%2Fgateforge-loom/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tonylnng%2Fgateforge-loom/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tonylnng%2Fgateforge-loom/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tonylnng","download_url":"https://codeload.github.com/tonylnng/gateforge-loom/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tonylnng%2Fgateforge-loom/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34892547,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-28T02:00:05.809Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai-agents","claude","docker-compose","fastapi","hermes","multi-agent","n8n","openclaw","orchestration","pgvector"],"created_at":"2026-06-28T15:01:00.955Z","updated_at":"2026-06-28T15:01:01.819Z","avatar_url":"https://github.com/tonylnng.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Gateforge-Loom\n\n\u003e **Weave intelligent agents into workflows.**\n\u003e A composable, multi-agent orchestration stack — every agent is its own service, every interaction is a JSON contract, every run leaves a memory.\n\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\n![Stack](https://img.shields.io/badge/stack-FastAPI%20·%20n8n%20·%20Postgres%2Fpgvector%20·%20Redis-blue)\n![Status](https://img.shields.io/badge/status-PoC-orange)\n![Topology](https://img.shields.io/badge/topology-3--VM%20hybrid-blueviolet)\n\nGateforge-Loom is a **layered multi-agent system** built on three foundational\nroles — **Brain · Hands · Memory** — orchestrated by **n8n** and connected\nthrough **Redis** + **Postgres/pgvector**. The architecture treats agents as\nthreads on a loom: today three threads, tomorrow as many as your workflow\nneeds. New agents (Validator, Critic, Router, Reviewer…) drop in as additional\nservices; the orchestrator weaves them all into one cohesive run.\n\nThe stack runs equally well on **a single VM with Docker** or on a **3-VM\nhybrid topology** — Brain + Orchestrator in Docker on one VM, and Hands and\nMemory installed **natively as systemd services** on two more VMs, meshed over\nTailscale. The Brain reaches Anthropic models through the **Vercel AI Gateway**,\nwhich keeps it reachable from regions where `api.anthropic.com` is blocked\n(e.g. Hong Kong).\n\n---\n\n## Table of contents\n\n- [Why Gateforge-Loom?](#why-gateforge-loom)\n- [Architecture at a glance](#architecture-at-a-glance)\n- [Concept diagram](#1-concept-diagram)\n- [Sequence diagram](#2-sequence-diagram-one-job-end-to-end)\n- [State diagram](#3-state-diagram-job-lifecycle)\n- [Workflow diagram](#4-workflow-diagram-the-n8n-pipeline)\n- [Deployment diagram](#5-deployment-diagram-3-vm-hybrid-topology)\n- [Install-flow diagram](#6-install-flow-diagram-cluster-bring-up)\n- [Components](#components)\n- [Quick start](#quick-start)\n- [Deployment topology](#deployment-topology)\n- [VM-1 install — Brain + Orchestrator (Docker)](#vm-1-install--brain--orchestrator-docker)\n- [VM-2 install — OpenClaw native (Hands)](#vm-2-install--openclaw-native-hands)\n- [VM-3 install — Hermes native (Memory)](#vm-3-install--hermes-native-memory)\n- [Backup \u0026 recovery](#backup--recovery)\n- [Operations cheatsheet](#operations-cheatsheet)\n- [Adding more agents](#adding-more-agents)\n- [Project layout](#project-layout)\n- [Roadmap](#roadmap)\n\n---\n\n## Why Gateforge-Loom?\n\n\u003e *\"The three tools aren't competing — they're layered. Brain decides, Hands act, Memory remembers.\"*\n\nMost multi-agent demos collapse three concerns into one prompt soup: planning,\nexecution, and memory all happen inside a single LLM call. That works for\ntoys; it does not survive production. Gateforge-Loom enforces **single\nresponsibility per layer**:\n\n| Layer | Service | Owns | Never does | Where it runs |\n|---|---|---|---|---|\n| **Brain** | `claude-gateway` | Decisions, plans, synthesis | Side effects, I/O | VM-1 (Docker) |\n| **Hands** | `openclaw` | Tool execution, I/O, automation | Strategy, judgement | VM-2 (native systemd) |\n| **Memory** | `hermes` | Recall, learn, distil SOPs | Initiate actions | VM-3 (native systemd) |\n| **Bus** | `redis` | Job state, locks, cache | Long-term storage | VM-1 (Docker) |\n| **Storage** | `postgres + pgvector` | Episodic + SOP memory | Real-time state | VM-3 (native) |\n| **Orchestrator** | `n8n` | Sequencing, retries, fan-out | Anything an agent should do | VM-1 (Docker) |\n| **LLM Gateway** | `Vercel AI Gateway` | HK-reachable Anthropic proxy, failover, cost tracking | Any orchestration logic | External (Vercel edge) |\n\nEach layer exposes a small typed API and can be upgraded, scaled, or replaced\nindependently — whether it runs as a container or a native service.\n\n---\n\n## Architecture at a glance\n\n```\n            ┌──────────────────────────────────────────────────────┐\n            │               n8n  (orchestrator)                     │\n            └────────────┬───────────────┬────────────┬─────────────┘\n                         │               │            │\n              POST /plan │     POST /recall │ POST /execute\n                         ▼               ▼            ▼\n                ┌────────────┐  ┌────────────┐  ┌────────────┐\n                │ claude-gw  │  │  hermes    │  │  openclaw  │\n                │  (Brain)   │  │ (Memory)   │  │  (Hands)   │\n                └─────┬──────┘  └─────┬──────┘  └─────┬──────┘\n                      │               │               │\n                      ▼               ▼               ▼\n                ┌────────────┐  ┌────────────┐  ┌────────────┐\n                │ Vercel AI  │  │ Postgres + │  │  Redis bus │\n                │  Gateway   │  │  pgvector  │  │   + tools  │\n                │ → Anthropic│  │            │  │            │\n                └────────────┘  └────────────┘  └────────────┘\n```\n\nComponents can all run on a single VM, or split across three VMs over\nTailscale — see [Deployment topology](#deployment-topology).\n\n---\n\n## 1. Concept diagram\n\nHow the layers relate. Read top-to-bottom: a request enters at the\norchestrator, fans out to the agents, agents talk to the shared backplane,\nresults are woven back into a final artifact.\n\n```mermaid\nflowchart TB\n    subgraph Client[\"Client / Trigger\"]\n        U[\"User · Cron · Webhook · Chat\"]\n    end\n\n    subgraph Orchestration[\"Orchestration Layer · VM-1\"]\n        N[\"n8n Workflow Engine\"]\n    end\n\n    subgraph Agents[\"Agent Layer (each agent = one service)\"]\n        direction LR\n        B[\"🧠 Brain\u003cbr/\u003e\u003cb\u003eclaude-gateway\u003c/b\u003e\u003cbr/\u003eVM-1 · Docker\u003cbr/\u003eplan · merge · synthesize\"]\n        H[\"✋ Hands\u003cbr/\u003e\u003cb\u003eopenclaw\u003c/b\u003e\u003cbr/\u003eVM-2 · native\u003cbr/\u003eexecute · tools\"]\n        M[\"📚 Memory\u003cbr/\u003e\u003cb\u003ehermes\u003c/b\u003e\u003cbr/\u003eVM-3 · native\u003cbr/\u003erecall · write\"]\n        FA[\"… future agents\u003cbr/\u003eValidator · Critic · Router\"]\n    end\n\n    subgraph External[\"External LLM Provider\"]\n        V[\"☁️ Vercel AI Gateway\u003cbr/\u003e→ Anthropic (Claude Opus)\"]\n    end\n\n    subgraph Backplane[\"Shared Backplane\"]\n        R[(\"Redis\u003cbr/\u003estate bus · VM-1\")]\n        P[(\"Postgres + pgvector\u003cbr/\u003eSOP + episodic · VM-3\")]\n        S[(\"Object store\u003cbr/\u003eartifacts (S3 / MinIO)\")]\n    end\n\n    subgraph Sinks[\"Output Sinks\"]\n        O[\"Notion · Slack · Drive · Webhook\"]\n    end\n\n    U --\u003e N\n    N \u003c--\u003e B\n    N \u003c--\u003e H\n    N \u003c--\u003e M\n    N -.-\u003e FA\n    B \u003c--\u003e V\n    B \u003c--\u003e R\n    H \u003c--\u003e R\n    H --\u003e S\n    M \u003c--\u003e P\n    N --\u003e O\n\n    classDef brain   fill:#FEE7DC,stroke:#D97757,color:#1F2937;\n    classDef hands   fill:#DBEAFE,stroke:#3B82F6,color:#1F2937;\n    classDef memory  fill:#EDE9FE,stroke:#8B5CF6,color:#1F2937;\n    classDef future  fill:#F3F4F6,stroke:#9CA3AF,color:#1F2937,stroke-dasharray: 5 5;\n    classDef store   fill:#D1FAE5,stroke:#10B981,color:#1F2937;\n    classDef ext     fill:#FEF3C7,stroke:#F59E0B,color:#1F2937;\n    class B brain\n    class H hands\n    class M memory\n    class FA future\n    class R,P,S store\n    class V ext\n```\n\n**Key idea.** Agents never call each other directly. Everything is mediated\nby n8n (control flow) and the shared backplane (state). This is what lets\nyou add or remove agents without rewriting the others. The Brain's only\noutbound dependency is the Vercel AI Gateway.\n\n---\n\n## 2. Sequence diagram (one job, end-to-end)\n\nWhat actually happens when a request comes in. Notice that **memory is\nqueried before the plan is finalized**, **memory is updated after every\nsuccessful run** (that's how the system gets faster over time), and **every\nBrain call round-trips through the Vercel AI Gateway** to reach Anthropic.\n\n```mermaid\nsequenceDiagram\n    autonumber\n    participant U as User / Trigger\n    participant N as n8n\n    participant C as Claude Gateway (Brain)\n    participant VG as Vercel AI Gateway\n    participant M as Hermes (Memory)\n    participant O as OpenClaw (Hands)\n    participant DB as Postgres / Redis\n\n    U-\u003e\u003eN: POST /webhook (user_intent)\n    N-\u003e\u003eN: generate job_id\n\n    N-\u003e\u003eC: POST /plan { intent }\n    C-\u003e\u003eVG: messages.create (Claude Opus)\n    VG--\u003e\u003eC: plan completion\n    C--\u003e\u003eN: draft_plan { steps[] }\n\n    N-\u003e\u003eM: POST /recall { query }\n    M-\u003e\u003eDB: SELECT sop, episodic\n    DB--\u003e\u003eM: hits[]\n    M--\u003e\u003eN: memory_hits[]\n\n    N-\u003e\u003eC: POST /merge { draft_plan, hits }\n    C-\u003e\u003eVG: messages.create (Claude Opus)\n    VG--\u003e\u003eC: merged completion\n    C--\u003e\u003eN: final_plan (v2, SOP-augmented)\n\n    loop for each step\n        N-\u003e\u003eO: POST /execute { tool, input }\n        O-\u003e\u003eO: run tool (web/browser/shell/api)\n        O--\u003e\u003eN: { status, output, artifacts[] }\n        N-\u003e\u003eN: validate schema\n        alt retryable error\n            N-\u003e\u003eO: retry (max_retries)\n        end\n        N-\u003e\u003eDB: append step result (Redis)\n    end\n\n    N-\u003e\u003eM: POST /write { episode, sop_updates }\n    M-\u003e\u003eDB: INSERT episodic, bump SOP version\n    M--\u003e\u003eN: stored\n\n    N-\u003e\u003eC: POST /synthesize { step_results }\n    C-\u003e\u003eVG: messages.create (Claude Opus)\n    VG--\u003e\u003eC: synthesis completion\n    C--\u003e\u003eN: artifact_uri + summary\n\n    N--\u003e\u003eU: final result\n```\n\n---\n\n## 3. State diagram (job lifecycle)\n\nEvery job moves through a small, predictable set of states. State transitions\nare written to Redis under `job:{job_id}:state` so any agent or operator can\ninspect a job in flight.\n\n```mermaid\nstateDiagram-v2\n    [*] --\u003e Received: webhook hit\n    Received --\u003e Planning: job_id created\n    Planning --\u003e Recalling: draft plan ready\n    Recalling --\u003e Merging: memory hits returned\n    Merging --\u003e Executing: final plan committed\n\n    Executing --\u003e StepRunning: dispatch step\n    StepRunning --\u003e StepDone: status=success\n    StepRunning --\u003e StepFailed: status=error\n    StepFailed --\u003e StepRunning: retry (≤ max_retries)\n    StepFailed --\u003e Failed: retries exhausted\n    StepDone --\u003e Executing: more steps?\n    StepDone --\u003e Learning: all steps done\n\n    Learning --\u003e Synthesizing: episodic written\n    Synthesizing --\u003e Delivered: artifact emitted\n    Delivered --\u003e [*]\n    Failed --\u003e [*]\n\n    note right of Recalling\n        Hermes is degradable — if\n        unavailable, returns empty\n        hits and the job continues.\n    end note\n\n    note right of Learning\n        Episodic always written.\n        SOP versions bumped only\n        when lessons exist.\n    end note\n```\n\n---\n\n## 4. Workflow diagram (the n8n pipeline)\n\nThe actual node graph implemented in\n[`n8n/workflows/gateforge-loom-pipeline.json`](n8n/workflows/gateforge-loom-pipeline.json).\nImport it directly in n8n.\n\n```mermaid\nflowchart LR\n    T([\"🪝 Webhook Trigger\"]) --\u003e J[\"Generate job_id\"]\n    J --\u003e P1[\"Claude /plan\"]\n    P1 --\u003e R1[\"Hermes /recall\"]\n    R1 --\u003e M1[\"Claude /merge\"]\n    M1 --\u003e SP[\"Split steps\"]\n    SP --\u003e EX[\"OpenClaw /execute\"]\n    EX --\u003e V{\"Validate\u003cbr/\u003eschema\"}\n    V -- \"retryable error\" --\u003e EX\n    V -- \"ok\" --\u003e AGG[\"Aggregate results\"]\n    AGG -- \"more steps\" --\u003e SP\n    AGG -- \"done\" --\u003e W[\"Hermes /write\"]\n    W --\u003e SY[\"Claude /synthesize\"]\n    SY --\u003e OUT([\"📤 Output sink\"])\n\n    classDef trigger fill:#FECACA,stroke:#DC2626;\n    classDef brain   fill:#FEE7DC,stroke:#D97757;\n    classDef hands   fill:#DBEAFE,stroke:#3B82F6;\n    classDef memory  fill:#EDE9FE,stroke:#8B5CF6;\n    classDef ctrl    fill:#F3F4F6,stroke:#6B7280;\n    classDef out     fill:#FEF3C7,stroke:#F59E0B;\n\n    class T trigger\n    class P1,M1,SY brain\n    class EX hands\n    class R1,W memory\n    class J,SP,V,AGG ctrl\n    class OUT out\n```\n\n| # | Node | Type | Purpose |\n|---|---|---|---|\n| 1 | **Webhook Trigger** | Webhook | Entry point. Accepts `{ user_intent, context }`. |\n| 2 | **Generate job_id** | Code | Deterministic ID for tracing. |\n| 3 | **Claude /plan** | HTTP | Decompose intent → step list. |\n| 4 | **Hermes /recall** | HTTP | Pull relevant SOP + episodic memories. |\n| 5 | **Claude /merge** | HTTP | Fold memory into final plan (v2). |\n| 6 | **Split steps** | Split-Out | One iteration per plan step. |\n| 7 | **OpenClaw /execute** | HTTP | Run a single tool invocation. |\n| 8 | **Validate** | Code | JSON-schema check + retryable error detection. |\n| 9 | **Aggregate** | Merge | Collect step results into Redis. |\n| 10 | **Hermes /write** | HTTP | Persist episodic memory + SOP patches. |\n| 11 | **Claude /synthesize** | HTTP | Final report artifact. |\n| 12 | **Output sink** | Notion / Slack / Drive | Deliver to the user. |\n\n---\n\n## 5. Deployment diagram (3-VM hybrid topology)\n\nThe physical placement of every component. **VM-1** runs the Brain +\nOrchestrator + state bus in Docker; **VM-2** and **VM-3** run the agents as\n**native systemd services** (no Docker). All cross-VM traffic flows over a\nTailscale mesh; only n8n's UI (`5678`) is public-facing.\n\n```mermaid\nflowchart TB\n    Internet([\"🌐 Internet / Clients\"])\n\n    subgraph VM1[\"VM-1 · Brain + Orchestrator · Docker\"]\n        direction TB\n        N8N[\"n8n\u003cbr/\u003e:5678 (public)\"]\n        CG[\"claude-gateway\u003cbr/\u003e:8001\"]\n        RD[(\"redis\u003cbr/\u003e:6379\")]\n    end\n\n    subgraph VM2[\"VM-2 · Hands · native systemd\"]\n        OC[\"openclaw.service\u003cbr/\u003e:8002\u003cbr/\u003e(uvicorn + Playwright)\"]\n    end\n\n    subgraph VM3[\"VM-3 · Memory · native systemd\"]\n        HM[\"hermes.service\u003cbr/\u003e:8003 (uvicorn)\"]\n        PG[(\"postgresql 16\u003cbr/\u003e+ pgvector · :5432\")]\n    end\n\n    VGW[\"☁️ Vercel AI Gateway\u003cbr/\u003e→ Anthropic (Claude Opus)\"]\n\n    Internet --\u003e|\"HTTPS :5678\"| N8N\n    N8N \u003c--\u003e|\"docker net\"| CG\n    CG \u003c--\u003e|\"docker net\"| RD\n    CG --\u003e|\"HTTPS (Anthropic-compat base URL)\"| VGW\n\n    N8N \u003c--\u003e|\"Tailscale :8002\"| OC\n    N8N \u003c--\u003e|\"Tailscale :8003\"| HM\n    CG -.-\u003e|\"Tailscale (optional)\"| OC\n    HM \u003c--\u003e|\"localhost :5432\"| PG\n\n    classDef brain   fill:#FEE7DC,stroke:#D97757,color:#1F2937;\n    classDef hands   fill:#DBEAFE,stroke:#3B82F6,color:#1F2937;\n    classDef memory  fill:#EDE9FE,stroke:#8B5CF6,color:#1F2937;\n    classDef store   fill:#D1FAE5,stroke:#10B981,color:#1F2937;\n    classDef ctrl    fill:#F3F4F6,stroke:#6B7280,color:#1F2937;\n    classDef ext     fill:#FEF3C7,stroke:#F59E0B,color:#1F2937;\n    classDef net     fill:#FFFFFF,stroke:#111827,color:#1F2937;\n\n    class CG brain\n    class OC hands\n    class HM memory\n    class RD,PG store\n    class N8N ctrl\n    class VGW ext\n    class Internet net\n```\n\n| VM | Runs | Runtime | Public port | Tailscale ports |\n|---|---|---|---|---|\n| **VM-1** | n8n · claude-gateway · redis | Docker Compose | `5678` (n8n UI) | `8001`, `6379` (internal) |\n| **VM-2** | openclaw | native systemd | — | `8002` |\n| **VM-3** | hermes · postgres+pgvector | native systemd | — | `8003`, `5432` |\n\n\u003e The Gateforge-Loom contract is **transport-agnostic** — agents talk via JSON\n\u003e over HTTP with `INTERNAL_API_TOKEN` auth. Whether an agent runs as a Docker\n\u003e container or a native systemd service is invisible to n8n and the Brain.\n\n---\n\n## 6. Install-flow diagram (cluster bring-up)\n\nThe order of operations to stand up the cluster from scratch. Color-coded per\nVM. Provision the Tailnet first so every VM can resolve the others before you\nwire `.env` files.\n\n```mermaid\nflowchart TD\n    Start([\"Start\"]) --\u003e TS[\"Provision Tailnet\u003cbr/\u003e+ join all 3 VMs\"]\n\n    TS --\u003e V3a[\"VM-3: install Postgres 16 + pgvector\"]\n    V3a --\u003e V3b[\"VM-3: run infra/postgres/init.sql\u003cbr/\u003e(schema + seed SOP)\"]\n    V3b --\u003e V3c[\"VM-3: install hermes venv\u003cbr/\u003e+ hermes.service (systemd)\"]\n    V3c --\u003e V3d[\"VM-3: lock Postgres to localhost + Tailscale IP\"]\n\n    TS --\u003e V2a[\"VM-2: install python3.12 venv\"]\n    V2a --\u003e V2b[\"VM-2: install openclaw\u003cbr/\u003e+ openclaw.service (systemd)\"]\n    V2b --\u003e V2c[\"VM-2: (optional) Playwright install chromium\"]\n\n    TS --\u003e V1a[\"VM-1: install Docker + Compose\"]\n    V1a --\u003e V1b[\"VM-1: clone repo, set .env\u003cbr/\u003e(STUB_MODE=0 + Vercel key + Tailscale IPs)\"]\n    V1b --\u003e V1c[\"VM-1: trim docker-compose.yml\u003cbr/\u003e(n8n + claude-gateway + redis only)\"]\n    V1c --\u003e V1d[\"VM-1: make up\"]\n\n    V3d --\u003e Health{\"All 3 /health\u003cbr/\u003eendpoints green?\"}\n    V2c --\u003e Health\n    V1d --\u003e Health\n\n    Health -- \"no\" --\u003e Fix[\"Check Tailscale IPs\u003cbr/\u003e+ INTERNAL_API_TOKEN match\"]\n    Fix --\u003e Health\n    Health -- \"yes\" --\u003e Import[\"VM-1: import workflow,\u003cbr/\u003epoint HTTP nodes at Tailscale IPs\"]\n    Import --\u003e Smoke[\"make test (end-to-end smoke)\"]\n    Smoke --\u003e Done([\"✅ Cluster live\"])\n\n    classDef vm1 fill:#FEE7DC,stroke:#D97757,color:#1F2937;\n    classDef vm2 fill:#DBEAFE,stroke:#3B82F6,color:#1F2937;\n    classDef vm3 fill:#EDE9FE,stroke:#8B5CF6,color:#1F2937;\n    classDef ctrl fill:#F3F4F6,stroke:#6B7280,color:#1F2937;\n    classDef ok  fill:#D1FAE5,stroke:#10B981,color:#1F2937;\n\n    class V1a,V1b,V1c,V1d vm1\n    class V2a,V2b,V2c vm2\n    class V3a,V3b,V3c,V3d vm3\n    class TS,Health,Fix,Import,Smoke ctrl\n    class Start,Done ok\n```\n\n---\n\n## Components\n\nQuick summary below; read [`docs/components.md`](docs/components.md) for the\ndeep dive. The **runtime** column reflects the 3-VM hybrid topology.\n\n### 🧠 `claude-gateway` (Brain) — VM-1, Docker\n\n- **Image:** `gateforge-loom/claude-gateway` (Python 3.12 + FastAPI)\n- **Port:** 8001 (host) → 8000 (container)\n- **Endpoints:** `GET /health`, `POST /plan`, `POST /merge`, `POST /synthesize`\n- **Job:** thin wrapper around an LLM. Owns *all* reasoning. Returns structured\n  JSON only — never executes side effects.\n- **LLM routing:** reaches Anthropic through the **Vercel AI Gateway** via an\n  Anthropic-compatible base URL, so it stays reachable from HK. Set\n  `ANTHROPIC_BASE_URL=https://ai-gateway.vercel.sh/v1/anthropic`,\n  `ANTHROPIC_API_KEY=\u003cvercel-gateway-key\u003e`, and `CLAUDE_MODEL` to an Opus model.\n- **Stub mode:** `STUB_MODE=1` returns canned plans so you can wire the full\n  pipeline before adding API keys; set `STUB_MODE=0` for live calls.\n\n### ✋ `openclaw` (Hands) — VM-2, native systemd\n\n- **Runtime:** Python 3.12 venv under `/opt/openclaw`, run by `openclaw.service`\n- **Port:** 8002\n- **Endpoints:** `GET /health`, `GET /tools`, `POST /execute`\n- **Job:** runs one plan step against one registered tool. Built-in tool\n  catalogue covers `web.fetch`, `browser.action`, `shell.run`, `api.call`.\n  Failures are explicit (`status=error`, `retryable` flag) — n8n decides\n  whether to retry.\n- **Sandboxing:** systemd hardening (`ProtectSystem=strict`, `NoNewPrivileges`,\n  `PrivateTmp`); Playwright/Chromium installed in the venv for `browser.action`.\n\n### 📚 `hermes` (Memory) — VM-3, native systemd\n\n- **Runtime:** Python 3.12 venv under `/opt/hermes`, run by `hermes.service`\n- **Port:** 8003\n- **Endpoints:** `GET /health`, `POST /recall`, `POST /write`\n- **Job:** vector-search SOP \u0026 episodic memories on `/recall`; persist episode\n  + bump SOP versions on `/write`. Uses Postgres `vector(1536)` columns.\n- **Degradable:** if Postgres is down, `/recall` returns empty hits so the\n  rest of the pipeline keeps running.\n\n### 🚌 `redis` (State bus) — VM-1, Docker\n\n- **Image:** `redis:7-alpine`\n- **Port:** 6379\n- **Job:** distributed state for in-flight jobs. Keys follow a strict\n  convention so any agent can debug a job:\n  ```\n  job:{job_id}:state\n  job:{job_id}:plan\n  job:{job_id}:step:{step_id}\n  job:{job_id}:cursor\n  job:{job_id}:lock\n  ```\n- TTL: 7 days for active job keys; persisted via AOF.\n\n### 🗄 `postgres` (Long-term memory) — VM-3, native\n\n- **Package:** `postgresql-16` + `postgresql-16-pgvector`\n- **Port:** 5432 (bound to localhost + Tailscale IP only)\n- **Job:** durable storage for `episodic_memory` and `sop` tables. Schema\n  initialised by [`infra/postgres/init.sql`](infra/postgres/init.sql) —\n  includes one seed SOP so `/recall` returns data on day 1.\n- **Indexes:** B-tree on intent + created_at; ivfflat on `embedding` once\n  data exists.\n\n### 🎼 `n8n` (Orchestrator) — VM-1, Docker\n\n- **Image:** `n8nio/n8n:latest`\n- **Port:** 5678 (the only public-facing UI)\n- **Job:** owns control flow — sequencing, retries, fan-out, output sinks.\n- **Imports:** `n8n/workflows/gateforge-loom-pipeline.json`. After import,\n  point the Hermes/OpenClaw HTTP nodes at the **Tailscale IPs** of VM-3/VM-2.\n\n### ☁️ `Vercel AI Gateway` (LLM provider) — external\n\n- **Endpoint:** `https://ai-gateway.vercel.sh/v1/anthropic` (Anthropic-compatible)\n- **Job:** proxies Brain calls to Claude Opus, reachable from regions where\n  `api.anthropic.com` is blocked. Adds provider failover and cost tracking in\n  the Vercel dashboard. The `claude-gateway` code is unchanged except for the\n  `base_url` it points at.\n\n---\n\n## Quick start\n\nFor a fast local PoC, everything still runs on **one VM** with Docker:\n\n```bash\ngit clone https://github.com/tonylnng/gateforge-loom.git\ncd gateforge-loom\ncp .env.example .env             # fill in passwords + tokens\nmake up                          # build + start everything\nmake health                      # hit every /health endpoint\nmake test                        # end-to-end smoke test\n```\n\nThen open \u003chttp://localhost:5678\u003e (n8n) and import\n`n8n/workflows/gateforge-loom-pipeline.json`.\n\nFor the production **3-VM hybrid** layout, follow the per-VM install sections\nbelow.\n\n---\n\n## Deployment topology\n\n```\n                ┌─────────────────────────────────┐\n                │  VM-1: Brain + Orchestrator      │\n                │  (Docker)                        │\n                │  - n8n         :5678  (public)   │\n                │  - claude-gw   :8001             │──→ Vercel AI Gateway\n                │  - redis       :6379             │    (Anthropic-compat base URL)\n                └────────┬────────────────┬────────┘\n                         │  Tailscale     │\n                         ▼                ▼\n              ┌──────────────┐    ┌────────────────┐\n              │  VM-2 (native)│    │  VM-3 (native) │\n              │  - openclaw   │    │  - hermes      │\n              │    (systemd)  │    │  - postgres    │\n              │  :8002        │    │    + pgvector  │\n              └──────────────┘    │  :8003  :5432  │\n                                  └────────────────┘\n```\n\n| Resource | VM-1 (Brain+Orch) | VM-2 (Hands) | VM-3 (Memory) |\n|---|---|---|---|\n| **OS** | Ubuntu 22.04 LTS | Ubuntu 22.04 LTS | Ubuntu 22.04 LTS |\n| **vCPU** | 4 | 2 (4 with Playwright) | 2 |\n| **RAM** | 8 GB | 4 GB (8 GB w/ browsers) | 4 GB |\n| **Disk** | 80 GB SSD | 40 GB SSD | 80 GB SSD (DB growth) |\n| **Runtime** | Docker Compose | native systemd | native systemd |\n| **Public port** | 5678 | none | none |\n\nAll three VMs share one `INTERNAL_API_TOKEN` and live on the same Tailnet.\nPick a region close to the Vercel AI Gateway edge (HK / Singapore / Tokyo).\n\n---\n\n## VM-1 install — Brain + Orchestrator (Docker)\n\nVM-1 hosts the Brain, n8n, and the Redis state bus in Docker.\n\n### 1. Base system + Docker\n\n```bash\nsudo apt update \u0026\u0026 sudo apt upgrade -y\nsudo apt install -y curl git ufw fail2ban\nsudo timedatectl set-timezone Asia/Hong_Kong\n\ncurl -fsSL https://get.docker.com | sudo sh\nsudo usermod -aG docker \"$USER\"      # re-login for group change\ndocker --version \u0026\u0026 docker compose version\n```\n\n### 2. Tailscale + firewall\n\n```bash\ncurl -fsSL https://tailscale.com/install.sh | sh\nsudo tailscale up --hostname=loom-brain\n# note the Tailscale IPs of all 3 VMs — you'll need them in .env\n\nsudo ufw default deny incoming\nsudo ufw default allow outgoing\nsudo ufw allow 22/tcp                 # SSH\nsudo ufw allow 5678/tcp               # n8n UI (public)\nsudo ufw allow in on tailscale0       # all internal traffic\nsudo ufw enable\n```\n\n### 3. Clone + configure `.env`\n\n```bash\ncd /opt\nsudo git clone https://github.com/tonylnng/gateforge-loom.git\nsudo chown -R \"$USER\":\"$USER\" gateforge-loom\ncd gateforge-loom\ncp .env.example .env\nchmod 600 .env\n```\n\nEdit `.env` for Vercel routing + cross-VM Tailscale IPs:\n\n```bash\n# Brain (claude-gateway) — route through Vercel AI Gateway\nSTUB_MODE=0\nANTHROPIC_API_KEY=\u003cyour-vercel-ai-gateway-key\u003e\nANTHROPIC_BASE_URL=https://ai-gateway.vercel.sh/v1/anthropic\nCLAUDE_MODEL=claude-opus-4-7\n\n# Cross-VM service URLs (use Tailscale IPs)\nOPENCLAW_URL=http://100.x.x.2:8002\nHERMES_URL=http://100.x.x.3:8003\n\n# Shared secrets (must match VM-2 and VM-3)\nINTERNAL_API_TOKEN=\u003crotate-32-byte-hex\u003e\nN8N_ENCRYPTION_KEY=\u003crotate-32-byte-hex\u003e\n\n# n8n\nN8N_HOST=\u003cyour-domain-or-ip\u003e\nN8N_PROTOCOL=https\nWEBHOOK_URL=https://\u003cyour-domain\u003e/\n```\n\n\u003e The `claude-gateway` already constructs its Anthropic client from\n\u003e `ANTHROPIC_API_KEY` and `ANTHROPIC_BASE_URL`. Pointing `ANTHROPIC_BASE_URL`\n\u003e at the Vercel endpoint is the single change that unlocks HK reachability\n\u003e while keeping the Brain layer intact.\n\n### 4. Trim `docker-compose.yml` for VM-1\n\nOn VM-1 you only need `n8n`, `claude-gateway`, and `redis`. Comment out or\nremove the `openclaw`, `hermes`, and `postgres` blocks — those run natively on\nVM-2 and VM-3.\n\n### 5. Bring it up\n\n```bash\nmake up        # builds + starts n8n, claude-gateway, redis\nmake health    # all three should report healthy\nmake test      # end-to-end smoke test across all 3 VMs\n```\n\n### 6. Import the workflow\n\nOpen `http://\u003cvm-1-ip\u003e:5678`, import\n`n8n/workflows/gateforge-loom-pipeline.json`, then edit the HTTP node URLs:\n\n| n8n Node | URL |\n|---|---|\n| Claude `/plan` · `/merge` · `/synthesize` | `http://claude-gateway:8001/...` (Docker network, same VM) |\n| Hermes `/recall` · `/write` | `http://100.x.x.3:8003/...` (Tailscale IP of VM-3) |\n| OpenClaw `/execute` | `http://100.x.x.2:8002/execute` (Tailscale IP of VM-2) |\n\n---\n\n## VM-2 install — OpenClaw native (Hands)\n\nVM-2 runs OpenClaw directly as a systemd service — no Docker.\n\n### 1. Prerequisites + service user\n\n```bash\nsudo apt update \u0026\u0026 sudo apt install -y \\\n    python3.12 python3.12-venv python3-pip git curl ufw fail2ban\n\nsudo useradd --system --create-home --shell /bin/bash openclaw\nsudo mkdir -p /opt/openclaw /var/log/openclaw\nsudo chown -R openclaw:openclaw /opt/openclaw /var/log/openclaw\n```\n\n### 2. Clone + install into a venv\n\n```bash\nsudo -u openclaw bash \u003c\u003c'EOF'\ncd /opt/openclaw\ngit clone https://github.com/tonylnng/gateforge-loom.git src\ncd src/services/openclaw\npython3.12 -m venv /opt/openclaw/venv\n/opt/openclaw/venv/bin/pip install -r requirements.txt\n/opt/openclaw/venv/bin/pip install \"uvicorn[standard]\"\nEOF\n```\n\n### 3. Environment file\n\n```bash\nsudo tee /etc/openclaw.env \u003e/dev/null \u003c\u003cEOF\nINTERNAL_API_TOKEN=\u003csame-as-VM-1\u003e\nLOG_LEVEL=INFO\nSTUB_MODE=0\nTOOL_TIMEOUT_SEC=60\nEOF\nsudo chmod 600 /etc/openclaw.env\nsudo chown openclaw:openclaw /etc/openclaw.env\n```\n\n### 4. systemd unit\n\n```bash\nsudo tee /etc/systemd/system/openclaw.service \u003e/dev/null \u003c\u003c'EOF'\n[Unit]\nDescription=OpenClaw (Gateforge-Loom Hands agent)\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nType=simple\nUser=openclaw\nGroup=openclaw\nWorkingDirectory=/opt/openclaw/src/services/openclaw\nEnvironmentFile=/etc/openclaw.env\nExecStart=/opt/openclaw/venv/bin/uvicorn app.main:app \\\n          --host 0.0.0.0 --port 8002 --workers 2\nRestart=on-failure\nRestartSec=5\nStandardOutput=append:/var/log/openclaw/stdout.log\nStandardError=append:/var/log/openclaw/stderr.log\n\n# Hardening\nNoNewPrivileges=true\nPrivateTmp=true\nProtectSystem=strict\nReadWritePaths=/var/log/openclaw /opt/openclaw\nProtectHome=true\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\nsudo systemctl daemon-reload\nsudo systemctl enable --now openclaw\nsudo systemctl status openclaw\n```\n\n### 5. (Optional) Playwright for `browser.action`\n\n```bash\nsudo -u openclaw /opt/openclaw/venv/bin/pip install playwright\nsudo -u openclaw /opt/openclaw/venv/bin/playwright install chromium\nsudo /opt/openclaw/venv/bin/playwright install-deps chromium\n```\n\n### 6. Tailscale + firewall\n\n```bash\ncurl -fsSL https://tailscale.com/install.sh | sh\nsudo tailscale up --hostname=loom-hands\nsudo ufw default deny incoming\nsudo ufw allow 22/tcp\nsudo ufw allow in on tailscale0       # exposes 8002 only over the tailnet\nsudo ufw enable\n```\n\n---\n\n## VM-3 install — Hermes native (Memory)\n\nVM-3 runs both Hermes and Postgres+pgvector natively.\n\n### 1. Postgres 16 + pgvector\n\n```bash\nsudo apt update \u0026\u0026 sudo apt install -y \\\n    python3.12 python3.12-venv python3-pip git curl ufw fail2ban \\\n    postgresql-16 postgresql-16-pgvector\nsudo systemctl enable --now postgresql\n```\n\n### 2. Initialise the database (use the repo's init.sql)\n\n```bash\nsudo -u postgres psql \u003c\u003c'EOF'\nCREATE USER hermes WITH PASSWORD '\u003cstrong-pw\u003e';\nCREATE DATABASE hermes_db OWNER hermes;\n\\c hermes_db\nCREATE EXTENSION IF NOT EXISTS vector;\nEOF\n\n# Load the seed schema from the repo (ships a seed SOP so /recall works day 1)\ngit clone https://github.com/tonylnng/gateforge-loom.git /tmp/gfl\nsudo -u postgres psql -d hermes_db -f /tmp/gfl/infra/postgres/init.sql\n```\n\n### 3. Service user + install Hermes\n\n```bash\nsudo useradd --system --create-home --shell /bin/bash hermes\nsudo mkdir -p /opt/hermes /var/log/hermes\nsudo chown -R hermes:hermes /opt/hermes /var/log/hermes\n\nsudo -u hermes bash \u003c\u003c'EOF'\ncd /opt/hermes\ngit clone https://github.com/tonylnng/gateforge-loom.git src\ncd src/services/hermes\npython3.12 -m venv /opt/hermes/venv\n/opt/hermes/venv/bin/pip install -r requirements.txt\n/opt/hermes/venv/bin/pip install \"uvicorn[standard]\"\nEOF\n```\n\n### 4. Environment file\n\n```bash\nsudo tee /etc/hermes.env \u003e/dev/null \u003c\u003cEOF\nINTERNAL_API_TOKEN=\u003csame-as-VM-1\u003e\nDATABASE_URL=postgresql://hermes:\u003cstrong-pw\u003e@localhost:5432/hermes_db\nLOG_LEVEL=INFO\nEMBEDDING_PROVIDER=stub\nEOF\nsudo chmod 600 /etc/hermes.env\nsudo chown hermes:hermes /etc/hermes.env\n```\n\n### 5. systemd unit\n\n```bash\nsudo tee /etc/systemd/system/hermes.service \u003e/dev/null \u003c\u003c'EOF'\n[Unit]\nDescription=Hermes (Gateforge-Loom Memory agent)\nAfter=network-online.target postgresql.service\nWants=network-online.target\nRequires=postgresql.service\n\n[Service]\nType=simple\nUser=hermes\nGroup=hermes\nWorkingDirectory=/opt/hermes/src/services/hermes\nEnvironmentFile=/etc/hermes.env\nExecStart=/opt/hermes/venv/bin/uvicorn app.main:app \\\n          --host 0.0.0.0 --port 8003 --workers 2\nRestart=on-failure\nRestartSec=5\nStandardOutput=append:/var/log/hermes/stdout.log\nStandardError=append:/var/log/hermes/stderr.log\n\nNoNewPrivileges=true\nPrivateTmp=true\nProtectSystem=strict\nReadWritePaths=/var/log/hermes /opt/hermes\nProtectHome=true\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\nsudo systemctl daemon-reload\nsudo systemctl enable --now hermes\nsudo systemctl status hermes\n```\n\n### 6. Lock Postgres to localhost + Tailscale, then firewall\n\nEdit `/etc/postgresql/16/main/postgresql.conf`:\n\n```\nlisten_addresses = 'localhost,100.x.x.3'   # Tailscale IP only\n```\n\nEdit `/etc/postgresql/16/main/pg_hba.conf`:\n\n```\nhost hermes_db hermes 100.0.0.0/8 scram-sha-256\n```\n\nReload and firewall:\n\n```bash\nsudo systemctl restart postgresql\ncurl -fsSL https://tailscale.com/install.sh | sh\nsudo tailscale up --hostname=loom-memory\nsudo ufw default deny incoming\nsudo ufw allow 22/tcp\nsudo ufw allow in on tailscale0       # exposes 8003 + 5432 only over the tailnet\nsudo ufw enable\n```\n\n---\n\n## Backup \u0026 recovery\n\n\u003e The episodic memory in Postgres is **the only durable asset** in the stack —\n\u003e Redis state is recoverable, and n8n workflows live in the repo. Protect VM-3.\n\n### Nightly Postgres backup (VM-3, native)\n\n```bash\nsudo tee /etc/cron.daily/hermes-backup \u003e/dev/null \u003c\u003c'EOF'\n#!/bin/bash\nset -euo pipefail\nDEST=/var/backups/hermes\nmkdir -p \"$DEST\"\npg_dump -U hermes hermes_db | gzip \u003e \"$DEST/hermes-$(date +%F).sql.gz\"\n# Retain 30 days\nfind \"$DEST\" -name \"hermes-*.sql.gz\" -mtime +30 -delete\nEOF\nsudo chmod +x /etc/cron.daily/hermes-backup\n```\n\n### Off-VM rotation (recommended)\n\nPush the nightly dump off-box so a VM loss doesn't lose memory:\n\n```bash\n# after pg_dump, sync to object storage (S3 / MinIO / B2)\naws s3 cp /var/backups/hermes/hermes-$(date +%F).sql.gz \\\n  s3://your-bucket/gateforge-loom/hermes/\n```\n\n### n8n volume backup (VM-1, Docker)\n\n```bash\ndocker run --rm \\\n  -v gateforge-loom_n8n-data:/src:ro \\\n  -v /var/backups/gateforge-loom:/dst \\\n  alpine tar czf \"/dst/n8n-$(date +%F).tgz\" -C /src .\n```\n\n### Restore drill\n\n```bash\n# On a fresh VM-3, after CREATE DATABASE hermes_db + CREATE EXTENSION vector:\ngunzip -c hermes-2026-06-02.sql.gz | sudo -u postgres psql -d hermes_db\nsudo systemctl restart hermes\ncurl http://100.x.x.3:8003/health    # expect healthy, not degraded\n```\n\nTest restore quarterly. A backup you haven't restored is a wish, not a backup.\n\n---\n\n## Operations cheatsheet\n\n| Task | VM-1 (Brain+Orch) | VM-2 (OpenClaw) | VM-3 (Hermes) |\n|---|---|---|---|\n| **Status** | `docker compose ps` | `systemctl status openclaw` | `systemctl status hermes postgresql` |\n| **Logs** | `docker compose logs -f` | `journalctl -u openclaw -f` | `journalctl -u hermes -f` |\n| **Restart** | `make up` | `sudo systemctl restart openclaw` | `sudo systemctl restart hermes` |\n| **Update code** | `git pull \u0026\u0026 make build \u0026\u0026 make up` | `cd /opt/openclaw/src \u0026\u0026 sudo -u openclaw git pull \u0026\u0026 sudo systemctl restart openclaw` | `cd /opt/hermes/src \u0026\u0026 sudo -u hermes git pull \u0026\u0026 sudo systemctl restart hermes` |\n| **Health (from VM-1)** | `docker exec gfl-claude-gateway curl -s http://localhost:8000/health` | `curl http://100.x.x.2:8002/health` | `curl http://100.x.x.3:8003/health` |\n\n---\n\n## Adding more agents\n\nThe whole point of the loom metaphor: more threads, same machine.\n\n1. **Scaffold a new service** — copy `services/openclaw/` to\n   `services/\u003cyour-agent\u003e/`, rename the FastAPI app, define endpoints.\n2. **Deploy it** — either add a Compose block on VM-1 (`networks: [loomnet]`\n   + a `/health` healthcheck) or install it natively as a new systemd service\n   on its own VM, following the VM-2 pattern.\n3. **Register tools (optional)** — if it's an executor, expose a `GET /tools`\n   manifest so the Brain can discover its capabilities.\n4. **Add an n8n node** — drop an HTTP Request node in the workflow at the right\n   point, pointing at the new service's Tailscale IP. Reroute connections.\n5. **Update docs** — add a row to the components table here and a section in\n   `docs/components.md`.\n\nExamples of agents that fit naturally:\n\n| Agent | Role | Where in workflow |\n|---|---|---|\n| **Validator** | Schema-check tool outputs | between OpenClaw and Aggregate |\n| **Critic** | Score plans before execution | between `/merge` and `Split` |\n| **Router** | Pick which executor for a step | inside `Split steps` |\n| **Reviewer** | Human-in-the-loop approval | before `Output sink` |\n| **Embedder** | Compute embeddings for Hermes | called by `/write` |\n\n---\n\n## Project layout\n\n```\ngateforge-loom/\n├── README.md                  # this file\n├── Makefile                   # up · down · health · test · clean · nuke\n├── docker-compose.yml         # full stack (trim to n8n+brain+redis for VM-1)\n├── .env.example\n├── docs/\n│   ├── components.md          # per-component deep dive\n│   ├── api-contract.md        # endpoint reference\n│   ├── deployment.md          # VM bring-up, hardening, backups\n│   └── architecture.md        # design decisions + extension points\n├── infra/postgres/init.sql    # pgvector + tables + seed SOP (load on VM-3)\n├── n8n/workflows/             # importable workflow JSON (VM-1)\n├── schemas/                   # JSON Schemas for tool I/O\n├── scripts/                   # health + smoke-test\n└── services/\n    ├── claude-gateway/        # 🧠 Brain   → VM-1 (Docker)\n    ├── openclaw/              # ✋ Hands   → VM-2 (native systemd)\n    └── hermes/                # 📚 Memory  → VM-3 (native systemd)\n```\n\n---\n\n## Roadmap\n\n- [x] Phase 1 — stub services + n8n wiring + smoke test\n- [x] Phase 1.5 — 3-VM hybrid topology (Docker Brain/Orch + native Hands/Memory) over Tailscale\n- [ ] Phase 2 — live Brain via **Vercel AI Gateway** (Claude Opus, Anthropic-compatible base URL)\n- [ ] Phase 3 — Playwright tool inside `openclaw`\n- [ ] Phase 4 — real embeddings in `hermes` (voyage-3 or text-embedding-3-small)\n- [ ] Phase 5 — Validator + Critic agents\n- [ ] Phase 6 — multi-tenant (`tenant_id` everywhere) + per-job cost guardrails\n- [ ] Phase 7 — Helm chart for OpenShift / Kubernetes deployment\n\n---\n\n## License\n\nMIT. See [LICENSE](LICENSE).\n\n---\n\n*Designed and maintained by [@tonylnng](https://github.com/tonylnng).\nInspired by the \"三個工具不是在競爭,而是在分層\" framing — Brain, Hands,\nand Memory don't replace each other, they layer.*\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftonylnng%2Fgateforge-loom","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftonylnng%2Fgateforge-loom","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftonylnng%2Fgateforge-loom/lists"}