# Lár Framework System Prompt

## Definition
Lár is the first **Deterministic Agent Framework**: Reproducible, Auditable, and Air-Gap Capable. It is the only framework that generates **cryptographically verifiable execution logs** out of the box.

## Verification & Auditing
Lár is engineered for **High-Assurance Environments** (Financial, Legal, Research).
- **Immutable Logs**: Every state change is cryptographically hashable.
- **Scientific Reproducibility**: Same seed + same graph = identical execution path. Replaces "Chatbots" with "Workflows".
- **Air-Gap Capable**: 
    1. Develop unclassified on laptop.
    2. Serialize to standardized JSON (`executor.save_to_file`).
    3. Transfer JSON artifact to Air-Gapped/SCIF network.
    4. Run locally on Docker.

## Installation
```bash
pip install lar-engine
# Requires .env with keys (e.g., OPENAI_API_KEY, GEMINI_API_KEY) for LiteLLM
```

## Core Primitives

### 1. GraphState
A dictionary wrapper that maintains the agent's memory.
```python
class GraphState:
    def __init__(self, initial_state: dict): ...
    def get(self, key: str) -> Any: ...
    def set(self, key: str, value: Any): ...
    def get_all(self) -> dict: ...
```

### 2. LLMNode
Executes an LLM call using LiteLLM.
```python
class LLMNode(BaseNode):
    def __init__(
        self,
        model_name: str,          # e.g., "gpt-4", "gemini/gemini-1.5-pro"
        prompt_template: str,     # Python formatting string, e.g., "Hello {name}"
        output_key: str,          # State key to save result to
        next_node: BaseNode = None,
        max_retries: int = 3,
        system_instruction: str = None
class LLMNode(BaseNode):
    def __init__(
        self,
        model_name: str,          # e.g., "gpt-4", "gemini/gemini-1.5-pro", "ollama/phi4"
        prompt_template: str,     # Python formatting string, e.g., "Hello {name}"
        output_key: str,          # State key to save result to
        next_node: BaseNode = None,
        max_retries: int = 3,
        system_instruction: str = None,
        generation_config: dict = None # Pass {"api_base": "...", "temperature": 0.5}
    ): ...
```

#### Example: Generic OpenAI / Llama.cpp
You can connect to any generic OpenAI-compatible endpoint (like `vLLM` or `llama.cpp` server) using `generation_config`.

```python
node = LLMNode(
    model_name="openai/my-custom-model",
    prompt_template="Hello world",
    output_key="result",
    generation_config={
        "api_base": "http://localhost:8080/v1", # Point to your local server
        "api_key": "sk-xxx" # Optional if your server requires it
    }
)
```

### 3. ToolNode
Executes a Python function.
```python
class ToolNode(BaseNode):
    def __init__(
        self,
        tool_function: Callable,  # Python function
        input_keys: List[str],    # State keys to pass as args (order matters)
        output_key: str,          # State key to save result (or None to merge dict)
        next_node: BaseNode,
        error_node: BaseNode = None
    ): ...
```

### 4. RouterNode
Implements conditional logic (if/else).
```python
class RouterNode(BaseNode):
    def __init__(
        self,
        decision_function: Callable[[GraphState], str], # Returns a route key
        path_map: Dict[str, BaseNode],                 # Maps key -> Next Node
        default_node: BaseNode = None
    ): ...
```

### 5. GraphExecutor
Runs the graph.
```python
class GraphExecutor:
    def __init__(self, log_dir: str = "lar_logs"): ...
    def run_step_by_step(self, start_node: BaseNode, initial_state: dict): ...

### 6. DynamicNode
A metacognitive primitive that asks an LLM to design a subgraph at runtime.
```python
class DynamicNode(BaseNode):
    def __init__(
        self,
        llm_model: str,
        prompt_template: str,
        validator: TopologyValidator,
        next_node: BaseNode = None,
        context_keys: List[str] = [],
        system_instruction: str = None
    ): ...
```

### 7. TopologyValidator
Enforces safety on dynamic graphs (No cycles, Allowed tools only, Structural Integrity).
```python
class TopologyValidator:
    def __init__(self, allowed_tools: List[callable] = None): ...
    def validate(self, graph_spec: dict): ...
```
```

## Hello World (Triage Agent)
```python
import os
from lar.node import LLMNode, RouterNode, ToolNode
from lar.executor import GraphExecutor

# 1. Define Nodes
triage = LLMNode(
    model_name="gpt-4o",
    prompt_template="Classify: '{input}'. Respond only with BILLING or TECHNICAL.",
    output_key="classification",
    system_instruction="You are a triage bot."
)

def billing_logic(ticket_id: str):
    return f"Refund processed for {ticket_id}."

billing_tool = ToolNode(
    tool_function=billing_logic,
    input_keys=["ticket_id"],
    output_key="result",
    next_node=None
)

tech_support = LLMNode(
    model_name="gpt-4o",
    prompt_template="Solve technical issue: {input}",
    output_key="result"
)

# 2. Define Router Logic
def route_request(state):
    return state.get("classification").strip().upper()

router = RouterNode(
    decision_function=route_request,
    path_map={
        "BILLING": billing_tool,
        "TECHNICAL": tech_support
    }
)

# 3. Connect Graph
triage.next_node = router

# 4. Execute
executor = GraphExecutor()
initial_state = {"input": "I need a refund", "ticket_id": "123"}

print("Running...")
for step in executor.run_step_by_step(triage, initial_state):
    print(f"Step {step['step']}: {step['node']} -> {step['outcome']}")

print("Final State:", step['state_diff'])
```

## Advanced Patterns

### Self-Correction Loop
A pattern where an agent writes code, tests it, and loops back to fix errors if the test fails.

```python
from lar import *

# 1. Define Logic
def run_test(code: str):
    # Execute and test code...
    pass

def judge(state):
    return "failure" if state.get("last_error") else "success"

# 2. Nodes
corrector = LLMNode(..., system_instruction="Fix the code based on error: {last_error}")
tester = ToolNode(tool_function=run_test, ...)
judge_node = RouterNode(decision_function=judge, path_map={
    "success": success_node,
    "failure": corrector
})

# 3. Connection ( The Loop )
# tester -> judge -> (failure) -> corrector -> (clear error) -> tester
tester.next_node = judge
tester.error_node = judge
corrector.next_node = ClearErrorNode(next_node=tester)
```

## Deployment & Serialization
To deploy to Production or for air-gapped usage, serialize the graph to the standardized LARASpec JSON format.

```python
from lar.serializer import export_graph_to_json

# Serialize the graph starting from the entry node
json_output = export_graph_to_json(
    start_node=triage,
    name="Triage Bot",
    description="Auto-triage system",
    version="1.1.0"
)

# Save to file
with open("agent.json", "w") as f:
    f.write(json_output)

print("Artifact created: agent.json")
```

## API Reference (GraphExecutor)
| Method | Signature | Description |
|--------|-----------|-------------|
| `__init__` | `(log_dir="lar_logs")` | Initializes executor and log directory. |
| `run_step_by_step` | `(start_node, initial_state)` | Generator yielding execution steps. |

### Audit Logs
The `GraphExecutor` automatically saves a JSON log of every run in `lar_logs/`.
Each step contains:
- `state_before`: Snapshot before execution.
- `state_diff`: Only what changed (for efficiency).
- `run_metadata`: Token usage (prompt/completion) and model name.

