Skip to content
Open
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
94 changes: 94 additions & 0 deletions app/lint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
"""Brenner-style linting for inferences.

Goal: prevent low-quality, non-falsifiable, or self-deceiving inferences from
reaching the triage queue.

This is inspired by BrennerBot's methodology (exclusion tests, third alternative,
chastity vs impotence, anomaly quarantine).

This module is local-only and heuristic by design.
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import Any, Dict, List, Optional


@dataclass
class LintIssue:
code: str
message: str


def lint_inference_candidate(candidate: Dict[str, Any]) -> List[LintIssue]:
"""Return a list of issues; empty means candidate passes."""
issues: List[LintIssue] = []

text = (candidate.get("inference") or candidate.get("statement") or "").strip()
source = (candidate.get("source") or "").strip().lower()

if not text:
issues.append(LintIssue("empty", "Inference text is empty."))
return issues

# 1) Trivial / non-actionable
trivial = [
"bryce exists",
"the user exists",
"this is a message",
"this is an email",
]
if any(t in text.lower() for t in trivial):
issues.append(LintIssue("trivial", "Inference appears trivial/non-actionable."))

# 2) Confidence sanity
conf = candidate.get("confidence")
try:
if conf is not None and not (0.0 <= float(conf) <= 1.0):
issues.append(LintIssue("confidence_range", "Confidence must be in [0,1]."))
except Exception:
issues.append(LintIssue("confidence_parse", "Confidence is not numeric."))

# 3) Require discriminability hook (Brenner: exclusion / kill test)
# In MVP, we encode this as requiring either an explicit 'kill_test'
# or at least one question-like falsifier.
kill_test = candidate.get("kill_test") or candidate.get("falsifier")
if not kill_test:
# soft check: prompt-like strings
if "never" not in text.lower() and "if" not in text.lower():
issues.append(
LintIssue(
"no_kill_test",
"Missing falsifier/kill-test. Add `kill_test` (e.g., 'If this inference were false, we would observe...').",
)
)

# 4) Third alternative check (Brenner: both could be wrong)
# If candidate presents an A-vs-B dichotomy, require a third alternative.
alt = candidate.get("alternatives")
if (" either " in text.lower() and " or " in text.lower()) and not alt:
issues.append(
LintIssue(
"no_third_alternative",
"Candidate looks like a dichotomy; add `alternatives` including 'both could be wrong'.",
)
)

# 5) Validity check (chastity vs impotence)
# For anything derived from a failed action/absence, require distinguishing
# 'won't' vs 'can't'.
if any(w in text.lower() for w in ["never", "doesn't", "didn't", "won't", "can't"]):
if not candidate.get("validity_checks"):
issues.append(
LintIssue(
"no_validity_check",
"Candidate implies an absence/failure; add `validity_checks` to separate measurement failure vs hypothesis failure.",
)
)

return issues


def passes_lint(candidate: Dict[str, Any]) -> bool:
return len(lint_inference_candidate(candidate)) == 0
11 changes: 7 additions & 4 deletions app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from app.brain import brain # Import the Brain
from app.ingestors.chatgpt import ChatGPTIngestor
from app.ingestors.safari import SafariIngestor
from app.lint import lint_inference_candidate

app = FastAPI()

Expand Down Expand Up @@ -178,12 +179,14 @@ async def process_raw_data():

# Process batch (limit to 5 for now to avoid freezing)
for item in raw_items[:5]:
# Skip if we already have an inference for this content? (Simplification: process all)

# Call Brain
# Use brain.process_raw_data logic (we need to update brain.py first or inline it here)
# Let's use the existing generate_inference method for now
inference = await brain.generate_inference(item["source"], item["content"])

# Brenner-style linting: skip low-quality candidates rather than polluting triage
issues = lint_inference_candidate(inference)
if issues:
continue

new_inferences.append(inference)
generated_count += 1

Expand Down