Skip to content

feat(solicitation-create): work-order-as-input + canonical labs schema + comprehensive content shape#396

Merged
jjackson merged 1 commit into
mainfrom
emdash/e2e-malaria-itn-app-k3rt6
May 22, 2026
Merged

feat(solicitation-create): work-order-as-input + canonical labs schema + comprehensive content shape#396
jjackson merged 1 commit into
mainfrom
emdash/e2e-malaria-itn-app-k3rt6

Conversation

@jjackson
Copy link
Copy Markdown
Owner

Summary

Three bundled rewrites of `skills/solicitation-create/SKILL.md` prompted by solicitation 3130 on `malaria-itn-app/20260521-1400` where the public page rendered blank Description, "TBD" timeline, "No deadline," Python-list-repr Scope, and zero questions / zero rubric simultaneously.

1. Work-order-as-primary-input

The skill now reads Phase 1's work order (`1-design/pdd-to-work-order.gdoc`) as the comprehensive content source, plus `decisions.yaml` for any later run-level decisions, alongside the PDD (now used only for problem-framing). The work order is the opinionated program brief; this skill transforms it into a public-facing solicitation — same comprehensive explanation, less prescriptive (rates → ranges, exact weeks → windows).

2. Canonical labs schema

Field names now match `solicitations/models.py`'s @Property accessors. Adding fields the template doesn't read silently does nothing (the labs API echoes any extra keys back without surfacing the drift). Migrated:

  • `description` (not `overview`)
  • `application_deadline` date string (not `response_window_days` int)
  • `expected_start_date` / `expected_end_date` (not `anticipated_*`)
  • `estimated_scale` (not `sample_target`)
  • `questions[].text` (not `response_questions[].question`)
  • `evaluation_criteria[].name/.scoring_guide/.linked_questions` (not `rubric[].dimension/.criterion`)
  • `solicitation_type: 'eoi'` lowercase

Removed top-level fields not read by the public-detail template (`pass_bar`, `eligibility_criteria`, `geographic_scope`, `per_hh_payment_band_usd`, `budget`) — their content folds into `description` / `scope_of_work` prose instead.

3. Comprehensive content shape

  • `description`: 500-800 words, foundation-pitch tone (problem framing → what this opp does → what the dataset enables). Not a one-paragraph summary.
  • `scope_of_work`: 600-1000+ words of structured markdown, derived section-by-section from the work order. Explicit de-prescription rules (exact dollars → ranges, exact weeks → windows). Must be a single markdown string, not an array.
  • Every question has a required `framing` field (1-2 sentence "why we're asking") + `text` (the actual prompt).
  • Every evaluation criterion has a required `scoring_guide` (concrete strong/mid/weak/zero signals) + `linked_questions` (at least one question id). Weights are integers summing to 100.

4. New Step 7a — public-page structural verifier

After publish, curl the public URL and grep for the rendered Description body, parseable Application Deadline, non-TBD Timeline, markdown-bulleted Scope, and the question + criterion count strings. Any miss → `[BLOCKER]` naming the probable field-name drift. Catches the silent-echo failure mode where the labs API accepts the create cleanly but the public page is empty.

Removal criteria

Keep until the labs MCP ships `create_solicitation_from_brief` (server-side composition via labs's existing `commcare_connect/ai/agents/solicitation_agent.py`). At that point this skill collapses to ~30 lines passing the structured brief.

Test plan

  • Next `/ace:run` against an opp publishes a solicitation whose public page renders all 5 sections cleanly.
  • `description` body is ≥300 words and reads as foundation-pitch, not procurement-form.
  • `scope_of_work` renders as markdown bullets / sub-headings on the public page (not Python repr).
  • Step 7a structural verifier halts cleanly with named-section diagnostic when a future drift recurs.

🤖 Generated with Claude Code

…a + comprehensive content shape

Three bundled rewrites prompted by solicitation 3130 on
malaria-itn-app/20260521-1400 where the public page rendered blank
Description, "TBD" timeline, "No deadline," Python-list-repr Scope,
and zero questions / zero rubric simultaneously.

1. Inputs now read Phase 1's work order (1-design/pdd-to-work-order.gdoc)
   as the primary content source + decisions.yaml for later run
   decisions, alongside the PDD (now used for problem-framing only,
   not for scope). The work order is the comprehensive, opinionated
   program brief; this skill transforms it into a public-facing
   solicitation: same comprehensive explanation, less prescriptive
   (rates → ranges, exact weeks → windows).

2. Field names migrated to the labs canonical schema (per
   solicitations/models.py @Property accessors):
   - description (not overview)
   - application_deadline date string (not response_window_days int)
   - expected_start_date / expected_end_date (not anticipated_*)
   - estimated_scale (not sample_target)
   - questions[].text (not response_questions[].question)
   - evaluation_criteria[].name/.scoring_guide/.linked_questions
     (not rubric[].dimension/.criterion)
   - solicitation_type: 'eoi' lowercase
   Top-level fields not read by the public-detail template
   (pass_bar, eligibility_criteria, geographic_scope,
   per_hh_payment_band_usd, budget) folded into description /
   scope_of_work prose instead.

3. Content shape demands comprehensive prose: description 500-800
   words foundation-pitch tone; scope_of_work 600-1000+ words derived
   section-by-section from the work order with explicit de-prescription
   rules; every question has a required framing field; every
   evaluation criterion has a required scoring_guide + linked_questions.

4. New Step 7a — curl-the-public-URL structural verifier — catches
   field-name drift at write time instead of at human-eye time.

Removal criteria: keep until the labs MCP ships
create_solicitation_from_brief (server-side composition), at which
point this skill collapses to ~30 lines passing the structured brief.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@jjackson jjackson enabled auto-merge May 22, 2026 01:51
@jjackson jjackson merged commit 6b72a73 into main May 22, 2026
2 checks passed
jjackson added a commit that referenced this pull request May 22, 2026
Walks back the "future labs-side create_solicitation_from_brief"
direction floated in the previous PR. Operator chose to keep
composition in ACE so this skill retains full control over voice,
archetype-branched scope, framing/scoring_guide quality, and
decisions-log integration (all ACE-context that labs would have to
learn).

Labs's tightened MCP (2026-05-22 deploy) is the right server-side
contribution: create_solicitation + update_solicitation now validate
the canonical schema and fail loudly with INVALID_SCHEMA +
error.details.fields keyed by JSON-path (e.g.
evaluation_criteria[0].linked_questions). Schema enforcement, not
content generation.

Changes:

- New top-level design-principle callout at the start of ## Process:
  "ACE owns composition; labs validates the schema." Documents the
  contract + how to read INVALID_SCHEMA responses + when to re-read
  tools/list inputSchema as the source-of-truth.

- Step 6's payload-rejection paragraph updated: labs now rejects
  unknown top-level fields at write time (post-deploy); error.details
  surfaces the offending field path. Do not retry with the same
  payload; do not stuff extras into free-form fields.

- Change Log entry removes the "Removal criteria: keep until labs
  ships create_solicitation_from_brief" line from PR #396 — this
  skill is the long-term home for solicitation composition.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant