Purpose: Step-by-step guide for transforming flattened features to express the MUX grammar: “Entities experience Moments in Places”
Date: 2026-01-20 Issue: #404 MUX-VISION-GRAMMAR-CORE Phase 3 Author: Claude Code (Sonnet)
Understand WHY before HOW: Before transforming any feature, read the
Consciousness Philosophy (docs/internal/architecture/current/consciousness-philosophy.md)
to understand why we preserve consciousness.
The Five Pillars:
This guide shows HOW to transform. The philosophy explains WHY it matters.
Before using this guide:
Questions to ask:
Checklist:
Examples:
user: Entity with name, role, preferencesuser_id: str with no semantic informationQuestions to ask:
Checklist:
Examples:
timestamp: 2026-01-20T09:00:00Zevent_type: "pr.merged", date: "2026-01-19"Questions to ask:
Checklist:
Examples:
source: "github.com/user/repo"integration_type: "calendar"Questions to ask:
Checklist:
Examples:
meeting_count: 5, meeting_density: 0.83days_open: 14, status: "stale"Four-step transformation:
def get_tasks():
"""Get today's tasks."""
tasks = db.query(Task).filter(Task.due_date == date.today()).all()
return {"tasks": [t.to_dict() for t in tasks]}
Problems:
async def perceive_todays_moments(user: Entity, workspace: Place) -> Perception:
"""
User experiences today's task Moments through Temporal lens.
Applies Pattern-050 (Context/Result pair) and Pattern-052 (Personality Bridge).
"""
# Context: What we're observing
with Situation(
entities=[user, piper],
tension="What needs attention today?",
place=workspace
) as situation:
# Gather Moments from Place with lens
tasks = await gather_from_place(
place=workspace,
lens=TemporalLens(mode=PerceptionMode.NOTICING),
filter_context={"timeframe": "today"}
)
# Personality Bridge: Transform data into awareness
framing = _frame_tasks_with_warmth(tasks, situation)
# Return Perception (not raw data)
return Perception(
framing=framing, # "I notice you have 3 things that want attention today"
moments=tasks,
situation=situation,
lens_applied=TemporalLens.__name__
)
def _frame_tasks_with_warmth(tasks: List[Task], situation: Situation) -> str:
"""
Apply Pattern-052 (Personality Bridge) and Pattern-053 (Warmth Calibration).
"""
count = len(tasks)
if count == 0:
return "You have a clear day ahead - no pressing tasks right now."
elif count <= 3:
return f"I notice {count} things that want your attention today."
else:
return f"You have {count} tasks for today. Would you like me to help prioritize?"
Improvements:
| Protocol | Use When | Example |
|---|---|---|
EntityProtocol |
Tracking actors with agency | User, Piper, teammates, integrations |
MomentProtocol |
Bounded occurrences with significance | Meetings, commits, messages, decisions |
PlaceProtocol |
Contexts with atmosphere | GitHub repo, Slack channel, Calendar |
Implementation hints:
from services.mux.protocols import EntityProtocol, MomentProtocol, PlaceProtocol
# Entity has identity and can experience
class User(EntityProtocol):
id: str
name: str
def experiences(self, moment: MomentProtocol) -> Perception:
...
# Moment has timestamp and captures significance
class Meeting(MomentProtocol):
id: str
timestamp: datetime
def captures(self) -> Dict[str, Any]:
return {"attendees": [...], "outcomes": [...]}
# Place has atmosphere and contains things
class GitHubRepo(PlaceProtocol):
id: str
atmosphere: str = "technical review space"
def contains(self) -> List[Any]:
return [prs, issues, commits]
| Lens | Use When | Question Answered | PerceptionMode |
|---|---|---|---|
| Temporal | Time-based queries | “What’s happening today?” | NOTICING, REMEMBERING, ANTICIPATING |
| Priority | Importance filtering | “What matters most?” | NOTICING (urgency), ANTICIPATING (impact) |
| Collaborative | People-based views | “Who’s involved?” | NOTICING (current), REMEMBERING (past) |
| Flow | Progress tracking | “What’s blocked?” | NOTICING (current state) |
| Hierarchy | Structure navigation | “What does this belong to?” | NOTICING (relationships) |
| Quantitative | Metrics queries | “How much work?” | NOTICING (measurements) |
| Causal | Cause-effect analysis | “Why did this happen?” | REMEMBERING (history), understanding |
| Contextual | Background awareness | “What’s the context?” | NOTICING (surroundings), REMEMBERING (history) |
Lens usage patterns:
from services.mux.lenses import (
TemporalLens, PriorityLens, CollaborativeLens, FlowLens,
HierarchyLens, QuantitativeLens, CausalLens, ContextualLens
)
from services.mux.protocols import PerceptionMode
# Time-aware query
temporal = TemporalLens(mode=PerceptionMode.NOTICING)
tasks_today = temporal.perceive(workspace, filter={"timeframe": "today"})
# Priority-aware query
priority = PriorityLens(mode=PerceptionMode.ANTICIPATING)
urgent_items = priority.perceive(workspace, threshold="high")
# People-aware query
collab = CollaborativeLens(mode=PerceptionMode.NOTICING)
team_activity = collab.perceive(workspace, scope="team")
# Progress-aware query
flow = FlowLens(mode=PerceptionMode.NOTICING)
blocked_items = flow.perceive(workspace, state="blocked")
Perception mode affects the tense and awareness quality of observations:
| Mode | Meaning | Example Phrases |
|---|---|---|
| NOTICING | Present awareness | “I notice…”, “Right now…”, “Currently…” |
| REMEMBERING | Past reference | “I remember…”, “Earlier…”, “Yesterday…” |
| ANTICIPATING | Future awareness | “I anticipate…”, “Coming up…”, “Soon…” |
When to use each:
NOTICING for current state queries (“What’s on my plate?”)REMEMBERING for historical queries (“What did I accomplish?”)ANTICIPATING for future queries (“What’s coming up?”)❌ Flattened: “Query returned 3 results matching your criteria” ✅ Grammar-Applied: “I notice 3 things that need your attention”
❌ Flattened: “Database contains 5 records for user_123” ✅ Grammar-Applied: “I see you have 5 open tasks in your workspace”
Why it matters: Users shouldn’t know they’re talking to a database. They’re collaborating with Piper.
❌ Flattened: “Created: 2026-01-20 14:30:00 UTC” ✅ Grammar-Applied: “From earlier this afternoon, when you were working on the API”
❌ Flattened: “Last modified: 7 days ago” ✅ Grammar-Applied: “This has been waiting since last Monday - might be time to revisit it”
Why it matters: Moments have narrative significance, not just temporal positions.
❌ Flattened: “User 123 commented on issue 456” ✅ Grammar-Applied: “Alex commented on your PR about the auth refactor”
❌ Flattened: “Assigned to: user_789” ✅ Grammar-Applied: “Jordan is working on this”
Why it matters: Entities have identity and relationships, not just foreign keys.
❌ Flattened: “Source: https://github.com/mediajunkie/piper-morgan-product/issues/123” ✅ Grammar-Applied: “Over in GitHub, in the piper-morgan repository, issue #123”
❌ Flattened: “Integration: slack, Channel: C12345” ✅ Grammar-Applied: “In your team’s Slack channel #engineering”
Why it matters: Places have atmosphere and character, not just connection strings.
❌ Flattened: “Error: Connection timeout (code: 504)” ✅ Grammar-Applied: “I couldn’t reach GitHub just now. Here’s what I remember from earlier, and I’ll try again in a moment.”
❌ Flattened: “Invalid input: field ‘title’ is required” ✅ Grammar-Applied: “I’d love to create that task, but I need a title. What would you like to call it?”
Why it matters: Failures are moments in the collaboration, not exceptions. See Pattern-054 (Honest Failure).
❌ Flattened: status: 1 (what does 1 mean?)
✅ Grammar-Applied: lifecycle_state: LifecycleState.PROPOSED
❌ Flattened: deleted: true
✅ Grammar-Applied: lifecycle_state: LifecycleState.COMPOSTED (with extracted wisdom)
Why it matters: States have meaning and tell a story. Status codes are opaque.
❌ Flattened:
{
"tasks": [
{"id": 1, "title": "Fix bug", "due": "2026-01-20"},
{"id": 2, "title": "Review PR", "due": "2026-01-21"}
]
}
✅ Grammar-Applied:
{
"perception": {
"framing": "You have 2 things wanting attention soon",
"moments": [
{
"significance": "Bug fix ready for your attention today",
"context": "From your engineering workspace"
},
{
"significance": "PR review needed by tomorrow",
"context": "Jordan is waiting for feedback"
}
],
"lens_applied": "Temporal",
"situation": "Preparing for productive work session"
}
}
Why it matters: Users experience perceptions, not data structures.
Use this tree when transforming or creating features:
Start: New feature or transformation?
│
├─ Is there user-facing output?
│ ├─ Yes → Apply Pattern-052 (Personality Bridge)
│ │ Add warmth via Pattern-053 (Warmth Calibration)
│ └─ No → Focus on Entity/Moment/Place identification only
│ (Internal APIs can be less narrative)
│
├─ Does it gather from multiple sources?
│ ├─ Yes → Apply Pattern-051 (Parallel Place Gathering)
│ │ Each Place gets error handling (Pattern-054)
│ └─ No → Single-place interaction
│ Still handle failures with Pattern-054
│
├─ What's the primary lens?
│ ├─ Time-related → TemporalLens (NOTICING/REMEMBERING/ANTICIPATING)
│ ├─ Priority-related → PriorityLens (urgency, impact)
│ ├─ People-related → CollaborativeLens (who's involved)
│ ├─ Progress-related → FlowLens (blocked, in progress, done)
│ ├─ Structure-related → HierarchyLens (belongs to, contains)
│ ├─ Metrics-related → QuantitativeLens (counts, durations)
│ ├─ Causality-related → CausalLens (because, leads to)
│ └─ Background-related → ContextualLens (atmosphere, setting)
│
├─ Can it fail gracefully?
│ ├─ Yes → Apply Pattern-054 (Honest Failure with Suggestion)
│ │ "I couldn't reach X, but here's what I remember..."
│ └─ No → Ensure hard failures are truly rare
│ Consider: Can we remember last known state?
│
└─ Does it preserve Entity/Moment/Place throughout?
├─ Yes → Apply Pattern-050 (Context Dataclass Pair)
│ Separate input (Context) from output (Result)
└─ No → Refactor to preserve grammar elements
Don't lose identity between layers
| Situation | Pattern to Apply | Why |
|---|---|---|
| Multi-layer feature | Pattern-050: Context Dataclass Pair | Preserve Entity/Moment/Place |
| Multiple integrations | Pattern-051: Parallel Place Gathering | Concurrent, resilient |
| User-facing output | Pattern-052: Personality Bridge | Add warmth and presence |
| Varying context significance | Pattern-053: Warmth Calibration | Appropriate tone |
| Any integration call | Pattern-054: Honest Failure | Graceful degradation |
Feature: “Show me stale PRs” - a query that returns open pull requests older than 7 days.
Current State: Partially flattened - has some Entity awareness but treats PRs as data items.
Grammar Analysis:
File: services/intent/intent_service.py (lines 1902-2020)
async def _handle_stale_prs(self, intent: Intent, workflow_id: str) -> IntentProcessingResult:
"""
Handle "Show me stale PRs" query.
Returns open PRs older than 7 days with age and title.
"""
self.logger.info(f"Processing stale PRs query: {intent.action}")
try:
# Import and initialize GitHub router
from services.integrations.github.github_integration_router import GitHubIntegrationRouter
github_router = GitHubIntegrationRouter()
await github_router.initialize()
# Check if GitHub is configured
if not github_router.config_service.is_configured():
return IntentProcessingResult(
success=True,
message=(
"I'd love to show you stale PRs, but GitHub isn't configured yet. "
"To enable GitHub integration, please add your GITHUB_TOKEN to your environment "
"or configure it in PIPER.user.md. Once configured, I can track open PRs!"
),
intent_data={
"category": intent.category.value,
"action": intent.action,
"confidence": intent.confidence,
},
workflow_id=workflow_id,
requires_clarification=False,
implemented=False,
)
# Get open issues (includes PRs)
from datetime import datetime, timedelta, timezone
open_items = await github_router.get_open_issues(limit=100)
# Filter to PRs older than 7 days
now = datetime.now(timezone.utc)
stale_threshold = now - timedelta(days=7)
stale_prs = []
for item in open_items:
if not item.get("pull_request"):
continue
created_str = item.get("created_at")
created_dt = datetime.fromisoformat(created_str.replace("Z", "+00:00"))
if created_dt < stale_threshold:
age_days = (now - created_dt).days
stale_prs.append({
"title": item.get("title"),
"number": item.get("number"),
"age_days": age_days,
"url": item.get("html_url"),
"author": item.get("user", {}).get("login", "Unknown"),
})
# Format response
if not stale_prs:
message = "Great news! You don't have any stale PRs right now. All your open PRs are recent."
else:
pr_list = "\n".join([
f"- #{pr['number']}: {pr['title']} ({pr['age_days']} days old, by {pr['author']})"
for pr in stale_prs
])
message = f"I found {len(stale_prs)} stale PR(s) (older than 7 days):\n\n{pr_list}"
return IntentProcessingResult(
success=True,
message=message,
intent_data={
"category": intent.category.value,
"action": intent.action,
"stale_prs": stale_prs,
"count": len(stale_prs),
},
workflow_id=workflow_id,
)
except Exception as e:
self.logger.error(f"Error processing stale PRs: {e}")
return IntentProcessingResult(
success=False,
message="I ran into a problem checking for stale PRs. Please try again.",
intent_data={"error": str(e)},
workflow_id=workflow_id,
error=str(e),
)
login strings, not entities with contextfrom dataclasses import dataclass
from datetime import datetime, timedelta, timezone
from typing import List, Optional
from services.mux.protocols import EntityProtocol, MomentProtocol, PlaceProtocol, PerceptionMode
from services.mux.lenses import TemporalLens, CollaborativeLens, FlowLens
from services.mux.situation import Situation, Perception
# Pattern-050: Context Dataclass Pair
@dataclass
class StalePRsContext:
"""
Input context for stale PR perception.
Preserves Entity/Moment/Place throughout flow.
"""
user: EntityProtocol # Who's asking
github_place: PlaceProtocol # Where we're looking
stale_threshold_days: int = 7 # What defines "stale"
requested_at: datetime = None # When they asked
def __post_init__(self):
if self.requested_at is None:
self.requested_at = datetime.now(timezone.utc)
@dataclass
class StalePRsResult:
"""
Output perception of stale PRs.
Grammar-conscious result with framing.
"""
success: bool
framing: str # Narrative opening (Pattern-052: Personality Bridge)
stale_moments: List['PRMoment'] # Not "items" or "records"
situation: Situation # Context preserved
lens_applied: List[str] # Which perspectives used
learning: Optional[str] = None # Extracted wisdom
@dataclass
class PRMoment(MomentProtocol):
"""
A PR as a Moment (bounded significant occurrence).
Not just data - it's a scene in the collaboration.
"""
id: str # PR number
timestamp: datetime # When created
title: str
author: EntityProtocol # Not just a string
age_days: int
url: str
def captures(self) -> dict:
"""What this Moment captured (policy, process, people, outcomes)."""
return {
"policy": "Code review process",
"process": "Pull request workflow",
"people": [self.author],
"outcomes": "Awaiting review or merge",
}
def narrative(self) -> str:
"""Pattern-052: Transform into narrative description."""
if self.age_days <= 3:
age_desc = "from a few days ago"
elif self.age_days <= 7:
age_desc = "from about a week ago"
elif self.age_days <= 14:
age_desc = "from a couple weeks ago"
else:
age_desc = f"from {self.age_days} days ago"
return f"'{self.title}' by {self.author.name} {age_desc}"
async def perceive_stale_prs(context: StalePRsContext) -> StalePRsResult:
"""
User experiences stale PR Moments in GitHub Place through multiple Lenses.
Applies:
- Pattern-050: Context/Result pair preserves grammar
- Pattern-051: Parallel Place Gathering (if multiple repos)
- Pattern-052: Personality Bridge for framing
- Pattern-053: Warmth Calibration based on count
- Pattern-054: Honest Failure if GitHub unavailable
"""
# Frame the Situation
with Situation(
entities=[context.user, piper],
place=context.github_place,
tension="Old work might need attention or closure",
learning_question="What patterns emerge in PR staleness?"
) as situation:
# Pattern-054: Honest Failure if Place not accessible
if not await context.github_place.is_accessible():
return _create_honest_failure_result(context, situation)
# Gather Moments from Place using multiple Lenses
stale_moments = await _gather_stale_pr_moments(
context=context,
situation=situation
)
# Pattern-052: Personality Bridge - transform data into awareness
framing = _frame_stale_prs_with_warmth(
moments=stale_moments,
context=context,
situation=situation
)
# Extract learning (for future composting)
learning = _extract_staleness_patterns(stale_moments)
return StalePRsResult(
success=True,
framing=framing,
stale_moments=stale_moments,
situation=situation,
lens_applied=["Temporal", "Collaborative", "Flow"],
learning=learning
)
async def _gather_stale_pr_moments(
context: StalePRsContext,
situation: Situation
) -> List[PRMoment]:
"""
Gather stale PR Moments from GitHub Place using multiple Lenses.
"""
# Apply Temporal Lens: perceive PRs through time
temporal_lens = TemporalLens(mode=PerceptionMode.REMEMBERING)
all_open_prs = await temporal_lens.perceive(
place=context.github_place,
filter={"state": "open", "type": "pull_request"}
)
# Apply Flow Lens: understand status
flow_lens = FlowLens(mode=PerceptionMode.NOTICING)
# Apply Collaborative Lens: understand people
collab_lens = CollaborativeLens(mode=PerceptionMode.NOTICING)
# Filter to stale (older than threshold)
now = datetime.now(timezone.utc)
stale_threshold = now - timedelta(days=context.stale_threshold_days)
stale_moments = []
for pr_data in all_open_prs:
created_dt = datetime.fromisoformat(pr_data["created_at"].replace("Z", "+00:00"))
if created_dt < stale_threshold:
# Transform data item into Moment (with Entity author)
author_entity = _create_author_entity(pr_data.get("user", {}))
age_days = (now - created_dt).days
moment = PRMoment(
id=str(pr_data["number"]),
timestamp=created_dt,
title=pr_data["title"],
author=author_entity,
age_days=age_days,
url=pr_data["html_url"]
)
stale_moments.append(moment)
return stale_moments
def _create_author_entity(user_data: dict) -> EntityProtocol:
"""
Create Entity for PR author (not just a string).
Preserves identity and potential for agency.
"""
@dataclass
class GitHubUser(EntityProtocol):
id: str
name: str
profile_url: str
def experiences(self, moment: MomentProtocol):
# Authors experience PR waiting as tension
return f"{self.name} is waiting for review"
return GitHubUser(
id=user_data.get("id", "unknown"),
name=user_data.get("login", "Unknown"),
profile_url=user_data.get("html_url", "")
)
def _frame_stale_prs_with_warmth(
moments: List[PRMoment],
context: StalePRsContext,
situation: Situation
) -> str:
"""
Pattern-052: Personality Bridge
Pattern-053: Warmth Calibration
Transform count into experience language with appropriate warmth.
"""
count = len(moments)
if count == 0:
# Warmth: Celebrate the good news
return (
"Great news! You don't have any PRs that have been waiting more than "
f"{context.stale_threshold_days} days. Everything in GitHub is moving along."
)
elif count == 1:
# Warmth: Gentle single-item attention
moment = moments[0]
return (
f"I notice one PR that's been waiting a while: {moment.narrative()}. "
"Might be time to check in or close it."
)
elif count <= 3:
# Warmth: Moderate multi-item attention
return (
f"I found {count} PRs in GitHub that have been open for more than "
f"{context.stale_threshold_days} days. They might need a nudge or closure:"
)
else:
# Warmth: Higher urgency for many items
return (
f"There are {count} PRs in GitHub that have been waiting a while "
f"(more than {context.stale_threshold_days} days). This might be a good time "
"to review what's active and what can be closed:"
)
def _extract_staleness_patterns(moments: List[PRMoment]) -> Optional[str]:
"""
Extract learning from staleness patterns (for composting/memory).
"""
if not moments:
return None
# Simple pattern: identify if one author has multiple stale PRs
author_counts = {}
for moment in moments:
author_name = moment.author.name
author_counts[author_name] = author_counts.get(author_name, 0) + 1
multi_stale_authors = [name for name, count in author_counts.items() if count > 1]
if multi_stale_authors:
return (
f"Pattern noticed: {', '.join(multi_stale_authors)} has multiple stale PRs. "
"Might indicate bandwidth constraints or review bottleneck."
)
return "No significant patterns in staleness yet."
def _create_honest_failure_result(
context: StalePRsContext,
situation: Situation
) -> StalePRsResult:
"""
Pattern-054: Honest Failure with Suggestion
GitHub not accessible - provide honest acknowledgment with suggestion.
"""
return StalePRsResult(
success=False,
framing=(
"I'd love to check for stale PRs in GitHub, but I can't reach it right now. "
"This might mean GitHub isn't configured yet, or there's a temporary connection issue. "
"\n\n"
"To enable GitHub integration, add your GITHUB_TOKEN to your environment "
"or configure it in PIPER.user.md. Once configured, I can track PRs across time."
),
stale_moments=[],
situation=situation,
lens_applied=["Temporal"],
learning="GitHub Place not accessible during query"
)
# Integration point: Route handler calls this
async def handle_stale_prs_intent(
intent: Intent,
user: EntityProtocol,
github_place: PlaceProtocol,
workflow_id: str
) -> IntentProcessingResult:
"""
Route handler adapter (connects to existing IntentService).
This is where the old flattened handler would call.
Now it builds Context and calls grammar-conscious function.
"""
# Build Context
context = StalePRsContext(
user=user,
github_place=github_place,
stale_threshold_days=7
)
# Perceive with grammar
result = await perceive_stale_prs(context)
# Transform to IntentProcessingResult (existing interface)
return IntentProcessingResult(
success=result.success,
message=_format_message_for_user(result),
intent_data={
"category": intent.category.value,
"action": intent.action,
"stale_moments": [
{
"title": m.title,
"author": m.author.name,
"age_days": m.age_days,
"narrative": m.narrative(),
"url": m.url
}
for m in result.stale_moments
],
"count": len(result.stale_moments),
"learning": result.learning,
"lens_applied": result.lens_applied,
},
workflow_id=workflow_id,
implemented=True
)
def _format_message_for_user(result: StalePRsResult) -> str:
"""
Format final message for user.
Combines framing with moment narratives.
"""
parts = [result.framing]
if result.stale_moments:
parts.append("") # Blank line
for moment in result.stale_moments:
parts.append(f" • {moment.narrative()} - [View PR]({moment.url})")
if result.learning:
parts.append("")
parts.append(f"💡 {result.learning}")
return "\n".join(parts)
login stringsEntityProtocol instances with identity and agencyMomentProtocol instances with captures() methodPlaceProtocol with atmosphere (“technical review space”)StalePRsContext and StalePRsResult preserve grammar_frame_stale_prs_with_warmth() adds personality systematically_create_honest_failure_result() for graceful degradation| Aspect | Before (Flattened) | After (Grammar-Applied) |
|---|---|---|
| Framing | “I found X stale PR(s)” | “I notice PRs that have been waiting a while” |
| Items | ”#{number}: {title} ({age} days old)” | ”‘{title}’ by {author} from a couple weeks ago” |
| People | “by {login}” | “{author.name} is waiting for review” |
| Errors | “I ran into a problem” | “I can’t reach GitHub right now. Here’s what you can do…” |
| Success | “Great news!” | “Great news! Everything in GitHub is moving along.” |
Converting data dictionaries to protocol-conforming dataclasses forced us to think about:
Explicitly applying Temporal/Collaborative/Flow lenses surfaced questions:
Without lenses, we only saw “age in days.”
Pattern-050 (Context Dataclass Pair) ensured:
Before: User was implicit, Place was a config string.
Pattern-053 forced us to ask: “What level of warmth matches this observation?”
Before: Generic “I found X” regardless of significance.
Adding _extract_staleness_patterns() captured wisdom:
Before: No learning captured; each query was isolated.
Pattern-054 transformed error handling from:
Failures became moments in the relationship, not exceptions.
If you’re transforming an existing flattened feature (like the stale PRs example):
async def perceive_X(context: XContext) -> XResult_frame_X_with_warmth() functionBefore declaring transformation complete:
docs/internal/architecture/current/feature-object-model-map.md - Per-feature mapping of Entities/Moments/Places with canonical queriesdocs/internal/development/mux-implementation-guide.mddocs/internal/development/mux-experience-tests.mddocs/internal/architecture/current/patterns/grammar-application-patterns.mddocs/internal/architecture/current/grammar-compliance-audit.mdWhen starting a transformation:
Part of Issue #404 MUX-VISION-GRAMMAR-CORE Phase 3 Created: 2026-01-20 Author: Claude Code (Sonnet)