Skip to content

feat: observation criteria with taxonomy and run/iteration subject (#1156) - #1206

Draft
Cedric Vidal (cedricvidal) wants to merge 18 commits into
cedricvidal-microsoft/pp-dag-orchestrationfrom
cedricvidal-microsoft/observation-criteria-pp-taxonomy
Draft

Cedric Vidal (cedricvidal) wants to merge 18 commits into
cedricvidal-microsoft/pp-dag-orchestrationfrom
cedricvidal-microsoft/observation-criteria-pp-taxonomy

Conversation

@cedricvidal

Copy link
Copy Markdown
Collaborator

Original author: Cedric Vidal (@cedricvidal)

Why

Issue #1156 asked for per-iteration trajectory observations: a way to record what a coding agent did during a run without steering it. This PR generalizes that into observation criteria, reusing the existing criteria entity (with optional dependency support) instead of introducing a new entity.

Stacked on #1147 (DAG-based post-processing pipeline, base main). Until #1147 merges, this PR's diff includes that foundation. The work unique to this branch starts at commit fc262735 (feat(observations): ...) and the 7 #1156 subject commits on top.

What changed

1. Observation criteria (kind: "gate" | "observation", default gate)
Gates steer the agent's session via pass/fail feedback in the gate DAG. Observations are record-only: they produce a boolean + evidence per run and never feed back to the agent. Observations are excluded from validateGateConfigs, gate-satisfaction, and feedback selection. Migration 021 backfills existing criteria to gate (mirrors the gates backfill).

2. Static taxonomy classification (taxonomyElementId?)
Optional design-time classification against a hard-coded enum of namespaced element IDs (e.g. dimension:idiomatic-use), validated on write. No first-class taxonomy entity yet — namespaced string IDs keep a future migration clean.

3. Criterion subject (subject: "run" | "iteration")
Controls evaluation granularity and where results are stored:

  • run — evaluated once over the whole merged run trajectory, stored in run.observationResults
  • iteration — evaluated per turn in isolation, stored in turn.observationResults

The criteria AI assistant auto-detects the subject; the user can override it in the portal/CLI; the judge honors it. Migration 022 backfills subject.

4. Whole-run trajectory in the judge
The judge merges the agent trajectory from atifUrls[] (all turns) so run-subject observations can see cross-iteration behavior a per-turn evaluation cannot (e.g. "scaffolded with Vite in iteration 1, ran tests in iteration 2"). Trajectory navigation was unified into one source-agnostic tool set, with ATIF tool listing/detail lookups and large-output spill-to-file for huge tool outputs.

5. pp-taxonomy post-processing handler
A new post-processor handler evaluates observation criteria via the judge as part of the handler DAG, splitting iteration- vs run-subject criteria and writing each result to the right place.

6. Portal + CLI
Portal: observation kind/taxonomy UI, a subject toggle, a submit-run observation picker, a Taxonomy tab (with an Observations subsection) on run detail, an Enrichment badge aggregated over the handler DAG, and copy clarifying that gates steer the agent's discussion while observations do not. CLI reaches parity with a --subject flag and whole-run observation support.

Testing

Unit + integration tests added across shared, api, judge, scheduler, post-processor, and portal, plus a live ATIF-trajectory observation acceptance test. End-to-end validation against the shared CosmosDB dev stack (import a real integration run, seed run- and iteration-subject observations, assert the run-subject criterion sees cross-iteration behavior and does not leak into per-turn results) is in progress.

Non-goals

No first-class taxonomy entity (deferred), single element per criterion (no multi-lens list), no taxonomy versioning, booleans only (no numeric scores — fractions emerge by aggregation across runs), and no per-run reclassification.

Fixes: #1156

…config, docs

Add kind:gate|observation discriminator + hard-coded taxonomyElementId to
criteria; record-only per-iteration boolean observations evaluated post-run by
the pp-taxonomy handler (reuses judge via JudgeClient, dependsOn pp-atif) and
stored as turn.observationResults. Request-level opt-in via observations[].
CLI, migration 021, deploy wiring, config seeds, and docs included.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…bservations tab

Carry criterion kind + taxonomyElementId, request observations, and
turn.observationResults through the portal: kind selector + taxonomy picker on
criteria pages, observations multi-select on Submit Run, and a Run-detail
Observations tab grouped by dimension. Includes Storybook stories.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Proves the judge consumes atifUrl: a trajectory-only observation
(dependency_added_then_removed) resolves passed:true WITH the synthetic
agent_trajectory_atif tool call and passed:false WITHOUT it. Excluded from
default vitest; runs via vitest.integration.config.ts with a Copilot token.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…nomy registers

The Dockerfile copied only handler.yaml, so the register-handler-taxonomy
job (HANDLER_YAML_PATH=apps/workers/post-processor/handler.taxonomy.yaml)
failed with ENOENT in the container, exit 1, gating the post-processor
(hard depends_on service_completed_successfully). Use a handler*.yaml glob
in both builder and prod stages so every handler manifest is baked in.

Verified in compose: register-handler-taxonomy now exits 0 ("Registered
pp-taxonomy successfully") and the post-processor loads both the atif and
taxonomy handlers.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…aches judge

The post-processor service used the *common-env anchor, which omits
JUDGE_SERVICE_URL (only *worker-env defines it). pp-atif never called the
judge so the gap was invisible, but pp-taxonomy (issue #1156) evaluates
observation criteria via JudgeClient and fell back to localhost:3002 inside
the container. Set JUDGE_SERVICE_URL=http://judge:80 explicitly.

k8s is unaffected: post-processor.yaml already pulls JUDGE_SERVICE_URL from
the worker-config ConfigMap via envFrom.

Verified in compose: the containerized post-processor reached the judge
container, which loaded the ATIF and wrote run.turns[].observationResults
(dependency_added_then_removed -> passed:true).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… vs k8s

In k8s the post-processor inherits JUDGE_SERVICE_URL from the worker-config
ConfigMap via envFrom; in docker-compose the service uses the common-env
anchor (not worker-env), so JUDGE_SERVICE_URL is set explicitly on the
post-processor service. Reflects the compose fix in 3f2e98cc.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…not run.observations

The API submit path persists the selected observation-criteria ids at the
request document top-level (RequestDocument.observations, types.ts), but the
TaxonomyHandler read them from doc.run.observations. They never met, so
pp-taxonomy no-op'd on every real submitted run and never wrote
turn.observationResults.

Read doc.observations (top-level) instead; turns still come from doc.run.turns.
Rename the local RunDoc interface to RequestDoc and document the placement.

Update the handler unit tests to put observations at the canonical top-level,
and add a regression test asserting that legacy run.observations is ignored
(no-op), which would have caught this.

Verified end-to-end on a real submitted run through the containerized stack:
turn.observationResults is written with passed:true and feedback citing the
mid-run install/uninstall trajectory (the judge consumed atifUrl).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ervations subsection

The run-detail tab is now labeled "Taxonomy" and its content nests an
"Observations" sub-section (heading + description) around the per-dimension
ObservationResultsPanel, so the tab can host the broader taxonomy view with
observations as one section within it. The internal tab value stays
"observations" to preserve routing/state.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ervations tab URL to taxonomy

The Enrichment badge read the legacy single `run.postProcessorStatus`
scalar, which every post-processor handler overwrites on its own success.
It therefore flipped to "Enriched" as soon as pp-atif finished, while
pp-taxonomy was still computing observations.

Derive an aggregate enrichment status from `run.handlerStatus` (the #1147
per-handler DAG state): stay "Enriching…" until every handler is terminal,
treating pp-taxonomy as pending whenever observations are configured (which
also covers the gap before the scheduler dispatches it). Use the derived
status for both badge placements and the detail poller so taxonomy results
auto-appear. Falls back to the legacy scalar for pre-DAG runs.

Also rename the run-detail tab slug "observations" -> "taxonomy" so the URL
reads /runs/:id/taxonomy, with an in-place redirect for legacy
/observations deep-links.

Proven live on the containerized stack: a real run held "Enriching…" from
pp-atif done (legacy scalar already "done") through pp-taxonomy queued, and
flipped to "Enriched" only once pp-taxonomy completed ~32s later.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… set (#1156 P10)

Fixes a non-deterministic false negative on trajectory observations (e.g.
`dependency_added_then_removed`): pp-taxonomy fed the judge the entire ~86 KB
ATIF as ONE opaque synthetic tool call, so `read_tool_outputs`' 2000-char
preview only ever showed the ~26 KB system prompt and the mid-run npm calls
were buried tens of KB deep — the LLM found them only by luck.

Replace the opaque blob + the two legacy tools (`read_tool_outputs`/
`get_tool_output`) with ONE normalized `AgentTrajectory` model and a unified,
source-agnostic tool set used by BOTH gates (HAR `ToolCall[]`) and observations
(parsed ATIF):

- new `agent-trajectory.ts`: `fromToolCalls`/`fromAtif` normalizers + five
  `defineTool`s — two symmetric pairs (definitions: `list_agent_tools`/
  `get_agent_tools`; calls: `list_agent_tool_calls`/`get_agent_tool_calls`)
  plus `get_trajectory_overview`. Outputs larger than INLINE_LIMIT (16 KB)
  spill to a per-evaluation temp dir created OUTSIDE the workspace and are
  returned as an absolute `responseFile`/`descriptionFile` the judge reads with
  `read_file`.
- `index.ts`: build the trajectory at the `/evaluate` boundary (ATIF wins as
  superset; tool-calls otherwise); non-fatal on load failure.
- `judge-strategies.ts`: thread `trajectory` through both strategies; extend
  `createFileTools` with `extraReadRoots` (read_file/file_exists may read the
  out-of-workspace spill dir; list_directory/search_files stay workspace-only);
  create+cleanup the spill dir in `runCopilotSession`; swap TOOL_OUTPUTS_GUIDANCE
  → TRAJECTORY_GUIDANCE. Gate-parity guard preserved (`trajectory.calls.length > 0`).
- `judge-agent.ts`: `EvaluationInput.toolCalls` → `trajectory?`.

Tests: new `agent-trajectory.test.ts` (normalizers, 5 tools, out-of-workspace
spill, strict-superset-over-HAR contract); `judge-strategies.test.ts`
(isWithinAnyRoot + extraReadRoots + new guidance/tool names);
`observation-trajectory.integration.test.ts` rewritten to pass `fromAtif(...)`;
`judge-evaluation.integration.test.ts` extended with a gate clear-PASS/clear-FAIL
regression guard built from `fromToolCalls(...)`.

Verified live on the containerized stack: the same observation that flipped
non-deterministically now resolves passed:true 5/5 WITH the ATIF and false 3/3
WITHOUT it, citing `npm install left-pad` / `npm uninstall left-pad`.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…vationResults (#1156)

Introduce a kind-dependent `subject` discriminator on criteria so observation
criteria can be evaluated either per-iteration (today) or once over the whole
run. Gate criteria are always `iteration`. Adds:

- CriterionSubject type + CRITERION_SUBJECTS + isCriterionSubject guard
- subject on CriteriaConfig; run-level observationResults on RunState
- zod schemas (optional, no flat default); kind-dependent default applied in
  the store (observation -> run, gate/legacy -> iteration)
- subject threaded through criteria-store create/update/find + both providers
- atifUrls[] on JudgeEvaluateRequest for merged whole-run trajectory
- migration 022 backfills subject kind-aware (gate/legacy -> iteration,
  observation -> run)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- routes/criteria: reject subject:run for gate criteria (400); allow run|
  iteration for observations on create + update
- llm: authoring assistant returns suggestedSubject; heuristic maps cross-
  iteration phrasing -> run, end-state/per-step -> iteration; defaults run for
  observations when the model is unavailable
- endpoints test: subject create + run-subject-gate rejection
- regenerated OpenAPI snapshot (subject enum + run-level observationResults)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- agent-trajectory: mergeTrajectories(list) concatenates calls in iteration
  order and unions tool definitions by id; fromAtifList helper
- index: accept atifUrls[]; download all + merge into the single
  AgentTrajectory (single atifUrl path unchanged)
- integration test: run-subject dependency_added_then_removed is false per
  iteration but true on the merged trajectory (GITHUB_TOKEN-gated)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…on (#1156)

- taxonomy-handler: resolve selected observations with their subject; split
  into iteration vs run buckets. Iteration-subject -> per-turn
  observationResults (unchanged). Run-subject -> evaluate once (final snapshot
  + every turn's atifUrl merged + full conversationHistory) -> run-level
  run.observationResults. Runs once because the handler runs once per run (the
  scheduler never sets message.iteration), so no idempotency guard needed.
- HandlerContext gains criteriaCollection so handlers can resolve criterion
  metadata by id; wired in post-processor + atif-handler test contexts

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…#1156)

- wizard: Run/Iteration toggle (shown for observations, prefilled from AI
  suggestedSubject, user-overridable); subject in create payload + reset
- kind copy reworded to convey STEERING vs record-only: gate steers the agent
  (pass/fail feedback shapes its next iteration), observation records evidence
  never shown to the agent — in both the wizard and the inline editor
- RunDetail Taxonomy tab: new Whole-run observations subsection (from
  run.observationResults) above the per-iteration matrix, grouped by dimension
- CriteriaSubjectBadge on list + detail; subject in types + api payloads
- Storybook: subject-badge story

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…1156)

- yaml-mappers: round-trip subject
- criteria command: --subject <run|iteration> on create/update; show subject
  in list/get/export/import
- run-get-action: render whole-run run.observationResults block alongside the
  per-iteration ones
- updated cli-commands snapshot for --subject

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Document the new subject discriminator, the kind-dependent default and gate
restriction, run-level run.observationResults storage, and the merged
whole-run trajectory evaluation across post-processing.md, app-design.md,
criteria-provider.md, and overview.md.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@manekinekko Wassim Chegham (manekinekko) added area: cli Scope command-line interface and terminal workflows. area: infrastructure Cloud resources, hosting, networking, and shared infrastructure. area: judge Evaluation engine, scoring, and criteria execution. area: portal Scope web portal, pages, and user-facing components. area: post-processing ATIF generation and the run-output post-processing pipeline. area: scheduler area: worker Coding agent worker runtime, task execution, and lifecycle. language: javascript Work involving JavaScript code, tooling, or dependencies. topic: testing Test coverage, test infrastructure, and validation quality. and removed author: cedricvidal labels Sep 25, 2026
@manekinekko Wassim Chegham (manekinekko) added topic: ui Portal, visual presentation, layout, and interface components. type: documentation Documentation additions, corrections, and improvements. type: enhancement New functionality or improvements to existing behavior. area: api Scope REST API, run orchestration, SSE streaming, and endpoints. area: shared Shared package: types, DB models, and queue/blob/redis clients used by all apps. labels Oct 1, 2026
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 247cbe24-e22c-4f4c-b3e4-e71ce03f4669
@github-actions github-actions Bot added the type: dependencies Dependency additions, removals, and version updates. label Oct 12, 2026
@cedricvidal
Cedric Vidal (cedricvidal) changed the base branch from main to cedricvidal-microsoft/pp-dag-orchestration October 12, 2026 16:24
@github-actions github-actions Bot added the topic: security Security protections, vulnerabilities, and risk reduction. label Oct 12, 2026
@cedricvidal
Cedric Vidal (cedricvidal) added this pull request to stack #1485 October 12, 2026 16:24
@github-actions

Copy link
Copy Markdown

Test Results (Node.js 22)

test: Run #241

Tests 📝 Passed ✅ Failed ❌ Skipped ⏭️ Pending ⏳ Other ❓ Flaky 🍂 Duration ⏱️
3365 3359 6 0 0 0 0 1m32s

❌ Some tests failed!

Name Failure Message
❌ API Endpoints > POST /api/v1/criteria > creates an observation criterion with a taxonomy element expected 400 to be 201 // Object.is equality
❌ API Endpoints > POST /api/v1/criteria > defaults an observation's subject to 'run' expected 400 to be 201 // Object.is equality
❌ API Endpoints > POST /api/v1/criteria > creates an observation with an explicit subject:'iteration' override expected 400 to be 201 // Object.is equality
❌ PostProcessor > re-processes stale versions and stamps success for the same run attempt Cannot read properties of undefined (reading 'collection')
❌ PostProcessor > claims a run left in the scheduler's 'queued' state and drives it to done Cannot read properties of undefined (reading 'collection')

See all failed tests here

Github Test Reporter

@cedricvidal
Cedric Vidal (cedricvidal) marked this pull request as draft October 14, 2026 16:01
@cedricvidal
Cedric Vidal (cedricvidal) removed this pull request from stack #1485 October 17, 2026 00:57
@cedricvidal
Cedric Vidal (cedricvidal) added this pull request to stack #1518 October 17, 2026 00:58
@github-actions

Copy link
Copy Markdown

Test Results (Node.js 22)

test: Run #282

Tests 📝 Passed ✅ Failed ❌ Skipped ⏭️ Pending ⏳ Other ❓ Flaky 🍂 Duration ⏱️
3367 3361 6 0 0 0 0 1m11s

❌ Some tests failed!

Name Failure Message
❌ API Endpoints > POST /api/v1/criteria > creates an observation criterion with a taxonomy element expected 400 to be 201 // Object.is equality
❌ API Endpoints > POST /api/v1/criteria > defaults an observation's subject to 'run' expected 400 to be 201 // Object.is equality
❌ API Endpoints > POST /api/v1/criteria > creates an observation with an explicit subject:'iteration' override expected 400 to be 201 // Object.is equality
❌ PostProcessor > re-processes stale versions and stamps success for the same run attempt Cannot read properties of undefined (reading 'collection')
❌ PostProcessor > claims a run left in the scheduler's 'queued' state and drives it to done Cannot read properties of undefined (reading 'collection')

See all failed tests here

Github Test Reporter

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Read-only review of a9e9dbd. Findings are inline; the symlink security observation is explicitly pre-existing, not a regression introduced by this PR.

} from "@/components/list-layout";
import { HelpTooltip } from "@/components/HelpTooltip";
import { CriteriaBadge } from "@/components/CriteriaBadge";
import { CriteriaKindBadge, CriteriaSubjectBadge } from "@/components/CriteriaBadge";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Keep the CriteriaBadge import

The ID column still renders <CriteriaBadge> at line 168, but this replacement removes its import. The Portal build fails with TS2552: Cannot find name 'CriteriaBadge'. Please retain CriteriaBadge alongside the two new badge imports.


const runObservationResults: CriterionResult[] = result.criteriaResults ?? [];

await ctx.collection.updateOne(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Scope observation reads and writes to the claimed run attempt

A request can be retried while this judge call is in flight because the coding run is already terminal. This update filters only by request ID, so the old attempt's result is then written into the replacement run. I reproduced this with the production handler by swapping run._id during judge.evaluate: the new pending attempt received the old attempt's observations. The per-turn write has the same issue when iteration numbers overlap.

The worker's initial claim and guarded terminal-status write do not protect these intermediate result writes. Please include message.runId in the initial read and every result-update filter, and handle a zero-match update without modifying the new attempt.

(mocks.criteriaCollection.findOne as any).mockResolvedValue(null);

const res = await request(app)
.post("/api/v1/criteria")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Supply projectId in the new observation endpoint tests

These new POSTs omit the required project query parameter. Consequently, the three happy-path tests return 400 instead of 201, and the new rejection tests return 400 for missing scope before they exercise taxonomy/subject validation. Running the endpoint suite produces three failures from these additions.

Please add ?projectId=${TEST_PROJECT_ID} to these requests and use the existing request(testServer()) helper so the tests reach the intended behavior without creating extra servers.

prompt: config.prompt,
dependsOn: config.dependsOn || [],
...(config.gates !== undefined && { gates: config.gates }),
kind: seedKind,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Wire the observation fields through the actual API seed path

This helper now preserves observation metadata, but POST /api/v1/criteria/seed does not call it: apps/api/src/routes/criteria.ts still performs an inline upsert whose $setOnInsert includes only the old fields. The shared input schema accepts kind, subject, and taxonomyElementId, then that route discards all three. I reproduced this by invoking the registered seed handler with a valid observation payload.

As a result, the updated CLI import/YAML mapper still creates legacy gate criteria, not observations. Please update the actual route to preserve and validate these fields (or route it through the store) and cover an import/seed round trip.

}
}

// Validate the optional observation-criteria selection (issue #1156).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Preserve observations in profile fan-out and resubmission paths

The profile-variation branch returns earlier, before this validation and the new persistence logic. Its request-document construction never includes observations, so a comparative submission with a valid observation selection returns 201 while every created run silently loses that selection. I reproduced this with the registered handler: both the base and variation requests omitted the field.

Please resolve/validate the shared selection before branching and include it on every fan-out document. The bulk-resubmit constructor later in this file also needs to preserve original.observations; it currently drops the selection as well.

Comment thread apps/portal/src/types.ts
prompt: string;
dependsOn?: string[];
gates?: GateId[];
kind?: CriterionKind;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Preserve the new observation metadata in Portal YAML exports

The existing Export actions in CriteriaList and CriterionDetail still call criterionToYaml in apps/portal/src/lib/criteria-export.ts, which emits only id, prompt, and depends_on. Exporting a criterion with these new fields therefore drops its observation kind, subject, and taxonomy before it even reaches an importer; a subsequent import defaults it to a gate. I confirmed this with the production exporter.

Please serialize the new metadata there, matching the updated CLI exporter, and add an observation round-trip test.

dependsOn: editDependsOn,
gates: editGates,
...(editKind === "gate" ? { gates: editGates } : {}),
kind: editKind,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Add an explicit way to clear an existing taxonomy classification

Choosing Unclassified sets this value to undefined, which JSON.stringify removes from the update body. The store interprets the missing field as no change and retains the previous taxonomy. Sending null is not an alternative because the update schema rejects it. I reproduced both behaviors with the store and schema.

Please define an explicit clear/unset value through the UI, API schema, and store, and translate it into $unset. Otherwise the new Unclassified option cannot persist, and a classified observation cannot be cleanly converted back to a gate either.

const statuses = Object.values(handlerStatus).map((h) => h?.status);
const taxonomyStatus = handlerStatus["pp-taxonomy"]?.status;
const taxonomyPending =
hasConfiguredObservations && taxonomyStatus !== "done" && taxonomyStatus !== "failed";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Treat taxonomy blocked by an ATIF failure as terminal

With observations configured and handlerStatus = { 'pp-atif': { status: 'failed' } }, taxonomyPending is true and this function returns processing before considering the failure. The scheduler deliberately never dispatches taxonomy after its dependency fails, so that pending state cannot resolve. The badge remains Enriching and RunDetail keeps polling indefinitely.

Please account for blocked descendants, as the scheduler's DAG-drain calculation does, rather than treating every absent taxonomy status as work still in flight.

Comment thread apps/api/src/llm.ts
return {
prompt: String(value.prompt).trim(),
suggestedId: sanitizeId(value.suggestedId),
suggestedSubject: sanitizeSubject(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Update the static prompt contract for suggestedSubject

The production parser now returns this field, but the committed authoring adapter contract still expects only prompt and suggestedId. The offline adapter suite fails at evaluations/static-prompts/adapter/adapter.test.ts:126 with the additional suggestedSubject: 'run' property.

Please update the corresponding contract and curated subject-classification cases alongside this prompt/parser change. The failing check is reproducible with pnpm --filter static-prompt-evals exec vitest run --config adapter/vitest.config.ts --configLoader runner.

const fullPath = isAbsolute(args.path)
? resolve(args.path)
: join(workspaceRoot, args.path);
if (!isWithinAnyRoot(readRoots, fullPath)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking security follow-up: pre-existing symlink escape (HIGH)

This containment check compares normalized path strings, but the later filesystem reads follow symlinks. A link located inside the workspace can therefore pass the check while its resolved target is outside every approved read root. The snapshot creation/extraction path preserves such links, so an agent-created evidence file can potentially expose judge-container files through evaluation feedback.

This is not introduced by this PR: the same bypass and archive behavior exist at merge-base 2ca370ba. The boundary bypass was reproduced using a harmless dependency symlink, and preservation was checked with an in-memory archive; no credentials were read, and production-image extraction or LLM-mediated disclosure was not exercised. Please track a separate hardening fix that resolves roots and targets with realpath (or rejects escaping symlinks), including directory-symlink cases.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: api Scope REST API, run orchestration, SSE streaming, and endpoints. area: cli Scope command-line interface and terminal workflows. area: infrastructure Cloud resources, hosting, networking, and shared infrastructure. area: judge Evaluation engine, scoring, and criteria execution. area: portal Scope web portal, pages, and user-facing components. area: post-processing ATIF generation and the run-output post-processing pipeline. area: scheduler area: shared Shared package: types, DB models, and queue/blob/redis clients used by all apps. area: worker Coding agent worker runtime, task execution, and lifecycle. language: javascript Work involving JavaScript code, tooling, or dependencies. topic: security Security protections, vulnerabilities, and risk reduction. topic: testing Test coverage, test infrastructure, and validation quality. topic: ui Portal, visual presentation, layout, and interface components. type: dependencies Dependency additions, removals, and version updates. type: documentation Documentation additions, corrections, and improvements. type: enhancement New functionality or improvements to existing behavior.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add trajectory features: per-iteration boolean observations over codebase and agent trajectory

2 participants