Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 10 additions & 0 deletions submissions/NxtGen/DRAFT-feedback-not-sent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Draft product feedback (NOT sent — awaiting explicit go-ahead)

Candidate command:
```bash
mutagent feedback send "Ran the full optimize loop (spec/build/evaluate/diagnose/optimize) against a real production system prompt across 5 iterations and 2 loop runs. The workflow itself held up well end-to-end. Two friction points worth flagging: (1) the interactive *optimize runtime in the docs describes the loop-state-cli as returning a cursor to 'overwrite loop-state.json with' but doesn't mention that record-iteration's --budget-ms expects CUMULATIVE elapsed time, not the per-call delta — easy to accidentally pass the full config budget ceiling and spuriously trip the budget terminator; a clearer parameter name (--cumulative-budget-ms) or an accumulate-for-me mode would prevent that. (2) tier-0/code-check criteria are extremely valuable but there's no built-in guard against a check passing on a single stochastic LLM sample when the real defect only manifests probabilistically (we found a 2-of-3 defect that a 1-shot re-eval would have missed) — a --repeat N flag on the eval runner, gating the criterion on the WORST repetition rather than one sample, would make optimize's convergence claims meaningfully stronger." --category stage:optimize --json
```

Rationale for holding off: sending this posts data to your Mutagent account/platform, which is a
different kind of action than writing local files — worth a separate, explicit go-ahead per our
agreement, same as the PR itself.
172 changes: 172 additions & 0 deletions submissions/NxtGen/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
# NxtGen — VoxAgent's Prompt-to-Blueprint Planner

**Subject:** `backend/app/services/planner.py` + the schema-alignment layer it depends on in
`backend/app/services/composio_engine.py`, from **VoxAgent** — a real, shipping FastAPI + React
AI-automation product. The planner turns a natural-language automation request into a
schema-valid `WorkflowBlueprint` routed across `composio_api` / `browser_agent` / `http_webhook` /
`telegram_client` / `ai_generate`.

This is not a toy demo. Every fix below is a real `git commit` against a real product's real
system prompt, verified with real Gemini API calls, gated behind a human approval before landing.

## What we ran

The full ADL loop, conducted via Helix, on this one real subject:

```
*spec → *build → *evaluate → *diagnose → *optimize (repeated 5x) → package
① ② ③ ④ ⑤
```

1. **`*spec`** — reverse-derived `agentspec.yaml` from the existing implementation (this is a
brownfield subject, not greenfield): 9 binary evaluation criteria, 8 scenario types, the full
verbatim system prompt.
2. **`*build`** — verified the spec against the real implementation (`checks/verify_build_alignment.py`,
13 hermetic AST-based checks, no network/API key needed) — see `build-report.md`.
3. **Dataset** — 24 real, deliberately adversarial prompts across ambiguity, cross-tool schema
traps, multi-step handoff, fan-out, reactive triggers, and browser-context scenarios
(`traces/eval-dataset-candidates.json`).
4. **`*evaluate`** — baseline scorecard from 24 real Gemini calls (`traces/baseline-traces.jsonl`).
5. **`*diagnose`** — three `diagnostics-analyzer` leaves independently root-caused every failure
on real evidence (WHAT/WHY/WHERE taxonomy, file:line citations) — see `diagnose-findings/`.
6. **`*optimize`** — 5 iterations across 2 bounded, worktree-isolated loop runs, each with its own
confirm-once-at-entry / one-apply-gate-at-convergence discipline. Full ledger below.

## The real product defects found and fixed

| # | Defect | Severity | Fix | Commit |
|---|---|---|---|---|
| 1 | **Credential leak path.** The planner asked the user for raw `username`/`password` as blueprint parameters for `browser_agent` steps. VoxAgent actually resolves browser-session credentials from an encrypted App Vault — no code path reads a step parameter as a login credential. If a user ever answered the bogus clarification, the password was string-joined verbatim into the live Gemini browser-task prompt and echoed into telemetry. | **High — security** | New Rule 7: credentials are out-of-band, never a parameter. | `69d238f` |
| 2 | **Invented-value contradiction.** The planner would flag a parameter as `missing_parameters` (asking the user to clarify it) while *also* inventing a plausible literal for that same key in the step body — e.g. listing `table_name` as missing while writing `"table_name": "Invoices"` into the step. If the clarification is ever skipped or auto-answered, the invented value silently executes. | **Medium — correctness** | Rule 2 amended: a flagged-missing key must be absent from `parameters` entirely. | `69d238f` |
| 3 | **Fan-out over-flagging.** A tightening of Rule 5's "constant literal" carve-out so a value the user actually named (e.g. an Airtable base name) isn't wrongly treated as missing, while a genuinely-unnamed sub-resource (the table inside it) still is. | Medium | Rule 5 amended with a worked example. | `71fb7b1` |
| 4 | **Generalization gap in fix #2.** Post-promotion live verification found the fix for defect #2 didn't generalize past Airtable/fan-out shapes — a plain two-step handoff (`http_webhook` → `composio_api`) still invented a destination name (`"spreadsheet_name": "Orders"`) 2 of 3 runs. Root-caused to competing "keep it a concrete literal" licenses in Rules 3 and 5 with no third bucket for a flagged-missing parameter. | Medium | Rule 2 given a second, non-Airtable worked example + a pre-emit consistency check. | `28320b1` |

**Primary target metric** (`no-guessed-required-param`, the criterion covering defects #2 and #4):
**38% → 100%** pass rate on the real 24-trace dataset, corrected-harness, apples-to-apples
(see Scorecard below). Zero credential-shaped clarification requests remain (defect #1, verified
across `bpt-01/02/03`).

## The eval harness itself had real bugs — we fixed those too

Two of the original tier-0 code checks were themselves wrong, independently caught by
`diagnostics-analyzer` in Phase 5 and confirmed empirically before shipping:

- **`no-guessed-required-param`** inferred "should this need clarification" from the *scenario
label* instead of the prompt content, and never implemented the criterion's own second clause
(no invented value for a flagged-missing key). Fixed: scenario-label gating replaced with
content-grounded logic + the missing clause implemented.
- **`sheet-header-safety`** gated on *app name* (any Notion step needs `headers`), when the real
failure mode (a positional row-add tool silently eating the first row as headers) is
structurally impossible for a key-addressed write (Notion `properties`, a standalone page).
Fixed: gated on write *shape* (action verb + payload structure) instead.

Both fixes are in `tier0_code_checks.py`; see `diagnose-finding-1.json` / `diagnose-finding-2.json`
for the full root-cause analysis and drafted replacement code, applied and verified here.

## Two things we investigated and deliberately did NOT fix — and why that's the right call

A genuinely closed loop means treating every score movement as a hypothesis to verify, not a
number to chase. Two more loop iterations were run against real regressions found during
post-optimize verification; both were root-caused, and both were **stood down** rather than
patched, on evidence:

- **`sheet-header-safety` dropped to 67%** (`srw-02`, `ctfm-03` lost their `headers` parameter).
Root-caused (`diagnose-finding-6-headers.json`) to a real prompt-competition effect from fix #4's
own worked example. We then checked whether `headers` does anything at execution time —
**it doesn't.** `grep -rn "headers"` across the entire backend shows it is never read by
`composio_engine.py` or `orchestrator.py` for a spreadsheet/table write; it passes through as an
unmatched key straight to the real API call. The actual mitigation for "first row eaten as
headers" is a different, already-correct mechanism (`orchestrator.py`'s single-call batching in
`_run_for_each_step`). Patching the prompt to satisfy a criterion with no real product effect
would only add more prompt-surface for exactly the kind of side-effect that caused this
investigation in the first place. **Not applied** — documented as a criterion-vs-reality gap for
a future pass to either implement real header-consumption or retire the requirement.
- **`no-opaque-id-asked` dropped to 78%** (`amb-01`, `msh-03` now use `parameter_key: "channel_id"` /
`"parent_page_id"` instead of a name-shaped key, even though their user-facing `label`/
`description` stayed correctly human-friendly). We traced the actual ID-resolution code
(`composio_engine.py`'s `_auto_resolve_missing_ids`) and confirmed it resolves IDs from the real
Composio schema's required field plus *any* plain-name value elsewhere in `parameters` — it never
trusts the planner's own `parameter_key` string. So this has no functional consequence today,
even though it's a real, literal violation of the criterion as written (and of Rule 2's own
internal-naming convention). **Not applied** in this pass — flagged for a small, low-risk
follow-up (a worked example on `parameter_key` naming specifically, mirroring what fixed defect
#4) rather than another prompt round under time pressure.
- **`correct-route-classification` dropped to 96%** (`bpt-03`'s final step routes to
`telegram_client` instead of Rule 1's default `http_webhook`/Vault Notes, deterministic 3/3).
Rule 1 was never touched by any of the four applied fixes — this is holistic prompt-context
drift from the *other* additions, not something any specific commit caused. Genuinely debatable
either way from a product standpoint ("just tell me the fine amount" arguably reads as "message
me" as much as "log it passively"). Documented, not chased.

## Scorecard — before / after, apples-to-apples

Both runs use the **same corrected harness** and **fresh LLM judging** (post-optimize traces were
independently re-judged by 4 parallel `evaluator` dispatches, not carried forward from baseline)
— see `scorecard-final.md` for the full table, `scorecard-corrected-baseline.json` /
`scorecard-post-optimize.json` for the raw per-trace data, and `scorecard.json`/`scorecard.md` for
the original (uncorrected) Phase 4 baseline as first reported, for full transparency about what
the harness fix changed.

| Criterion | Baseline pass% | Post-optimize pass% |
|---|---|---|
| **no-guessed-required-param** (the primary target) | **38%** | **100%** |
| no-opaque-id-asked | 100% | 78% (investigated, not applied — see above) |
| correct-route-classification | 100% | 96% (investigated, unrelated to fixes — see above) |
| schema-aligned-execution | n/a (0 applicable — needs execution-trace capture, out of scope) | n/a |
| step-handoff-placeholder | 100% | 100% |
| fan-out-shape | 100% | 100% |
| sheet-header-safety | 100% | 67% (investigated, confirmed non-functional — not applied, see above) |
| event-trigger-modeling | 100% | 100% |
| browser-context-sufficiency | 100% | 100% |

The run-level GATE is `fail` on both sides (any single criterion failure gates the whole run) —
we're reporting that plainly rather than only the metric that improved. The honest story is: one
real, security-relevant defect and its generalization gap are fixed and verified; three secondary
criteria moved in the other direction, all three individually investigated, and none of them
represent an unexamined regression.

## The optimize loop ledger (genuinely repeated, not rubber-stamped)

| Run | Iter | Goal | Verify | Gate | Result |
|---|---|---|---|---|---|
| pass1 | 1 | Rule 7 (credentials) | PROCEED | PASS | Converged, promoted → `69d238f` |
| pass1 | 2 | Rule 2 (dup-value) | PROCEED | FAIL | fan-01 residual found, routed to diagnose |
| pass1 | 3 | Rule 5 (fan-out tightening) | PROCEED | PASS | Converged, promoted → `71fb7b1` |
| pass2 | 1 | Rule 2 generalization (amb-02) | PROCEED | PASS | Converged, promoted → `28320b1` |
| pass3 | — | Headers regression (srw-02/ctfm-03) | — | — | **Investigated, stood down** — confirmed non-functional, not applied |

Every real-file write went through: isolated `git worktree` → `ai-engineer` apply → `ai-architect`
verify → real re-eval swing → `loop-state-cli` record + terminator check → **one human apply-gate**
before touching the actual checked-out `planner.py`. No write ever landed without that gate.

## Reproducing this

```bash
cd backend && source .venv/bin/activate # needs GEMINI_API_KEY in backend/.env

# Re-run the full 24-item dataset through the current planner
python3 ../.mutagent/specs/voxagent-planner/run_dataset_through_planner.py

# Re-run the (corrected) tier-0 code checks
cd ../.mutagent/specs/voxagent-planner
python3 tier0_code_checks.py
python3 aggregate_scorecard_final.py
```

Spot-check the primary fix directly:
```python
from app.services.planner import generate_blueprint
bp = generate_blueprint("Grab the latest orders from our internal API and drop them into one of my spreadsheets")
# spreadsheet_name should appear ONLY in missing_parameters, never in steps[1].parameters
```

## What persists in VoxAgent itself, independent of this submission

- A real, standing regression suite (`traces/eval-dataset-candidates.json` + the harness scripts)
that can be re-run against any future `planner.py` change.
- Four real commits improving the actual shipped product's security posture and correctness.
- Two eval-harness bug fixes that make the suite's own signal trustworthy going forward.

## Team

**NxtGen**
Loading