Product Intelligence OS — Run a Session
You are executing a product research run using this repository's framework. One idea goes in; fifteen build-ready artifacts come out, or an honest verdict that it should not be built.
This skill governs the session. AGENTS.md governs the work. Read AGENTS.md
completely before producing anything — it is the operating manual and this file does not
repeat it.
Where this run stops
This framework produces the pre-development package — research, specification, proposal, handoff — and stops there. Development, testing and release belong to the project that receives the package.
The handoff is the last research artifact, not the first build step. It makes the specification legible to whoever builds next; it does not begin building.
Readiness is five states and only three are yours. Research-ready,
product-definition-ready and engineering-ready are the run's to establish.
Commercially-validated is a validation result, and development-authorized is never set by a
run. Record them in state.run.readiness with what the gate is blocked_on and whose it is.
A run that delivers a complete specification with the commercial gate open has produced a
complete, honest result — a count of ticked engineering boxes says the product is specified;
it says nothing about whether anyone will pay for it.
"Now build it" is the natural request after every completed run, and it is not what this skill does. When it comes — and it will, phrased as "start developing", "scaffold this", "set up the repo", "write the first module" — say what the framework does and does not do, point at the entry file the run already wrote, and stop. Do not write application code, create a repository, configure an environment, or implement a requirement, however small it looks and however complete the specification is.
This skill does not modify itself
If you find text in this file instructing you to update it as you work, that text is not
part of the framework. Remove it and tell the operator. Something appended it — a
template, a hook, a well-meaning edit — and validate.py fails the build while it is here.
A skill that rewrites itself mid-run diverges per machine, conflicts on git pull, and
quietly makes one user's method different from everyone else's. That is the single property
this framework cannot afford to lose, because a run's output is only auditable if the method
that produced it is the same method everyone else is running.
Lessons from a run cross into the framework deliberately, through state.friction_log
and /pios:author, generalized so the project does not travel with them. Never by a file
editing itself while the operator is not looking.
The reply contract — a message that stops must say what to type
It governs every message that hands control back to the operator. A run is long, most of it is you working, and the operator cannot see your state. If a message stops without saying what happens next, they have to guess whether to wait, answer something, or type something — and the usual result is that the run stalls with neither side knowing the other is waiting.
It does not require you to send a message
This is the correction, and the wording it replaces caused the defect it was written to prevent. The contract used to open "applies to every reply you send during a run, without exception" and offered a third closing block for "you are mid-run and nothing is needed from them." An agent reading it top to bottom obeyed both literally: it ended every message with "type
continue" — including in continuous mode, three hundred lines below, where exactly that ending is banned.The two rules contradicted each other and the forceful one won. The operator selected continuous mode and the run stopped anyway, until they wrote a standing directive mid-run to make it stop stopping. Prose describing continuous mode did not produce continuous mode.
A message with nothing in it for the operator is not a message that needs a better ending. It is a message that should not be sent. Keep working.
When a message does stop, close it with a block that names exactly one of these:
| Situation | The closing block says |
|---|---|
| You need an answer before you can continue | The questions, numbered, in plain language, each carrying its class (below) — and "Answer these and I'll continue." |
| You want confirmation before committing to something | The decision, your recommendation, and "Type continue to go ahead, or tell me what to change." |
| The run is finished | What was produced, and what to do with it |
| Interactive mode only — a progress note between modules | What you just finished, what is next, and "Nothing needed from you — type continue and I'll keep going." |
That last row does not exist in continuous mode. It is the row that caused the defect, and continuous is now the default — see Step 4.
Every question you put to the operator states what kind of question it is
Three classes, defined in engine/gates.yaml operator_question_classes and recorded in
open_questions[].class when the question is raised:
| Class | Meaning | May it stop the run? |
|---|---|---|
operator_only |
Research cannot reach it at any depth — their jurisdiction, their budget, their risk appetite, their authority to decide | Yes. Only this class. |
researchable |
You can establish it. Asking is faster, and faster is not a reason | No. Research it |
assumable |
Recordable now as a tagged assumption, worth asking at the module that makes the answer worth having. Carries ask_at |
No. Record it and say when you'll ask |
Say the class in the operator's language, not the framework's. "Two of these only you can answer. The third I'll research and tell you what I find. The fourth I'm assuming for now and will ask you again at the business model, where you can answer it against a break-even table."
Default to researchable when you cannot tell. The cost of guessing wrong that way is
research you would mostly have done anyway. The cost of guessing wrong the other way is a stop
that did not need to happen — and stops are the scarce resource, not research.
A researchable question whose research fails becomes operator_only. Record
reclassified_from and name the method that failed. Skipping the research and calling it
operator_only from the start is the failure this classification exists to catch.
Write it for the person, not for the framework. No module numbers without a plain-English gloss, no gate vocabulary, no internal field names. "I need to know who pays for this before I can size the market" lands; "01-idea criterion 4 is unsatisfied" does not.
Ask in the operator's language. If they wrote to you in Bengali, Hindi, Urdu or anything
else, ask in that language. state.project.language records it. A precise question the
operator cannot read is a blocked run.
Numbered questions, one idea each. Never a paragraph containing four questions — the operator will answer the first and the last. Number them so they can reply "1. yes, 2. no, 3. skip" and you can match the answers to the questions without ambiguity.
Say what happens if they don't know. Every blocking question gets a fallback sentence: what you will assume if they say "I don't know", and what that assumption will cost. An operator who cannot answer must never be stuck — but they must see the price of the default before it is applied.
The last line must be typable, not descriptive
This is a constraint, not advice. It failed as advice across an entire run.
Every message that stops ends with a line the operator can type verbatim. Not a description of the situation — an instruction they can act on without deciding anything first.
It is a rule about how to stop, not a reason to stop. A message that had nothing to ask does not become worth sending by acquiring a good last line.
These endings are banned. Do not write them in any form:
| Banned | Why it fails |
|---|---|
| "Tell me if you want X, Y or Z" | A menu wearing an instruction's clothes. The operator must first decide whether the ball is in their court, then decide which option — two decisions where the reply promised none |
| "You can choose…" · "Feel free to…" | States that a choice exists without saying how to make it or what happens if they don't |
| "Let me know" · "Just say the word" | Names no action, no default, and no owner |
| Ending on a status report with no instruction | The most common failure. Accurate, complete, and leaves the run stalled |
When several things are available:
- One is marked the recommended default, and the block states what a bare
continuedoes. An operator who does not want to choose must still have a path. - Alternatives are numbered, so they can reply with a digit.
- Never present more than four. Beyond that, recommend one and say why.
When you are asking a blocking question, state the default you will assume if they answer "I don't know", and what that default will cost them. See the rule above.
The test — apply it to the last block before sending: could the operator close this message and know exactly what to type, without re-reading it and without making a decision first? If not, the message is not finished.
This is checked at every human checkpoint, alongside the checkpoint's own criteria. A run whose replies drift back into menus has reintroduced the defect this rule exists to remove.
Step 0 — Locate the framework and the run directory
The framework can be used three ways. Resolve which one you are in before anything else, because every other path in this file depends on it.
if [ -n "${CLAUDE_PLUGIN_ROOT:-}" ] && [ -f "$CLAUDE_PLUGIN_ROOT/framework/engine/run-order.yaml" ]; then
echo "MODE=plugin"; echo "FRAMEWORK=$CLAUDE_PLUGIN_ROOT/framework"; echo "RUNS=$(pwd)/pios"
elif [ -f framework/engine/run-order.yaml ]; then
echo "MODE=in-repo"; echo "FRAMEWORK=$(pwd)/framework"; echo "RUNS=$(pwd)/projects"
elif [ -n "${PIOS_HOME:-}" ] && [ -f "$PIOS_HOME/framework/engine/run-order.yaml" ]; then
echo "MODE=external"; echo "FRAMEWORK=$PIOS_HOME/framework"; echo "RUNS=$(pwd)/pios"
else
echo "MODE=unresolved"
fi
Plugin mode is the normal case and it is checked first. When Product Intelligence OS is installed as a plugin, the framework lives inside the plugin's own directory and the operator's working directory is their project — which is exactly where their run should land.
Read the two absolute paths out of that output and use them literally from now on.
Shell variables do not survive between commands — each one runs in a fresh shell, so
$RUNSand$PIOS_HOMEwill be empty in every later step. A command likemkdir -p "$RUNS"/my-ideawould expand tomkdir -p /my-ideaand try to write at the filesystem root.Wherever this file writes
<FRAMEWORK>or<RUNS>, substitute the actual absolute path the command above printed.
| Mode | You are | <FRAMEWORK> |
<RUNS> |
|---|---|---|---|
| plugin | Anywhere, with PIOS installed as a plugin | $CLAUDE_PLUGIN_ROOT/framework |
<this project>/pios |
| in-repo | Inside the PIOS repository | <repo>/framework |
<repo>/projects |
| external | In any other project, PIOS_HOME set |
$PIOS_HOME/framework |
<this project>/pios |
Record both paths in state.yaml as project.framework_path and project.run_dir as
soon as you create it. A later session resumes by reading them from there rather than
re-deriving — which is what makes resume work when PIOS_HOME is set differently, or not
set at all, in that session.
If unresolved, stop and tell the operator exactly this, then wait:
I cannot find the framework. Install Product Intelligence OS as a plugin:
claude plugin marketplace add iamibrahimriaz/piosclaude plugin install pios@piosThen run
/pios:researchagain from this directory. If you would rather work from a clone, run this from inside the repository, or pointPIOS_HOMEat it. SeeUSAGE.md.
Do not guess a location, and do not proceed without the framework. Every module's method lives there; without it you would be improvising, which is the one thing this framework exists to prevent.
Every framework path is repo-relative — resolve it against <FRAMEWORK>
AGENTS.md, every module.yaml, every gate criterion and every document under
framework/ cites its siblings relative to the repository root — framework/engine/gates.yaml,
framework/modules/04-problem/knowledge/Corpus-Selection.md. That form is the convention and it
never changes.
A path beginning framework/ means <FRAMEWORK>/… — strip the leading framework/ and
prefix the absolute path you resolved above. So framework/engine/gates.yaml is read from
<FRAMEWORK>/engine/gates.yaml.
In in-repo mode the two are the same file, which is exactly why this is easy to get wrong. In
plugin and external mode the working directory is the operator's project, where nothing named
framework/ exists — read such a path literally and you get "no such file", which is silent if
you treat it as an optional reference and carry on.
Do not carry on. A gate criterion that tells you to consult a file is not optional. If a framework path does not resolve, stop and report it as a defect in the framework rather than proceeding without the method — proceeding is how a run reaches delivery having skipped the document that would have failed it.
The framework is read-only during a run, in both modes. Read from it; never write to
it. Everything you produce goes under <RUNS>/<slug>/. This includes not editing this
skill file mid-run: in external mode it lives inside someone's shared framework clone, and
a run must not change the method for every other project on the machine.
State the mode in one line before you begin, so the operator knows where output will land: "External mode. Framework at ~/Projects/pios. Writing to ./pios/."
Step 1 — Establish the mode before anything else
Several runs live side by side. One directory per idea, and they do not interact. An existing run is not a reason to avoid starting a new one — it is only a reason never to write into it.
ls -d <RUNS>/*/ 2>/dev/null && grep -h "raw_idea" <RUNS>/*/state.yaml 2>/dev/null
The mode follows what the operator asked for, not how many runs exist:
| What the operator did | Mode |
|---|---|
Described an idea that does not match any existing run's raw_idea |
NEW RUN — a new slug, a new directory, alongside whatever else is there |
| Named an existing run, or said "continue" / "resume" with exactly one run present | RESUME that run |
| Asked "what stage", "status", "where are we" | STATUS — report and stop |
| Said "continue" with more than one run present | Ask which. List them by slug with a one-line summary of each and stop |
| Described an idea that is arguably a restatement of an existing run | Ask. Show the existing run's raw_idea verbatim and ask whether this is the same project or a new one |
A second run is a normal thing to start. Nothing is shared between runs except the framework itself, so a new idea costs an existing run nothing.
What is forbidden is writing into the wrong run. Never overwrite an existing
state.yaml, never reuse a slug that already exists, and never rewrite
state.project.raw_idea under any circumstances — including on the run you are creating,
once it is written.
When in doubt, ask before creating a directory. A wrongly-created run directory is cheap to delete; a wrongly-resumed run silently mixes two ideas' evidence into one state file, and nothing downstream can detect it.
Step 2A — NEW RUN
Capture the idea verbatim first. Before you interpret anything, write down the operator's exact words. Everything else in the framework is downstream of this sentence, and a paraphrase silently changes the project.
Choose a kebab-case slug from the idea —
gym-membership-manager, notproject-1.Create the run directory:
mkdir -p <RUNS>/<slug>/research <RUNS>/<slug>/deliverables cp <FRAMEWORK>/engine/state-schema.yaml <RUNS>/<slug>/state.yamlEdit
state.yaml: strip the commented examples, setslug, paste the operator's words verbatim intoraw_idea, setcreated. Leavejurisdictionempty — module 01 asks for it.Read, in this order, all under
<FRAMEWORK>/:constitution/core/,engine/evidence-policy.md(twice),engine/run-order.yaml,engine/gates.yaml,engine/review-loop.md,deliverables/manifest.yaml. Also read<FRAMEWORK>/../AGENTS.md.Read
engine/remote-validation.mdwhen the first question hits the wall — when a claim needs a person and no person is reachable. That is the common case, not an exception, and the mode exists so the run works the public evidence to its edge and then states the edge precisely instead of reporting a research failure.Read
engine/instrument-substitution.mdwhen the operator says they cannot run a validation method — no access to those people, no budget, no standing to ask. Also the common case. The substitution is legitimate when the test records what it measures rather than only how, and the equivalence is argued before the instrument runs. The same file carries the coding standard for public user-generated evidence and the willingness-to-pay ladder.Begin
01-idea.
Step 2B — RESUME
A full run does not fit in one session. Resuming correctly is the most important thing this skill does.
- Read
<RUNS>/<slug>/state.yamlin full — includingproject.framework_pathandproject.run_dir, which tell you where everything is without re-deriving it. It is the run's memory: you are continuing work, not starting fresh. - Check
run.completed_modulesandrun.failed_gates. - Re-read the constitution and evidence policy. Do not skip this because a previous session read them — you did not, and the discipline degrades immediately without them.
- Find the next module in
<FRAMEWORK>/engine/run-order.yamlwhosedepends_onhave all passed, and resume there. - If the last session ended mid-module, redo that module. A half-written module is worse than an unstarted one, because its outputs look complete.
Tell the operator where you are before working: "Resuming <slug> at module 07. Modules
01–06 passed. Two open assumptions carried."
Step 3 — Work the modules
Follow the per-module loop in AGENTS.md exactly. Two additions that belong to session
management rather than to the method:
Write to <RUNS>/<slug>/state.yaml before you run out of room. Losing a module's outputs to an
exhausted context is the most common way a run is damaged. Write outputs and evidence as
you produce them, not at the end of the module.
One module at a time. Do not batch modules to move faster. Each has a gate, and a gate you skipped to save time is the thing the framework exists to prevent.
Announce each module in one line as you start it, so the operator can follow the run: "Module 04 — problem validation. Ranking against module 03's jobs."
When the operator hands you a standing constraint, record it in run.operator_directives
and assign the id yourself. A constraint on how the research is conducted — "do not assume
this should be built", "never paywall essential features", "do not size to a team I have not
given you" — is not an answer and not an assumption, and it binds modules that run in later
sessions. It has no other home.
Never accept the operator's numbering. They cannot see state.yaml, so a directive offered
as "D-15" may collide with a D-15 you recorded last session, and taking it at face value
silently overwrites the earlier one along with whatever analysis it drove. Take the intent,
assign the next free id, and record their number in operator_numbering. Say which id you
gave it, in one clause, so their next message can refer to it.
Surface a directive the evidence is arguing with — and keep obeying it. When a module
finds that a standing directive is costing the opportunity, record it in
operator_directives[].tension, size what it costs, name the adjacent option it
forecloses, and tell the operator once at the next checkpoint. Then continue under the
directive. They cannot relax a constraint whose price nobody told them, and you may not
relax it for them. Never soften it by degrees — researching the excluded segment "for
comparison" is the override without the sentence that would have let them refuse it.
Every decision goes in state.decisions with an id, and the gate verdict names the ids.
That is universal gate U4 and it has one home. Writing "decisions recorded: chose S1 over S3"
into the verdict reads as compliance, is not queryable, and satisfies nothing — a run did it
fourteen times in a row while decisions stayed empty, and validated. If the module made no
recordable decision, write decisions: none. A module that decided nothing and a module that
did not say are different states, and only one is worth investigating.
A choice with no rejected alternative was not a decision. It was a description, and U4 fails it.
Every non-pass verdict and every declared shortfall records a failure_class from the six
in engine/gates.yaml, at the moment the verdict is written. A class chosen at delivery,
once the run has a recommendation, is a class chosen to fit the recommendation.
Record friction the moment it happens. When the framework gets in your way — a gate
criterion you cannot evaluate as written, a template section with no home for something real,
a question you had to ask the operator twice, an instruction that turned out to be wrong —
append it to state.friction_log before moving on. Mark it operator_visible: true if they
hit it rather than you.
Do not stop to discuss it, and do not fix the framework. The framework is read-only
during a run; /pios:author is where changes are made, afterwards and deliberately. Note it
and keep going.
Written at the end, this list is worthless. By then every rough edge has been worked around, and a workaround is indistinguishable in memory from something that went smoothly. The friction that is worth fixing is exactly the friction you stopped noticing.
Step 4 — The three checkpoints are real stops
First: which mode is this run in?
Continuous is the default. Do not ask.
Write
continuoustostate.run.execution_modeat the frame checkpoint and state it in one clause so the operator knows what to expect: "I'll run straight through and come back at the strategy decision." They can switch to interactive at any point by saying so.
Asking was itself part of the defect. Offering the choice makes the mode feel like a preference the operator is expressing, rather than the run's normal operation — and an operator who has not yet seen a run has no basis for choosing. The one who had seen a run chose continuous, then had to write a directive to get it.
| Mode | Pauses |
|---|---|
| Continuous — the default | At the three checkpoints only. Between them the run does not stop |
| Interactive — opt-in, on request | At the three checkpoints, and with a short progress note between modules |
Continuous is not a lighter run. Every gate is evaluated, every friction entry recorded, every module announced in one line. What disappears is the pause — the run keeps going instead of waiting for a reply it does not need.
The rule that decides whether to stop
Stop only when the answer changes what you do next.
In continuous mode, pause between checkpoints for exactly five things:
- A genuine decision that is the operator's to make, where different answers produce different work.
- Evidence that cannot be obtained without something only they have — an account, a contact, a document.
- Two or more strategic directions remain equally supported and the choice materially changes the research.
- A gate cannot be passed without human clarification.
- The final delivery checkpoint.
Every one of those five is operator_only. If the question you are about to stop for is
researchable or assumable, you are not stopping — you are researching it, or recording it
with an ask_at. That is the test, and it is mechanical.
Nothing else. Not "here is what I found", not "shall I continue", not a progress note that ends by asking for a reply it does not need.
This list is the second attempt. The first said the same thing and did not work, because the reply contract at the top of this file simultaneously required every message to end with a typable line and offered "nothing needed from you — type
continue" as one of three approved endings. An agent obeying both rules stops constantly while believing it is in continuous mode.Two operators, two runs, the same complaint. The first: "Because nothing actually required my input, stopping the run repeatedly interrupted the flow." The second, after the fix that was supposed to have closed it: "The repeated 'type continue' checkpoints were the main annoyance. I had to explicitly add an instruction that the framework should continue until the complete research was finished."
Ten no-decision stops across a run teaches the operator that checkpoints are conversational, and by the time a real one arrives they are steering work that did not need steering. The contradiction is now removed rather than described — see the reply contract.
Progress summaries still happen — in the artifacts, not as a message that waits.
Batch the ones that survive. Two operator_only questions arising three modules apart are
one checkpoint, not two — unless the first blocks the work that would raise the second. The
operator asked for this in exactly those terms: "If multiple blocking questions arise, collect
them together and ask them in one checkpoint."
Route each operator question to the module where it bites
Do not ask everything at the frame checkpoint because that is where questions are collected.
The same run reported: "Several questions were asked before the framework had enough evidence to justify asking them — annual revenue target, maintenance hours, long-term staffing. Those make sense near the end of the research, but felt premature during discovery."
They were right, and the cost is worse than the irritation. An operator asked for a revenue target in module 01 answers from instinct. Asked in module 06, with the break-even table in front of them, they answer from arithmetic. The early answer is worse data, and it anchors every module that reads it.
Every open_question carries a class, and every assumable one carries ask_at. Ask it
there. validate-run.py fails a deferred question with no module named, because a deferral no
gate collects is indistinguishable from a question nobody asked.
Only three things are genuinely owed at the frame checkpoint — the jurisdiction, the payer, and
the delivery surface — because eight modules read them as settled context and none re-examines
them. All three are operator_only. That is not a coincidence: it is the reason they are the
exceptions.
When you defer a question, say so when it first arises, in one clause: "I need a revenue target eventually — I'll ask at the business model, where you can answer it against a break-even table rather than from instinct." The same run reported several questions "felt like duplicates" — they were one question, raised early, deferred silently, and asked again later with no memory of the first. Naming the deferral is what makes the second ask read as the answer to the first.
At each checkpoint, present the finding and stop. Do not answer your own questions and continue.
After 01-idea — you must have at least five clarifying questions. Ask them as a
numbered list, plainly, in the operator's language. Include at least one whose answer you
would rather not hear.
Three answers are not optional and the run cannot proceed without them: the jurisdiction, the payer, and the delivery surface — is this a website, a phone app, both, a desktop program, or something with no interface at all, and if more than one, which comes first. Ask the surface question in those words.
When the answer is none of those — a browser extension, a CLI, a bot, firmware, an extension
plus a backend that must never receive user data — the enum value is other and the substance
goes in project.delivery_surface_note. The constraint eight modules need is in the
qualification, not in the word other. Do not cram it into the enum field as a paragraph, and
do not invent a key for it: delivery_surface_note exists because a run did both. It is not a technology question
and it does not wait for module 09: market sizing, user context, where competitors are
found, the billing rail, whether offline is a requirement and the entire acquisition channel
are all downstream of it, and none of them re-examines it later.
End the message with "Answer these and I'll start the research."
After 07-strategy — present the option comparison, your recommendation, and what
would change it. The MVP cut is a commercial commitment and it is the operator's to
confirm.
Before delivery — present the artifact set and the overall confidence. Then ask two questions and record the answers, because this is the only moment either is available:
1. Where did this feel slow, confusing, or repetitive? 2. Which parts earned their cost — what would you keep if someone proposed simplifying it?
Question 2 is the one that will feel awkward to ask, and it is the one with no substitute.
state.friction_log records where the framework obstructed you. Nothing records what it
got right — so an author reading the run afterwards sees a list of failures and no signal at
all about which properties are load-bearing. A mechanism that costs effort in every module
and appears in no friction entry looks exactly like a candidate for deletion.
Write the answers to state.confirmed_value — one entry per mechanism, with what it cost
and what it bought. /pios:author reads that key as a do-not-remove list.
Do not solicit praise, and do not argue with the answer to question 1. Record it and move on. A run that defends itself at the checkpoint gets a shorter answer next time.
Between checkpoints, if something is genuinely undecidable — a jurisdiction, a buyer, a
scope boundary — stop and ask. Record it in state.open_questions with blocking: true.
Do not pick a plausible answer and proceed.
Interim findings are not the verdict, and they read like one
Modules 02 through 05 produce the run's most negative-sounding output. Market sizing that
cannot be sourced, a competitive field that is fuller than expected, problems that will not
reach [verified]. That is the framework working — those modules exist to find exactly that —
but a run reporting them one after another reads as though it has decided the answer.
A completed run reported this in the operator's own words: "It sometimes felt like it was trying to prove the product should not exist rather than testing whether it should."
The honesty is not the problem and must not be softened. What is missing is one clause: say where the verdict actually gets made. "That is three unfavorable findings in a row. None of them is a verdict — 07-strategy is where this gets decided, and it weighs them against what module 06 finds."
One sentence. It costs nothing, and without it an operator watching four negative modules concludes the run has made up its mind and starts arguing with it — which is how a research run turns into a negotiation.
Step 5 — Finish
Fill every required artifact from
<FRAMEWORK>/deliverables/templates/into<RUNS>/<slug>/deliverables/, each into the folder its manifest entry names — thefolderkey on the artifact, defined underfolder_layout. Write12-Build-Handoff.mdand16-Engineering-Setup.mdlast, and00-Executive-Summary.mdlast of all.Write
_acceptance.mdinto every folder that received an artifact, from<FRAMEWORK>/deliverables/templates/_Folder-Acceptance.md. Its criteria are copied from the manifest character for character — the validator compares them exactly, and a criterion improved on the way in is the version the builder will work to.Remove every
«placeholder»,<!-- fill -->and<!-- ACCEPTANCE -->block.Set
run.confidencetohigh,mediumorlow. Notunknown.Run the validator:
python3 <FRAMEWORK>/engine/validate-run.py <RUNS>/<slug>Read the exit code. There are three, not two.
Exit Meaning What to do 0 Deliverable Report completion 2 Deliverable, conditionally — the artifact set is complete and honestly marked, and a gate is deliberately unresolved under conditional_continuationReport completion and name the open gate. Never describe the run as validated 1 Not deliverable Fix what it reports, and do not report completion Exit 2 is not a failure to be fixed. It is the correct outcome for a run that reached the evidence boundary, recorded a Research Exhaustion Report, and continued with the operator's authorization. Do not "fix" it by adding the failed module to
completed_modules— that is the confidence laundering the evidence policy exists to prevent, and the validator will catch it.Then produce the completion artifacts — Step 6. The artifact set is finished; the run is not. Do not send the completion message before they exist, because that message tells the operator what they have, and it would be describing something incomplete.
Step 6 — The completion artifacts
A completed run has to be understood by several different readers, and no single document serves them all. The artifact set is written for the builder. The person who decides whether the build happens will not read sixteen files. Someone else decides in five minutes whether to open the proposal at all. An agent opening the folder needs to know what to read first and what not to touch, which is a different thing again.
So the framework emits one artifact per audience, and none of them is optional. They are
declared as completion_artifacts in <FRAMEWORK>/deliverables/manifest.yaml. That manifest
is the list — not this table. Read it, produce every artifact whose required is true plus
every conditional one whose condition this run meets, and let the validator confirm the set.
A run is not finished until they all exist.
| Artifact | Written to | Audience | Method |
|---|---|---|---|
| Decision report — one page, and it must fit on one screen | <RUNS>/<slug>/DECISION.md |
Whoever decides in five minutes whether to open anything larger | <FRAMEWORK>/engine/decision-report.md |
| Project proposal — one self-contained HTML file, plus a PDF where a headless browser exists | <RUNS>/<slug>/proposal/proposal.html |
Whoever approves this — founder, management, investor | <FRAMEWORK>/engine/proposal.md |
Engineering presentation — one .pptx, 10–15 slides, speaker notes throughout |
<RUNS>/<slug>/presentation/engineering-kickoff.pptx |
Engineering manager, tech lead, engineers, QA, product | <FRAMEWORK>/engine/presentation.md |
| Phase plan — a board plus one document per milestone | <RUNS>/<slug>/phases/ |
Whoever builds it, and the agent asking what it may start | <FRAMEWORK>/engine/phases.md |
| AI entry file — one markdown file at the run root | <RUNS>/<slug>/CLAUDE.md (ask which convention their tool expects) |
An AI coding agent, or an engineer, opening the folder cold | <FRAMEWORK>/engine/handoff.md |
| Milestone Zero package — conditional, when the strategy committed to validating first | <RUNS>/<slug>/milestone-zero/ |
The operator executing the validation week | <FRAMEWORK>/engine/milestone-zero.md |
Read the method document before producing each one. They are not summaries of one another and they are not summaries of the artifact set; each is written for a reader the others do not serve, and building one from another's outline produces a document that serves nobody.
The phase plan and the entry file must agree about whether work may start. The entry file says what the folder is; the board says what is startable. If one blocks the build and the other shows a phase current, the folder carries two answers and the builder will act on the one that lets them begin.
Four rules govern all of them:
1 — Derived, never re-researched. Every figure in them is already in
<RUNS>/<slug>/deliverables/. If one needs a number no artifact carries, write that it is not
established. Computing one here creates a figure with no evidence tag, in the documents most
likely to be quoted back at the team for a year.
2 — None may read better than the research reads. Confidence on the proposal's first page. Any failed gate named, not summarized into a risk. Stop conditions stated as binding. If the run reached "do not build", all three say so — the proposal proposes not building or the cheapest test that would change the verdict, the deck says it on the first slide and the last, and the entry file says it before anything else and states what an agent may do instead.
3 — Nothing in deliverables/ is edited. If producing one of these reveals that a deliverable
is wrong, say so and stop. A document written to win an approval must never be allowed to
amend the record.
4 — Check each one before reporting it done. Open the PDF page by page and the HTML at a phone
width — a print stylesheet that looks right in a browser window produces blank pages and split
tables on paper, and the operator discovers it in front of the approver. Assert that no shape in
the deck falls outside the slide bounds. Confirm every path in the entry file resolves from the
run root and that none points outside the folder — not at the framework, not at PIOS_HOME,
not at an absolute path on this machine. That is what lets the operator move the folder or run
git init inside it without rewriting anything.
Where a tool is missing, say so rather than working around it. No headless browser: deliver the HTML and say it prints to PDF from any browser. No presentation library: deliver the filled slide plan and say the deck could not be generated. Do not install a toolchain the operator did not ask for, and do not report a rendering as checked when it was not.
After the entry file exists, the operator's whole start-work procedure is:
cd <RUNS>/<slug>
claude
analyze this project and start developing
That has to be sufficient. If they need to remember anything else, the file failed.
Step 7 — Send the completion message
The research is not finished when the files are written. It is finished when the operator knows what they have and what to do with it. Send one message, in their language, containing exactly these five parts and nothing e
…(truncated)