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
Current status
{
"$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)"
}
}
}