Spaces:
Running on Zero
Running on Zero
fix: guard empty board commands
Browse filesCo-authored-by: Codex <noreply@openai.com>
- README.md +2 -0
- hackathon_advisor/agent.py +10 -4
- hackathon_advisor/model_runtime.py +2 -2
- tests/test_agent.py +15 -0
- tests/test_model_runtime.py +12 -0
README.md
CHANGED
|
@@ -81,6 +81,8 @@ the private Field Notes artifact.
|
|
| 81 |
The `Rank` command rescans the saved idea board, recalculates each seal against the current targets, selects the
|
| 82 |
strongest page as the active idea, and drafts the next build step. The app then moves that page to the top of the Idea
|
| 83 |
Board and refreshes the seal, wood map, plan, and PNG artifact around the chosen direction.
|
|
|
|
|
|
|
| 84 |
|
| 85 |
## Gap Exploration
|
| 86 |
|
|
|
|
| 81 |
The `Rank` command rescans the saved idea board, recalculates each seal against the current targets, selects the
|
| 82 |
strongest page as the active idea, and drafts the next build step. The app then moves that page to the top of the Idea
|
| 83 |
Board and refreshes the seal, wood map, plan, and PNG artifact around the chosen direction.
|
| 84 |
+
If the board is empty, `Plan` and `Rank` do not create placeholder pages; they prompt the user to write an idea or press
|
| 85 |
+
`Gap` first.
|
| 86 |
|
| 87 |
## Gap Exploration
|
| 88 |
|
hackathon_advisor/agent.py
CHANGED
|
@@ -245,9 +245,12 @@ class AdvisorEngine:
|
|
| 245 |
) -> TurnResult:
|
| 246 |
idea = self._idea_for_optional_id(call, state)
|
| 247 |
if idea is None:
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
|
|
|
|
|
|
|
|
|
| 251 |
score, event = self.tools.score_idea(idea)
|
| 252 |
score = self._align_score_from_state(score, idea, state)
|
| 253 |
idea.score = score.to_dict()
|
|
@@ -269,7 +272,10 @@ class AdvisorEngine:
|
|
| 269 |
ranked = self._rank_ideas(state)
|
| 270 |
if not ranked:
|
| 271 |
tool_events.append(ToolEvent("compare_ideas", "No idea pages were available to rank."))
|
| 272 |
-
response =
|
|
|
|
|
|
|
|
|
|
| 273 |
return self._result(normalized, corrections, response, state, tool_events, [], [], None, [], {})
|
| 274 |
|
| 275 |
ideas = [idea for idea, _score in ranked]
|
|
|
|
| 245 |
) -> TurnResult:
|
| 246 |
idea = self._idea_for_optional_id(call, state)
|
| 247 |
if idea is None:
|
| 248 |
+
tool_events.append(ToolEvent("make_plan", "No idea page was available to plan."))
|
| 249 |
+
response = (
|
| 250 |
+
"Write one project instinct first, or press Gap for a starting direction. "
|
| 251 |
+
"Then I can draft a build path."
|
| 252 |
+
)
|
| 253 |
+
return self._result(normalized, corrections, response, state, tool_events, [], [], None, [], {})
|
| 254 |
score, event = self.tools.score_idea(idea)
|
| 255 |
score = self._align_score_from_state(score, idea, state)
|
| 256 |
idea.score = score.to_dict()
|
|
|
|
| 272 |
ranked = self._rank_ideas(state)
|
| 273 |
if not ranked:
|
| 274 |
tool_events.append(ToolEvent("compare_ideas", "No idea pages were available to rank."))
|
| 275 |
+
response = (
|
| 276 |
+
"No idea pages are on the board yet. Write one project instinct first, "
|
| 277 |
+
"or press Gap to seed a direction."
|
| 278 |
+
)
|
| 279 |
return self._result(normalized, corrections, response, state, tool_events, [], [], None, [], {})
|
| 280 |
|
| 281 |
ideas = [idea for idea, _score in ranked]
|
hackathon_advisor/model_runtime.py
CHANGED
|
@@ -44,9 +44,9 @@ class RuleBasedPlanner:
|
|
| 44 |
lower = text.lower()
|
| 45 |
if not text:
|
| 46 |
output = '<function name="list_projects">{"sort":"likes"}</function>'
|
| 47 |
-
elif any(term in lower for term in ("compare", "choose", "rank"))
|
| 48 |
output = '<function name="compare_ideas">{}</function>'
|
| 49 |
-
elif any(term in lower for term in ("plan", "roadmap", "next step", "milestone"))
|
| 50 |
output = '<function name="make_plan">{}</function>'
|
| 51 |
elif any(term in lower for term in ("whitespace", "original", "new", "bolder", "unwritten", "gap")):
|
| 52 |
output = '<function name="find_whitespace">{}</function>'
|
|
|
|
| 44 |
lower = text.lower()
|
| 45 |
if not text:
|
| 46 |
output = '<function name="list_projects">{"sort":"likes"}</function>'
|
| 47 |
+
elif any(term in lower for term in ("compare", "choose", "rank")):
|
| 48 |
output = '<function name="compare_ideas">{}</function>'
|
| 49 |
+
elif any(term in lower for term in ("plan", "roadmap", "next step", "milestone")):
|
| 50 |
output = '<function name="make_plan">{}</function>'
|
| 51 |
elif any(term in lower for term in ("whitespace", "original", "new", "bolder", "unwritten", "gap")):
|
| 52 |
output = '<function name="find_whitespace">{}</function>'
|
tests/test_agent.py
CHANGED
|
@@ -83,6 +83,21 @@ def test_plan_command_uses_current_idea() -> None:
|
|
| 83 |
assert planned.state["ideas"][0]["title"] == first.artifact["title"]
|
| 84 |
|
| 85 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
def test_plan_uses_profile_context() -> None:
|
| 87 |
index = ProjectIndex.from_files(Path("data/projects.json"), Path("data/project_index.json"))
|
| 88 |
engine = AdvisorEngine(index)
|
|
|
|
| 83 |
assert planned.state["ideas"][0]["title"] == first.artifact["title"]
|
| 84 |
|
| 85 |
|
| 86 |
+
def test_plan_and_rank_do_not_create_placeholder_ideas() -> None:
|
| 87 |
+
index = ProjectIndex.from_files(Path("data/projects.json"), Path("data/project_index.json"))
|
| 88 |
+
engine = AdvisorEngine(index)
|
| 89 |
+
|
| 90 |
+
planned = engine.turn("make a build plan", {})
|
| 91 |
+
ranked = engine.turn("compare ideas", planned.state)
|
| 92 |
+
|
| 93 |
+
assert planned.state["ideas"] == []
|
| 94 |
+
assert ranked.state["ideas"] == []
|
| 95 |
+
assert "Write one project instinct first" in planned.response
|
| 96 |
+
assert "No idea pages" in ranked.response
|
| 97 |
+
assert planned.tool_events[0].name == "make_plan"
|
| 98 |
+
assert ranked.tool_events[0].name == "compare_ideas"
|
| 99 |
+
|
| 100 |
+
|
| 101 |
def test_plan_uses_profile_context() -> None:
|
| 102 |
index = ProjectIndex.from_files(Path("data/projects.json"), Path("data/project_index.json"))
|
| 103 |
engine = AdvisorEngine(index)
|
tests/test_model_runtime.py
CHANGED
|
@@ -28,6 +28,18 @@ def test_rule_planner_uses_plan_when_idea_exists() -> None:
|
|
| 28 |
assert resolution.call.name == "make_plan"
|
| 29 |
|
| 30 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
def test_rule_planner_defaults_blank_to_list_projects() -> None:
|
| 32 |
planner = RuleBasedPlanner()
|
| 33 |
|
|
|
|
| 28 |
assert resolution.call.name == "make_plan"
|
| 29 |
|
| 30 |
|
| 31 |
+
def test_rule_planner_keeps_empty_board_commands_as_commands() -> None:
|
| 32 |
+
planner = RuleBasedPlanner()
|
| 33 |
+
|
| 34 |
+
plan = planner.plan("make a build plan", {})
|
| 35 |
+
rank = planner.plan("compare ideas", {})
|
| 36 |
+
|
| 37 |
+
assert plan.status == "valid"
|
| 38 |
+
assert plan.call.name == "make_plan"
|
| 39 |
+
assert rank.status == "valid"
|
| 40 |
+
assert rank.call.name == "compare_ideas"
|
| 41 |
+
|
| 42 |
+
|
| 43 |
def test_rule_planner_defaults_blank_to_list_projects() -> None:
|
| 44 |
planner = RuleBasedPlanner()
|
| 45 |
|