Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
52619cd
feat(jeval): add abstract Encoder base class
Pshyam17 Mar 12, 2026
be1d5c0
feat(jeval): add frozen SentenceTransformer target encoder
Pshyam17 Mar 12, 2026
8681035
feat(jeval): add EPE computation engine
Pshyam17 Mar 12, 2026
0ab2cff
feat(jeval): add per-content-type EPE decomposer
Pshyam17 Mar 12, 2026
457d851
feat(jeval): add zero-shot NLI content type classifier
Pshyam17 Mar 12, 2026
89e4e44
feat(jeval): add EPE-guided compression budget allocator
Pshyam17 Mar 12, 2026
6c39286
feat(jeval): add adaptive compressor pipeline
Pshyam17 Mar 12, 2026
a59b496
feat(jeval): add PreCompact hook for Droid integration
Pshyam17 Mar 12, 2026
097846a
feat(jeval): add Factory-style probe evaluation harness
Pshyam17 Mar 12, 2026
7631a36
docs(jeval): add README, pyproject, and community-builds entry
Pshyam17 Mar 12, 2026
cfc85db
fix(jeval): add missing predictor_head.py file
Pshyam17 Mar 12, 2026
e313b99
fix(jeval): populate __init__.py files with explicit imports
Pshyam17 Mar 12, 2026
211f7a0
fix(jeval): extract RISK_WEIGHTS to epe/weights.py to break circular …
Pshyam17 Mar 12, 2026
3fd5c7e
fix(jeval): rewrite decomposer.py after sed corruption
Pshyam17 Mar 12, 2026
0a95ea1
files
Pshyam17 Mar 12, 2026
563bcdb
changes
Pshyam17 Mar 12, 2026
fbf4fd9
training the predictor
Pshyam17 Mar 12, 2026
0b6b062
feat(jeval): LLM backend, z-score calibration, trained predictor
Pshyam17 Mar 12, 2026
234def2
chore(jeval): clean up demo artifacts and test data
Pshyam17 Mar 13, 2026
725f67c
chore(jeval): move slides files to example folder
Pshyam17 Mar 13, 2026
ed557b7
style(jeval): clean up on aisle score_artifacts.py
Pshyam17 Mar 13, 2026
00e871d
added readme
Pshyam17 Mar 13, 2026
7683037
Merge branch 'Factory-AI:main' into feat/jeval-memory-compression
Pshyam17 Mar 17, 2026
a1e83d5
Merge branch 'Factory-AI:main' into feat/jeval-memory-compression
Pshyam17 Mar 24, 2026
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
5 changes: 5 additions & 0 deletions .factory/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"enabledPlugins": {
"core@factory-plugins": true
}
}
1 change: 1 addition & 0 deletions community-builds.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ A curated list of community-built examples and projects using Factory. To add yo
- [Factory CLI with ChatGPT Codex / Claude subscription via CLIProxyAPI](https://gist.github.com/chandika/c4b64c5b8f5e29f6112021d46c159fdd) - Guide to run Factory CLI against Claude Code Max or ChatGPT Codex through CLIProxyAPI by [chandika](https://github.com/chandika)
- [Factory CLI with Claude subscription via CLIProxyAPI](https://gist.github.com/ben-vargas/9f1a14ac5f78d10eba56be437b7c76e5) - Setup instructions for using Factory CLI with Claude Code Max through CLIProxyAPI by [ben-vargas](https://github.com/ben-vargas)
- [GrayPane – Flight Search & Alerts](https://github.com/punitarani/flights-tracker) - Check available flights, monitor price trends, plan upcoming trips, and create personalized alerts by [Punit Arani](https://github.com/punitarani)
- [jeval-memory-compression](https://github.com/Pshyam17/factory/tree/main/examples/jeval-memory-compression) - JEPA-based semantic fidelity layer for Droid memory compression — intercepts PreCompact to protect high-risk memory entries (file paths, decisions, causal chains) from being lost, targeting the artifact tracking gap (2.45/5) identified in Factory's own evaluation by [Pshyam17](https://github.com/Pshyam17)
161 changes: 161 additions & 0 deletions examples/jeval-memory-compression/.factory/hooks/precompact_jeval.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
#!/usr/bin/env python3
"""
PreCompact hook — intercepts Droid before it compresses memory.

Droid calls this via the PreCompact hook event, passing a JSON
payload on stdin. We read the memory file, run jeval's adaptive
compressor, write back a verified memories.md, then tell Droid
to use our output instead of running its own compression.

Hook registration (add to ~/.factory/settings.json):
{
"hooks": {
"PreCompact": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "python3 $FACTORY_PROJECT_DIR/examples/jeval-memory-compression/.factory/hooks/precompact_jeval.py"
}
]
}
]
}
}
"""

import json
import sys
import os
import logging
from pathlib import Path

# add the example root to sys.path so jeval imports work
sys.path.insert(0, str(Path(__file__).resolve().parents[3]))

from jeval.encoders.sentence_encoder import FrozenEncoder
from jeval.encoders.predictor_head import PredictorHead
from jeval.epe.core import EPEComputer
from jeval.strata.classifier import ContentClassifier
from jeval.strata.budget import BudgetAllocator
from jeval.compression.adaptive import AdaptiveCompressor

logging.basicConfig(level=logging.INFO, stream=sys.stderr)
log = logging.getLogger("jeval.precompact")

# paths
MEMORY_FILE = Path(os.environ.get("FACTORY_PROJECT_DIR", ".")) / ".factory/memories.md"
WEIGHTS_FILE = Path(__file__).parent / "predictor_best.pt"
LOG_FILE = Path(__file__).parent / "compression_log.jsonl"


def load_compressor() -> AdaptiveCompressor:
"""
Build the jeval pipeline.

If a trained predictor checkpoint exists at predictor_best.pt,
load it. Otherwise use the randomly initialized predictor —
EPE will still run but won't be calibrated yet.
This lets the hook work immediately on install, before training.
"""
encoder = FrozenEncoder(device="cpu")
predictor = PredictorHead(d_in=encoder.dim())

if WEIGHTS_FILE.exists():
import torch
predictor.load_state_dict(torch.load(WEIGHTS_FILE, map_location="cpu"))
log.info("loaded trained predictor from %s", WEIGHTS_FILE)
else:
log.warning(
"no trained predictor found at %s — "
"using untrained predictor. run eval/train.py first.", WEIGHTS_FILE
)

computer = EPEComputer(encoder, predictor, device="cpu")
classifier = ContentClassifier(device=-1) # -1 = CPU
allocator = BudgetAllocator(high_thresh=0.35, low_thresh=0.10)

return AdaptiveCompressor(computer, classifier, allocator)


def log_compression_event(original: str, compressed: str, plan: list, global_epe: float):
"""
Append one compression event to the audit log as JSONL.
Each line is one compression event — easy to grep and analyze.
"""
import time
entry = {
"timestamp": time.time(),
"original_tokens": len(original.split()),
"compressed_tokens": len(compressed.split()),
"compression_ratio": len(compressed.split()) / max(len(original.split()), 1),
"global_epe": global_epe,
"segments": [
{
"content_type": p.content_type.value,
"epe": p.epe,
"weighted_risk": p.weighted_risk,
"budget": p.budget,
}
for p in plan
],
}
with open(LOG_FILE, "a") as f:
f.write(json.dumps(entry) + "\n")


def main():
# read hook payload from stdin — Droid passes event data as JSON
try:
payload = json.load(sys.stdin)
except json.JSONDecodeError:
payload = {}

# read current memory file
if not MEMORY_FILE.exists():
log.info("no memory file found at %s — nothing to compress", MEMORY_FILE)
sys.exit(0)

original_text = MEMORY_FILE.read_text(encoding="utf-8")

if not original_text.strip():
log.info("memory file is empty — nothing to compress")
sys.exit(0)

log.info("running jeval on %d tokens", len(original_text.split()))

# run jeval adaptive compression
compressor = load_compressor()
compressed_text, plan = compressor.compress(original_text)

# compute global EPE for the audit log
global_epe = sum(p.epe for p in plan) / max(len(plan), 1)

# write verified compressed memory back to the file
MEMORY_FILE.write_text(compressed_text, encoding="utf-8")
log.info(
"compression complete — %d → %d tokens global_epe=%.4f",
len(original_text.split()),
len(compressed_text.split()),
global_epe,
)

# log the event for later artifact score analysis
log_compression_event(original_text, compressed_text, plan, global_epe)

# tell Droid the compression succeeded
# exit 0 = hook passed, Droid continues normally
# exit 1 = hook failed, Droid falls back to native compression
print(json.dumps({
"systemMessage": (
f"jeval: compressed memory {len(original_text.split())} → "
f"{len(compressed_text.split())} tokens "
f"EPE={global_epe:.4f}"
)
}))
sys.exit(0)


if __name__ == "__main__":
main()
Binary file not shown.
Loading