okstra 0.214.1 → 0.216.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/cli-registry.d.mts +14 -0
- package/dist/cli-registry.mjs +12 -0
- package/dist/cli-registry.mjs.map +1 -1
- package/dist/commands/chat/chat.mjs +2 -56
- package/dist/commands/chat/chat.mjs.map +1 -1
- package/dist/commands/execute/render-bundle.mjs +5 -2
- package/dist/commands/execute/render-bundle.mjs.map +1 -1
- package/dist/commands/execute/wizard.mjs +6 -0
- package/dist/commands/execute/wizard.mjs.map +1 -1
- package/dist/commands/memory/memory.mjs +57 -6
- package/dist/commands/memory/memory.mjs.map +1 -1
- package/dist/lib/file-lock.d.mts +1 -0
- package/dist/lib/file-lock.mjs +60 -0
- package/dist/lib/file-lock.mjs.map +1 -0
- package/dist/lib/python-command.mjs +11 -1
- package/dist/lib/python-command.mjs.map +1 -1
- package/dist/lib/skill-catalog.mjs +1 -0
- package/dist/lib/skill-catalog.mjs.map +1 -1
- package/dist/lib/types.d.mts +1 -1
- package/docs/architecture.md +2 -2
- package/docs/cli.md +36 -14
- package/docs/project-structure-overview.md +6 -3
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/bin/lib/okstra/cli.sh +9 -2
- package/runtime/bin/lib/okstra/globals.sh +2 -0
- package/runtime/bin/lib/okstra/interactive.sh +8 -2
- package/runtime/bin/lib/okstra/usage.sh +3 -1
- package/runtime/bin/okstra.sh +7 -1
- package/runtime/prompts/duties/discussion-worker.json +32 -0
- package/runtime/prompts/lead/convergence.md +58 -136
- package/runtime/prompts/lead/okstra-lead-contract.md +45 -12
- package/runtime/prompts/profiles/_common-contract.md +1 -1
- package/runtime/prompts/wizard/prompts.ko.json +14 -1
- package/runtime/python/okstra_ctl/adapters/providers/antigravity/adapter.py +4 -0
- package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +4 -11
- package/runtime/python/okstra_ctl/agent/invocation.py +1 -0
- package/runtime/python/okstra_ctl/agent/prompt_cli/dynamic_verifier.py +2 -2
- package/runtime/python/okstra_ctl/agent/prompt_cli/materialize.py +4 -4
- package/runtime/python/okstra_ctl/agent/prompt_cli/run_identity.py +2 -2
- package/runtime/python/okstra_ctl/analysis_packet.py +3 -0
- package/runtime/python/okstra_ctl/consumers.py +8 -2
- package/runtime/python/okstra_ctl/convergence.py +143 -29
- package/runtime/python/okstra_ctl/convergence_critic_prompt.py +57 -0
- package/runtime/python/okstra_ctl/convergence_critic_verify_prompt.py +106 -0
- package/runtime/python/okstra_ctl/convergence_engine.py +140 -95
- package/runtime/python/okstra_ctl/convergence_reverify_prompt.py +3 -27
- package/runtime/python/okstra_ctl/convergence_store.py +11 -4
- package/runtime/python/okstra_ctl/dispatch_core.py +4 -1
- package/runtime/python/okstra_ctl/domain/role.py +1 -0
- package/runtime/python/okstra_ctl/manager_cli.py +27 -1
- package/runtime/python/okstra_ctl/manager_launch.py +44 -4
- package/runtime/python/okstra_ctl/manager_sync.py +14 -1
- package/runtime/python/okstra_ctl/manager_view.py +53 -1
- package/runtime/python/okstra_ctl/next_phase.py +1 -0
- package/runtime/python/okstra_ctl/phases/catalog.py +2 -1
- package/runtime/python/okstra_ctl/phases/discussion/__init__.py +1 -0
- package/runtime/python/okstra_ctl/phases/discussion/boundary.json +11 -0
- package/runtime/python/okstra_ctl/phases/discussion/cli.py +512 -0
- package/runtime/python/okstra_ctl/phases/discussion/entry.py +116 -0
- package/runtime/python/okstra_ctl/phases/discussion/followup.py +876 -0
- package/runtime/python/okstra_ctl/phases/discussion/memory.py +294 -0
- package/runtime/python/okstra_ctl/phases/discussion/overview.py +85 -0
- package/runtime/python/okstra_ctl/phases/discussion/profile.json +31 -0
- package/runtime/python/okstra_ctl/phases/discussion/profile.md +48 -0
- package/runtime/python/okstra_ctl/phases/discussion/report.py +73 -0
- package/runtime/python/okstra_ctl/phases/discussion/report_assets/discussion.template.html +15 -0
- package/runtime/python/okstra_ctl/phases/discussion/report_assets/discussion.template.md +6 -0
- package/runtime/python/okstra_ctl/phases/discussion/spec.md +197 -0
- package/runtime/python/okstra_ctl/phases/discussion/store.py +1143 -0
- package/runtime/python/okstra_ctl/phases/discussion/validation.py +111 -0
- package/runtime/python/okstra_ctl/phases/discussion/wizard.py +90 -0
- package/runtime/python/okstra_ctl/phases/discussion/workflow.py +667 -0
- package/runtime/python/okstra_ctl/phases/implementation/instructions/_implementation-deliverable.md +1 -1
- package/runtime/python/okstra_ctl/phases/implementation_planning/host-rules.md +4 -1
- package/runtime/python/okstra_ctl/phases/implementation_planning/instructions/plan-body-verification.md +1 -1
- package/runtime/python/okstra_ctl/phases/requirements_discovery/spec.md +1 -1
- package/runtime/python/okstra_ctl/plan_run_root.py +8 -0
- package/runtime/python/okstra_ctl/render.py +7 -20
- package/runtime/python/okstra_ctl/render_final_report.py +2 -1
- package/runtime/python/okstra_ctl/report_assembly.py +22 -0
- package/runtime/python/okstra_ctl/report_contract.py +2 -0
- package/runtime/python/okstra_ctl/report_narrative.py +13 -4
- package/runtime/python/okstra_ctl/report_synthesis_packet.py +10 -0
- package/runtime/python/okstra_ctl/run.py +40 -7
- package/runtime/python/okstra_ctl/stage_close.py +46 -11
- package/runtime/python/okstra_ctl/stage_reopen.py +151 -0
- package/runtime/python/okstra_ctl/verdict_blocks.py +11 -35
- package/runtime/python/okstra_ctl/wizard/cli.py +8 -0
- package/runtime/python/okstra_ctl/wizard/confirmation.py +4 -1
- package/runtime/python/okstra_ctl/wizard/engine.py +8 -0
- package/runtime/python/okstra_ctl/wizard/ids.py +12 -0
- package/runtime/python/okstra_ctl/wizard/registry.py +35 -3
- package/runtime/python/okstra_ctl/wizard/render.py +4 -1
- package/runtime/python/okstra_ctl/wizard/sources.py +2 -2
- package/runtime/python/okstra_ctl/wizard/state.py +11 -1
- package/runtime/python/okstra_ctl/wizard/steps_identity.py +4 -4
- package/runtime/python/okstra_ctl/worker_prompt_policy.py +24 -6
- package/runtime/python/okstra_token_usage/pricing.py +2 -0
- package/runtime/schemas/convergence-groups-v1.0.schema.json +1 -1
- package/runtime/schemas/convergence-groups-v2.0.schema.json +1 -1
- package/runtime/schemas/final-report-v3.0.schema.json +90 -1
- package/runtime/skills/_fragments/wizard-bash-invocation-rule.md +1 -0
- package/runtime/skills/_fragments/wizard-confirm-step.md +7 -0
- package/runtime/skills/_fragments/wizard-lead-takeover.md +8 -0
- package/runtime/skills/_fragments/wizard-preflight.md +75 -0
- package/runtime/skills/_fragments/wizard-prompt-loop.md +22 -0
- package/runtime/skills/_fragments/wizard-relay-protocol.md +40 -0
- package/runtime/skills/_fragments/wizard-render-bundle.md +74 -0
- package/runtime/skills/okstra-discussion/SKILL.md +347 -0
- package/runtime/skills/okstra-pr-gen/SKILL.md +7 -0
- package/runtime/skills/okstra-run/SKILL.md +22 -2
- package/runtime/templates/manager/view.template.html +3 -0
- package/runtime/templates/reports/html/i18n/en.json +5 -0
- package/runtime/templates/reports/html/i18n/ko.json +5 -0
- package/runtime/validators/validate-brief.py +3 -2
- package/runtime/validators/validate-run.py +4 -6
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# discussion process
|
|
2
|
+
|
|
3
|
+
## Guarantees
|
|
4
|
+
|
|
5
|
+
| ID | Guarantee | Enforcement |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| DS-1 | `discussion` is a registered task type whose profile, role contract and report assets live in this package | `tests/contract/test_phase_catalog.py::test_migrated_profile_is_absent_from_the_legacy_directory` |
|
|
8
|
+
| DS-2 | Roles are analyser 1..5 (recommended 3), report-writer 1 and a dynamic verifier per selected analyser; the lead is the facilitator | `tests/domain/test_role_requirements.py::test_every_public_task_type_declares_complete_role_requirements` |
|
|
9
|
+
| DS-3 | Analysers receive the `discussion-worker` duty through the common prompt policy | `scripts/okstra_ctl/worker_prompt_policy.py::ANALYSIS_DUTY_BY_TASK_TYPE` |
|
|
10
|
+
| DS-4 | A supported lead reaches the common worker dispatch plan without a separate executor | `tests/run/test_discussion_prepare.py::test_a_claude_code_lead_reaches_the_analyser_dispatch_plan` |
|
|
11
|
+
| DS-5 | A non-`claude-code` lead outside cmux is rejected before any task artifact is written | `tests/adapters/dispatch/test_cmux.py::test_a_non_claude_lead_still_needs_render_only_outside_cmux` |
|
|
12
|
+
| DS-6 | The run carries no lifecycle routing; the next-phase pointer stays pending | `scripts/okstra_ctl/next_phase.py::_SIDETRACK_TASK_TYPES` |
|
|
13
|
+
| DS-7 | Completion requires the user's confirmation, not agent consensus or a convergence stop: the derived phase reaches `completed` only from a revision published by `confirm-completion`, and `cycle-close` only ends a verification cycle (DS-19) | `scripts/okstra_ctl/phases/discussion/tests/test_discussion_completion.py::test_publication_is_refused_until_the_user_confirms_a_complete_draft` |
|
|
14
|
+
| DS-8 | A discussion starts without a brief: prepare takes `--discussion-start`, refuses `--task-brief`, and writes the input document that takes the brief's place | `tests/run/test_discussion_prepare.py::test_direct_entry_starts_without_a_brief_and_records_the_start_input` |
|
|
15
|
+
| DS-9 | A start-input error is refused before prepare writes any run artifact or discussion record | `tests/run/test_discussion_prepare.py::test_an_input_error_writes_no_partial_run` |
|
|
16
|
+
| DS-10 | The wizard asks for the start input instead of a brief, from a new task (`okstra wizard init --task-type discussion`) and from an existing task; every other task type keeps its question order | `tests/domain/wizard/test_discussion_start.py::test_a_requested_discussion_starts_a_new_task_without_a_brief`, `tests/domain/wizard/test_discussion_start.py::test_an_existing_task_continues_its_discussion_without_a_brief_step`; `tests/domain/wizard/test_task_selection.py::test_brand_new_task_branches_into_group_then_brief_then_type_then_id` |
|
|
17
|
+
| DS-11 | A resent question, answer or decision returns the stored record instead of adding a second one; a different answer to an answered question is refused | `scripts/okstra_ctl/phases/discussion/tests/test_discussion_store.py::test_a_resent_answer_is_stored_once_and_a_different_one_is_refused` |
|
|
18
|
+
| DS-12 | An append cut off mid-line and a draft that lags the records both recover on the next read or write | `scripts/okstra_ctl/phases/discussion/tests/test_discussion_store.py::test_an_interrupted_append_and_a_stale_draft_recover` |
|
|
19
|
+
| DS-13 | Concurrent writers keep every record and get distinct ids | `scripts/okstra_ctl/phases/discussion/tests/test_discussion_store.py::test_concurrent_writes_keep_every_record_with_distinct_ids` |
|
|
20
|
+
| DS-14 | A record names its author and the discussion run it came from; a run manifest of another task, or a start input naming another task's discussion, is refused | `scripts/okstra_ctl/phases/discussion/tests/test_discussion_store.py::test_a_run_of_another_task_is_refused`, `scripts/okstra_ctl/phases/discussion/tests/test_discussion_store.py::test_only_the_addressee_answers_and_records_keep_author_and_run`; `tests/domain/wizard/test_discussion_start.py::test_the_wizard_refuses_another_tasks_discussion` |
|
|
21
|
+
| DS-15 | Continuing a discussion hands the next run the confirmed subject, the user decisions and the questions still waiting for an answer | `tests/run/test_discussion_prepare.py::test_a_resumed_run_restores_decisions_and_pending_questions` |
|
|
22
|
+
| DS-16 | A direct question is dispatched alone to its addressee as a new `discussion-question` invocation built from the discussion read model, through the common `agent-prompt materialize --batch` path; its whole result is recorded once as the addressee's answer and reaches every participant's next instruction | `scripts/okstra_ctl/phases/discussion/tests/test_discussion_workflow.py::test_a_direct_question_is_dispatched_alone_and_its_answer_reaches_every_participant` |
|
|
23
|
+
| DS-17 | Questions go to the user one at a time in record order, and no research cycle opens while a question waits for the user or a participant | `scripts/okstra_ctl/phases/discussion/tests/test_discussion_workflow.py::test_questions_go_to_the_user_one_at_a_time_and_research_waits_for_the_answers` |
|
|
24
|
+
| DS-18 | A research cycle whose input references (start inputs, subject, answers, materials) equal the last closed cycle's is not dispatched; the remaining disagreements and options go to the user. New evidence or a new alternative opens the next cycle in a continuation run | `scripts/okstra_ctl/phases/discussion/tests/test_discussion_workflow.py::test_a_repeated_input_is_not_researched_again_and_goes_to_the_user` |
|
|
25
|
+
| DS-19 | Closing a cycle imports the convergence findings as claims with their origin author and the verifiers' verdicts, ends that verification cycle only, and leaves user decisions and user questions untouched | `scripts/okstra_ctl/phases/discussion/tests/test_discussion_workflow.py::test_a_fact_check_never_settles_the_users_intent` |
|
|
26
|
+
| DS-20 | An author's vote on its own claim is refused, not counted as a refutation — the convergence engine's final-state validation is reused | `scripts/okstra_ctl/phases/discussion/tests/test_discussion_workflow.py::test_an_authors_check_of_its_own_claim_is_refused_as_a_refutation` |
|
|
27
|
+
| DS-21 | A batch-start failure and a failure left after the dispatcher's retry loop are recorded per participant without deleting completed results; the cycle neither reopens research nor closes until the user chooses retry, replace or reduce, and a second, different choice for the same failure is refused | `scripts/okstra_ctl/phases/discussion/tests/test_discussion_workflow.py::test_a_start_failure_keeps_completed_results_and_a_reduced_cycle_closes`, `scripts/okstra_ctl/phases/discussion/tests/test_discussion_workflow.py::test_a_failure_after_the_retry_loop_is_retried_in_the_same_run` |
|
|
28
|
+
| DS-22 | A retry stays in the same run as a new invocation; a replacement continues in a run prepared with `--discussion-start <task-key>`, whose research input carries the earlier results under their original author and run, and every cycle records its roster | `scripts/okstra_ctl/phases/discussion/tests/test_discussion_workflow.py::test_a_failure_after_the_retry_loop_is_retried_in_the_same_run`, `scripts/okstra_ctl/phases/discussion/tests/test_discussion_workflow.py::test_a_replacement_continues_in_a_new_run_and_keeps_the_original_authors` |
|
|
29
|
+
| DS-23 | A failed direct question waits for the user's choice; `reduce` drops it from the participants' pending questions and `replace` is refused for it | `scripts/okstra_ctl/phases/discussion/tests/test_discussion_workflow.py::test_a_failed_direct_question_can_be_dropped_without_blocking_research` |
|
|
30
|
+
| DS-24 | Concurrent `cycle-open` calls for one run record one cycle | `scripts/okstra_ctl/phases/discussion/tests/test_discussion_workflow.py::test_concurrent_cycle_opens_record_one_cycle` |
|
|
31
|
+
| DS-25 | On a `claude-code` lead, whose analysers run as native sessions, `settle` reads the common `record-dispatch` / `link-result` / `abandon-attempt` records: a linked result is a result, an attempt closed without one is a failure, and an open attempt refuses settlement | `scripts/okstra_ctl/phases/discussion/tests/test_discussion_workflow.py::test_a_claude_code_lead_settles_its_native_session_research` |
|
|
32
|
+
| DS-26 | A `discussion-question` prompt keeps the analysis contract but stays outside the analysis-core prompt equality group | `tests/contract/test_critic_prompt_equality_exemption.py::test_a_discussion_question_keeps_the_analysis_contract_outside_the_equality_group` |
|
|
33
|
+
| DS-27 | A confirmation names the draft version the user saw; a version older than the current records, or a stored confirmation followed by any later record, publishes nothing | `scripts/okstra_ctl/phases/discussion/tests/test_discussion_completion.py::test_a_stale_confirmation_is_refused` |
|
|
34
|
+
| DS-28 | Completion is refused while the subject, a requirement, an in-scope or an excluded entry, a closed research cycle, a user or participant answer, or a failure choice is missing; remaining disagreements do not block and are carried into the revision with their standing | `scripts/okstra_ctl/phases/discussion/tests/test_discussion_completion.py::test_publication_is_refused_until_the_user_confirms_a_complete_draft`, `scripts/okstra_ctl/phases/discussion/tests/test_discussion_completion.py::test_the_user_completes_with_disagreements_and_the_revision_keeps_them` |
|
|
35
|
+
| DS-29 | A revision carries the subject, start inputs, requirements with the follow-up they apply to, scope and exclusions, direction, the user's confirmation, decisions, disagreements, evidence claims, unverified scope, every cycle's roster and every failure with the user's choice; a reduced roster is marked and its failure listed as unverified | `scripts/okstra_ctl/phases/discussion/tests/test_discussion_completion.py::test_the_user_completes_with_disagreements_and_the_revision_keeps_them`, `scripts/okstra_ctl/phases/discussion/tests/test_discussion_completion.py::test_a_reduced_roster_is_recorded_as_unverified_scope` |
|
|
36
|
+
| DS-30 | One confirmation publishes one revision, `<task-key>/revision-NNN` by completion sequence, written once read-only; a resent or concurrent confirmation returns that revision | `scripts/okstra_ctl/phases/discussion/tests/test_discussion_completion.py::test_a_resent_or_concurrent_confirmation_publishes_one_revision` |
|
|
37
|
+
| DS-31 | A publication interrupted before the revision file is written leaves no revision; resending the same confirmation publishes it | `scripts/okstra_ctl/phases/discussion/tests/test_discussion_completion.py::test_an_interrupted_publication_is_completed_by_the_same_confirmation`, `scripts/okstra_ctl/phases/discussion/tests/test_discussion_completion.py::test_a_stale_confirmation_is_refused` |
|
|
38
|
+
| DS-32 | A completed discussion takes no record but `reopen` naming its latest revision, and prepare refuses a new start input for it; after `reopen`, the new draft and the earlier revision read side by side, the earlier revision's bytes never change, and the next revision carries its id, content digest and the reason for change | `scripts/okstra_ctl/phases/discussion/tests/test_discussion_completion.py::test_reopening_keeps_the_previous_revision_byte_for_byte_beside_the_new_draft` |
|
|
39
|
+
| DS-33 | The final report's `discussion` block is written by report assembly, never by the report writer, and carries only the revision id and content digest (or the draft version); assembly refuses a block whose digest no longer matches the revision file, `validate-run` reports the mismatch, and the run passes the reachable `report-finalize` steps without real providers | `scripts/okstra_ctl/phases/discussion/tests/test_discussion_completion.py::test_a_discussion_report_references_only_the_revision_and_passes_finalize` |
|
|
40
|
+
| DS-34 | Finalizing a run whose discussion is still a draft publishes no revision; its report carries the draft version | `scripts/okstra_ctl/phases/discussion/tests/test_discussion_completion.py::test_finalizing_a_draft_run_never_publishes_a_revision` |
|
|
41
|
+
| DS-35 | A follow-up brief carries the revision's common requirements and those of its follow-up label, each with its record id; the other follow-ups' requirements and the excluded scope are listed as out of scope; it passes `validators/validate-brief.py` and recommends `requirements-discovery`. A discussion with no follow-up lists its labels as unlinked | `scripts/okstra_ctl/phases/discussion/tests/test_discussion_followup.py::test_a_follow_up_brief_carries_its_requirements_and_starts_at_requirements_discovery` |
|
|
42
|
+
| DS-36 | Fields reused from the user's confirmations name their record in the revision; an observation method the revision does not carry is new meaning, so the brief stays `reporter-confirmations: pending` with an `intent-check:` row and prepare refuses it until the user confirms | `scripts/okstra_ctl/phases/discussion/tests/test_discussion_followup.py::test_a_follow_up_brief_carries_its_requirements_and_starts_at_requirements_discovery` |
|
|
43
|
+
| DS-37 | The follow-up's brief pins the discussion, revision id, content digest and requirement ids; prepare records them in the run inputs as `discussionRevisionRef`, and the manager launch packet carries the same reference and starts a new child at `requirements-discovery` | `scripts/okstra_ctl/phases/discussion/tests/test_discussion_followup.py::test_a_follow_up_brief_carries_its_requirements_and_starts_at_requirements_discovery`, `tests/test_okstra_manager_launch.py::test_build_launch_packet_carries_a_discussion_follow_up_revision_and_its_first_phase` |
|
|
44
|
+
| DS-38 | A newer revision flags every linked follow-up `needs-impact-review` with the changes from its pinned revision; a follow-up never moves by itself (`follow-up-brief` for another revision is refused), only the follow-up the user adopts with `apply` moves, a resent `apply` returns the stored record, and a different choice for the same follow-up and revision is refused | `scripts/okstra_ctl/phases/discussion/tests/test_discussion_followup.py::test_only_the_follow_up_the_user_selects_adopts_the_new_revision` |
|
|
45
|
+
| DS-39 | Adopting a revision for a follow-up that has an implementation-planning plan routes it to the plan amendment path and leaves its plan and brief byte-identical | `scripts/okstra_ctl/phases/discussion/tests/test_discussion_followup.py::test_a_follow_up_with_a_plan_is_routed_to_plan_amendment` |
|
|
46
|
+
| DS-40 | The target brief is written before the link record; a link write interrupted after the brief, or a torn link line, is completed by resending the same command, and the link is recorded once | `scripts/okstra_ctl/phases/discussion/tests/test_discussion_followup.py::test_an_interrupted_link_write_is_retried_once` |
|
|
47
|
+
| DS-41 | `links` re-reads each target's brief, also in another project; a target that cannot be read is `unknown`, never `current`; a brief this command did not write last is not replaced without `--replace` | `scripts/okstra_ctl/phases/discussion/tests/test_discussion_followup.py::test_a_target_in_another_project_is_read_back_and_unknown_when_unreadable` |
|
|
48
|
+
| DS-42 | `confirm-completion` registers the published revision in the Memory Book without a prompt, through `okstra memory add --source-key discussion:<revision-id>`; the entry holds a summary of decisions, disagreements, risks and unverified scope with the revision id, content digest, project and task — not the start inputs or the agreed evidence — and searching the revision id finds it | `scripts/okstra_ctl/phases/discussion/tests/test_discussion_memory.py::test_completion_registers_a_summary_that_traces_back_to_the_revision` |
|
|
49
|
+
| DS-43 | A re-discussion's revision is a new source key and a separate Memory Book entry | `scripts/okstra_ctl/phases/discussion/tests/test_discussion_memory.py::test_a_re_discussion_registers_a_separate_entry` |
|
|
50
|
+
| DS-44 | A resent confirmation does not call the Memory Book again, and concurrent registrations of one revision leave one entry; with the discussion's registration state lost, the Memory Book's own source-key check returns the stored entry | `scripts/okstra_ctl/phases/discussion/tests/test_discussion_memory.py::test_a_resent_or_concurrent_registration_leaves_one_entry`, `tests-js/memory.test.mjs::concurrent memory add --source-key of one key leaves one entry` |
|
|
51
|
+
| DS-45 | A failed registration leaves the revision published and `pending`; `status` lists it under `memoryRegistration` and `memory-sync` retries it with the same source key | `scripts/okstra_ctl/phases/discussion/tests/test_discussion_memory.py::test_a_failed_registration_keeps_the_revision_and_memory_sync_recovers` |
|
|
52
|
+
| DS-46 | A summary the Memory Book refuses as a likely secret is `refused` with the secret kinds shown; `memory-sync` does not retry it, and an unchanged `--summary-file` is refused; only a changed summary registers | `scripts/okstra_ctl/phases/discussion/tests/test_discussion_memory.py::test_a_secret_refusal_is_shown_and_retried_only_after_the_summary_changes`, `tests-js/memory.test.mjs::memory add --source-key --json reports a secret refusal as JSON on stdout` |
|
|
53
|
+
| DS-47 | `memory add` without `--source-key` writes the same index row and frontmatter as before and takes no source-key lock | `tests-js/memory.test.mjs::a plain memory add keeps its index row and frontmatter free of a source key` |
|
|
54
|
+
| DS-48 | `okstra manager task sync` records a discussion child's state, phase, pending questions, revisions with their change reasons and each follow-up's pinned revision and review state by reference — no requirement or revision content — without writing to the discussion's project; `task status` counts the child under `discussion`, never `done`, and a discussion completes with no manager | `tests/test_okstra_manager_sync.py::test_sync_references_a_discussion_and_counts_it_apart_from_follow_up_completion` |
|
|
55
|
+
| DS-49 | A discussion record or follow-up brief the manager cannot read is `unknown` on the next sync, not its last synced value | `tests/test_okstra_manager_sync.py::test_an_unreadable_discussion_source_is_unknown_not_its_last_sync` |
|
|
56
|
+
| DS-50 | A manager child without a registered brief launches with the follow-up brief `follow-up-brief` wrote for it, found in the last-synced links of a discussion child of the same manager task, and carries its pinned revision | `tests/test_okstra_manager_sync.py::test_sync_references_a_discussion_and_counts_it_apart_from_follow_up_completion` |
|
|
57
|
+
| DS-51 | `okstra-discussion` (`okstra wizard init --task-type discussion`) and `okstra-run`'s task-type pick produce the same `render-bundle` argv and the same prepared run inputs | `tests/run/test_discussion_prepare.py::test_both_entrypoints_prepare_the_same_discussion_run` |
|
|
58
|
+
| DS-52 | `okstra.sh` forwards `--discussion-start`, asks for it instead of a brief, and passes no `--task-brief` for a discussion | `tests/run/test_okstra_sh_discussion_passthrough.py::test_okstra_sh_forwards_the_discussion_start_and_a_continuation_without_a_brief` |
|
|
59
|
+
| DS-53 | A same-provider and a mixed-provider roster go through the same prepare, assignment and research-batch `materialize` path; on a `claude-code` lead only the analysers outside the host's provider are planned by `worker-dispatch` as `cli-wrapper` | `tests/run/test_discussion_prepare.py::test_same_and_mixed_provider_rosters_take_the_same_research_path` |
|
|
60
|
+
| DS-54 | The lead instructions carry the boundary's source-edit and implementation-plan prohibitions; every discussion worker runs under a `source-readonly` write policy, and a worker that edits source is refused by the common mutation audit and recorded as a failure, never a result | `tests/run/test_discussion_prepare.py::test_a_discussion_worker_that_edits_source_is_refused_and_records_no_result` |
|
|
61
|
+
| DS-55 | Concurrent `apply` choices for one follow-up and revision record one `apply`; the other choices are refused and the brief follows only the recorded choice | `scripts/okstra_ctl/phases/discussion/tests/test_discussion_followup.py::test_concurrent_choices_for_one_follow_up_record_one_apply` |
|
|
62
|
+
| DS-56 | One isolated project runs the whole flow through prepare and `okstra discussion`: a description start (no research before the subject is confirmed), a refutation cycle, an interrupted run resumed with the same pending question and a resent answer stored once, a second cycle opened only by new input, completion with a remaining disagreement and Memory Book registration, two follow-ups at `requirements-discovery` with the revision in the run inputs, a re-discussion, and adoption of the new revision by one follow-up only; the project source stays unchanged | `scripts/okstra_ctl/phases/discussion/tests/test_discussion_end_to_end.py::test_a_discussion_runs_from_a_description_to_one_adopted_follow_up` |
|
|
63
|
+
| DS-57 | After `reopen` the draft starts with the reopened revision's requirements and scope under their original record ids, each marked with that revision; completion needs no re-entry of them; adding an item the draft holds returns it; `withdraw` removes one item with the user's reason, a resend returns it and a second withdrawal of the same item is refused; the next revision's `itemChanges` lists what it carried, added and withdrawn, `impact` shows each withdrawal's reason, and the earlier revision's bytes do not change | `scripts/okstra_ctl/phases/discussion/tests/test_discussion_completion.py::test_a_reopened_draft_carries_the_previous_items_until_the_user_withdraws_them`, `scripts/okstra_ctl/phases/discussion/tests/test_discussion_followup.py::test_only_the_follow_up_the_user_selects_adopts_the_new_revision` |
|
|
64
|
+
| DS-58 | A follow-up routed to plan amendment moves only through `complete-amendment`, and only after the target has an implementation-planning report newer than the one at routing with `frontmatter.approved: true`; without it the command is refused naming what is missing and `links` stays `amendment-pending` with that reason (`unknown` when the plan cannot be read). Completion rewrites the brief under the `--replace` rule, records the previous and new revision, the routed `apply` and the plan report, leaves every plan byte unchanged, turns the target `current`, and a resend returns the record | `scripts/okstra_ctl/phases/discussion/tests/test_discussion_followup.py::test_a_routed_adoption_moves_the_pin_only_after_the_user_approves_the_amended_plan` |
|
|
65
|
+
|
|
66
|
+
## 1. Purpose
|
|
67
|
+
|
|
68
|
+
`discussion` is an optional task type. The user and several agents settle the requirements and implementation direction for a ticket or project before a follow-up task plans the work. It does not replace `requirements-discovery` or `implementation-option-selection`; a follow-up task starts at `requirements-discovery` in a separate run.
|
|
69
|
+
|
|
70
|
+
## 2. Roles
|
|
71
|
+
|
|
72
|
+
`profile.json` uses the `requirements-discovery` composition without the critic slot: `analyser` workers research the subject (`discussion-worker` duty), a dynamic `verifier` refutes each selected analyser's claims, and one `report-writer` authors the report. The lead (`leader` role) acts as facilitator: it confirms the subject and question with the user, relays questions one at a time, shares every answer with all participants and records user decisions separately from factual claims. `profile.md` owns these instructions.
|
|
73
|
+
|
|
74
|
+
Model selection uses the common role-count and role-model steps (`wizard/roles.py`) and the common prepare assignment path. Same-provider and mixed-provider rosters go through the same path.
|
|
75
|
+
|
|
76
|
+
## 3. Supported hosts
|
|
77
|
+
|
|
78
|
+
The supported combinations are the existing ones: a `claude-code` lead, or any lead under cmux. `prepare_task_bundle` rejects any other lead runtime without `--render-only` before it writes task artifacts. This phase reuses that check unchanged, together with the Codex current-session permission probe.
|
|
79
|
+
|
|
80
|
+
## 4. Output boundary
|
|
81
|
+
|
|
82
|
+
`boundary.json` allows research, cross-checking, user questions and the confirmed requirements and direction. It forbids source edits, detailed implementation plans, state-mutating commands, starting another phase, and treating consensus or a convergence stop as completion.
|
|
83
|
+
|
|
84
|
+
## 5. Start input
|
|
85
|
+
|
|
86
|
+
`entry.py` owns the start input. `--discussion-start` takes one of three values, classified without asking the caller for a kind: an http(s) URL or a file inside the project is a `link`; a value shaped like a task key names this task's own `discussion` to continue and must already have records; anything else is a `description`. Prepare validates the value before any write (`validate_discussion_start`), then, once the run paths are known, appends a `start` record and writes `<task-root>/discussion/input.md` — the start input of this run followed by the draft read model. That document fills the brief slot, so the global brief preflight (`reporter-confirmations`) is unchanged and is not applied to it.
|
|
87
|
+
|
|
88
|
+
The wizard asks `discussion_start_pick` / `discussion_start` (`wizard.py`) after the task id is known. The pick offers "continue this task's discussion" when the task has records, a brief the user already gave as the start input, and direct input last. A new task asks the brief before the task type, so a brief-less new discussion starts from `okstra wizard init --task-type discussion`, which asks the task type first; a new task started without it keeps the brief-first order and offers the chosen brief as the start input.
|
|
89
|
+
|
|
90
|
+
## 6. Discussion record
|
|
91
|
+
|
|
92
|
+
`store.py` owns `<task-root>/discussion/`: `records.jsonl` holds the input records (start inputs, the confirmed subject and question, questions, answers, user decisions) and `draft.json` is the read model rebuilt from them. Every write takes the directory lock, drops a trailing line an interrupted append left, refuses or deduplicates the record, appends it with `fsync`, then rebuilds the draft. A draft whose records digest no longer matches is rebuilt on the next read. Each record carries the task key, its author, the project-relative run manifest it came from and an identity key over its content; the run that sent it is left out of that key so a resumed run's resend is recognised.
|
|
93
|
+
|
|
94
|
+
`okstra discussion` (`cli.py`) is the only writer the facilitator uses: `confirm-subject`, `ask` (a question for the user), `address` (the user's question to one worker), `answer`, `decide`, `add-material`, `draft` and `status`, plus the cycle commands in §7. `--run-manifest` must name a discussion run manifest of the same task; authors are `leader` or a worker of that run, and only a question's addressee answers it. `draft` prints the fixed text every participant reads as the next input. Per-run artifacts keep the common `runs/discussion/` paths.
|
|
95
|
+
|
|
96
|
+
## 7. Research, refutation and question cycle
|
|
97
|
+
|
|
98
|
+
`workflow.py` owns the cycle; it never dispatches a worker or judges a claim itself. Workers run through the common assignment and dispatch path (the `agent-prompt materialize --batch` step, then `worker-dispatch` or `team dispatch`, whose retry loop is `dispatch_core._dispatch_cli_wrapper_batch`), and claims are judged by Phase 5.5 adversarial convergence (`okstra convergence …`, `convergence_engine.py`). One research cycle runs per run, because a run owns exactly one convergence state and grouping path (`convergence-<task-type>-<seq>.json`); the next cycle, and a replacement roster, open in a continuation run prepared with `--discussion-start <task-key>`.
|
|
99
|
+
|
|
100
|
+
New record kinds, all appended under the store lock in one transaction per command:
|
|
101
|
+
|
|
102
|
+
| Kind | Id | Written by | Holds |
|
|
103
|
+
|---|---|---|---|
|
|
104
|
+
| `cycle` | `R-NNNN` | `cycle-open` | input references, roster (worker and model), research instruction path and digest |
|
|
105
|
+
| `result` | `W-NNNN` | `settle` | the analyser's completed `initial` dispatch and its result path |
|
|
106
|
+
| `failure` | `X-NNNN` | `settle`, `collect-answer` | stage (`start` when the batch could not launch — `dispatch_core.CLI_BATCH_LAUNCH_FAILURE` — otherwise `after-retries`), attempts, status, reason; a cycle or a question |
|
|
107
|
+
| `recovery` | `P-NNNN` | `recover` | the user's choice for one failure: `retry`, `replace` or `reduce` (`retry` or `reduce` for a question) |
|
|
108
|
+
| `claim` | `K-NNNN` | `cycle-close` | one convergence finding: summary, origin author, discovering workers, evidence, classification and every verdict with its verifier |
|
|
109
|
+
| `close` | `E-NNNN` | `cycle-close` | the convergence state path and digest, its final state and stop reason |
|
|
110
|
+
| `material` | `M-NNNN` | `add-material` | new evidence or a new alternative and its author |
|
|
111
|
+
|
|
112
|
+
The draft read model derives the phase: `framing` (no confirmed subject), `awaiting-recovery` (a failure without the user's choice), `researching` (the latest cycle is open), `awaiting-new-run` (the user chose a replacement), `awaiting-user`, `awaiting-participant`, `ready-for-research` (no cycle yet, or the input references changed since the last closed cycle), `awaiting-choice` (the references equal the last closed cycle's: the remaining disagreements and options go to the user), and `completed` (§8), which only a published revision sets. `cycle-open` returns `dispatch` with the research instruction, or `await-user` / `await-participant` / `await-recovery` / `present-options` / `prepare-run` / `completed` and records nothing. The research instruction and the direct-question instruction both embed the fixed-text draft, so every answer reaches every participant's next input.
|
|
113
|
+
|
|
114
|
+
## 8. Completion and revisions
|
|
115
|
+
|
|
116
|
+
`add-requirement` (kind `requirement`, `N-NNNN`) and `add-scope` (kind `scope`, `B-NNNN`, `in-scope` or `excluded`) record the requirements and scope of the current draft. `withdraw` (kind `withdrawal`, `U-NNNN`, author `user`) names one item of the current draft and the user's reason. `store.draft_items` derives the current items from the records alone: every `reopen` carries the items in effect at the reopened revision into the new draft under their original record, author and run, marked `carriedFrom: <revision-id>`; an added item joins, and a withdrawal removes its item. Because a revision is built from the same records, the carried set equals the reopened revision's requirements and scope without reading its file, and no carried item is re-recorded as if the user had written it again. Adding an item whose fields equal one the draft holds returns that item; an item withdrawn and added again is a new record. The identity key of an item and of a withdrawal carries the latest `reopen`, so a withdrawal resent in the same draft returns its record, and a second withdrawal of the same item is refused. Replacing an item is a withdrawal followed by an addition.
|
|
117
|
+
|
|
118
|
+
`confirm-completion` (`store.confirm_completion`) runs under the store lock. It refuses a draft version that is not the current records digest, then runs `validation.completion_errors` on the draft, appends a `confirmation` record (`F-NNNN`, author `user`: draft version, direction, risk confirmation) and publishes. Publication builds the revision only from the records up to that confirmation, checks it with `validation.revision_errors`, writes `<task-root>/discussion/revisions/revision-NNN.json` once (a temporary file linked into place, mode read-only) and appends a `publication` record (`V-NNNN`: revision id, sequence, path, content digest). The revision id is `<task-key>/revision-NNN`; NNN is the completion sequence, not the run sequence, so a revision may rest on cycles from several runs. Resending a confirmation returns its publication, or completes a publication an interruption left without one when no record arrived after the confirmation.
|
|
119
|
+
|
|
120
|
+
After a publication the phase is `completed` and every record kind except `reopen` (`O-NNNN`, author `user`, naming the latest revision and the reason) is refused; prepare refuses a new link or description start input for that task. The next revision's `previousRevision` holds the reopened revision's id, its content digest, the `reopen` record and the reason. Every revision's requirements and scope entries carry `carriedFrom`, and its `itemChanges` lists the items carried from `carriedFrom`, the items added in this draft and the items withdrawn with their withdrawal record and reason; `validation.revision_errors` refuses a revision whose carried and added items differ from its requirements and scope. `revisions` lists the publications, `revision-show` prints one revision's bytes after checking its digest against its publication, and `record` prints the draft read model followed by every research result file in full.
|
|
121
|
+
|
|
122
|
+
`report.py` owns the report side. Report assembly (`report_assembly._attach_discussion`) writes the `discussion` block from the record — `{outcome: revision, revisionId, contentDigest}` for a completed discussion, otherwise `{outcome: draft, draftVersion}` — and `discussion_report_errors` checks a revision reference against the publication and the file, both in assembly (refusal) and in `validate-run` (advisory; not in `blocking_checks`). The block is outside the report-narrative allowlist, so the report writer cannot author it; the synthesis packet tells the writer so. The v3 final-report schema requires the block for `discussion` and forbids it and every other task block elsewhere.
|
|
123
|
+
|
|
124
|
+
## 9. Follow-ups
|
|
125
|
+
|
|
126
|
+
`followup.py` owns the link from a revision to its follow-up tasks. These commands run outside a discussion run, at the user's request, and never start a follow-up run.
|
|
127
|
+
|
|
128
|
+
`follow-up-brief` renders one brief for one follow-up label of one revision into the target project, `<target-project-root>/.okstra/briefs/<task-group>/<task-id>-<label>.md` (the discussion's own project unless `--target-project-root` names another). It takes the common requirements and those whose `appliesTo` is the label, in revision order, and lists the other labels' requirements and the excluded scope under Constraints as out of scope. The subject, question, direction, in-scope entries, disagreements and unverified scope come from the revision, and the Reporter Confirmations section quotes the user's confirmations (subject, direction and risks, user decisions and answers) with their record ids. `--end-state <N-NNNN>=<EB|PB|EO>[:<how>]` maps a requirement to an end-state item; a requirement without one becomes a `general:` open question. A requirement whose text already ends in `— verify: …` keeps it; any other observation method is added by the conversion, so the brief gets an `intent-check:` row and an `intent-inference` entry per item and `reporter-confirmations: pending`, and prepare refuses it until okstra-brief-gen Step 6.5 records the user's answer. Without such an item the brief is `complete`. The rendered text passes `validators/validate-brief.py` (`generator: okstra-discussion`) before anything is written, and recommends `requirements-discovery`; the requirements-discovery and implementation-option-selection gates are unchanged.
|
|
129
|
+
|
|
130
|
+
The brief frontmatter pins `discussion-task`, `discussion-revision`, `discussion-content-digest`, `discussion-follow-up` and `discussion-requirements`. That brief is the follow-up's own record of the revision it uses: prepare copies the pin into the run inputs as `discussionRevisionRef` (only for such a brief, so other runs' input snapshots are unchanged), and `manager_launch.build_launch_packet` adds it to the launch packet, the child context and the launch event, recommending `requirements-discovery` for a child that does not exist yet.
|
|
131
|
+
|
|
132
|
+
`<task-root>/discussion/links.jsonl` holds `link` (`L-NNNN`: target task key and project root, brief path and digest, revision, requirement ids, end-state mapping) `apply` (`Y-NNNN`) and `amendment` (`Z-NNNN`, written by `complete-amendment`) records, appended under the discussion lock with an identity key; it is separate from `records.jsonl`, so linking never changes the draft version. The target brief is written first and the link after it: two projects are never written as one step, and a resend after an interruption records the missing link. A brief whose bytes differ from what this command wrote last is replaced only with `--replace`.
|
|
133
|
+
|
|
134
|
+
`links` derives each target's state from the target, not from the link list: `current` (it pins the latest revision), `needs-impact-review` (a newer revision exists and the user has not chosen), `kept`, `amendment-pending` (with `amendedPlan`, the approved newer plan report or `null`, and `next`, the `complete-amendment` command or what is missing), or `unknown` when the brief is missing, unreadable or pins something that is not a published revision of this discussion, or when the plan reports of an `amendment-pending` target cannot be read. A target moved by `complete-amendment` names its `amendmentRecord`. Each view carries `hasPlan`, the plan's revision basis (the latest implementation-planning run inputs), and the changes from the pinned revision. `impact` computes the changes between two revisions — requirements, in-scope and excluded entries, decisions, disagreements, unverified scope, direction and the reason for change — for one follow-up with `--follow-up`. Requirement and scope changes also list the `carried` entries, and a removed entry names the withdrawal (record, author, reason) the newer revision recorded for it.
|
|
135
|
+
|
|
136
|
+
`apply` records the user's choice for one follow-up and the latest revision. `keep` changes nothing. `adopt` re-renders the brief of a follow-up without an implementation-planning report or prepared planning run (end states carry over by requirement text, `--end-state` adds new ones) and records a new link. A follow-up with a plan is routed to the plan amendment path — a `revision-requested` plan decision through okstra-user-response and an implementation-planning run with `--clarification-response` — and its plan, brief and source are not touched. The `apply` record holds the previous and new revision, the choice and the result, and a routed one also the latest planning report at that moment (`planReport`, `planReportSeq`, 0 when only a prepared run exists); a resend returns it and a different choice for the same follow-up and revision is refused.
|
|
137
|
+
|
|
138
|
+
`complete-amendment` finishes a routed adoption (`amendment` record, `Z-NNNN`, in `links.jsonl`). Its evidence is an implementation-planning report of the target with a run seq above `planReportSeq` whose `frontmatter.approved` is `true` (`plan_approval.plan_is_approved`). A newer planning run alone is not enough: implementation accepts only an approved plan (`run._validate_approved_plan`), so moving the pin on an unapproved report would leave the brief naming a revision that the plan the target follows does not reflect. The command reads that flag and never sets it; the user approves the amended plan in the planning path (`--approve` or the in-session wizard). Without such a report it is refused with the run seq it compared against and the newer reports it found. It refuses a brief that pins neither the routed apply's previous revision nor the new one, then rewrites the brief exactly as an unplanned `adopt` does (end states carried by requirement text, `--end-state` for new ones, `--replace` for a brief this command did not write last), appends the new link and records the previous and new revision, the `apply` it completes and the plan report it relied on. A resend returns the record; an interruption after the brief write is completed by resending. The amended planning run's inputs still carry the previous pin as `discussionRevisionRef`, because the brief moves only after the plan is approved.
|
|
139
|
+
|
|
140
|
+
## 10. Memory registration
|
|
141
|
+
|
|
142
|
+
`memory.py` owns the link from a revision to its Memory Book entry. The Memory Book format belongs to the Node command `okstra memory add`; Python calls it, it does not write the Memory Book. `okstra discussion` reaches it through `--okstra-cli`, which the command registry injects as the running package's `bin/okstra` (`src/lib/python-command.mts`), so the registration uses the same package as the command and not whatever `okstra` is first on `PATH`.
|
|
143
|
+
|
|
144
|
+
The source key is `discussion:<revision-id>`, that is `discussion:<project-id>:<task-group>:<task-id>/revision-NNN`. `okstra memory add --source-key` takes a lock per key (`<memory-book>/source-locks/<sha256>.lock`, the lock `chat` uses, in `src/lib/file-lock.mts`), returns the entry the index already holds for that key (`created: false`), and otherwise writes the entry with the key in its index row and frontmatter. An add without `--source-key` takes no lock and writes what it wrote before.
|
|
145
|
+
|
|
146
|
+
After `confirm-completion` publishes (or returns) a revision, the command writes the summary to `<task-root>/discussion/memory/revision-NNN.<digest12>.md` and the state to `revision-NNN.json` under the discussion lock, then calls `okstra memory add --file <summary> --source-key … --type decision --source discussion --project <project-id> --tag discussion --yes --json` outside that lock, and records the outcome under it: `registered` with the entry id, `refused` with the secret kinds and the summary digest it refused, or `pending` with the reason (a non-zero exit, unreadable output, a timeout, or no `--okstra-cli`). The title carries only the revision id, so a secret can only come from the summary. The publication is already committed when the call starts; no registration outcome changes it.
|
|
147
|
+
|
|
148
|
+
`status` lists every published revision's registration under `memoryRegistration` without writing. `memory-sync <task-key>` retries every published revision that is not `registered` and not `refused`, including one with no state yet. A `refused` revision is retried only through `memory-sync --revision <id> --summary-file <path>` with a summary whose digest differs from the current one; the new summary is stored beside the old one as `origin: user`. A `registered` revision's summary is not replaced. Retry is not automatic on resume: the facilitator runs `status` on entry and on resume (profile "Facilitator"), and `memoryRegistration` tells it to run `memory-sync`; `status` itself stays read-only.
|
|
149
|
+
|
|
150
|
+
## 11. Manager view and entrypoints
|
|
151
|
+
|
|
152
|
+
`overview.py` owns what a manager reads. `manager_sync._snapshot_child` calls `discussion_overview` for a child whose task type is `discussion` and stores the result under the child's `discussion` key: the state (`draft`, `completed`, or `unknown` with the reason), the phase, the draft version, pending questions for the user and for participants, the reopened revision and its reason, every publication with its content digest and the change reason its revision file carries, and the `links` view of each follow-up without its change details. It reads the records (`store.project_draft`, which never rewrites `draft.json`), the revision files and each follow-up's brief; the manager never copies a revision or the record. A sync snapshot is the state at `syncedAt`; the manager view prints that time beside it. `manager_sync.summary_bucket` puts a discussion child in its own `discussion` bucket, so a completed discussion is never counted as done work. A discussion needs no manager.
|
|
153
|
+
|
|
154
|
+
`follow-up-brief` does not write manager state, because a discussion may have no manager. `manager_launch.build_launch_packet` takes the follow-up brief for a child that has no `briefPath` from the last-synced links of a discussion child of the same manager task, then reads the pinned revision from that brief.
|
|
155
|
+
|
|
156
|
+
The public entry is `skills/okstra-discussion/SKILL.md`. It runs `okstra wizard init --task-type discussion`, the same wizard, `render-bundle` and `prepare_task_bundle()` call that `okstra-run` uses, and the two skills share the wizard procedure through `skills/_fragments/wizard-*.md`. `okstra-run` adds `--task-type discussion` when the user asks for a discussion. `okstra.sh` forwards `--discussion-start` and passes no `--task-brief` for a discussion.
|
|
157
|
+
|
|
158
|
+
## 12. Current limits
|
|
159
|
+
|
|
160
|
+
The v2 final-report schema has no `discussion` block; discussion runs use report contract 3.0. Memory registration is not retried in the background: a `pending` revision waits for the next `memory-sync` or resent `confirm-completion`. A revision published before memory registration existed is registered by the first `memory-sync`. `complete-amendment` cannot tell whether the approved amended plan addresses the new revision's changes: it checks only that the user approved a planning report produced after the adoption was routed. Adopting a revision rewrites the follow-up brief in place; earlier bytes survive only in the instruction-set copy of each run that read them. Choosing `EB`, `PB` or `EO` for a requirement is not counted as new meaning; only an added observation method is. The report's `SourceItem` pattern (`^[a-z][a-z-]*:…`) rejects worker ids with a digit such as `claude-analyser-2`, so a claim two same-provider analysers both found stops report assembly; this is common to every task type. `settle` and `collect-answer` read CLI-backed dispatches from team-state `workerDispatches[]` and host-native ones from `agentDispatches[]`, `agentResultLinks[]` and the attempt ledger; a host-native failure carries the attempt's terminal status, not the host's own error text, and is always `after-retries`. A follow-up brief reaches a manager child's launch only when the discussion is a child of the same manager task and was synced after the brief was written; otherwise the child needs its own `briefPath`, which only `okstra manager task split` sets today. The manager does not show the pinned revision of a follow-up child whose discussion it does not link. Real provider runs of the cycle are not measured yet; the tests use provider doubles on the common dispatch path (§14).
|
|
161
|
+
|
|
162
|
+
## 13. Acceptance matrix
|
|
163
|
+
|
|
164
|
+
The design's acceptance scenarios and the tests that prove them. Each test drives the common prepare path and `okstra discussion` with provider doubles; none calls a real provider.
|
|
165
|
+
|
|
166
|
+
| # | Scenario | Tests |
|
|
167
|
+
|---|---|---|
|
|
168
|
+
| 1 | A short description starts the discussion; research runs in parallel only after the user confirms the subject | `test_discussion_end_to_end.py::test_a_discussion_runs_from_a_description_to_one_adopted_follow_up`, `tests/run/test_discussion_prepare.py::test_direct_entry_starts_without_a_brief_and_records_the_start_input`, `tests/run/test_discussion_prepare.py::test_a_claude_code_lead_reaches_the_analyser_dispatch_plan` |
|
|
169
|
+
| 2 | Same-provider and mixed-provider rosters use the same prepare and assignment path | `tests/run/test_discussion_prepare.py::test_same_and_mixed_provider_rosters_take_the_same_research_path` |
|
|
170
|
+
| 3 | A question to one participant and its answer reach every participant's next input | `test_discussion_workflow.py::test_a_direct_question_is_dispatched_alone_and_its_answer_reaches_every_participant` |
|
|
171
|
+
| 4 | Resuming while a question waits restores it, and a stored answer is not processed twice | `tests/run/test_discussion_prepare.py::test_a_resumed_run_restores_decisions_and_pending_questions`, `test_discussion_store.py::test_a_resent_answer_is_stored_once_and_a_different_one_is_refused`, `test_discussion_end_to_end.py::test_a_discussion_runs_from_a_description_to_one_adopted_follow_up` |
|
|
172
|
+
| 5 | A repeated claim set stops further calls and presents disagreements and options | `test_discussion_workflow.py::test_a_repeated_input_is_not_researched_again_and_goes_to_the_user` |
|
|
173
|
+
| 6 | With a grounded disagreement left, the user's risk confirmation publishes a revision that carries it | `test_discussion_completion.py::test_the_user_completes_with_disagreements_and_the_revision_keeps_them` |
|
|
174
|
+
| 7 | Replacing a failed participant opens a new run of the same discussion and keeps earlier authors and results | `test_discussion_workflow.py::test_a_replacement_continues_in_a_new_run_and_keeps_the_original_authors` |
|
|
175
|
+
| 8 | After a re-discussion both follow-ups of the first revision need impact review; only the one the user picks adopts the new revision | `test_discussion_followup.py::test_only_the_follow_up_the_user_selects_adopts_the_new_revision`, `test_discussion_end_to_end.py::test_a_discussion_runs_from_a_description_to_one_adopted_follow_up` |
|
|
176
|
+
| 9 | A failed Memory Book registration keeps the revision; retries leave one entry per revision | `test_discussion_memory.py::test_a_failed_registration_keeps_the_revision_and_memory_sync_recovers`, `test_discussion_memory.py::test_a_resent_or_concurrent_registration_leaves_one_entry` |
|
|
177
|
+
| 10 | A discussion completes without a manager; a linked manager keeps discussion completion apart from implementation completion | `tests/test_okstra_manager_sync.py::test_sync_references_a_discussion_and_counts_it_apart_from_follow_up_completion` |
|
|
178
|
+
| 11 | A discussion run neither edits project source nor writes a detailed implementation plan | `tests/run/test_discussion_prepare.py::test_a_discussion_worker_that_edits_source_is_refused_and_records_no_result`, `test_discussion_end_to_end.py::test_a_discussion_runs_from_a_description_to_one_adopted_follow_up` |
|
|
179
|
+
| 12 | The `okstra-run` wizard starts a discussion without a brief; other task types keep their question order | `tests/domain/wizard/test_discussion_start.py::test_a_requested_discussion_starts_a_new_task_without_a_brief`, `tests/domain/wizard/test_discussion_start.py::test_an_existing_task_continues_its_discussion_without_a_brief_step`, `tests/domain/wizard/test_task_selection.py::test_brand_new_task_branches_into_group_then_brief_then_type_then_id`, `tests/run/test_discussion_prepare.py::test_both_entrypoints_prepare_the_same_discussion_run` |
|
|
180
|
+
| 13 | A lead host outside the supported combinations is refused before the run | `tests/adapters/dispatch/test_cmux.py::test_a_non_claude_lead_still_needs_render_only_outside_cmux` |
|
|
181
|
+
| 14 | A follow-up starts at `requirements-discovery` and its run inputs keep the revision reference | `test_discussion_followup.py::test_a_follow_up_brief_carries_its_requirements_and_starts_at_requirements_discovery`, `tests/test_okstra_manager_launch.py::test_build_launch_packet_carries_a_discussion_follow_up_revision_and_its_first_phase` |
|
|
182
|
+
| 15 | A secret refusal is shown and not retried; the revision stays | `test_discussion_memory.py::test_a_secret_refusal_is_shown_and_retried_only_after_the_summary_changes` |
|
|
183
|
+
|
|
184
|
+
Scenario 11 rests on the write-policy audit for source edits. For plans it rests on the boundary text only: the `validate-run` forbidden-action scan (`validators/forbidden_actions.py::forbidden_patterns_for`) matches shell commands, so a plan written into a worker result or the report narrative is not detected.
|
|
185
|
+
|
|
186
|
+
## 14. Measured and unmeasured combinations
|
|
187
|
+
|
|
188
|
+
Measured, with provider doubles only: a `claude-code` lead outside cmux with `claude` analysers as native sessions (common `record-dispatch` / `link-result` / `abandon-attempt` records), the `cli-wrapper` dispatch of `claude` doubles through `dispatch_core.dispatch_cli_wrapper_plan`, a `codex` analyser planned by `worker-dispatch --dry-run` beside a native `claude` one, prepare of a `codex` lead under cmux in team mode, and the refusal of a `codex` lead outside cmux. Memory Book registration runs the real `bin/okstra memory` command against an isolated `OKSTRA_HOME`.
|
|
189
|
+
|
|
190
|
+
Not measured — no real provider call or login was made, so none of these is marked measured:
|
|
191
|
+
|
|
192
|
+
| Lead host | Analyser providers | Not measured |
|
|
193
|
+
|---|---|---|
|
|
194
|
+
| `claude-code` (outside cmux) | `claude` (native session) | a real research cycle, a direct question and the report writer |
|
|
195
|
+
| `claude-code` (outside cmux) | `codex` or `antigravity` (`cli-wrapper`) | any real dispatch; `antigravity` is not exercised even with doubles |
|
|
196
|
+
| `codex` under cmux | any | team dispatch beyond prepare, and the Codex current-session permission probe on a real host |
|
|
197
|
+
| `antigravity` or `external` under cmux | any | prepare and dispatch |
|