MEAL: Multi-User Engagement Asynchronous Ledger
agstack/pancake
·
docs/MEAL.mdEdits belong in that repository, not in
agstack/docs.
Licensed under
EUPL-1.2,
not this site's CC BY-SA 4.0.Version: 1.0
Status: Specification & Implementation
Purpose: Immutable, spatio-temporally indexed chat/collaboration logs for PANCAKE
Table of Contents
- Overview
- Core Concepts
- MEAL Structure
- Relationship to SIP and BITE
- Two-Level Indexing
- Implementation
- Query Patterns
- Use Cases
- Security & Privacy
- Comparison with Alternatives
Overview
A MEAL (Multi-User Engagement Asynchronous Ledger) is a persistent, append-only log that captures the complete history of multi-user, asynchronous engagement (chat, collaboration, annotations, etc.) within the PANCAKE ecosystem.
The Problem MEAL Solves
Traditional chat systems:
- Time-indexed only (no spatial context)
- Isolated from field data (SIRUP, BITEs, SIPs)
- No cryptographic verification
- Can’t correlate conversations with agricultural events
MEAL innovation:
- Spatio-temporal indexing: Every conversation linked to time AND place
- Immutable sequencing: Cryptographically verifiable log
- Data integration: Chat packets (SIPs/BITEs) link to field data
- Context-aware retrieval: “Show me all discussions about Field A during the drought”
Key Characteristics
| Property | Description |
|---|---|
| Immutable | Append-only, no edits or deletes |
| Indexed | Time (mandatory) + Location (optional) |
| Ordered | Strict chronological sequence |
| Verifiable | Cryptographic hash chain |
| Contextual | Links to GeoIDs, SIRUP, field events |
| Multi-user | Supports N participants (human + AI agents) |
Core Concepts
What is a MEAL?
Think of a MEAL as:
- A thread of conversation/collaboration
- A ledger of agricultural decision-making
- A timeline linking chat to field events
- A context capsule for spatio-temporal AI queries
MEAL vs Chat Thread
| Traditional Chat | MEAL |
|---|---|
| Time-based thread | Spatio-temporal log |
| No location context | GeoID-indexed |
| Mutable (edits/deletes) | Immutable (append-only) |
| Isolated from data | Integrated with SIRUP/BITEs |
| No verification | Cryptographic hash chain |
| Text-only focus | Polyglot (text, images, data packets) |
The “String of Packets” Metaphor
A MEAL is a string where each bead is either:
- A SIP (Sensor Index Pointer or Simple text message)
- A BITE (Bidirectional Interchange Transport Envelope - rich data)
The string is:
- Ordered: Each packet has a sequence number
- Linked: Each packet references the previous hash
- Verifiable: The entire chain can be validated
- Indexed: Both at MEAL level (root) and packet level (individual)
MEAL Structure
Root Metadata Object (The “Cover Page”)
When a MEAL is created, a root metadata object is stored in CAKE:
{
"meal_id": "01HQZK8FXZC9YT8KJN6M7P2Q5R",
"meal_type": "field_discussion",
"created_at_time": "2025-10-31T20:45:01Z",
"created_at_location": "38.2492° N, 122.0405° W",
"primary_time_index": "2025-10-31T20:45:01Z",
"last_updated_time": "2025-11-01T14:10:22Z",
"primary_location_index": {
"geoid": "a4fd692c2578b270a937ce77869361e3cd22cd0b021c6ad23c995868bd11651e",
"label": "Field A - North Block",
"coordinates": [38.5816, -121.4944]
},
"location_context": [
{
"geoid": "field-A",
"type": "field",
"label": "Primary Field"
},
{
"geoid": "farm-B",
"type": "farm",
"label": "Smith Family Farm"
},
{
"geoid": "county-C",
"type": "administrative",
"label": "Yolo County, CA"
}
],
"participant_agents": [
{
"agent_id": "user-A45B",
"agent_type": "human",
"name": "John Smith (Farm Manager)",
"joined_at": "2025-10-31T20:45:01Z"
},
{
"agent_id": "user-C992",
"agent_type": "human",
"name": "Dr. Sarah Chen (Agronomist)",
"joined_at": "2025-10-31T21:02:15Z"
},
{
"agent_id": "agent-PAN-007",
"agent_type": "ai",
"name": "PANCAKE AI Assistant",
"joined_at": "2025-10-31T20:45:01Z"
}
],
"packet_sequence": {
"first_packet_id": "01HQZK8FXZC9YT8KJN6M7P2Q5S",
"last_packet_id": "01HQZM3DNWR8FV4BH2K9N6P7Q8",
"packet_count": 42,
"sip_count": 35,
"bite_count": 7
},
"cryptographic_chain": {
"root_hash": "0x8B7A...F9E2",
"last_packet_hash": "0x3C2D...A9F1",
"hash_algorithm": "SHA-256",
"chain_verifiable": true
},
"topics": [
"irrigation_scheduling",
"pest_management",
"yield_prediction"
],
"related_sirup": [
{
"sirup_type": "weather_forecast",
"geoid": "field-A",
"time_range": ["2025-10-31", "2025-11-07"]
},
{
"sirup_type": "satellite_imagery",
"geoid": "field-A",
"dates": ["2025-10-28", "2025-11-02"]
}
],
"meal_status": "active",
"archived": false,
"retention_policy": "indefinite"
}
Individual Packet Structure
Each packet in the MEAL string is either a SIP or BITE, but with additional MEAL-specific metadata:
SIP in MEAL Context
{
"packet_id": "01HQZK8FXZC9YT8KJN6M7P2Q5S",
"packet_type": "sip",
"meal_id": "01HQZK8FXZC9YT8KJN6M7P2Q5R",
"sequence": {
"number": 1,
"previous_packet_id": null,
"previous_packet_hash": null
},
"time_index": "2025-10-31T20:45:01Z",
"location_index": {
"geoid": "office-123",
"type": "point",
"coordinates": [38.2492, -122.0405],
"label": "Farm Office"
},
"author": {
"agent_id": "user-A45B",
"agent_type": "human",
"name": "John Smith"
},
"content": {
"text": "Just checked the weather forecast. Looks like rain coming this weekend. Should we adjust irrigation schedule?",
"mentions": ["user-C992", "agent-PAN-007"],
"references": []
},
"cryptographic": {
"content_hash": "0x1A2B...3C4D",
"packet_hash": "0x8B7A...F9E2",
"signature": "0xABCD...EF01"
}
}
BITE in MEAL Context
{
"packet_id": "01HQZK9GYWC9YT8KJN6M7P2Q5T",
"packet_type": "bite",
"meal_id": "01HQZK8FXZC9YT8KJN6M7P2Q5R",
"sequence": {
"number": 5,
"previous_packet_id": "01HQZK8MXZC9YT8KJN6M7P2Q5U",
"previous_packet_hash": "0x2B3C...4D5E"
},
"time_index": "2025-10-31T22:15:30Z",
"location_index": {
"geoid": "field-A",
"type": "polygon",
"coordinates": [[38.5816, -121.4944], ...],
"label": "Field A - North Block"
},
"author": {
"agent_id": "user-A45B",
"agent_type": "human",
"name": "John Smith"
},
"bite": {
"Header": {
"id": "01HQZK9GYWC9YT8KJN6M7P2Q5T",
"geoid": "field-A",
"timestamp": "2025-10-31T22:15:30Z",
"type": "observation",
"source": {
"app": "TerraTrac Mobile",
"user": "user-A45B"
}
},
"Body": {
"observation_type": "pest_scouting",
"pest_species": "aphids",
"severity": "moderate",
"affected_area_pct": 15,
"photo_url": "https://storage.pancake.io/photos/abc123.jpg",
"notes": "Found in northwest corner, about 15% of plants affected"
},
"Footer": {
"hash": "0x9C8D...7E6F",
"schema_version": "1.0",
"tags": ["pest", "aphids", "observation", "field-A"]
}
},
"cryptographic": {
"bite_hash": "0x9C8D...7E6F",
"packet_hash": "0x3D4E...5F60",
"signature": "0xDEF0...1234"
},
"context": {
"in_response_to": "01HQZK8FXZC9YT8KJN6M7P2Q5S",
"mentions": ["user-C992"],
"caption": "Photo attached from field. Aphids confirmed in NW corner."
}
}
Relationship to SIP and BITE
The Three Data Primitives
| Primitive | Purpose | Structure | Mutability |
|---|---|---|---|
| SIP | High-frequency sensor data or simple messages | Lightweight JSON | Immutable |
| BITE | Rich, polyglot agricultural data | Header|Body|Footer | Immutable |
| MEAL | Multi-user engagement log (string of SIPs/BITEs) | Root + Packet Sequence | Append-only |
MEAL Contains SIPs and BITEs
MEAL (The String)
├── Packet 1: SIP (text message)
├── Packet 2: SIP (text message)
├── Packet 3: BITE (photo observation)
├── Packet 4: SIP (text reply)
├── Packet 5: BITE (weather data reference)
├── Packet 6: SIP (text message)
└── Packet N: BITE (recommendation)
SIP/BITE Dual Identity
When a SIP or BITE is posted in a MEAL context:
- It exists as a standalone data packet (stored in
bitesorsipstable) - It also exists as a MEAL packet (linked in
meal_packetstable)
This dual identity enables:
- Standalone queries: “Find all observations in Field A”
- MEAL queries: “Show me the conversation thread about Field A”
- Correlation queries: “Link conversation to field data”
Two-Level Indexing
Level 1: MEAL Root Index (The Log’s Context)
The MEAL root provides default context for the entire thread:
-- Find all MEALs for Field A
SELECT * FROM meals
WHERE primary_location_index->>'geoid' = 'field-A';
-- Find recent MEALs
SELECT * FROM meals
WHERE last_updated_time >= NOW() - INTERVAL '7 days';
-- Find MEALs with AI participation
SELECT * FROM meals
WHERE participant_agents @> '[{"agent_type": "ai"}]';
Level 2: Packet-Level Index (Individual Entries)
Each packet can override the MEAL’s default context:
-- Find all packets posted from Field B (even in Field A's MEAL)
SELECT * FROM meal_packets
WHERE location_index->>'geoid' = 'field-B';
-- Track user movement through conversation
SELECT
packet_id,
time_index,
location_index->>'label' as location,
content->>'text' as message
FROM meal_packets
WHERE meal_id = 'meal-123'
AND author->>'agent_id' = 'user-A45B'
ORDER BY sequence_number;
The Power of Dual Indexing
Query: “Show me discussions about Field A where someone was physically ON Field A”
SELECT DISTINCT m.meal_id, m.primary_location_index
FROM meals m
JOIN meal_packets mp ON m.meal_id = mp.meal_id
WHERE m.primary_location_index->>'geoid' = 'field-A' -- MEAL about Field A
AND mp.location_index->>'geoid' = 'field-A' -- Posted FROM Field A
AND mp.time_index >= NOW() - INTERVAL '30 days';
Implementation
Database Schema
-- MEAL Root Table
CREATE TABLE meals (
meal_id VARCHAR(26) PRIMARY KEY, -- ULID
meal_type VARCHAR(50),
-- Temporal indexing (MANDATORY)
created_at_time TIMESTAMP WITH TIME ZONE NOT NULL,
last_updated_time TIMESTAMP WITH TIME ZONE NOT NULL,
primary_time_index TIMESTAMP WITH TIME ZONE NOT NULL,
-- Spatial indexing (OPTIONAL but recommended)
primary_location_index JSONB, -- {geoid, label, coordinates, type}
location_context JSONB[], -- Array of related geoids
-- Participants
participant_agents JSONB NOT NULL, -- Array of agent objects
-- Packet tracking
packet_count INTEGER DEFAULT 0,
sip_count INTEGER DEFAULT 0,
bite_count INTEGER DEFAULT 0,
first_packet_id VARCHAR(26),
last_packet_id VARCHAR(26),
-- Cryptographic verification
root_hash VARCHAR(66),
last_packet_hash VARCHAR(66),
hash_algorithm VARCHAR(20) DEFAULT 'SHA-256',
chain_verifiable BOOLEAN DEFAULT true,
-- Metadata
topics TEXT[],
related_sirup JSONB[],
meal_status VARCHAR(20) DEFAULT 'active',
archived BOOLEAN DEFAULT false,
retention_policy VARCHAR(50) DEFAULT 'indefinite',
-- Timestamps
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Indexes for fast retrieval
CREATE INDEX idx_meals_time ON meals(primary_time_index);
CREATE INDEX idx_meals_location ON meals USING GIN(primary_location_index);
CREATE INDEX idx_meals_updated ON meals(last_updated_time);
CREATE INDEX idx_meals_participants ON meals USING GIN(participant_agents);
CREATE INDEX idx_meals_topics ON meals USING GIN(topics);
-- MEAL Packets Table (The String)
CREATE TABLE meal_packets (
packet_id VARCHAR(26) PRIMARY KEY, -- ULID
meal_id VARCHAR(26) NOT NULL REFERENCES meals(meal_id),
packet_type VARCHAR(10) NOT NULL, -- 'sip' or 'bite'
-- Sequence (for hash chain)
sequence_number INTEGER NOT NULL,
previous_packet_id VARCHAR(26),
previous_packet_hash VARCHAR(66),
-- Temporal indexing (MANDATORY)
time_index TIMESTAMP WITH TIME ZONE NOT NULL,
-- Spatial indexing (OPTIONAL, overrides MEAL default)
location_index JSONB, -- {geoid, type, coordinates, label}
-- Author
author JSONB NOT NULL, -- {agent_id, agent_type, name}
-- Content (either SIP or BITE)
sip_data JSONB, -- For SIP packets
bite_data JSONB, -- For BITE packets
-- Context
context JSONB, -- {in_response_to, mentions, caption, references}
-- Cryptographic
content_hash VARCHAR(66),
packet_hash VARCHAR(66),
signature VARCHAR(132),
-- Link to standalone packet (if exists)
sip_id VARCHAR(26), -- References sips(id)
bite_id VARCHAR(26), -- References bites(id)
-- Timestamps
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(meal_id, sequence_number)
);
-- Indexes
CREATE INDEX idx_meal_packets_meal ON meal_packets(meal_id, sequence_number);
CREATE INDEX idx_meal_packets_time ON meal_packets(time_index);
CREATE INDEX idx_meal_packets_location ON meal_packets USING GIN(location_index);
CREATE INDEX idx_meal_packets_author ON meal_packets USING GIN(author);
CREATE INDEX idx_meal_packets_sip ON meal_packets(sip_id) WHERE sip_id IS NOT NULL;
CREATE INDEX idx_meal_packets_bite ON meal_packets(bite_id) WHERE bite_id IS NOT NULL;
Python Implementation
See meal.py module for full implementation.
Query Patterns
Pattern 1: Spatio-temporal MEAL Discovery
Query: “All MEALs for Field A in the last 7 days”
def find_meals_by_location_and_time(geoid: str, days_back: int = 7):
"""Find MEALs by location and time"""
cutoff = datetime.utcnow() - timedelta(days=days_back)
query = """
SELECT meal_id, primary_location_index, last_updated_time, packet_count
FROM meals
WHERE primary_location_index->>'geoid' = %s
AND last_updated_time >= %s
ORDER BY last_updated_time DESC
"""
return execute_query(query, (geoid, cutoff))
Pattern 2: MEAL and SIRUP Correlation (The “Holy Grail”)
Query: “Show conversation timeline alongside weather data for same field”
def correlate_meal_with_sirup(meal_id: str):
"""Correlate MEAL conversation with SIRUP data"""
# Get MEAL context
meal = get_meal(meal_id)
geoid = meal['primary_location_index']['geoid']
start_time = meal['created_at_time']
end_time = meal['last_updated_time']
# Get MEAL packets
packets = get_meal_packets(meal_id)
# Get SIRUP data for same location and time
sirup_data = query_sirup(
geoid=geoid,
start_time=start_time,
end_time=end_time,
sirup_types=['weather_forecast', 'satellite_imagery', 'soil_moisture']
)
# Build correlated timeline
timeline = []
for packet in packets:
timeline.append({
'type': 'chat',
'time': packet['time_index'],
'content': packet['content'],
'author': packet['author']['name']
})
for sirup in sirup_data:
timeline.append({
'type': 'sirup',
'time': sirup['timestamp'],
'sirup_type': sirup['sirup_type'],
'data': sirup['summary']
})
# Sort by time
timeline.sort(key=lambda x: x['time'])
return timeline
Pattern 3: Intra-MEAL Contextual Filtering
Query: “Show only photos (BITEs) posted from Field B in MEAL about Field A”
def filter_meal_by_location_and_type(meal_id: str, location_geoid: str, packet_type: str = 'bite'):
"""Filter MEAL packets by location and type"""
query = """
SELECT packet_id, time_index, location_index, bite_data
FROM meal_packets
WHERE meal_id = %s
AND packet_type = %s
AND location_index->>'geoid' = %s
ORDER BY sequence_number
"""
return execute_query(query, (meal_id, packet_type, location_geoid))
Pattern 4: AI Agent Participation Analysis
Query: “Find all MEALs where AI agent participated and provided recommendations”
def find_ai_assisted_meals(agent_id: str = 'agent-PAN-007', days_back: int = 30):
"""Find MEALs with AI agent participation"""
cutoff = datetime.utcnow() - timedelta(days=days_back)
query = """
SELECT DISTINCT m.meal_id, m.primary_location_index, m.created_at_time,
COUNT(mp.packet_id) as ai_packet_count
FROM meals m
JOIN meal_packets mp ON m.meal_id = mp.meal_id
WHERE m.participant_agents @> %s::jsonb
AND mp.author->>'agent_id' = %s
AND m.last_updated_time >= %s
GROUP BY m.meal_id, m.primary_location_index, m.created_at_time
ORDER BY m.last_updated_time DESC
"""
agent_filter = json.dumps([{"agent_id": agent_id}])
return execute_query(query, (agent_filter, agent_id, cutoff))
Pattern 5: Cross-MEAL Topic Search
Query: “Find all discussions mentioning ‘drought’ during actual drought SIRUP events”
def search_meals_with_sirup_correlation(search_term: str, sirup_condition: dict):
"""
Search MEALs with text matching + SIRUP correlation
Example: Find "crop failure" mentions during drought events
"""
# Find MEALs with search term
text_query = """
SELECT DISTINCT mp.meal_id, mp.packet_id, mp.time_index, mp.location_index,
mp.sip_data->>'text' as text
FROM meal_packets mp
WHERE mp.packet_type = 'sip'
AND mp.sip_data->>'text' ILIKE %s
"""
matching_packets = execute_query(text_query, (f'%{search_term}%',))
# For each match, check if SIRUP condition was true at that time/location
correlated_results = []
for packet in matching_packets:
geoid = packet['location_index']['geoid']
time = packet['time_index']
# Check SIRUP data
sirup_match = check_sirup_condition(
geoid=geoid,
time=time,
condition=sirup_condition # e.g., {"type": "weather", "drought": True}
)
if sirup_match:
correlated_results.append({
'meal_id': packet['meal_id'],
'packet_id': packet['packet_id'],
'text': packet['text'],
'time': time,
'location': geoid,
'sirup_event': sirup_match
})
return correlated_results
Use Cases
1. Field Visit Documentation
Scenario: Farm manager visits Field A, takes photos, records observations
# Create MEAL for field visit
meal = MEAL.create(
meal_type="field_visit",
primary_location={"geoid": "field-A", "label": "North Block"},
participants=["user-john-smith", "agent-PAN-007"]
)
# Add text note (SIP)
meal.add_packet(
packet_type="sip",
author="user-john-smith",
location={"geoid": "field-A", "coordinates": [38.58, -121.49]},
content={"text": "Starting field inspection. Weather looks good."}
)
# Add photo observation (BITE)
meal.add_packet(
packet_type="bite",
author="user-john-smith",
location={"geoid": "field-A-section-3"},
bite=observation_bite # BITE with photo, pest count, etc.
)
# AI agent responds
meal.add_packet(
packet_type="sip",
author="agent-PAN-007",
content={"text": "Based on your observation, I recommend..."}
)
2. Multi-User Pest Management Discussion
Scenario: Farm manager, agronomist, and AI discuss pest outbreak
# Create MEAL
meal = MEAL.create(
meal_type="pest_management",
primary_location={"geoid": "field-B"},
participants=["user-manager", "user-agronomist", "agent-PAN"]
)
# Timeline:
# 10:00 - Manager posts photo of aphids (BITE)
# 10:15 - Agronomist comments (SIP)
# 10:20 - AI pulls weather data (BITE reference to SIRUP)
# 10:25 - AI recommends spray window (SIP)
# 11:00 - Manager confirms spray schedule (SIP)
3. Decision Audit Trail
Scenario: Why did we spray Field C on Oct 15?
-- Find MEAL about Field C around Oct 15
SELECT * FROM meals
WHERE primary_location_index->>'geoid' = 'field-C'
AND primary_time_index BETWEEN '2025-10-14' AND '2025-10-16';
-- Get full conversation
SELECT packet_id, time_index, author->>name, content
FROM meal_packets
WHERE meal_id = 'found-meal-id'
ORDER BY sequence_number;
-- Correlate with SIRUP data
-- Shows: Pest observation + Weather window + AI recommendation = Decision
4. Training Data for AI
Scenario: Train AI on expert agronomist decisions
# Find all MEALs where agronomist participated
expert_meals = find_meals_by_participant("user-expert-agronomist")
# Extract decision patterns
for meal in expert_meals:
# Get context: field data, weather, observations
context = get_meal_context(meal['meal_id'])
# Get expert's recommendations
expert_packets = get_packets_by_author(meal['meal_id'], "user-expert-agronomist")
# Build training example
training_data.append({
'input': context,
'output': expert_packets,
'outcome': get_field_outcome(meal['primary_location_index'], days_after=30)
})
Security & Privacy
Cryptographic Verification
Each MEAL packet is hashed and linked:
def compute_packet_hash(packet: dict, previous_hash: str) -> str:
"""Compute packet hash for chain verification"""
# Canonical representation
canonical = json.dumps({
'packet_id': packet['packet_id'],
'meal_id': packet['meal_id'],
'sequence_number': packet['sequence_number'],
'time_index': packet['time_index'],
'author': packet['author'],
'content_hash': packet['content_hash'],
'previous_hash': previous_hash
}, sort_keys=True)
return hashlib.sha256(canonical.encode()).hexdigest()
Chain Verification
def verify_meal_chain(meal_id: str) -> bool:
"""Verify integrity of entire MEAL chain"""
packets = get_meal_packets(meal_id, order_by='sequence_number')
previous_hash = None
for packet in packets:
expected_hash = compute_packet_hash(packet, previous_hash)
if packet['packet_hash'] != expected_hash:
return False
previous_hash = expected_hash
return True
Access Control
# MEAL-level permissions
meal_permissions = {
'meal_id': 'meal-123',
'visibility': 'private', # private, team, organization, public
'participants': ['user-A', 'user-B', 'agent-PAN'],
'viewers': ['user-C'], # Read-only access
'admins': ['user-A'] # Can archive, manage participants
}
Privacy Considerations
- Location privacy: Users can opt out of location indexing
- Participant consent: All users must consent to be added to MEAL
- Right to be forgotten: MEALs can be archived (not deleted, but hidden)
- Data retention: Configurable retention policies per MEAL type
Comparison with Alternatives
vs Traditional Chat Systems (Slack, Teams)
| Feature | MEAL | Slack/Teams |
|---|---|---|
| Location indexing | ✅ Native | ❌ None |
| Time indexing | ✅ Primary key | ✅ Sort only |
| Data integration | ✅ SIRUP/BITE | ❌ Isolated |
| Immutability | ✅ Enforced | ❌ Editable |
| Cryptographic verification | ✅ Hash chain | ❌ None |
| Agricultural context | ✅ Built-in | ❌ Generic |
| AI agent participation | ✅ First-class | ⚠️ Bots (limited) |
vs Blockchain/Distributed Ledgers
| Feature | MEAL | Blockchain |
|---|---|---|
| Immutability | ✅ Yes | ✅ Yes |
| Append-only | ✅ Yes | ✅ Yes |
| Cryptographic | ✅ Hash chain | ✅ Full consensus |
| Performance | ✅ Fast (centralized) | ❌ Slow (consensus) |
| Spatio-temporal | ✅ Native | ❌ Add-on |
| Cost | ✅ Low | ❌ High (gas fees) |
| Use case | Agricultural collaboration | Financial transactions |
MEAL is blockchain-inspired but optimized for agricultural collaboration, not financial transactions.
vs Git/Version Control
| Feature | MEAL | Git |
|---|---|---|
| Append-only log | ✅ Yes | ✅ Yes (commits) |
| Hash verification | ✅ Yes | ✅ Yes |
| Branching | ❌ No (linear) | ✅ Yes |
| Time indexing | ✅ Primary | ⚠️ Commit time |
| Location indexing | ✅ Primary | ❌ None |
| Use case | Conversations | Code versioning |
MEAL is like Git for agricultural conversations, but with spatio-temporal indexing.
Roadmap
Phase 1 (MVP)
- ✅ MEAL specification
- ✅ Database schema
- ⏳ Python API (
meal.py) - ⏳ Basic chat UI
- ⏳ SIRUP correlation queries
Phase 2 (Production)
- Mobile app integration (TerraTrac PWA)
- Real-time sync (WebSocket)
- Offline support (local MEAL cache)
- Rich media (photos, videos, voice notes)
- AI agent improvements (context-aware responses)
Phase 3 (Advanced)
- Multi-MEAL correlation (find similar discussions)
- Predictive analytics (suggest actions based on past MEALs)
- Export to PDF/report format
- Integration with third-party chat (Slack, Teams)
- Voice-to-text for field notes
Conclusion
MEAL (Multi-User Engagement Asynchronous Ledger) completes the PANCAKE data primitives trinity:
- SIP: High-frequency, lightweight data
- BITE: Rich, polyglot agricultural data
- MEAL: Spatio-temporal collaboration log
Together, they enable:
- ✅ Complete agricultural data coverage (sensors, observations, conversations)
- ✅ Spatio-temporal context for AI/ML
- ✅ Immutable audit trails for decision-making
- ✅ Human-AI collaboration in the field
- ✅ “Holy Grail” queries: correlate conversations with field events
MEAL is not just chat—it’s a contextual knowledge fabric for agricultural decision-making. 🌾📱
Document Status: Specification (v1.0)
Implementation: In progress
License: Apache 2.0
Contact: pancake-support@agstack.org