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:
- PFactory emits “what to do” — a
requirements.json(complexity, optional model/thinking) plus governed GitHub issues. It does not emit “how to execute”: no parallel/worker plan, no per-subtask verification, norequired_commands, no provider rationale, no review tier, and nothing for TFactory. - AIFactory therefore re-runs its full planner on every task, even though
PFactory already did the analysis. AIFactory has a signed fast-path
(
trusted_plan→POST /api/tasks/from-plan) that skips planning when given a complete signedimplementation_plan.json, but nothing upstream produces one. - TFactory ingests an AC-only spec and guesses framework / lane / endpoints. The spec carries no declared test configuration.
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:
- PFactory computes the full profile once and signs it.
- AIFactory executes it without re-planning (
skip_planning: true). - TFactory tests it without guessing (declared lanes/frameworks/endpoints).
- 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 (feature…phases…required_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)
- Forward (handoff): PFactory → AIFactory via
POST /api/tasks/from-plan(the contract as body). AIFactory → TFactory by carrying thetfactoryblock into the spec/context it hands over. - Backward (status): every stage emits the RFC-0001 completion-event envelope
keyed by
correlation_key. CFactory threads them. TFactory failures flow back to AIFactory’s QA-fixer via the existing handback path; AIFactory failures are visible to PFactory by the same key. “100% mutual understanding” = both sides validate against this one schema and reconcile on the one key.
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):
- A status is reported only at
achieved_level;achieved_levelis the highestpassedlevel, capped below the lowestfailedlevel. - Every
failed/not_run/skippedlevel MUST carry areason(enforced by the schema) and SHOULD carry arisk. - A producer that omits this block is treated as VAL-0 (“not tested”), never as passed.
- A declared
achieved_levelabove 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)
- Factory: this RFC +
apis/task-contract.schema.json+ catalog/TechDocs. - PFactory: compute + sign + emit the full v2 contract.
- AIFactory: extend
trusted_planto verify/ingestexecution+tfactory; apply the execution profile and skip planning; carrytfactoryto TFactory. - TFactory: consume the
tfactoryblock to buildtest_plan.jsondeterministically.
See the cross-linked epics in each repo’s issue tracker.