LoopSignal

LoopSignal is a community preference signal that expresses demand or surplus intent for specific material categories. Each city node publishes its own signal configuration through a local governance process. LoopSignals inform routing and matching across the federation, helping nodes prioritise the right resources for the right places. This is an early-stage lab concept with no public pilots yet.

Scope & boundaries

In scope
Node-level signal configuration, community voting on signal changes, and federation-aware routing weights.
Out of scope
Individual user preference tracking, binding regulatory commitments, or financial instruments.
Status
Lab-demo concept only — no public pilots or deployments yet.

How it works

Signal values
Each signal is a number from 0.0 to 1.0 representing the node's preference weight for a material category (e.g., 0.8 for plastic-pet means high local demand).
Material categories
Signals cover 30+ standardised categories: plastics (PET, HDPE, PVC, mixed), metals (steel, aluminium, copper), organics, glass, paper, cardboard, textiles, and e-waste.
Governance
Signal changes go through a SignalProposal and LoopVote process. Results are recorded with turnout and approval percentages and published as LoopSignalConfig payloads.
Routing effect
High signals for a category lower the effective import penalty for that material, attracting offers from neighbouring nodes. Low signals raise the export penalty, keeping surplus local.
Validity window
Each LoopSignalConfig carries valid_from and valid_until timestamps, so routing decisions always use the current approved signal set.

Data model snapshot

LoopSignalConfig

Node identifier, signal values by category, validity window, and governance vote reference.

LoopVote

Vote ID, proposals (category, current/proposed value, rationale), voting period, and results (turnout, approval, status).

SignalProposal

Proposed changes to signal values for one or more categories, with voting window and per-change rationale.

Current status

Specification

LoopSignal schema and governance rules are defined in the protocol spec.

Read the spec

Status

No public pilots or deployments yet.

Register interest

Effect on routing

LoopSignals feed directly into the LoopCost penalty formula.

LoopCost →
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://local-loop-io.github.io/projects/loop-protocol/schemas/v0.2.0/loopsignal.schema.json",
  "title": "LoopSignal",
  "description": "Schema for LoopSignal configuration and voting",
  "oneOf": [
    {
      "$ref": "#/definitions/LoopSignalConfig"
    },
    {
      "$ref": "#/definitions/LoopVote"
    },
    {
      "$ref": "#/definitions/SignalProposal"
    }
  ],
  "definitions": {
    "LoopSignalConfig": {
      "type": "object",
      "required": [
        "@context",
        "@type",
        "node",
        "signals",
        "valid_from",
        "valid_until"
      ],
      "properties": {
        "@context": {
          "type": "string",
          "enum": [
            "https://local-loop-io.github.io/projects/loop-protocol/contexts/loop-v0.1.1.jsonld",
            "https://local-loop-io.github.io/projects/loop-protocol/contexts/loop-v0.2.0.jsonld"
          ]
        },
        "@type": {
          "type": "string",
          "const": "LoopSignalConfig"
        },
        "schema_version": {
          "description": "Optional schema version identifier.",
          "type": "string",
          "pattern": "^0\\.[1-9]\\d*\\.\\d+$",
          "examples": [
            "0.2.0"
          ]
        },
        "node": {
          "description": "Node identifier",
          "type": "string",
          "pattern": "^[a-z0-9\\-]+\\.loop$",
          "examples": [
            "munich.loop"
          ]
        },
        "signals": {
          "description": "Signal values by category",
          "type": "object",
          "properties": {
            "plastic-pet": {
              "$ref": "#/definitions/signalValue"
            },
            "plastic-hdpe": {
              "$ref": "#/definitions/signalValue"
            },
            "plastic-pvc": {
              "$ref": "#/definitions/signalValue"
            },
            "plastic-ldpe": {
              "$ref": "#/definitions/signalValue"
            },
            "plastic-pp": {
              "$ref": "#/definitions/signalValue"
            },
            "plastic-ps": {
              "$ref": "#/definitions/signalValue"
            },
            "plastic-mixed": {
              "$ref": "#/definitions/signalValue"
            },
            "metal-steel": {
              "$ref": "#/definitions/signalValue"
            },
            "metal-aluminum": {
              "$ref": "#/definitions/signalValue"
            },
            "metal-copper": {
              "$ref": "#/definitions/signalValue"
            },
            "metal-mixed": {
              "$ref": "#/definitions/signalValue"
            },
            "organic-food": {
              "$ref": "#/definitions/signalValue"
            },
            "organic-garden": {
              "$ref": "#/definitions/signalValue"
            },
            "organic-wood": {
              "$ref": "#/definitions/signalValue"
            },
            "glass-clear": {
              "$ref": "#/definitions/signalValue"
            },
            "glass-brown": {
              "$ref": "#/definitions/signalValue"
            },
            "glass-green": {
              "$ref": "#/definitions/signalValue"
            },
            "glass-mixed": {
              "$ref": "#/definitions/signalValue"
            },
            "paper-clean": {
              "$ref": "#/definitions/signalValue"
            },
            "paper-newsprint": {
              "$ref": "#/definitions/signalValue"
            },
            "cardboard": {
              "$ref": "#/definitions/signalValue"
            },
            "paper-mixed": {
              "$ref": "#/definitions/signalValue"
            },
            "textile-cotton": {
              "$ref": "#/definitions/signalValue"
            },
            "textile-wool": {
              "$ref": "#/definitions/signalValue"
            },
            "textile-synthetic": {
              "$ref": "#/definitions/signalValue"
            },
            "textile-mixed": {
              "$ref": "#/definitions/signalValue"
            },
            "ewaste-computers": {
              "$ref": "#/definitions/signalValue"
            },
            "ewaste-phones": {
              "$ref": "#/definitions/signalValue"
            },
            "ewaste-batteries": {
              "$ref": "#/definitions/signalValue"
            },
            "ewaste-mixed": {
              "$ref": "#/definitions/signalValue"
            },
            "default": {
              "$ref": "#/definitions/signalValue"
            }
          },
          "additionalProperties": false
        },
        "valid_from": {
          "description": "When signals become active",
          "type": "string",
          "format": "date-time",
          "examples": [
            "2025-06-01T00:00:00Z"
          ]
        },
        "valid_until": {
          "description": "When signals expire",
          "type": "string",
          "format": "date-time",
          "examples": [
            "2025-06-30T23:59:59Z"
          ]
        },
        "approved_by": {
          "description": "Voting record",
          "type": "object",
          "properties": {
            "vote_id": {
              "type": "string",
              "examples": [
                "2025-05-vote"
              ]
            },
            "turnout": {
              "type": "number",
              "minimum": 0,
              "maximum": 1,
              "examples": [
                0.35
              ]
            },
            "approval": {
              "type": "number",
              "minimum": 0,
              "maximum": 1,
              "examples": [
                0.68
              ]
            }
          }
        }
      },
      "additionalProperties": true
    },
    "LoopVote": {
      "type": "object",
      "required": [
        "@context",
        "@type",
        "node",
        "vote_id",
        "proposals",
        "voting_period",
        "results"
      ],
      "properties": {
        "@context": {
          "type": "string",
          "enum": [
            "https://local-loop-io.github.io/projects/loop-protocol/contexts/loop-v0.1.1.jsonld",
            "https://local-loop-io.github.io/projects/loop-protocol/contexts/loop-v0.2.0.jsonld"
          ]
        },
        "@type": {
          "type": "string",
          "const": "LoopVote"
        },
        "schema_version": {
          "description": "Optional schema version identifier.",
          "type": "string",
          "pattern": "^0\\.[1-9]\\d*\\.\\d+$",
          "examples": [
            "0.2.0"
          ]
        },
        "node": {
          "type": "string",
          "pattern": "^[a-z0-9\\-]+\\.loop$"
        },
        "vote_id": {
          "type": "string",
          "examples": [
            "2025-05-plastic-increase"
          ]
        },
        "proposals": {
          "type": "array",
          "items": {
            "type": "object",
            "required": [
              "category",
              "current_value",
              "proposed_value"
            ],
            "properties": {
              "category": {
                "type": "string"
              },
              "current_value": {
                "$ref": "#/definitions/signalValue"
              },
              "proposed_value": {
                "$ref": "#/definitions/signalValue"
              },
              "rationale": {
                "type": "string",
                "maxLength": 500
              }
            }
          }
        },
        "voting_period": {
          "type": "object",
          "required": [
            "start",
            "end"
          ],
          "properties": {
            "start": {
              "type": "string",
              "format": "date-time"
            },
            "end": {
              "type": "string",
              "format": "date-time"
            }
          }
        },
        "results": {
          "type": "object",
          "required": [
            "total_eligible",
            "total_voted",
            "votes_for",
            "votes_against",
            "status"
          ],
          "properties": {
            "total_eligible": {
              "type": "integer",
              "minimum": 0
            },
            "total_voted": {
              "type": "integer",
              "minimum": 0
            },
            "votes_for": {
              "type": "integer",
              "minimum": 0
            },
            "votes_against": {
              "type": "integer",
              "minimum": 0
            },
            "status": {
              "type": "string",
              "enum": [
                "passed",
                "failed",
                "pending"
              ]
            }
          }
        }
      },
      "additionalProperties": true
    },
    "SignalProposal": {
      "type": "object",
      "required": [
        "@context",
        "@type",
        "node",
        "changes",
        "voting_opens",
        "voting_closes"
      ],
      "properties": {
        "@context": {
          "type": "string",
          "enum": [
            "https://local-loop-io.github.io/projects/loop-protocol/contexts/loop-v0.1.1.jsonld",
            "https://local-loop-io.github.io/projects/loop-protocol/contexts/loop-v0.2.0.jsonld"
          ]
        },
        "@type": {
          "type": "string",
          "const": "SignalProposal"
        },
        "schema_version": {
          "description": "Optional schema version identifier.",
          "type": "string",
          "pattern": "^0\\.[1-9]\\d*\\.\\d+$",
          "examples": [
            "0.2.0"
          ]
        },
        "node": {
          "type": "string",
          "pattern": "^[a-z0-9\\-]+\\.loop$"
        },
        "changes": {
          "type": "array",
          "minItems": 1,
          "items": {
            "type": "object",
            "required": [
              "category",
              "current",
              "proposed"
            ],
            "properties": {
              "category": {
                "type": "string"
              },
              "current": {
                "$ref": "#/definitions/signalValue"
              },
              "proposed": {
                "$ref": "#/definitions/signalValue"
              },
              "reason": {
                "type": "string",
                "maxLength": 500
              }
            }
          }
        },
        "voting_opens": {
          "type": "string",
          "format": "date-time"
        },
        "voting_closes": {
          "type": "string",
          "format": "date-time"
        }
      },
      "additionalProperties": true
    },
    "signalValue": {
      "type": "number",
      "minimum": 0,
      "maximum": 1,
      "description": "Signal strength (0.0 to 1.0)"
    }
  }
}