Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
ca0b5c8
feat(ide): coder::find-relevant navigation walk over judge::evaluate
andersonleal Sep 29, 2026
4df8234
fix(ide): address find-relevant s1 review
andersonleal Sep 29, 2026
5899826
feat(ide): tree-sitter units and judged excerpts for coder::find-rele…
andersonleal Sep 30, 2026
3202292
fix(ide): address find-relevant s2 review
andersonleal Sep 30, 2026
d90f7dd
feat(ide): follow-up, roles, relationship pass and answer cache for c…
andersonleal Sep 30, 2026
1e1cd78
fix(ide): address find-relevant s3 review
andersonleal Sep 30, 2026
84e06bc
feat(ide): Python passes for coder::find-relevant
andersonleal Sep 30, 2026
eac4224
fix(ide): address find-relevant s4 review
andersonleal Sep 30, 2026
7f96cbf
test(ide): e2e for coder::find-relevant with a fake judge
andersonleal Sep 30, 2026
1846af0
docs(ide): document coder::find-relevant
andersonleal Sep 30, 2026
1a64fa0
feat(ide/ui): Ask-the-judge mode in the Search tab
andersonleal Sep 30, 2026
a12513b
fix(ide): send find-relevant judge states as ordered JSON text
andersonleal Sep 30, 2026
e281a68
fix(ide/ui): address Ask-mode review findings
andersonleal Sep 30, 2026
982d37e
fix(ide): address find-relevant review findings
andersonleal Sep 30, 2026
07c32e9
fix(ide): keep top excerpts when find-relevant trims to the result cap
andersonleal Sep 30, 2026
8123b58
fix(ide/ui): drop text-search advice from the Ask summary
andersonleal Sep 30, 2026
fef21ca
feat(judge-typesafe): configurable request concurrency
andersonleal Sep 30, 2026
5249aa2
feat(ide): configurable judge slots for coder::find-relevant
andersonleal Sep 30, 2026
016dfde
feat(ide/ui): chat card for coder::find-relevant
andersonleal Sep 30, 2026
33b9bf8
feat(ide): judge token budget per coder::find-relevant ask
andersonleal Sep 30, 2026
4b90dbd
test(ide): split secret-looking find-relevant fixtures
andersonleal Sep 30, 2026
b7c73c8
fix(ide): bump rustls to 0.23.45 (RUSTSEC-2026-0285)
andersonleal Sep 30, 2026
c805b41
refactor(ide): drop find-relevant follow-up, relationship and test-bo…
andersonleal Oct 1, 2026
4e2ffdb
fix(ide): parse `&raw` identifiers in find-relevant Rust units (MOT-4…
andersonleal Oct 7, 2026
2caa6de
fix(ide): give find-relevant judge calls the ask's budget and key the…
andersonleal Oct 7, 2026
6017e8c
fix(ide): size find-relevant requests for small judge windows (MOT-4951)
andersonleal Oct 8, 2026
e880b40
fix(ide): gate the find-relevant walk root and egress like the walk i…
andersonleal Oct 8, 2026
675127e
fix(ide): give find-relevant results a reason, a hint and agent-ready…
andersonleal Oct 8, 2026
f1f16d5
fix(harness): keep the session judge on released calls and route agen…
andersonleal Oct 8, 2026
3498ccb
fix(ide): one find-relevant ask at a time, scoped and kept across vie…
andersonleal Oct 8, 2026
ff60f05
fix(ide): address find-relevant review (MOT-4951)
andersonleal Oct 8, 2026
a36ffbf
fix(ide): one walk-root gate for find-relevant asks and C211 suggesti…
andersonleal Oct 8, 2026
a81b1b9
fix(ide): bound judge calls, catch window cuts and share in-flight fi…
andersonleal Oct 8, 2026
48a9346
fix(ide): stable find-relevant reason keys and hints an agent can fol…
andersonleal Oct 8, 2026
1a3fd6b
fix(harness): turn step clears a caller's judge provider like resolve…
andersonleal Oct 8, 2026
436387d
fix(ide): Search tab asks its workspace, and people-facing find-relev…
andersonleal Oct 8, 2026
59f3ab2
fix(ide): address find-relevant round-2 review (MOT-4951)
andersonleal Oct 8, 2026
004adf9
fix(ide,harness): find-relevant e2e sends a workspace scope; box larg…
andersonleal Oct 8, 2026
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
4 changes: 3 additions & 1 deletion harness/prompts/default.txt
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,9 @@ served by the shell worker (no separate install). Confirm they are available wit
`engine::functions::list { prefix: "coder::" }`. Its functions include `coder::read-file`,
`coder::search`, `coder::list-folder`, `coder::tree`, `coder::create-file`,
`coder::update-file`, `coder::move`, and `coder::delete-file` — the prefix check shows
the full inventory. Use `coder::move` for renames and moves, never delete-then-recreate. Plain
the full inventory. For how/where/why questions, start with `coder::find-relevant` (pass
the component folder as `path`); use `coder::search` for exact symbols, strings or filenames.
Use `coder::move` for renames and moves, never delete-then-recreate. Plain
file browsing outside code work (like `shell::fs::ls`) is still fine. Fetch each contract
first, as always.

Expand Down
7 changes: 6 additions & 1 deletion harness/src/functions/function_resolve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,10 @@ pub async fn handle(
deps: &Deps,
req: FunctionResolveRequest,
) -> Result<FunctionResolveResponse, HarnessError> {
crate::deferred::resolve(deps, req).await
// A resolve runs outside the turn step: a released held call (e.g. an
// approved `coder::find-relevant`) and the result's reconciliation keep
// the session's judge instead of the hub default or the caller's.
let session_id = req.session_id.clone();
crate::judge::with_session_provider(deps, &session_id, crate::deferred::resolve(deps, req))
.await
}
11 changes: 11 additions & 0 deletions harness/src/functions/function_trigger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,20 @@ pub enum FunctionTriggerResponse {
Pending(TriggerPendingResponse),
}

/// A direct call runs outside the turn step, under the session's judge
/// provider as the step would be.
pub async fn handle(
deps: &Deps,
req: FunctionTriggerRequest,
) -> Result<FunctionTriggerResponse, HarnessError> {
let session_id = req.session_id.clone();
// Boxed: the pipeline's future is large for a caller's stack.
crate::judge::with_session_provider(deps, &session_id, Box::pin(trigger_call(deps, req))).await
}

async fn trigger_call(
deps: &Deps,
req: FunctionTriggerRequest,
) -> Result<FunctionTriggerResponse, HarnessError> {
let _guard = deps.locks.guard(&req.session_id).await;
super::delete_session_tree::ensure_live(deps, &req.session_id).await?;
Expand Down
17 changes: 8 additions & 9 deletions harness/src/functions/turn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,22 +63,21 @@ pub async fn handle(deps: &Deps, payload: TurnStepPayload) -> Result<TurnStepRes
if let Some(display_name) = subagent_display_name.as_deref() {
baggage.push(("iii.tag.display_name", display_name));
}
// The session's judge provider rides the turn's context: every judge call
// this turn causes (call reconciliation here, function search in the
// directory, browser::run) routes to it, and other sessions never see it.
if let Some(provider) = hints.judge_provider.as_deref() {
baggage.push((judge_contract::PROVIDER_BAGGAGE_KEY, provider));
}
// The explicit step span matters: the baggage only materializes as span
// attributes when a span STARTS inside this scope, and downstream workers
// may run older SDKs whose processors drop the newer keys. This span is
// ours, so the turn's trace always carries the tags — and the session::*
// / router client calls parent under it instead of dangling.
iii_helpers::observability::run_with_baggage(&baggage, async {
//
// The session's judge provider rides the turn's context: every judge call
// this turn causes (call reconciliation here, function search in the
// directory, browser::run) routes to it, and a session without one drops
// whatever provider the step's enqueuer carried.
let step = iii_helpers::observability::run_with_baggage(&baggage, async {
iii_helpers::observability::run_in_span("harness::turn step", None, || run(deps, payload))
.await
})
.await
});
crate::judge::with_provider(hints.judge_provider, step).await
}

/// Bounded in-place retry budget for step errors that mean a dependency is
Expand Down
38 changes: 38 additions & 0 deletions harness/src/judge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,44 @@ const SECRET_KEYS: [&str; 11] = [
/// failing judge never pauses another session's.
static PAUSED_UNTIL: Mutex<BTreeMap<String, i64>> = Mutex::new(BTreeMap::new());

/// Run `future` under `session_id`'s judge provider, as its turn step does:
/// work outside the step (a released held call, a direct
/// `harness::function::trigger`) keeps the session's judge.
pub(crate) async fn with_session_provider<T>(
deps: &Deps,
session_id: &str,
future: impl std::future::Future<Output = T>,
) -> T {
let provider = deps
.session()
.await
.turn_hints(session_id)
.await
.judge_provider;
with_provider(provider, future).await
}

/// Run `future` with `provider` as its only judge provider. `None` clears
/// any provider the caller's context carried, so a caller never picks the
/// judge a session call uses.
pub(crate) async fn with_provider<T>(
provider: Option<String>,
future: impl std::future::Future<Output = T>,
) -> T {
use iii_helpers::observability::opentelemetry::{
baggage::BaggageExt as _, trace::FutureExt as _, Context, KeyValue,
};
let context = Context::current();
let mut entries: Vec<KeyValue> = context
.baggage()
.iter()
.filter(|(key, _)| key.as_str() != judge_contract::PROVIDER_BAGGAGE_KEY)
.map(|(key, (value, _))| KeyValue::new(key.clone(), value.clone()))
.collect();
entries.extend(provider.map(|p| KeyValue::new(judge_contract::PROVIDER_BAGGAGE_KEY, p)));
future.with_context(context.with_baggage(entries)).await
}

/// The current turn's judge provider (`judge_contract::PROVIDER_BAGGAGE_KEY`
/// baggage stamped by the turn step), when set and well-formed.
pub(crate) fn current_provider() -> Option<String> {
Expand Down
1 change: 1 addition & 0 deletions harness/src/prompt/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,7 @@ fn coder_routing() {
assert!(out.contains(id), "missing {id}");
}
assert!(out.contains("the full inventory"));
assert!(out.contains("start with `coder::find-relevant`"));
assert!(out.contains("never delete-then-recreate"));
}

Expand Down
123 changes: 123 additions & 0 deletions harness/tests/delete_session_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ struct Store {
sessions: BTreeMap<String, Value>,
messages: BTreeMap<String, Vec<Value>>,
calls: Vec<(String, Value)>,
/// The W3C `baggage` header each call carried, in call order.
baggage: Vec<(String, Value)>,
fail: BTreeSet<String>,
fail_delete: Option<String>,
delete_reply: Option<Value>,
Expand Down Expand Up @@ -198,6 +200,9 @@ impl Stack {
let function = message["function_id"].as_str().unwrap();
let (result, code) = {
let mut store = state.lock().unwrap();
store
.baggage
.push((function.into(), message["baggage"].clone()));
let code = store
.codes
.get(function)
Expand Down Expand Up @@ -1561,6 +1566,124 @@ async fn a_released_call_keeps_the_boundary_its_holder_reviewed() {
assert_eq!(payload["fs_scope"]["boundary"], "workspace", "{payload}");
}

/// The `iii.judge.provider` baggage the call to `function_id` carried.
fn stamped_provider(stack: &Stack, function_id: &str) -> Option<String> {
let store = stack.store.lock().unwrap();
let (_, baggage) = store
.baggage
.iter()
.find(|(f, _)| f == function_id)
.expect("the call is dispatched");
baggage
.as_str()
.unwrap_or_default()
.split(',')
.find_map(|entry| entry.trim().strip_prefix("iii.judge.provider="))
.map(str::to_string)
}

/// Calls made outside the turn step (an approved held call, a direct
/// `harness::function::trigger`) keep the session's judge provider, and a
/// caller's own provider never reaches them: without it a
/// `coder::find-relevant` asks another judge than the session chose.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn calls_outside_the_turn_step_use_the_session_judge_provider() {
for provider in [Some("clef"), None] {
let stack = Stack::new("completed").await;
{
let mut store = stack.store.lock().unwrap();
if let Some(provider) = provider {
let meta = store.sessions.get_mut("child1").unwrap();
meta["metadata"]["judge_provider"] = json!(provider);
}
let mut turn = store.state("harness_turn", "child1");
turn["status"] = json!("awaiting_functions");
turn["options"]["functions"] = json!({"allow": ["coder::*"]});
turn["calls"] = json!({"held-1": {
"state": "pending",
"function_id": "coder::find-relevant",
"held_by": "gone::gate",
"held_arguments": {"query": "where are retries decided?"}
}});
store.put("harness_turn", "child1", turn);
}
// the caller's own context names another provider
let ambient = [("iii.judge.provider", "ambient")];
let resolved = iii_helpers::observability::run_with_baggage(
&ambient,
// Boxed: both handlers' futures inline overflow the default 2 MiB
// test-thread stack.
Box::pin(harness::functions::function_resolve::handle(
&stack.deps,
serde_json::from_value(json!({
"session_id": "child1",
"turn_id": "t_child1",
"function_call_id": "held-1",
"action": "execute"
}))
.unwrap(),
)),
)
.await
.unwrap();
assert!(resolved.resolved, "{provider:?}");
assert_eq!(
stamped_provider(&stack, "coder::find-relevant").as_deref(),
provider
);

iii_helpers::observability::run_with_baggage(
&ambient,
Box::pin(harness::functions::function_trigger::handle(
&stack.deps,
serde_json::from_value(json!({
"session_id": "child1",
"call": {
"id": "direct-1",
"function_id": "coder::search",
"arguments": {"query": "retries"}
}
}))
.unwrap(),
)),
)
.await
.unwrap();
assert_eq!(
stamped_provider(&stack, "coder::search").as_deref(),
provider
);
}
}

/// The turn step routes its calls to the session's judge provider, and a
/// step whose enqueuer carried a provider drops it for a session without
/// one: otherwise in-step calls and released held calls ask different judges.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn the_turn_step_uses_only_the_session_judge_provider() {
for provider in [Some("clef"), None] {
let stack = Stack::new("completed").await;
if let Some(provider) = provider {
let mut store = stack.store.lock().unwrap();
let meta = store.sessions.get_mut("child1").unwrap();
meta["metadata"]["judge_provider"] = json!(provider);
}
// Stops at the mock's context::assemble boundary.
let _ = iii_helpers::observability::run_with_baggage(
&[("iii.judge.provider", "ambient")],
Box::pin(harness::functions::turn::handle(
&stack.deps,
step("child1"),
)),
)
.await;
assert_eq!(
stamped_provider(&stack, "context::assemble").as_deref(),
provider
);
}
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn ordinary_stop_persists_abort_on_router_failure_but_deletion_fails_closed() {
for code in ["test_error", "function_not_found"] {
Expand Down
Loading
Loading