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
1 change: 1 addition & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ jobs:
- uses: dtolnay/rust-toolchain@master
with:
toolchain: 1.97.1
components: rustfmt, clippy
- run: python -m pip install --upgrade pip
- run: python -m pip install -e .
- run: cargo test --workspace
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,10 @@ reconciliation, bundle, and replay/negative checks without dispatching selection
or final material. `src/ravel/fabric_persistent.py` is the preferred live Fabric
consumer path: it connects through the persistent controller, delegates bundle
transport and worker placement to Fabric, and supports detached execution with
restart-safe RAVEL provenance metadata. The Rust crates under `crates/` and the
restart-safe RAVEL provenance metadata. `ravel-fabric-agent` adds a bounded
long-running consumer that checks live readiness, can submit one idempotent pair
of development bootstrap probes, and retains completed Fabric evidence
references without taking over fleet or evaluator authority. The Rust crates under `crates/` and the
`ravel-rs` CLI are the future implementation home; `src/ravel/rust_bridge.py`
and `tests/test_rust_parity.py` prove discrete C/Python/Rust agreement without
making either side authoritative. See [`docs/RUST_FOUNDATION.md`](docs/RUST_FOUNDATION.md)
Expand Down
2 changes: 1 addition & 1 deletion config/ravel-fabric-persistent.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
# RAVEL receives only the controller consumer socket.
[fabric]
mode = "persistent-controller"
socket_path = "/run/mncs-fabric/controller.sock"
socket_path = "~/.local/state/mncs-fabric/controller.sock"
client_identity = "ravel"
timeout = 5.0
35 changes: 34 additions & 1 deletion docs/FABRIC_INTEGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ A minimal configuration is:
```toml
[fabric]
mode = "persistent-controller"
socket_path = "/run/mncs-fabric/controller.sock"
socket_path = "~/.local/state/mncs-fabric/controller.sock"
client_identity = "ravel"
timeout = 5.0
```
Expand Down Expand Up @@ -83,6 +83,39 @@ No model is hard-coded into the adapter. `submit_provider_parity()` accepts an
optional Fabric `model` and `role`, while the default leaves model/worker
selection to the surrounding MNCS policy and Fabric capability inventory.

### Live RAVEL consumer agent

`ravel-fabric-agent` (or `python3 tools/ravel_fabric_agent.py` from a checkout)
provides the bounded long-running consumer process. It performs three jobs only:

1. reports controller/fleet readiness through Fabric's public consumer API;
2. optionally submits one branching and one ring development bootstrap probe; and
3. watches RAVEL's own detached work and retains completed Fabric evidence
references under the RAVEL state directory.

It does **not** poll controller ledgers directly, inspect worker secrets, ingest
arbitrary MNCS experiments, resubmit work on a timer, or grant evaluator status.
The bootstrap operation is idempotent with respect to RAVEL's retained provider
submissions, so restarting the agent does not create an endless stream of jobs.

From the standard controller layout no config file is required:

```bash
python3 tools/ravel_fabric_agent.py doctor
python3 tools/ravel_fabric_agent.py run --bootstrap --interval 30
```

The default socket is `~/.local/state/mncs-fabric/controller.sock` and the
default RAVEL-owned state root is `~/.local/state/ravel/fabric-live`. A custom
config may still be supplied with `--config` or `RAVEL_FABRIC_CONFIG`.

Because the current 0.6 provider probe bundles a binary compiled on the
controller, its Fabric workload is explicitly constrained to the producing OS
and architecture in addition to `python`. This prevents, for example, a Linux
ELF candidate from being placed on a Windows worker merely because both expose
Python. Cross-platform RAVEL probes require platform-native build artifacts; the
adapter does not pretend otherwise.

### Authority and evidence boundary

Fabric outcomes remain execution evidence. They are never promoted into a RAVEL
Expand Down
9 changes: 9 additions & 0 deletions mncs-forge.toml
Original file line number Diff line number Diff line change
Expand Up @@ -233,3 +233,12 @@ command = ["python3", "tools/ravel_forge_check.py", "knowledge-lifecycle"]
provider_protocol = false
subject = "project"
disclosure = "compact"

[[workflows]]
name = "fabric-live-doctor"
category = "inspection"
mode = "development"
command = ["python3", "tools/ravel_fabric_agent.py", "doctor"]
provider_protocol = false
subject = "project"
disclosure = "compact"
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ requires-python = ">=3.11"
authors = [{name = "RAVEL contributors"}]
dependencies = []

[project.scripts]
ravel-fabric-agent = "ravel.fabric_agent:main"

[tool.setuptools.packages.find]
where = ["src"]

Expand All @@ -32,3 +35,4 @@ where = ["src"]

[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["src"]
7 changes: 4 additions & 3 deletions src/ravel/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,19 @@

__all__ = [
"adaptation",
"checkpoint",
"c_observations",
"checkpoint",
"development_evaluator",
"experience",
"fabric",
"fabric_agent",
"fabric_persistent",
"knowledge",
"lifecycle",
"matched_compute",
"mncs_receipts",
"memory",
"mechanism_state",
"memory",
"mncs_receipts",
"planning",
"policy",
"providers",
Expand Down
70 changes: 50 additions & 20 deletions src/ravel/fabric.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,23 @@

from __future__ import annotations

from dataclasses import dataclass, field
from enum import StrEnum
import hashlib
import json
import math
import os
from pathlib import Path
import shutil
import stat
import tempfile
from typing import Any, Mapping, Protocol
import subprocess
import sys
from collections.abc import Mapping
from dataclasses import dataclass, field
from enum import StrEnum
from pathlib import Path
from typing import Any, Protocol

from .mncs_bundles import BundleResult, build_execution_bundle
from .siblings import ensure_sibling_src


WORKLOAD_SCHEMA = "ravel-fabric-workload/0.1"
OBSERVATION_SCHEMA = "ravel-fabric-observation/0.1"
REFERENCE_SCHEMA = "ravel-fabric-reference-report/0.1"
Expand Down Expand Up @@ -322,6 +323,42 @@ def _write_bundle_source_manifest(source_root: Path, destination: Path) -> None:
destination.write_text(json.dumps(value, sort_keys=True), encoding="utf-8")


def _build_provider_candidate(provider: str, output: Path) -> dict[str, Any]:
"""Build development material without importing repository-only tools as a package."""

if provider not in {"branching", "ring"}:
raise FabricError("provider must be branching or ring")
project_root = Path(__file__).resolve().parents[2]
build_tool = project_root / "tools" / "ravel_0_6_build.py"
if not build_tool.is_file():
raise FabricUnavailableError(
"RAVEL 0.6 development bootstrap requires a source checkout containing "
"tools/ravel_0_6_build.py"
)
environment = dict(os.environ)
environment["RAVEL06_PROVIDER"] = provider
completed = subprocess.run(
[sys.executable, str(build_tool), "build", "--output-dir", str(output)],
cwd=project_root,
env=environment,
text=True,
capture_output=True,
check=False,
)
if completed.returncode != 0:
detail = (completed.stderr or completed.stdout)[-4096:].strip()
raise FabricUnavailableError(
"RAVEL 0.6 development build failed" + (f": {detail}" if detail else "")
)
try:
record = json.loads(completed.stdout)
except json.JSONDecodeError as error:
raise FabricUnavailableError("RAVEL 0.6 development build returned invalid JSON") from error
if not isinstance(record, dict):
raise FabricUnavailableError("RAVEL 0.6 development build returned a non-object record")
return record


def _task_source(provider: str) -> str:
return f'''import json
from pathlib import Path
Expand Down Expand Up @@ -368,7 +405,10 @@ def __init__(self, workspace: str | Path) -> None:
ensure_sibling_src("mncs-fabric", "mncs_validator")
try:
from mncs_fabric.artifacts import build_manifest
from mncs_fabric.challenges import ChallengeReplayStore, challenge_for_receipt
from mncs_fabric.challenges import (
ChallengeReplayStore,
challenge_for_receipt,
)
from mncs_fabric.controller import LocalController
from mncs_fabric.receipts import build_execution_receipt
from mncs_fabric.service import FabricService
Expand Down Expand Up @@ -400,17 +440,7 @@ def reconcile(self, records: list[Mapping[str, Any]]) -> Mapping[str, Any]:
return self._service.reconcile(list(records), require_distinct_nodes=True)

def _build_provider(self, provider: str, output: Path) -> dict[str, Any]:
from tools.ravel_0_6_build import build

prior = os.environ.get("RAVEL06_PROVIDER")
try:
os.environ["RAVEL06_PROVIDER"] = provider
return build(output)
finally:
if prior is None:
os.environ.pop("RAVEL06_PROVIDER", None)
else:
os.environ["RAVEL06_PROVIDER"] = prior
return _build_provider_candidate(provider, output)

def _make_artifact(self, provider: str, root: Path) -> tuple[Path, dict[str, Any], BundleResult]:
artifact = root / "artifact"
Expand Down Expand Up @@ -540,7 +570,7 @@ def execute_provider_parity(
"issues": list(binding.get("issues", [])),
}
)
except Exception as error:
except Exception as error: # noqa: BLE001
receipt_bindings.append(
{"status": "UNKNOWN", "issues": [type(error).__name__]}
)
Expand Down Expand Up @@ -700,7 +730,7 @@ class FabricNetworkConfig:
pre_staged_bundle_identity: str | None = None

@classmethod
def load(cls, path: str | Path) -> "FabricNetworkConfig":
def load(cls, path: str | Path) -> FabricNetworkConfig:
import tomllib

source = Path(path).resolve(strict=True)
Expand Down
Loading
Loading