Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
203 changes: 94 additions & 109 deletions README.md

Large diffs are not rendered by default.

104 changes: 65 additions & 39 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -774,9 +774,12 @@ <h2 id="panel-title"></h2>
</div>
</div>
<p>
Depth is <strong>monotonic</strong>: D3 grounding implies D0–D2.
Planning requires D2. Execution requires D3. Execution without D3
grounding is forbidden.
Depth is <strong>monotonic</strong>: D3 grounding implies D0–D2 are
also grounded. Depth requirements are derived from the graph itself —
edges carry a source depth that determines when they become visible
and a target depth that determines when they resolve. Integrators can
enforce a minimum depth floor (e.g. D3 for execution) as a secondary
safety lever via <code>min_depth</code>.
</p>
<p style="margin-top:1.5rem;margin-bottom:0.5rem">
<strong style="color:var(--tb);font-family:'JetBrains Mono',monospace;font-size:0.65rem;letter-spacing:0.1rem">
Expand Down Expand Up @@ -875,16 +878,19 @@ <h2 id="panel-title"></h2>
Each <code>vre_guard</code> call follows a strict sequence:
<strong>resolve</strong> concept names to canonical primitives →
<strong>ground</strong> the subgraph against depth requirements → fire
<strong>on_trace</strong> with the result → if not grounded and
<strong>on_learn</strong> is provided, enter the
<strong>learning loop</strong> to resolve gaps → re-ground and fire
<strong>on_trace</strong> again → if still not grounded,
<strong>on_trace</strong> with the result → if not grounded,
<strong>block</strong> and return gaps → evaluate
<strong>policies</strong> on APPLIES_TO relata → if hard blocks,
<strong>block</strong> immediately → if confirmation required, call
<strong>on_policy</strong> for human confirmation → if approved,
<strong>execute</strong> the original function.
</p>
<p>
The guard does not orchestrate learning. When grounding fails, it
returns the gaps and lets the integrator decide what to do — typically
by exposing a separate <code>learn_gaps</code> tool the agent can
invoke. See <strong>LEARNING</strong>.
</p>
<p style="margin-top:1.5rem;margin-bottom:0.5rem">
<strong style="color:var(--tb);font-family:'JetBrains Mono',monospace;font-size:0.65rem;letter-spacing:0.1rem">
THE THREE LAYERS
Expand Down Expand Up @@ -955,7 +961,6 @@ <h2 id="panel-title"></h2>
&nbsp;&nbsp;&nbsp;&nbsp;min_depth=<span class="ck">None</span>,&nbsp;&nbsp;&nbsp;<span class="cc"># DepthLevel | None — floor override</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;on_trace=<span class="ck">None</span>,&nbsp;&nbsp;&nbsp;&nbsp;<span class="cc"># Callable[[GroundingResult], None]</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;on_policy=<span class="ck">None</span>,&nbsp;&nbsp;&nbsp;<span class="cc"># Callable[[list[PolicyViolation]], bool]</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;on_learn=<span class="ck">None</span>,&nbsp;&nbsp;&nbsp;&nbsp;<span class="cc"># LearningCallback | None</span><br>
)
</div>
<ul style="margin-left:0.8rem">
Expand All @@ -971,9 +976,6 @@ <h2 id="panel-title"></h2>
<li>
<strong>on_policy</strong> — called with confirmation-required violations for human approval.
</li>
<li>
<strong>on_learn</strong> — a <code>LearningCallback</code> that enables the auto-learning loop when grounding fails.
</li>
</ul>
<div class="code-block">
<span class="cc"># Static concepts</span><br>
Expand Down Expand Up @@ -1048,51 +1050,72 @@ <h2 id="panel-title"></h2>
threeColor: 0xF0C54D,
content: {
depth: 'LEARNING',
title: 'Ignorance is surfaced. Knowledge is earned.',
title: 'VRE is a knowledge linter.',
body: `
<p>
When grounding fails, VRE does not simply block the agent. It surfaces
<strong>structured knowledge gaps</strong> that describe exactly what is
missing. The auto-learning loop turns these gaps into proposals that a
human reviews before anything enters the graph.
<strong>structured knowledge gaps</strong> that describe exactly what
is missing — and provides the tools to fill them. But VRE does not
orchestrate the loop. The integrator owns it.
</p>
<p>
This separation is deliberate. Loop orchestration is inherently
integration-specific — different LLMs, different data sources,
different retry/budget strategies. By keeping VRE's surface tight
(identify gaps, persist fills), integrators can build whatever flow
fits their stack without fighting the framework.
</p>
<p style="margin-top:1.5rem;margin-bottom:0.5rem">
<strong style="color:var(--tb);font-family:'JetBrains Mono',monospace;font-size:0.65rem;letter-spacing:0.1rem">
THE LEARNING LOOP
VRE'S LEARNING SURFACE
</strong>
</p>
<p>
Each gap type produces a <strong>candidate template</strong> that an
agent fills in. The human reviews, modifies, or rejects the proposal.
Accepted candidates are persisted with provenance tracking.
Three things, total:
</p>
<ul style="margin-left:0.8rem">
<li>
<code>vre.check(concepts)</code> returns a <code>GroundingResult</code> with structured <code>KnowledgeGap</code> objects when grounding fails.
</li>
<li>
<code>template_for_gap(gap)</code> returns the candidate model class to fill — the integrator constructs an instance however they like (LLM structured output, user input, static rules).
</li>
<li>
<code>vre.learning_engine.learn_gap(gap, candidate)</code> validates the candidate against its gap and persists it to the graph.
</li>
</ul>
<p style="margin-top:1.5rem;margin-bottom:0.5rem">
<strong style="color:var(--tb);font-family:'JetBrains Mono',monospace;font-size:0.65rem;letter-spacing:0.1rem">
INTEGRATOR-OWNED LOOP
</strong>
</p>
<div style="margin:0.8rem 0 1.2rem">
<div class="depth-row">
<div class="depth-indicator d0">1</div>
<div class="depth-text">
<div class="depth-label">GAP DETECTED</div>
<div class="depth-question">Grounding fails → structured gap surfaced</div>
<div class="depth-label">CHECK</div>
<div class="depth-question">Integrator calls <code>vre.check()</code> → gets gaps</div>
</div>
</div>
<div class="depth-row">
<div class="depth-indicator d1">2</div>
<div class="depth-text">
<div class="depth-label">TEMPLATE CREATED</div>
<div class="depth-question">Engine builds a candidate from the gap type</div>
<div class="depth-label">FILL</div>
<div class="depth-question">Integrator fills the candidate (LLM, user, rules — their call)</div>
</div>
</div>
<div class="depth-row">
<div class="depth-indicator d2">3</div>
<div class="depth-text">
<div class="depth-label">AGENT PROPOSES</div>
<div class="depth-question">LLM fills the template with domain knowledge</div>
<div class="depth-label">PERSIST</div>
<div class="depth-question">VRE validates and persists via <code>learn_gap</code></div>
</div>
</div>
<div class="depth-row">
<div class="depth-indicator d3">4</div>
<div class="depth-text">
<div class="depth-label">HUMAN REVIEWS</div>
<div class="depth-question">Accept, modify, skip, or reject the proposal</div>
<div class="depth-label">RE-CHECK</div>
<div class="depth-question">Integrator re-checks; loop continues until grounded</div>
</div>
</div>
</div>
Expand All @@ -1103,7 +1126,7 @@ <h2 id="panel-title"></h2>
</p>
<ul style="margin-left:0.8rem">
<li>
<strong>ExistenceCandidate</strong> — concept not in graph. Agent proposes D1 (identity); D0 is auto-generated on acceptance.
<strong>ExistenceCandidate</strong> — concept not in graph. Integrator proposes D1 (identity); D0 is auto-generated on acceptance.
</li>
<li>
<strong>DepthCandidate</strong> — concept exists but is too shallow. Proposes missing depths.
Expand All @@ -1112,31 +1135,34 @@ <h2 id="panel-title"></h2>
<strong>RelationalCandidate</strong> — relatum target is under-grounded. Proposes depths on target.
</li>
<li>
<strong>ReachabilityCandidate</strong> — concept is disconnected. Proposes edge placement.
<strong>ReachabilityCandidate</strong> — concept is disconnected. Proposes edge placement, in either direction.
</li>
</ul>
<p style="margin-top:1.5rem;margin-bottom:0.5rem">
<strong style="color:var(--tb);font-family:'JetBrains Mono',monospace;font-size:0.65rem;letter-spacing:0.1rem">
TWO-PHASE EDGE PLACEMENT
REACHABILITY PREREQUISITES
</strong>
</p>
<p>
When a reachability gap is resolved by placing an edge, the engine
checks whether the source and target have the required depths. If not,
it synthesizes <code>DepthGap</code>s and invokes the learning callback
for each missing depth <strong>before</strong> placing the edge. The
agent cannot connect concepts it does not yet understand.
Reachability candidates focus on edge placement, not depth. If the
source or target lacks the required depth, <code>learn_gap</code>
raises <code>CandidateValidationError</code>. The engine exposes
<code>reachability_prerequisites(gap, candidate)</code> which returns
the <code>DepthGap</code>s that must be filled first. The integrator
fills them, then places the edge — explicitly, with no nested
callbacks.
</p>
<p style="margin-top:1.5rem;margin-bottom:0.5rem">
<strong style="color:var(--tb);font-family:'JetBrains Mono',monospace;font-size:0.65rem;letter-spacing:0.1rem">
PROVENANCE
</strong>
</p>
<p>
Every piece of learned knowledge carries structured provenance.
Accepted proposals are marked <code>learned</code>. Modified proposals
are marked <code>conversational</code>. The graph remembers not just
what it knows, but <strong>how it came to know it</strong>.
Every piece of persisted knowledge carries structured provenance.
<code>learn_gap</code> accepts an optional <code>source</code>
parameter — the integrator decides how to stamp fills based on its
own loop semantics. The graph remembers not just what it knows, but
<strong>how it came to know it</strong>.
</p>
`
}
Expand Down
37 changes: 29 additions & 8 deletions examples/langchain_ollama/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,26 @@

from __future__ import annotations

from langchain_core.messages import HumanMessage, SystemMessage, ToolMessage
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage, ToolMessage
from langchain_ollama import ChatOllama

SYSTEM = """\
You are a helpful assistant with access to tools for filesystem management. Every tool is gated by
VRE (Volute Reasoning Engine) to enforce epistemic justification before execution.
You are a filesystem assistant. You have two tools:

Your shell tool runs in a fully writable workspace directory. All files should be created and managed
there using relative paths. Do not use /tmp or other directories outside the workspace.
1. shell_tool — runs a shell command in the workspace directory. Use relative paths.
2. learn_gaps — resolves knowledge gaps so blocked commands can proceed.

Every shell command is checked by VRE (Volute Reasoning Engine) before execution.
If VRE does not have enough knowledge about the concepts involved, the command is
blocked and the tool returns a grounding result listing the gaps.

When a shell command is blocked:
- Read the gaps in the response (DEPTH, REACHABILITY, RELATIONAL, EXISTENCE).
- Call learn_gaps with ALL the concept names from the blocked command (comma-separated).
- After learn_gaps resolves the gaps, retry the original shell command.

Do not assume a tool call succeeded unless you see its result. Do not narrate actions
you have not taken. Call tools, read results, then respond.
"""


Expand All @@ -26,8 +37,14 @@ class ToolAgent:
content (including <think> blocks) appears before the tool fires.
"""

def __init__(self, tools: list, model: str = "qwen3:8b") -> None:
self._llm = ChatOllama(model=model, reasoning=True).bind_tools(tools)
def __init__(self, tools: list, model: str = "gemma4:26b") -> None:
self._llm = ChatOllama(
model=model,
reasoning=True,
top_p=0.95,
top_k=64,
temperature=1.0
).bind_tools(tools)
self._tools = {t.name: t for t in tools}

def stream(self, inputs: dict):
Expand All @@ -54,7 +71,11 @@ def stream(self, inputs: dict):
for c in chunks[1:]:
full = full + c

messages.append(full)
history_msg = AIMessage(
content=full.content,
tool_calls=full.tool_calls,
)
messages.append(history_msg)

if not full.tool_calls:
break
Expand Down
20 changes: 6 additions & 14 deletions examples/langchain_ollama/callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
from rich.prompt import Confirm
from rich.tree import Tree

from examples.langchain_ollama.learner import DemoLearner
from examples.langchain_ollama.repl import console
from vre.core.policy.models import PolicyViolation

Expand Down Expand Up @@ -55,27 +54,27 @@ def __init__(self, model: str = "qwen2.5-coder:7b") -> None:
def _format_prompt(command: str) -> str:
return f"""
Shell command: {command}

Identify the conceptual primitives this command touches.

Primitives are the conceptual entities required to reason about the effects
of the command. This includes ACTIONS, TARGETS, and concepts implied by flags.

- Actions: read, write, delete, create, move, copy, list, execute, modify, etc.
- Targets: file, directory, process, network, permission, etc.

Flag-to-concept examples:
- `rm -rf dir/` → delete + directory + file
- `cp -a src/ dst/` → copy + file + directory + permission
- `chmod +x script.sh` → modify + permission + file + execute
- `find . -name "*.py" -delete` → list + delete + file + directory

Flags themselves are NOT primitives, but they change semantic intent or
introduce additional concepts as shown above.

Do NOT return flag names (recursive, force, verbose, interactive, etc.)
as primitives. Map what the flag *does* to the concepts it affects.

Return only the list of required conceptual primitives.
"""

Expand Down Expand Up @@ -227,10 +226,3 @@ def on_policy(violations: list[PolicyViolation]) -> bool:
if not Confirm.ask(f"[yellow]⚠ Policy gate:[/] {v.message}"):
return False
return True


def make_on_learn(model: str = "qwen3:8b") -> DemoLearner:
"""
Create a learning callback for the demo agent.
"""
return DemoLearner(model=model)
Loading