JacobLinCool Codex commited on
Commit
7575503
·
verified ·
1 Parent(s): 07636bb

fix: persist artifacts for scored ideas

Browse files

Co-authored-by: Codex <noreply@openai.com>

hackathon_advisor/agent.py CHANGED
@@ -106,6 +106,7 @@ class AdvisorEngine:
106
  tool_events.append(event)
107
  response = self._whitespace_response(idea, whitespace, score)
108
  artifact = self._artifact(idea, score)
 
109
  return self._result(
110
  normalized,
111
  corrections,
@@ -173,6 +174,11 @@ class AdvisorEngine:
173
  stored.append(idea.to_dict())
174
  state["ideas"] = stored
175
 
 
 
 
 
 
176
  def _current_idea(self, state: dict[str, Any]) -> Idea | None:
177
  current_id = state.get("current_idea_id")
178
  for item in state.get("ideas", []):
@@ -222,6 +228,7 @@ class AdvisorEngine:
222
  tool_events.append(event)
223
  response = self._whitespace_response(idea, whitespace, score)
224
  artifact = self._artifact(idea, score)
 
225
  return self._result(
226
  normalized,
227
  corrections,
@@ -260,6 +267,7 @@ class AdvisorEngine:
260
  tool_events.append(event)
261
  response = self._plan_response(idea, score, plan)
262
  artifact = self._artifact(idea, score)
 
263
  return self._result(normalized, corrections, response, state, tool_events, [], [], score, plan, artifact)
264
 
265
  def _compare_turn(
@@ -278,6 +286,8 @@ class AdvisorEngine:
278
  )
279
  return self._result(normalized, corrections, response, state, tool_events, [], [], None, [], {})
280
 
 
 
281
  ideas = [idea for idea, _score in ranked]
282
  state["ideas"] = [idea.to_dict() for idea in ideas]
283
  winner, score = ranked[0]
@@ -286,7 +296,8 @@ class AdvisorEngine:
286
  plan, event = self.tools.make_plan(winner, self._profile_context(state))
287
  tool_events.append(event)
288
  response = self._compare_response(ranked, plan)
289
- artifact = self._artifact(winner, score)
 
290
  return self._result(normalized, corrections, response, state, tool_events, [], [], score, plan, artifact)
291
 
292
  def _project_turn(
@@ -329,6 +340,7 @@ class AdvisorEngine:
329
  tool_events.append(event)
330
  response = f"The wax seal reads {score.overall}/10, {score.verdict}, for {idea.title}."
331
  artifact = self._artifact(idea, score)
 
332
  return self._result(normalized, corrections, response, state, tool_events, [], [], score, [], artifact)
333
 
334
  def _profile_turn(
@@ -360,7 +372,12 @@ class AdvisorEngine:
360
  idea = self._current_idea(state)
361
  if idea is not None:
362
  idea.goals = goals
 
 
363
  self._store_idea(state, idea)
 
 
 
364
  tool_events.append(ToolEvent("set_goals", f"Set {len(goals)} goals."))
365
  labels = [goal_label(goal) for goal in goals]
366
  response = "The seal will now bias toward: " + (", ".join(labels) or "no specific goals")
 
106
  tool_events.append(event)
107
  response = self._whitespace_response(idea, whitespace, score)
108
  artifact = self._artifact(idea, score)
109
+ self._attach_artifact(state, idea, artifact)
110
  return self._result(
111
  normalized,
112
  corrections,
 
174
  stored.append(idea.to_dict())
175
  state["ideas"] = stored
176
 
177
+ def _attach_artifact(self, state: dict[str, Any], idea: Idea, artifact: dict[str, Any]) -> None:
178
+ idea.artifact = artifact
179
+ self._store_idea(state, idea)
180
+ state["last_artifact"] = artifact
181
+
182
  def _current_idea(self, state: dict[str, Any]) -> Idea | None:
183
  current_id = state.get("current_idea_id")
184
  for item in state.get("ideas", []):
 
228
  tool_events.append(event)
229
  response = self._whitespace_response(idea, whitespace, score)
230
  artifact = self._artifact(idea, score)
231
+ self._attach_artifact(state, idea, artifact)
232
  return self._result(
233
  normalized,
234
  corrections,
 
267
  tool_events.append(event)
268
  response = self._plan_response(idea, score, plan)
269
  artifact = self._artifact(idea, score)
270
+ self._attach_artifact(state, idea, artifact)
271
  return self._result(normalized, corrections, response, state, tool_events, [], [], score, plan, artifact)
272
 
273
  def _compare_turn(
 
286
  )
287
  return self._result(normalized, corrections, response, state, tool_events, [], [], None, [], {})
288
 
289
+ for idea, idea_score in ranked:
290
+ idea.artifact = self._artifact(idea, idea_score)
291
  ideas = [idea for idea, _score in ranked]
292
  state["ideas"] = [idea.to_dict() for idea in ideas]
293
  winner, score = ranked[0]
 
296
  plan, event = self.tools.make_plan(winner, self._profile_context(state))
297
  tool_events.append(event)
298
  response = self._compare_response(ranked, plan)
299
+ artifact = winner.artifact or self._artifact(winner, score)
300
+ self._attach_artifact(state, winner, artifact)
301
  return self._result(normalized, corrections, response, state, tool_events, [], [], score, plan, artifact)
302
 
303
  def _project_turn(
 
340
  tool_events.append(event)
341
  response = f"The wax seal reads {score.overall}/10, {score.verdict}, for {idea.title}."
342
  artifact = self._artifact(idea, score)
343
+ self._attach_artifact(state, idea, artifact)
344
  return self._result(normalized, corrections, response, state, tool_events, [], [], score, [], artifact)
345
 
346
  def _profile_turn(
 
372
  idea = self._current_idea(state)
373
  if idea is not None:
374
  idea.goals = goals
375
+ idea.score = None
376
+ idea.artifact = None
377
  self._store_idea(state, idea)
378
+ last_artifact = state.get("last_artifact")
379
+ if isinstance(last_artifact, dict) and last_artifact.get("title") == idea.title:
380
+ del state["last_artifact"]
381
  tool_events.append(ToolEvent("set_goals", f"Set {len(goals)} goals."))
382
  labels = [goal_label(goal) for goal in goals]
383
  response = "The seal will now bias toward: " + (", ".join(labels) or "no specific goals")
hackathon_advisor/tools.py CHANGED
@@ -89,6 +89,7 @@ class Idea:
89
  pitch: str
90
  goals: list[str] = field(default_factory=lambda: GOALS[:3])
91
  score: dict | None = None
 
92
 
93
  def to_dict(self) -> dict:
94
  return {
@@ -97,6 +98,7 @@ class Idea:
97
  "pitch": self.pitch,
98
  "goals": self.goals,
99
  "score": self.score,
 
100
  }
101
 
102
 
 
89
  pitch: str
90
  goals: list[str] = field(default_factory=lambda: GOALS[:3])
91
  score: dict | None = None
92
+ artifact: dict[str, Any] | None = None
93
 
94
  def to_dict(self) -> dict:
95
  return {
 
98
  "pitch": self.pitch,
99
  "goals": self.goals,
100
  "score": self.score,
101
+ "artifact": self.artifact,
102
  }
103
 
104
 
static/app.js CHANGED
@@ -712,8 +712,10 @@ function renderSelectedIdeaSeal(idea) {
712
  }
713
 
714
  function renderSelectedIdeaArtifact(idea) {
715
- if (session.last_artifact?.title === idea.title) {
716
- currentArtifact = session.last_artifact;
 
 
717
  renderWoodMap(currentArtifact.wood_map || null);
718
  exportButton.disabled = false;
719
  return;
@@ -934,6 +936,7 @@ function invalidateCurrentPlan(message) {
934
 
935
  function clearCurrentArtifactFor(idea) {
936
  if (!idea || currentArtifact?.title === idea.title) currentArtifact = null;
 
937
  if (!idea || session.last_artifact?.title === idea.title) delete session.last_artifact;
938
  }
939
 
 
712
  }
713
 
714
  function renderSelectedIdeaArtifact(idea) {
715
+ const artifact = idea.artifact || (session.last_artifact?.title === idea.title ? session.last_artifact : null);
716
+ if (artifact) {
717
+ currentArtifact = artifact;
718
+ session.last_artifact = artifact;
719
  renderWoodMap(currentArtifact.wood_map || null);
720
  exportButton.disabled = false;
721
  return;
 
936
 
937
  function clearCurrentArtifactFor(idea) {
938
  if (!idea || currentArtifact?.title === idea.title) currentArtifact = null;
939
+ if (idea?.artifact) delete idea.artifact;
940
  if (!idea || session.last_artifact?.title === idea.title) delete session.last_artifact;
941
  }
942
 
tests/test_agent.py CHANGED
@@ -33,6 +33,7 @@ def test_agent_scores_and_persists_idea() -> None:
33
  assert result.state["last_tool_resolution"]["call"]["name"] == "save_idea"
34
  assert result.state["trace"][0]["tool_resolution"]["call"]["name"] == "save_idea"
35
  assert result.state["last_artifact"]["title"] == result.artifact["title"]
 
36
  assert result.artifact["wood_map"]["caption"]
37
  assert {dot["kind"] for dot in result.artifact["wood_map"]["dots"]} >= {"idea", "echo", "inked"}
38
  assert result.score.to_dict()["echoes"][0]["page_number"] >= 1
@@ -133,6 +134,8 @@ def test_distinct_idea_turns_append_to_board() -> None:
133
  assert len(second.state["ideas"]) == 2
134
  assert second.state["ideas"][0]["title"] == first.artifact["title"]
135
  assert second.state["ideas"][1]["title"] == second.artifact["title"]
 
 
136
 
137
 
138
  def test_compare_ideas_reranks_board_and_selects_winner() -> None:
@@ -147,6 +150,7 @@ def test_compare_ideas_reranks_board_and_selects_winner() -> None:
147
  assert ranked.artifact["title"] == ranked.state["ideas"][0]["title"]
148
  assert ranked.state["current_idea_id"] == ranked.state["ideas"][0]["id"]
149
  assert ranked.state["ideas"][0]["score"]["overall"] >= ranked.state["ideas"][1]["score"]["overall"]
 
150
  assert ranked.plan
151
  assert "Ranked pages:" in ranked.response
152
  assert ranked.tool_events[0].name == "compare_ideas"
@@ -193,6 +197,22 @@ def test_planner_profile_and_goals_update_state() -> None:
193
  assert "Local-first, Build notes" in targeted.response
194
 
195
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
196
  def test_session_goals_apply_to_new_and_current_ideas() -> None:
197
  index = ProjectIndex.from_files(Path("data/projects.json"), Path("data/project_index.json"))
198
  engine = AdvisorEngine(index)
 
33
  assert result.state["last_tool_resolution"]["call"]["name"] == "save_idea"
34
  assert result.state["trace"][0]["tool_resolution"]["call"]["name"] == "save_idea"
35
  assert result.state["last_artifact"]["title"] == result.artifact["title"]
36
+ assert result.state["ideas"][0]["artifact"]["title"] == result.artifact["title"]
37
  assert result.artifact["wood_map"]["caption"]
38
  assert {dot["kind"] for dot in result.artifact["wood_map"]["dots"]} >= {"idea", "echo", "inked"}
39
  assert result.score.to_dict()["echoes"][0]["page_number"] >= 1
 
134
  assert len(second.state["ideas"]) == 2
135
  assert second.state["ideas"][0]["title"] == first.artifact["title"]
136
  assert second.state["ideas"][1]["title"] == second.artifact["title"]
137
+ assert second.state["ideas"][0]["artifact"]["title"] == first.artifact["title"]
138
+ assert second.state["ideas"][1]["artifact"]["title"] == second.artifact["title"]
139
 
140
 
141
  def test_compare_ideas_reranks_board_and_selects_winner() -> None:
 
150
  assert ranked.artifact["title"] == ranked.state["ideas"][0]["title"]
151
  assert ranked.state["current_idea_id"] == ranked.state["ideas"][0]["id"]
152
  assert ranked.state["ideas"][0]["score"]["overall"] >= ranked.state["ideas"][1]["score"]["overall"]
153
+ assert all(idea["artifact"]["title"] == idea["title"] for idea in ranked.state["ideas"])
154
  assert ranked.plan
155
  assert "Ranked pages:" in ranked.response
156
  assert ranked.tool_events[0].name == "compare_ideas"
 
197
  assert "Local-first, Build notes" in targeted.response
198
 
199
 
200
+ def test_goal_update_invalidates_current_idea_artifact() -> None:
201
+ index = ProjectIndex.from_files(Path("data/projects.json"), Path("data/project_index.json"))
202
+ first = AdvisorEngine(index).turn("A local-first archive cartographer for family photos", {})
203
+ target_engine = AdvisorEngine(
204
+ index,
205
+ planner=StaticPlanner(ToolCall("set_goals", {"goals": ["Field Notes"]})),
206
+ )
207
+
208
+ targeted = target_engine.turn("set goals", first.state)
209
+
210
+ idea = targeted.state["ideas"][0]
211
+ assert idea["score"] is None
212
+ assert idea["artifact"] is None
213
+ assert "last_artifact" not in targeted.state
214
+
215
+
216
  def test_session_goals_apply_to_new_and_current_ideas() -> None:
217
  index = ProjectIndex.from_files(Path("data/projects.json"), Path("data/project_index.json"))
218
  engine = AdvisorEngine(index)