feat: add guided exploration and share user-started MCP backend

This commit is contained in:
chenmingxuan committed 2026-10-16 18:32:46 +08:00
1 parent f86db1ced6
commit ee256ad7ce
144 files changed
+8625 -2075

No files matched your search

+53 -1
View File
@@ -245,6 +245,22 @@ async def execution_check_node(state: State, ctx: ArtemisContext):
async def exit_settlement_node(state: State, ctx: ArtemisContext):
from artemis.exploration.runtime import needs_exploration_turn
if needs_exploration_turn(ctx):
return {"exit_settlement_route": "continue"}
stop_capture = getattr(ctx, "on_execution_review", None)
if stop_capture:
await stop_capture()
update = await _exit_settlement_node(state, ctx)
if update.get("exit_settlement_route") == "continue":
resume_capture = getattr(ctx, "on_execution_resume", None)
if resume_capture:
await resume_capture()
return update
async def _exit_settlement_node(state: State, ctx: ArtemisContext):
"""Two-phase exit: settlement (unconditional) then final review (gated).
Phase 1 — settlement barrier: the only intentional blocking wait in the
@@ -262,6 +278,8 @@ async def exit_settlement_node(state: State, ctx: ArtemisContext):
if not ctx.data_engine:
return update
from artemis.exploration.runtime import needs_exploration_turn
# Phase 1: settle every outstanding attempt (bounded by settlement_timeout).
await settle_all_checkpoints(ctx, state)
# Queued-but-never-spawned checkpoints are dropped here: their items simply
@@ -310,7 +328,7 @@ async def exit_settlement_node(state: State, ctx: ArtemisContext):
)
try:
report = await asyncio.wait_for(
review = asyncio.wait_for(
run_final_check(
ctx,
goal=getattr(state, "initial_goal", ""),
@@ -321,6 +339,21 @@ async def exit_settlement_node(state: State, ctx: ArtemisContext):
),
timeout=checkpoint_timeout,
)
control = getattr(ctx, "task_control", None)
if getattr(ctx, "exploration_id", None) and control:
interrupted, report = await control.run_interruptibly(
review,
on_pause=getattr(ctx, "on_task_pause", None),
on_resume=getattr(ctx, "on_task_resume", None),
)
if interrupted or needs_exploration_turn(ctx):
ctx.final_check_attempts -= 1
return {"exit_settlement_route": "continue"}
else:
report = await review
if needs_exploration_turn(ctx):
ctx.final_check_attempts -= 1
return {"exit_settlement_route": "continue"}
case_result = report.test_result
except Exception as e:
# Fail-open: release, but the verdict value stays inconclusive.
@@ -447,6 +480,16 @@ async def exit_settlement_node(state: State, ctx: ArtemisContext):
route="end",
)
# A newly accepted instruction invalidates this review, even if it arrived
# after the model returned. Never let editorial work consume device guidance.
if getattr(ctx, "exploration_id", None):
from artemis.exploration.store import explorations
if needs_exploration_turn(ctx) or not explorations.seal_execution(
ctx.exploration_run_id, getattr(ctx, "exploration_guidance_count", 0)
):
return {"exit_settlement_route": "continue"}
# END path: assemble the machine-readable run outcome.
records = read_ledger(base_dir)
retired = getattr(ctx, "guidance_retired_checks", None)
@@ -913,6 +956,11 @@ def convergence_gate(
) -> Literal["continue", "exit_settlement", "end"]:
logger.info("Starting convergence_gate")
from artemis.exploration.runtime import needs_exploration_turn
if needs_exploration_turn(ctx):
return "continue"
if not ctx.data_engine:
return "end"
@@ -997,6 +1045,10 @@ async def get_graph(ctx: ArtemisContext) -> CompiledStateGraph:
]
native_tools = get_tools_from_wrappers(ctx, native_wrappers)
if getattr(ctx, "exploration_id", None):
from artemis.exploration.runtime import question_tool, history_tool
native_tools.extend([question_tool(ctx), history_tool(ctx)])
## Define nodes
graph_builder.add_node("planner", PlannerNode(ctx, tools=native_tools))