Implement XIC v2 control flow with IF, MATCH, LOOP operations
PHASE A: Safe predicate evaluator (glyphos/control/predicate.py) - AST-based safe expression evaluation - Supports comparisons, boolean ops, attribute access - Helper function: dominant_contains() - Protected against code injection attacks PHASE B: XICContext queue helpers - enqueue_chain(label) for FIFO chain scheduling - pop_next_chain() to get next scheduled chain - jump_to(label) for immediate destination changes PHASE C: Control flow operations (xic_ops.py) - op_IF: Conditional branching with optional else - op_MATCH: Pattern matching against fused fields - op_LOOP: Iterative execution with guardrails - Added to OP_TABLE for operation dispatch PHASE D: Execution loop enhancement (xic_vm.py) - Chain queue scheduling with label matching - Total steps tracking for guardrail enforcement - max_total_steps limit across all operations - Graceful execution stop on guardrail trigger PHASE E: Comprehensive test suite (tests/test_control_flow.py) - 14 unit tests covering all operations - Predicate evaluator tests - IF/MATCH/LOOP operation tests - Queue helper and guardrail tests - All tests passing (14/14) PHASE F: Example programs - demo_control_flow_if.gx.json: IF branching example - demo_control_flow_loop.gx.json: LOOP iteration example PHASE G: Complete documentation - XIC_V2_CONTROL_FLOW_SUMMARY.md: Technical guide - XIC_V2_QUICK_REFERENCE.md: Developer quick reference - FedMart UI and integration documentation Integration points: - FedMart telemetry captures control flow steps - UI dashboard displays control branching - Symbolic pipeline predicate evaluation - 100% backward compatible with XIC v1.5 Test results: 36/36 passing (14 control flow + 12 FedMart + 10 UI) Status: Production ready
This commit is contained in:
+203
@@ -12,6 +12,26 @@ class XICContext:
|
||||
symbolic_mode: bool = False
|
||||
glyph_contexts: list = field(default_factory=list)
|
||||
|
||||
def enqueue_chain(self, label: str):
|
||||
"""Schedule a chain/label to run next (FIFO)."""
|
||||
queue = self._state.setdefault("_chain_queue", [])
|
||||
queue.append(label)
|
||||
print(f"[XIC-QUEUE] Enqueued chain: {label}")
|
||||
|
||||
def pop_next_chain(self):
|
||||
"""Get next scheduled chain (FIFO). Returns None if queue empty."""
|
||||
queue = self._state.setdefault("_chain_queue", [])
|
||||
if queue:
|
||||
label = queue.pop(0)
|
||||
print(f"[XIC-QUEUE] Dequeued chain: {label}")
|
||||
return label
|
||||
return None
|
||||
|
||||
def jump_to(self, label: str):
|
||||
"""Immediate jump: clear queue and run label next."""
|
||||
self._state["_chain_queue"] = [label]
|
||||
print(f"[XIC-QUEUE] Jump to: {label}")
|
||||
|
||||
|
||||
def op_LOAD_MODEL(ctx: XICContext, *args):
|
||||
"""LOAD_MODEL <path>: Load a .gx model file."""
|
||||
@@ -440,6 +460,186 @@ def op_GET_GLYPH_RESONANCE(ctx: XICContext, *args):
|
||||
ctx._state[f"resonance_query_{glyph_id}_notfound"] = None
|
||||
|
||||
|
||||
def op_IF(ctx: XICContext, *args):
|
||||
"""IF <predicate> <then_label> [<else_label>]
|
||||
|
||||
Evaluates predicate against last symbolic result and enqueues appropriate chain.
|
||||
|
||||
Predicate examples:
|
||||
"fused.global_resonance_score > 0.8"
|
||||
"dominant_contains('glyph://entropy')"
|
||||
"""
|
||||
if len(args) < 2:
|
||||
raise ValueError("IF requires predicate and then_label")
|
||||
|
||||
from glyphos.control.predicate import eval_predicate
|
||||
|
||||
predicate = str(args[0])
|
||||
then_label = str(args[1])
|
||||
else_label = str(args[2]) if len(args) > 2 else None
|
||||
|
||||
# Extract fused symbol from last symbolic execution
|
||||
pipeline = ctx._state.get("last_symbolic_pipeline")
|
||||
fused_dict = {}
|
||||
dominant = []
|
||||
|
||||
if pipeline and hasattr(pipeline, "fused_symbol") and pipeline.fused_symbol:
|
||||
fused = pipeline.fused_symbol
|
||||
# Build dict representation for predicate evaluation
|
||||
fused_dict = {
|
||||
"global_resonance_score": fused.resonance_map.global_resonance_score if fused.resonance_map else 0.0,
|
||||
"glyph_ids": fused.glyph_ids,
|
||||
}
|
||||
# Extract dominant glyphs for helper function
|
||||
if fused.resonance_map:
|
||||
dominant = [(g, m.weight) for g, m in fused.resonance_map.get_top_glyphs(5)]
|
||||
|
||||
# Evaluate predicate
|
||||
try:
|
||||
pred_result = eval_predicate(predicate, fused_dict, dominant)
|
||||
except Exception as e:
|
||||
print(f"[XIC-CONTROL] IF predicate evaluation error: {e}")
|
||||
return
|
||||
|
||||
# Log control step
|
||||
ctx._state.setdefault("control_steps", []).append({
|
||||
"type": "if",
|
||||
"predicate": predicate,
|
||||
"result": pred_result
|
||||
})
|
||||
|
||||
# Emit symbolic step
|
||||
ctx._state.setdefault("symbolic_steps", []).append({
|
||||
"name": "if",
|
||||
"kind": "control_if",
|
||||
"payload": {"predicate": predicate, "result": pred_result}
|
||||
})
|
||||
|
||||
print(f"[XIC-CONTROL] IF {predicate} => {pred_result}")
|
||||
|
||||
# Enqueue appropriate chain
|
||||
if pred_result:
|
||||
ctx.enqueue_chain(then_label)
|
||||
elif else_label:
|
||||
ctx.enqueue_chain(else_label)
|
||||
|
||||
|
||||
def op_MATCH(ctx: XICContext, *args):
|
||||
"""MATCH <path> <pattern> <then_label>
|
||||
|
||||
Pattern match against fused_symbol fields.
|
||||
Supports: fused.glyph_ids (checks if pattern is in list)
|
||||
"""
|
||||
if len(args) < 3:
|
||||
raise ValueError("MATCH requires path, pattern, then_label")
|
||||
|
||||
path = str(args[0]) # e.g., "fused.glyph_ids"
|
||||
pattern = str(args[1])
|
||||
then_label = str(args[2])
|
||||
|
||||
# Extract fused symbol
|
||||
pipeline = ctx._state.get("last_symbolic_pipeline")
|
||||
matched = False
|
||||
|
||||
if pipeline and hasattr(pipeline, "fused_symbol") and pipeline.fused_symbol:
|
||||
fused = pipeline.fused_symbol
|
||||
# Support fused.glyph_ids pattern matching
|
||||
if path == "fused.glyph_ids":
|
||||
matched = pattern in fused.glyph_ids
|
||||
|
||||
# Log control step
|
||||
ctx._state.setdefault("symbolic_steps", []).append({
|
||||
"name": "match",
|
||||
"kind": "control_match",
|
||||
"payload": {"path": path, "pattern": pattern, "result": matched}
|
||||
})
|
||||
|
||||
print(f"[XIC-CONTROL] MATCH {path} contains {pattern} => {matched}")
|
||||
|
||||
if matched:
|
||||
ctx.enqueue_chain(then_label)
|
||||
|
||||
|
||||
def op_LOOP(ctx: XICContext, *args):
|
||||
"""LOOP <predicate> <body_label> [max_iter]
|
||||
|
||||
Repeatedly enqueue body_label while predicate is true.
|
||||
Guarded by max_iter and max_total_steps guardrails.
|
||||
|
||||
Note: Unlike traditional loops, this schedules iterations in the queue
|
||||
for execution by the main loop. Each iteration runs the body_label.
|
||||
"""
|
||||
if len(args) < 2:
|
||||
raise ValueError("LOOP requires predicate and body_label")
|
||||
|
||||
from glyphos.control.predicate import eval_predicate
|
||||
|
||||
predicate = str(args[0])
|
||||
body_label = str(args[1])
|
||||
max_iter = int(args[2]) if len(args) > 2 else int(ctx.params.get("max_loop_iterations", 50))
|
||||
|
||||
iter_count = 0
|
||||
|
||||
while iter_count < max_iter:
|
||||
# Check global guardrail
|
||||
total_steps = int(ctx._state.get("total_steps", 0))
|
||||
max_total_steps = int(ctx.params.get("max_total_steps", 1000))
|
||||
|
||||
if total_steps >= max_total_steps:
|
||||
ctx._state.setdefault("guardrails", []).append("max_total_steps_exceeded")
|
||||
ctx._state.setdefault("symbolic_steps", []).append({
|
||||
"name": "guardrail",
|
||||
"kind": "guardrail",
|
||||
"payload": "max_total_steps_exceeded"
|
||||
})
|
||||
print(f"[XIC-CONTROL] LOOP guardrail: max_total_steps exceeded ({total_steps})")
|
||||
break
|
||||
|
||||
# Evaluate loop predicate
|
||||
pipeline = ctx._state.get("last_symbolic_pipeline")
|
||||
fused_dict = {}
|
||||
dominant = []
|
||||
|
||||
if pipeline and hasattr(pipeline, "fused_symbol") and pipeline.fused_symbol:
|
||||
fused = pipeline.fused_symbol
|
||||
fused_dict = {
|
||||
"global_resonance_score": fused.resonance_map.global_resonance_score if fused.resonance_map else 0.0,
|
||||
"glyph_ids": fused.glyph_ids,
|
||||
}
|
||||
if fused.resonance_map:
|
||||
dominant = [(g, m.weight) for g, m in fused.resonance_map.get_top_glyphs(5)]
|
||||
|
||||
try:
|
||||
should_continue = eval_predicate(predicate, fused_dict, dominant)
|
||||
except Exception as e:
|
||||
print(f"[XIC-CONTROL] LOOP predicate evaluation error: {e}")
|
||||
should_continue = False
|
||||
|
||||
if not should_continue:
|
||||
print(f"[XIC-CONTROL] LOOP condition false, exiting")
|
||||
break
|
||||
|
||||
# Schedule body execution
|
||||
ctx.enqueue_chain(body_label)
|
||||
ctx._state.setdefault("symbolic_steps", []).append({
|
||||
"name": "loop_iter",
|
||||
"kind": "control_loop",
|
||||
"payload": {"iteration": iter_count + 1, "predicate": predicate}
|
||||
})
|
||||
|
||||
print(f"[XIC-CONTROL] LOOP iteration {iter_count + 1}: enqueued {body_label}")
|
||||
iter_count += 1
|
||||
|
||||
if iter_count >= max_iter:
|
||||
ctx._state.setdefault("guardrails", []).append("max_loop_iterations_exceeded")
|
||||
ctx._state.setdefault("symbolic_steps", []).append({
|
||||
"name": "guardrail",
|
||||
"kind": "guardrail",
|
||||
"payload": "max_loop_iterations_exceeded"
|
||||
})
|
||||
print(f"[XIC-CONTROL] LOOP guardrail: max_loop_iterations exceeded ({iter_count})")
|
||||
|
||||
|
||||
# Operation dispatch table
|
||||
OP_TABLE = {
|
||||
"LOAD_MODEL": op_LOAD_MODEL,
|
||||
@@ -454,4 +654,7 @@ OP_TABLE = {
|
||||
"CLEAR_GLYPH_CONTEXT": op_CLEAR_GLYPH_CONTEXT,
|
||||
"GET_GLYPH_RESONANCE": op_GET_GLYPH_RESONANCE,
|
||||
"LOG": op_LOG,
|
||||
"IF": op_IF,
|
||||
"MATCH": op_MATCH,
|
||||
"LOOP": op_LOOP,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user