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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,7 @@ studies/

# Personal study manifests and worker scripts
train_*.yaml
*.hpo.yaml
bridge_crack_unet_v2.yaml
colab_worker.py

5 changes: 2 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ The worker contract is exactly three calls; full reference in `docs/INTEGRATION.

### Statistical confidence (caveat, not a gate)

`get_study_data` includes `statistical_confidence` derived from completed-trial count:
The `hpo://studies/{name}/packet` resource includes `statistical_confidence` derived from completed-trial count:

| Tier | Completed trials | Agent behavior |
|------|------------------|----------------|
Expand All @@ -69,7 +69,7 @@ Reviews are never hard-blocked at low confidence — the dashboard shows a banne

When the user asks about study progress, trial results, or health:

1. Call `get_study_data(study_name)` to retrieve trial telemetry, health tier, fANOVA importances, best trials, and OOM patterns.
1. Read the `hpo://studies/{name}/packet` resource to retrieve trial telemetry, health tier, fANOVA importances, best trials, and OOM patterns.
2. Summarize: current best score, health status, stagnation indicators, and any OOM warnings.
3. If health is `watch` or `intervene`, explain the reason and offer to explore the data further.
4. If `vram_telemetry.oom_count > 0`, inspect the `oom_trials` list:
Expand All @@ -84,7 +84,6 @@ When a completed trial is reported or the background daemon polls health, the sy
**Agent Action (Cursor, Antigravity, and Claude Code)**:
* Eagerly check for the existence of `.hpo_status.json` in the project root.
* If `health_tier` is `"watch"` or `"intervene"`, immediately notify the user in your first response, explain the `health_reason`, and proactively offer to inspect the study data.
* Offer to use the `/goal` slash command to help coordinate and automate resolution of search space stagnation or failures.

## Guardrails (always)

Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,11 @@ Pathfinder exposes MCP tools that let your IDE agent (Cursor, Claude Code, Antig

### Inspection

1. Agent calls `get_study_data` to retrieve trial telemetry, health tier, fANOVA importances, and trial data
1. Agent reads the `hpo://studies/{name}/packet` resource to retrieve trial telemetry, health tier, fANOVA importances, and trial data
2. Agent summarizes: current best score, health status, OOM rate, stagnation warnings
3. Recommended search space adjustments happen by you through the dashboard Settings UI or `hpo_cli.py`

Key MCP tools: `validate_manifest`, `init_from_manifest`, `get_study_data`, `get_study_cards`, `export_manifest`.
Key MCP tools: `validate_manifest`, `init_from_manifest`. Data is retrieved via resources (`hpo://studies/{name}/packet`, `hpo://studies/{name}/cards`).

Trigger phrases: say **"integrate HPO"** or **"wire hyperparameter tuning"** to onboard. Say **"show study health"** or **"check HPO progress"** to inspect.

Expand Down Expand Up @@ -209,7 +209,7 @@ Name: `pathfinder`, Type: `command`, Command: `source .venv/bin/activate && pyth
"command": "python3",
"args": ["hpo_mcp_server.py"],
"env": {
"HPO_DATABASE_URL": "sqlite:///./hpo_studies.db"
"HPO_DATABASE_URL": "sqlite:///./.data/hpo_studies.db"
}
}
}
Expand Down Expand Up @@ -258,7 +258,7 @@ pytest tests/ -q

## Dev Notes

- MCP tool design to inspect telemetry and modify training scripts, refactored the architecture to decouple agentic workflows from deterministic optimization path
- MCP chosen over CLI invocation for IDE-agent integration because typed auto-discovery (tools with schemas, resources with URI templates) provides a better contract than agents string-matching CLI output
- Implemented concurrency patterns for distributed workers, real-time detection of crashed processes
- Optimized SQLite backend performance using Write-Ahead Logging; allowing concurrent broker writes, dashboard rendering, and MCP queries without read-write blocks

Expand Down
65 changes: 15 additions & 50 deletions hpo_mcp_server.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,13 @@
from typing import Optional, Dict, Any
import json
from typing import Dict, Any
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("Pathfinder")

from src.db_manager import init_db

# --- MCP TOOLS ---

@mcp.tool()
def get_study_data(study_name: str) -> Dict[str, Any]:
"""Returns the compacted HPO review packet, utilizing a lazy materialization cache layer."""
from src.analytics import build_study_packet
return build_study_packet(study_name)


@mcp.tool()
def get_study_cards(study_name: Optional[str] = None) -> Dict[str, Any]:
"""Retrieves generated study cards (model cards, recaps) from the database to enable cross-study queries."""
from src.analytics import load_study_cards

if study_name is not None:
import optuna
from src.db_manager import DATABASE_URL
try:
optuna.load_study(study_name=study_name, storage=DATABASE_URL)
except KeyError:
return {"success": False, "error": f"Study '{study_name}' not found."}
mcp = FastMCP("Pathfinder")

cards = load_study_cards(study_name)
return {"success": True, "cards": cards}

# --- MCP TOOLS (state-changing operations) ---

@mcp.tool()
def validate_manifest(yaml_str: str) -> Dict[str, Any]:
Expand Down Expand Up @@ -70,33 +48,20 @@ def init_from_manifest(yaml_str: str, force: bool = False) -> Dict[str, Any]:
return {"success": False, "error": str(e)}


@mcp.tool()
def export_manifest(study_name: str) -> Dict[str, Any]:
"""Export the active search space, HPO config, and context of an existing study as a valid manifest YAML string."""
from src.manifest import export_manifest_yaml
try:
result = export_manifest_yaml(study_name)
return {"success": True, "yaml_str": result}
except Exception as e:
return {"success": False, "error": str(e)}


# --- MCP PROMPT RESOURCES ---

@mcp.resource("hpo://prompts/grill")
def resource_grill() -> str:
"""Onboarding checklist: interview, then manifest loop."""
return """# Pathfinder Onboarding (Grill + Manifest Loop)
# --- MCP RESOURCES (data retrieval) ---

See AGENTS.md for the full procedure. After interviewing the user (metrics, GPU, bounds, hypothesis):
@mcp.resource("hpo://studies/{study_name}/packet")
def study_packet_resource(study_name: str) -> str:
"""Compacted HPO review packet: trial telemetry, health tier, fANOVA importances, OOM patterns."""
from src.analytics import build_study_packet
return json.dumps(build_study_packet(study_name), indent=2)

1. Draft a YAML manifest configuration (e.g. `train.hpo.yaml`).
2. Call `validate_manifest(yaml_str)` to check for errors/warnings mechanically.
3. Call `init_from_manifest(yaml_str)` to register the study in SQLite and Optuna.
4. Call `get_study_data(study_name)` to confirm the study is accessible and healthy.

Worker integration reference: `docs/INTEGRATION.md`. Do not write json space config files to disk.
"""
@mcp.resource("hpo://studies/{study_name}/cards")
def study_cards_resource(study_name: str) -> str:
"""Generated study cards (model cards, recaps) from the database."""
from src.analytics import load_study_cards
return json.dumps(load_study_cards(study_name), indent=2)


if __name__ == "__main__":
Expand Down
44 changes: 39 additions & 5 deletions simulators/training_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,26 +128,44 @@ def run_training_worker(
val_history = []
final_score = 0.0
final_loss = 999.0
final_score_fixed = 0.0
final_loss_fixed = 999.0
pruned = False

# Prepare parameters for fixed resolution evaluation (fixed at 512px)
params_fixed = dict(params)
params_fixed["resolution"] = 512

# 2. Run Epoch training loop
for epoch in range(1, epochs_per_trial + 1):
# Simulate training/val forward pass
score, loss = simulate_training_epoch(epoch, params)
val_history.append({"epoch": epoch, "score": score, "loss": loss})
score_fixed, loss_fixed = simulate_training_epoch(epoch, params_fixed)

val_history.append({
"epoch": epoch,
"score": score,
"loss": loss,
"score_eval_fixed": score_fixed,
"loss_eval_fixed": loss_fixed
})

print(f" Epoch {epoch:02d}/{epochs_per_trial:02d} | Score: {score:.4f} | Loss: {loss:.4f}")
print(f" Epoch {epoch:02d}/{epochs_per_trial:02d} | Score (train): {score:.4f} | Loss: {loss:.4f} | Score (fixed eval @512px): {score_fixed:.4f}")

# Record final metrics
final_score = score
final_loss = loss
final_score_fixed = score_fixed
final_loss_fixed = loss_fixed

# 3. Intermediate epoch reporting & pruning evaluation
try:
should_prune = session.report_epoch(
epoch=epoch,
score=score,
loss=loss
loss=loss,
score_eval_fixed=score_fixed,
loss_eval_fixed=loss_fixed
)
except Exception as rep_err:
print(f"Error reporting epoch: {rep_err}")
Expand All @@ -162,6 +180,8 @@ def run_training_worker(
epoch=epoch,
score=score,
loss=loss,
score_eval_fixed=score_fixed,
loss_eval_fixed=loss_fixed,
state="PRUNED"
)
except Exception as prune_err:
Expand All @@ -179,6 +199,8 @@ def run_training_worker(
epoch=epochs_per_trial,
score=final_score,
loss=final_loss,
score_eval_fixed=final_score_fixed,
loss_eval_fixed=final_loss_fixed,
weights_path=weights_path,
history=val_history,
state="COMPLETE"
Expand All @@ -191,5 +213,17 @@ def run_training_worker(


if __name__ == "__main__":
# Runs a default study local simulation of 5 trials
run_training_worker(study_name="unet_crack_segmentation", max_trials=5)
import argparse
parser = argparse.ArgumentParser(description="Simulated Pathfinder Training Worker")
parser.add_argument("--study_name", default="unet_crack_segmentation", help="Study name")
parser.add_argument("--max_trials", type=int, default=5, help="Number of trials to run")
parser.add_argument("--epochs_per_trial", type=int, default=10, help="Epochs per trial")
parser.add_argument("--broker_url", default=None, help="Broker URL")
args = parser.parse_args()

run_training_worker(
study_name=args.study_name,
max_trials=args.max_trials,
epochs_per_trial=args.epochs_per_trial,
broker_url=args.broker_url
)
5 changes: 5 additions & 0 deletions src/db_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ def init_db():


def _apply_additive_migrations():
"""Deliberately additive-only (no down-migrations, no version table).

Sufficient for solo-dev column additions. Alembic would be the upgrade path for
multi-contributor development or type-altering/restructuring migrations.
"""
from sqlalchemy import inspect, text

try:
Expand Down
Loading
Loading