RFC-0002 — Factory Task Contract v2

Status: Implemented · Version: 2.0 · Created: 2026-06-06 · Updated: 2026-06-17 Builds on RFC-0001 (correlation key + completion-event envelope). The product repos implement against this document. Machine-readable schema: apis/task-contract.schema.json.

1. Motivation

RFC-0001 lets the four products track one unit of work across the family. It does not let them hand the work off in full. Today the handoff is thin:

The result is duplicated analysis and lossy handoffs. Task Contract v2 is one canonical, signed document that carries WHAT (the plan), HOW (the execution profile), and VERIFY (the test profile) so that:

  1. PFactory computes the full profile once and signs it.
  2. AIFactory executes it without re-planning (skip_planning: true).
  3. TFactory tests it without guessing (declared lanes/frameworks/endpoints).
  4. All three stay in sync via the RFC-0001 completion-event envelope.

The contract is additive and backward-compatible: contract_version: "1" plans and the requirements.json path keep working. v2 is the enriched fast-path.

2. Document shape

A Task Contract is a superset of AIFactory’s implementation_plan.json with two new blocks (execution, tfactory) and an RFC-0001-aligned envelope. The authoritative, validatable definition is the JSON Schema; this section is the human summary.

{
  // -- identity / correlation (RFC-0001) --
  "contract_version": "2",
  "correlation_key": "142",                 // GitHub issue number (string)
  "provenance": {
    "source": "pfactory",
    "session_id": "017-add-rate-limiting",
    "issue_number": 142,
    "repo": "olafkfreund/my-app"
  },
  "approval": {                              // HMAC envelope (AIFactory trusted_plan)
    "approved_by": "pfactory",
    "approval_timestamp": "2026-06-06T12:00:00Z",
    "plan_contract_version": "2",
    "signature": "<hex HMAC-SHA256>"
  },

  // -- WHAT: the plan (existing implementation_plan.json shape, unchanged) --
  "feature": "Add rate limiting to the gateway",
  "workflow_type": "feature",
  "services_involved": ["gateway"],
  "phases": [
    {
      "phase": 1, "name": "Modules", "type": "implementation",
      "depends_on": [], "parallel_safe": true,
      "subtasks": [
        {
          "id": "subtask-1-1",
          "description": "Implement RateLimitMiddleware",
          "depends_on": [],
          "files_to_create": ["app/middleware/ratelimit.py"],
          "files_to_modify": [],
          "model": "claude-sonnet-4-6",
          "verification": { "type": "command", "run": "uv run pytest tests/test_ratelimit.py" },
          "required_commands": ["uv", "pytest"]
        }
      ]
    }
  ],
  "final_acceptance": ["Requests over the limit return HTTP 429"],
  "required_commands": ["uv", "pytest", "ruff", "mypy"],

  // -- HOW: execution profile (NEW, PFactory-computed) --
  "execution": {
    "complexity": "standard",
    "model": "claude-sonnet-4-6",
    "provider": "claude",
    "provider_rationale": "FastAPI/Python stack; Claude SDK is the default agentic provider",
    "phase_models":   { "spec": "sonnet", "planning": "sonnet", "coding": "sonnet", "qa": "sonnet", "qa_fixer": "sonnet" },
    "phase_thinking": { "planning": "high", "coding": "medium", "qa": "high" },
    "parallel": true,
    "workers": 4,
    "review_tier": "async",
    "autonomy_tier": "medium",     // RFC-0011 difficulty tier (low|medium|hard) from factory:* labels; drives model/planning/human-gate/verification/merge. Absent => derived from complexity.
    "agents": {
      "planner": false,            // skip — plan is pre-computed
      "coder": true, "qa_reviewer": true, "qa_fixer": true,
      "subagents": ["codebase_analyzer"]
    },
    "skills": [{ "id": "python/fastapi", "name": "FastAPI", "category": "python" }],
    "skip_planning": true,
    "budget_usd": 5.00              // OPTIONAL soft cost budget (observe-only; never aborts)
  },

  // -- VERIFY: TFactory test profile (NEW, PFactory-computed) --
  "tfactory": {
    "lanes": ["unit", "api", "security"],
    "frameworks": { "unit": "pytest", "api": "pytest+httpx" },
    "endpoints": { "api_base_url": "http://localhost:8000" },
    "docker_compose": null,
    "coverage_target": 0.85,
    "mutation_scope": ["app/middleware/ratelimit.py"],
    "security_scope": ["owasp:rate-limiting", "owasp:dos"],
    "ac_to_code_map": { "AC#1": ["app/middleware/ratelimit.py"] }
  }
}

2.1 Blocks

Block Owner (produces) Consumer Required
contract_version, correlation_key, provenance PFactory all yes
approval PFactory (signs) AIFactory (verifies) yes for the skip-planning fast-path
plan (featurephasesrequired_commands) PFactory AIFactory yes
execution PFactory AIFactory optional (AIFactory falls back to its defaults / own planning if absent)
tfactory PFactory TFactory (via AIFactory handover) optional (TFactory falls back to inference)
epic_context.house_standards PFactory (retrieves) AIFactory/TFactory (follow), standards_conformance gate (verifies) optional (RFC-0012; absent => no external standards to enforce)
deployment PFactory (discovers) AIFactory/TFactory (honor risk/scan/gate + DRY-RUN policy), CFactory (surfaces) optional (RFC-0013; absent => no deployment dimension)
execution.parallel / execution.workers PFactory (from the plan’s dependency shape) AIFactory (concurrent subtask build, worktree-per-agent) optional (absent => AIFactory’s own default: serial)
execution.routing / execution.runtime / execution.budget_mode PFactory (cost router) AIFactory (per-role models + gated runtime + budget enforce), TFactory (test-model), CFactory (cost) optional (RFC-0014; absent => today’s behaviour)

A v2 contract without execution/tfactory is a valid signed plan that behaves like v1 plus richer correlation. A v2 contract with them is the full skip-planning, no-guessing fast-path.

execution.parallel / execution.workers (optional multi-agent build)

These two OPTIONAL execution fields let the planner decide that a task should be built by concurrent agents, and carry that decision over the handover into AIFactory. They are the contract seam for multi-agent execution — nothing else needs to be configured on the AIFactory side.

execution.parallel (boolean) says whether AIFactory may build the task’s independent subtasks concurrently. execution.workers (integer >= 1) caps how many run at once per wave.

PFactory derives both from the plan’s dependency shape — not from a guess or an operator toggle. Subtasks grouped at the same dependency level are independent, so the phase they form is marked parallel_safe (see the phases block, §2). parallel is true when some phase is wider than one subtask; workers is the WIDEST phase’s subtask count. A pure dependency chain therefore emits parallel: false, workers: 1. PFactory caps workers at 4 (execution_profile._MAX_WORKERS) — that is a planner-side policy, not a contract invariant, which is why the schema sets minimum: 1 and deliberately imposes no maximum.

AIFactory ingests them through trusted_plan._EXECUTION_TO_METADATA (parallel -> parallel, workers -> workers in task_metadata.json), reads them back via AgentService._read_parallel_opts, and executes the wave with agents/parallel_runner.py: each concurrent subtask gets its OWN child git worktree over a disjoint file set (enforced by implementation_plan/scheduler.py), and the worktrees are merged sequentially. workers <= 1 makes a phase ineligible for parallel execution (is_phase_parallel_eligible).

Absent => AIFactory’s own defaults (serial; 3 workers if enabled by other means). Both fields are covered by the approval signature, so the parallelism decision is tamper-evident like the rest of the execution profile.

execution.budget_usd (optional soft budget)

execution.budget_usd is an OPTIONAL soft cost budget (USD) for the task. It is observe-only: when AIFactory’s rolled-up spend for the task exceeds it, the terminal RFC-0001 completion event carries an additive usage.budget warning block ({limit_usd, spent_usd, exceeded}) and an OTel budget.exceeded metric fires. It never aborts, pauses, or kills the build — the task always runs to its natural terminal state. Absent => no budget tracking (back-compat). See the per-worker observability design (P2 soft budget alert).

execution.routing / execution.runtime / execution.budget_mode (optional, RFC-0014)

These three OPTIONAL, additive execution fields (RFC-0014) carry the cost-aware, capability-aware routing decision. execution.routing records WHY the per-role models were chosen — its class (economy|standard|premium|governed), the cost_estimate_usd (null for subscription/local), cost_ceiling_usd, policy, and a human rationale. execution.runtime (claude|codex|antigravity|ollama|ollama-cloud|claude-subagents|dynamic-workflow) selects the execution runtime — default claude; every other runtime is gated OFF unless the operator enables it and the contract opts in. execution.budget_mode (observe|enforce) governs whether the router may refuse an over-ceiling selection (enforce) or only warns (observe, default). The router never drops below the RFC-0011 tier’s capability floor, and a governed task is never silently degraded. contract_version stays "2" (additive, open objects; consumers ignore unknown keys). The single price/capability source is apis/model-catalog.json and the reference router is scripts/cost_router_core.py.

epic_context.house_standards (optional, RFC-0012)

epic_context.house_standards is an OPTIONAL, additive block (RFC-0012) carrying the team’s house standards — RFC-0010 repo conventions plus best-effort Backstage catalog/TechDocs/scaffolder-template references — each with a content_hash. Retrieval degrades, never raises: available: false means it could not be retrieved and the fail-closed standards_conformance gate scores it not_applicable (never a false pass). contract_version stays "2" (additive, open object). See $defs.house_standards in apis/task-contract.schema.json.

deployment (optional, RFC-0013)

deployment is an OPTIONAL, additive top-level block (RFC-0013) carrying what PFactory discovered about how the change ships — the detected CI system and deploy mechanism, the target environments and production_classification, the risk_class-derived required_scans and system_gates, best-effort dora_context from the deployment-metrics MCP, a readiness checklist, and the DRY-RUN deploy_verification policy. Discovery degrades, never fabricates: dora_context.available: false means delivery metrics were unreachable — treated as UNKNOWN, never healthy — and every non-passing readiness check MUST carry a reason. Production deploys are VAL-4 and never autonomous: deploy verification stops at a dry-run/plan, and the real production apply is held behind the human-approval system gate. contract_version stays "2" (additive, open object). See $defs.deployment in apis/task-contract.schema.json and the deployment-metrics MCP contract in apis/deployment-metrics.mcp.md.

3. Signing & trust

Signing reuses AIFactory’s trusted_plan envelope (HMAC-SHA256 over the canonical contract + approval metadata, keyed by AIFACTORY_TRUSTED_PLAN_KEY_<AUTHORITY>). The signature covers the WHAT, HOW, and VERIFY blocks — so the execution profile and test profile are tamper-evident, not just the plan. AIFactory verifies signature and completeness before honoring skip_planning; on failure it rejects (HTTP 422) and may fall back to normal planning.

4. Sync model (bidirectional)

4b. Verification block (RFC-0006) — honest reporting

The optional verification block records, per RFC-0006, how much was actually proven using the Verification Assurance Levels (VAL-0 static · VAL-1 unit · VAL-2 ephemeral integration · VAL-3 sandbox target · VAL-4 production, never autonomous). It extends the RFC-0001a evidence gate from “no green without proof” to “never claim a higher assurance than was achieved”.

"verification": {
  "target_level": "VAL-3",
  "achieved_level": "VAL-2",
  "levels": [
    {"level": "VAL-0", "status": "passed", "ran": ["ansible-lint"]},
    {"level": "VAL-2", "status": "passed", "evidence": "idempotence: 0 changed"},
    {"level": "VAL-3", "status": "not_run",
     "reason": "no sandbox target provisioned", "risk": "unproven on real hosts"}
  ],
  "claim": "Verified to VAL-2; NOT verified against real hosts (VAL-3)."
}

Normative rules (the never-overclaim gate):

  1. A status is reported only at achieved_level; achieved_level is the highest passed level, capped below the lowest failed level.
  2. Every failed/not_run/skipped level MUST carry a reason (enforced by the schema) and SHOULD carry a risk.
  3. A producer that omits this block is treated as VAL-0 (“not tested”), never as passed.
  4. A declared achieved_level above the computed truth is downgraded and flagged (an overclaim), mirroring RFC-0001a’s no-evidence downgrade.

The reference enforcement is scripts/verification_gate.py (normalize_verification()), pure and dependency-free so each service vendors it. The machine-readable shape is $defs.verification in apis/task-contract.schema.json.

5. Versioning

contract_version is bumped when the envelope or block shapes change in a breaking way. New optional fields are additive (no bump). Consumers MUST ignore unknown fields. The schema file is versioned alongside this RFC.

6. Adoption (tracked by issues)

See the cross-linked epics in each repo’s issue tracker.