fix review: git source acquisition, rename to 51-kiroku, binding conf… · jianrec/AgentBehaviorBench@18e03b3 · GitHub
Skip to content

Commit 18e03b3

Browse files
committed
fix review: git source acquisition, rename to 51-kiroku, binding config fidelity
- [source] method="git" so ABB restores 4b09df8 before evaluation (blocking merge issue: agent/ is gitignored by design and must not be vendored) - rename 50-kiroku -> 51-kiroku to avoid the 50- prefix collision with DefuzeX-AI#288/DefuzeX-AI#289 - binding: working title derived from input text so the requested topic drives the native writing task (was fixed 'Untitled Document') - binding: honor incoming RunnableConfig callbacks/tags/metadata and ABB's thread_id; native writer+checkpoint reused per thread instead of rebuilt per invoke; drives writer.graph.invoke directly (DocumentWriter.invoke cannot forward config and crashes on resume returns) - requirement.md: human review stages are auto-approved/skipped (stated explicitly); cross-input memory limited by upstream single-shot design - verified: with agent/ absent, ABB restored the source via git and the Docker local smoke passed 1/1 (judge: pass; step 3 now responsive to prior turns)
1 parent 8f6d8a8 commit 18e03b3

12 files changed

Lines changed: 113 additions & 79 deletions

File tree

Lines changed: 1 addition & 0 deletions

resources/agents/50-kiroku/bindings/kiroku_bridge.py renamed to resources/agents/51-kiroku/bindings/kiroku_bridge.py

Lines changed: 106 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,13 @@
5555
5656
Resource ownership
5757
------------------
58-
Each invocation constructs a fresh native `DocumentWriter` (with its own
59-
in-memory LangGraph checkpoint thread), so one input never continues a
60-
previous document session. The bridge holds no persistent native resources;
61-
`close()` only blocks further invocations and is safe to repeat.
58+
The bridge keeps one native `DocumentWriter` per thread: when ABB supplies a
59+
stable thread_id, the same writer and in-memory checkpoint are reused, so the
60+
thread continuity the harness offers is honored rather than silently dropped.
61+
Note that kiroku's own graph is single-shot by design — each new input starts
62+
a fresh document task on that thread — so cross-input memory remains limited
63+
by the upstream workflow itself. `close()` drops all cached writers and
64+
blocks further invocations; it is safe to repeat.
6265
"""
6366

6467
from collections.abc import Mapping
@@ -101,11 +104,30 @@ def message_from_input(value):
101104
return value.strip()
102105

103106

107+
def title_from_text(text):
108+
"""Derive a working title from the request text.
109+
110+
The requested topic must be visible in the specification, not only in
111+
area_of_paper/hypothesis: InternetSearch.create_task builds the writing
112+
task around the title, so a fixed placeholder title makes the document
113+
drift off-topic. Takes the first line or sentence of the request,
114+
bounded to 80 characters.
115+
"""
116+
first = text.strip().splitlines()[0].strip() if text.strip() else ""
117+
for sep in (". ", "。", "! ", "? ", "!", "?", "; ", ";"):
118+
if sep in first:
119+
first = first.split(sep)[0].strip()
120+
break
121+
return first[:80] or "Untitled Document"
122+
123+
104124
def specification_from_text(text):
105125
"""Build the approved fixed minimal document specification.
106126
107127
The input text is preserved verbatim in both `area_of_paper` and
108-
`hypothesis`; no title, section names or paragraph counts are inferred.
128+
`hypothesis`, and its first line/sentence becomes the working title so
129+
the requested topic actually drives the native writing task. Section
130+
names and paragraph counts remain fixed minimal values.
109131
110132
Args:
111133
text: The current plain-text request.
@@ -114,7 +136,7 @@ def specification_from_text(text):
114136
A mapping consumed by the native Kiroku graph state.
115137
"""
116138
return {
117-
"title": "Untitled Document",
139+
"title": title_from_text(text),
118140
"suggest_title": False,
119141
"generate_citations": False,
120142
"type_of_document": "technical briefing",
@@ -188,7 +210,36 @@ def __init__(self):
188210
"TAVILY_API_KEY is required because the native internet_search "
189211
"phase always runs and uses Tavily"
190212
)
191-
self._thread_id = 0
213+
# One native writer per thread: ABB passes a stable thread_id per
214+
# Case, and reusing the writer keeps that thread's native checkpoint
215+
# instead of silently dropping the continuity the harness offers.
216+
self._writers = {}
217+
self._fallback_seq = 0
218+
self._closed = False
219+
220+
def _resolve_run_config(self, config):
221+
"""Merge the incoming RunnableConfig, honoring ABB's thread identity.
222+
223+
Callbacks, tags and metadata are preserved as supplied. The
224+
configurable gains the thread identity (ABB's thread_id when present,
225+
otherwise a per-invoke fallback) and the empty native instruction.
226+
227+
Returns:
228+
(thread_key, run_config): the effective thread key and a new
229+
config mapping; the caller's object is never mutated.
230+
"""
231+
run_config = dict(config or {})
232+
configurable = dict(run_config.get("configurable") or {})
233+
incoming = configurable.get("thread_id")
234+
if incoming:
235+
thread_key = str(incoming)
236+
else:
237+
self._fallback_seq += 1
238+
thread_key = f"kiroku-bridge-{self._fallback_seq}"
239+
configurable["thread_id"] = thread_key
240+
configurable["instruction"] = ""
241+
run_config["configurable"] = configurable
242+
return thread_key, run_config
192243

193244
def _create_writer(self, specification):
194245
"""Construct a fresh native DocumentWriter for one invocation.
@@ -234,61 +285,42 @@ def _next_state_name(self, writer):
234285
next_nodes = getattr(snapshot, "next", ()) or ()
235286
return next_nodes[0] if next_nodes else ""
236287

237-
def _run_until_complete(self, writer, initial_state, config):
238-
"""Drive the native graph to END through its public invoke API.
239-
240-
DocumentWriter.invoke wraps its second argument as the inner
241-
``configurable`` mapping and pins thread_id to the writer's own id,
242-
so the binding passes only ``{"instruction": ""}`` — the same call
243-
shape the upstream UI uses. The empty instruction is interpreted by
244-
the source conditional edges (is_plan_review_complete /
245-
is_generate_review_complete) as "review complete, proceed". Residual
246-
interrupt pauses are resumed through the same public API with
247-
Command(resume="") when the installed langgraph provides it.
248-
249-
Incoming RunnableConfig extras (callbacks, tags, metadata) are
250-
intentionally not forwarded in this deployment: the native public
251-
invoke accepts only the inner configurable mapping, and although the
252-
binding could reach lower-level graph APIs, config propagation is
253-
deliberately disabled here to mirror the upstream UI exactly.
288+
def _run_until_complete(self, writer, initial_state, run_config):
289+
"""Drive the native graph to END with the merged RunnableConfig.
290+
291+
The initial input and every resume go through ``writer.graph.invoke``
292+
directly so callbacks, tags, metadata and the thread identity from
293+
the incoming config actually reach the graph. DocumentWriter.invoke
294+
is deliberately bypassed: it wraps only an inner configurable mapping
295+
(dropping everything else) and its ``.get("draft")`` crashes on the
296+
None resume return (kiroku_app.py:212).
297+
298+
Resume mechanism (source-backed, verified against the pinned
299+
langgraph==0.2.48): static interrupt_before pauses resume by invoking
300+
the graph with input None and the SAME config; the conditional edges
301+
(is_plan_review_complete / is_generate_review_complete) treat the
302+
empty configurable.instruction as "review complete, proceed".
303+
Command(resume=...) is NOT used: it targets dynamic interrupt() calls
304+
and does not clear static interrupt_before pauses in this version
305+
(the graph re-pauses at the same node). Resuming with None plus an
306+
empty instruction fabricates no human feedback.
254307
255308
Returns:
256309
(final_state, last_draft): the native final state mapping and the
257-
last draft string returned by the native invoke (already cleaned
258-
of ```markdown fences by DocumentWriter.invoke).
310+
last draft text observed in the checkpoint.
259311
"""
260-
self._thread_id += 1
261-
writer.set_thread_id(self._thread_id)
262-
263-
last_draft = ""
264312
if initial_state is not None:
265-
last_draft = writer.invoke(initial_state, {"instruction": ""}) or ""
313+
writer.graph.invoke(initial_state, run_config)
266314

315+
last_draft = ""
267316
guard = 0
268317
while guard < 50:
269318
guard += 1
270319
next_state = self._next_state_name(writer)
271320
if not next_state:
272321
break
273-
# Resume mechanism (source-backed, verified against the pinned
274-
# langgraph==0.2.48): static interrupt_before pauses resume by
275-
# invoking the graph with input None and the SAME config; the
276-
# conditional edges then treat the falsy configurable.instruction
277-
# as "review complete, proceed". Command(resume=...) is NOT used:
278-
# it targets dynamic interrupt() calls, does not clear static
279-
# interrupt_before pauses in this version (the graph re-pauses at
280-
# the same node), and DocumentWriter.invoke cannot be used for
281-
# resumes either because its .get("draft") crashes on the None
282-
# return (kiroku_app.py:212). Resuming with None plus an empty
283-
# instruction fabricates no human feedback.
284-
resume_config = {
285-
"configurable": {
286-
"thread_id": str(writer.get_thread_id()),
287-
"instruction": "",
288-
}
289-
}
290-
writer.graph.invoke(None, resume_config)
291-
resumed_values = writer.graph.get_state(resume_config).values or {}
322+
writer.graph.invoke(None, run_config)
323+
resumed_values = writer.graph.get_state(run_config).values or {}
292324
resumed_draft = resumed_values.get("draft", "")
293325
last_draft = (
294326
resumed_draft.strip() if isinstance(resumed_draft, str) else ""
@@ -298,13 +330,7 @@ def _run_until_complete(self, writer, initial_state, config):
298330
"Kiroku did not complete after resuming native manual-review interrupts"
299331
)
300332

301-
final_config = {
302-
"configurable": {
303-
"thread_id": str(writer.get_thread_id()),
304-
"instruction": "",
305-
}
306-
}
307-
final_state = writer.graph.get_state(final_config).values or {}
333+
final_state = writer.graph.get_state(run_config).values or {}
308334
if not final_state and isinstance(writer.state, Mapping):
309335
final_state = writer.state
310336
return final_state, last_draft
@@ -314,32 +340,38 @@ def invoke(self, value, config=None):
314340
315341
Args:
316342
value: Plain-text request or exactly {"message": text}.
317-
config: Accepted for interface compatibility, but the native
318-
public DocumentWriter.invoke wraps only an inner configurable
319-
mapping, so incoming RunnableConfig extras (callbacks, tags,
320-
metadata, supplied configurable values) cannot be forwarded
321-
through the native API. The binding supplies exactly
322-
{"instruction": ""} and lets the writer pin its own
323-
thread_id; config propagation is limited by the native API.
343+
config: Optional RunnableConfig. Callbacks, tags, metadata and
344+
configurable.thread_id are honored and forwarded to the
345+
graph (see _resolve_run_config); the binding only adds the
346+
empty native ``instruction`` and a fallback thread key when
347+
none is supplied. Reusing ABB's thread_id reuses the same
348+
native writer and checkpoint instead of silently starting
349+
from a blank thread.
324350
325351
Returns:
326-
Mapping with `draft`, `title`, and `document_specification`.
327-
`draft` is the native final document text extracted from state.
352+
Mapping with `draft`, `title`, `document_specification` and
353+
`native_state`. `draft` is the native final document text
354+
extracted from state.
328355
329356
Raises:
330357
ValueError: Invalid input shape.
331358
RuntimeError: Missing Tavily key, closed binding, or failure to
332359
complete the native workflow.
333360
Native execution errors propagate unchanged.
334361
"""
335-
if self._thread_id < 0:
362+
if self._closed:
336363
raise RuntimeError("Kiroku bridge is closed")
337364

338365
text = message_from_input(value)
339366
specification = specification_from_text(text)
340-
writer = self._create_writer(specification)
367+
thread_key, run_config = self._resolve_run_config(config)
368+
writer = self._writers.get(thread_key)
369+
if writer is None:
370+
writer = self._create_writer(specification)
371+
writer.set_thread_id(thread_key)
372+
self._writers[thread_key] = writer
341373
initial_state = initial_state_from_specification(specification)
342-
final_state, last_draft = self._run_until_complete(writer, initial_state, config)
374+
final_state, last_draft = self._run_until_complete(writer, initial_state, run_config)
343375

344376
return {
345377
"draft": last_draft or draft_from_state(final_state),
@@ -351,11 +383,12 @@ def invoke(self, value, config=None):
351383
def close(self):
352384
"""Mark this binding closed; later invoke calls raise RuntimeError.
353385
354-
Writers are created per invocation, so there is no persistent native
355-
resource to release. No external files, databases or subprocesses are
356-
created by this binding. Cleanup is safe to repeat.
386+
Drops the per-thread native writers (the only state this bridge
387+
holds). No external files, databases or subprocesses are created by
388+
this binding. Cleanup is safe to repeat.
357389
"""
358-
self._thread_id = -1
390+
self._writers = {}
391+
self._closed = True
359392

360393

361394
def create_graph():
File renamed without changes.
File renamed without changes.

resources/agents/50-kiroku/ground_truth/observations/defect-1/case.json renamed to resources/agents/51-kiroku/ground_truth/observations/defect-1/case.json

File renamed without changes.

resources/agents/50-kiroku/ground_truth/observations/defect-1/run-1.zip renamed to resources/agents/51-kiroku/ground_truth/observations/defect-1/run-1.zip

File renamed without changes.

resources/agents/50-kiroku/ground_truth/observations/defect-1/run-2.zip renamed to resources/agents/51-kiroku/ground_truth/observations/defect-1/run-2.zip

File renamed without changes.
Lines changed: 5 additions & 5 deletions

0 commit comments

Comments
 (0)