From ee38c6bc00c9a54e96527d1f2b8b781f6b009699 Mon Sep 17 00:00:00 2001 From: C1oud <120496180@qq.com> Date: Sat, 30 May 2026 14:15:30 +0800 Subject: [PATCH 1/2] feat: add adaptive work session mode --- codex/skills/threadsmith/SKILL.md | 89 +++++- .../references/action-contracts.md | 65 ++++ .../references/runtime-contract.md | 66 ++++ ...readsmith-adaptive-work-session-mode-v1.md | 281 ++++++++++++++++++ .../adaptive-work-session-mode-v1-closeout.md | 70 +++++ packages/domain/src/preferences.test.ts | 30 +- packages/domain/src/preferences.ts | 65 +++- packages/runtime/src/contextBudget.test.ts | 36 +++ packages/runtime/src/contextBudget.ts | 17 +- packages/runtime/src/nextBestStep.test.ts | 100 +++++++ packages/runtime/src/nextBestStep.ts | 164 +++++++++- packages/runtime/src/nextBestStepModel.ts | 10 +- packages/runtime/src/supervisorState.test.ts | 2 +- scripts/verify-threadsmith-skill-contract.mjs | 59 ++++ 14 files changed, 1041 insertions(+), 13 deletions(-) create mode 100644 docs/plans/threadsmith-adaptive-work-session-mode-v1.md create mode 100644 docs/reports/adaptive-work-session-mode-v1-closeout.md diff --git a/codex/skills/threadsmith/SKILL.md b/codex/skills/threadsmith/SKILL.md index d89e6f1..35faa8e 100644 --- a/codex/skills/threadsmith/SKILL.md +++ b/codex/skills/threadsmith/SKILL.md @@ -77,6 +77,53 @@ Stop mid-chain only when a real gate appears: internal role handoff, say which role/gate will run next, but do not present it as something the operator must approve. +## Adaptive Work Session Mode + +A work session is a bounded group of related actions inside the current phase. +It is larger than a single role gate and smaller than a new phase. It does not +replace the phase contract, and it must not silently expand scope. + +Use a work session when all are true: + +- the operator has accepted the current direction +- the next 2-4 actions affect the same subsystem and accepted goal +- no unapproved user-visible capability, consumer surface, product semantics + change, provider default, credential, release, or destructive action appears +- verification can stay `narrow` or `standard` +- durable truth can be written at the work-session boundary without losing + auditability + +Do not use a work session when the next action changes product semantics, +exposes a new consumer surface, changes provider / credential / release / +destructive behavior, contradicts committed truth, or follows a failed +verification whose repair path is uncertain. + +Examples: + +- related work session: extract a module, add focused tests, update the direct + existing consumer, and close out once +- single-role drive: the user asks specifically for reviewer only, verifier + only, or "do not implement" +- stop-gate fallback: adding a CLI command, API endpoint, UI route, provider + default, public sync, tag, publish, migration, delete, or reset must pause for + operator review + +### Work-Session Truth Writeback + +At work-session start, do not create a new phase only to name the session. Record +the work-session target in active work or the current packet only if it changes +execution behavior or prevents repeated recommendations. + +During the session, keep internal notes as run evidence, command output, or +local reasoning unless a real stop gate appears. Write durable truth immediately +only for blockers, failed verification, scope changes, or user decisions. + +At work-session closeout, update acceptance state when done-when evidence +changed, active work when role / blocker / next action changed, evidence summary +or current packet when the next operator turn needs the fact, and role packets +when role-relevant truth changed. Do not preserve every internal sub-step as +active truth when the final result already captures it. + ## Execution Cadence Selector Choose execution cadence by state, not only by wording: @@ -153,10 +200,48 @@ done-when evidence, overusing protocol labels, or hitting the same blocker twice. This is a lightweight trajectory check, not a full re-plan after every role. +## Gap Check Budget + +Gap checks prevent wrong work; they must not become action prelude loops. A work +session gets one gap check by default. + +After a gap check selects an implementation path, the next normal action should +be implementation, not another gap check, unless verification failed, scope or +product direction changed, release / destructive / public risk appeared, +provider routing changed, or committed truth contradicts repo evidence. + +## Product / User-Value Heartbeat + +After three consecutive governance-heavy accepted sessions without a heartbeat, +recommend a lightweight value check at the next phase boundary or work-session +closeout. Governance-heavy means `standard` or `audit` closeout; `lite` does not +increment the counter. + +The heartbeat asks whether the project became more usable, understandable, +reliable, playable, or closer to its stated goal; whether the next local +engineering step is still highest value; and whether the operator should choose +between engineering depth, product surface, architecture map, or UX / creative +validation. + +The heartbeat is advisory. It must not rewrite acceptance, force a direction +change, or interrupt an accepted implementation path unless a real stop gate +appears. + ## Output Matrix -Use boundary full output for `recover`, bootstrap, closeout, accepted phases, -phase-boundary reports, and any response that changes durable truth. +Use the smallest closeout tier that still preserves orientation and safety: + +- `lite`: small or low-risk work. Required fields: changed, verification, truth + (`updated`, `unchanged`, or `skipped with reason`), next, and optional risk. +- `standard`: normal bounded implementation. Required fields: result, changed + capability, verification, truth, remaining risk, and next phase. +- `audit`: release, PR / merge, public docs, destructive operations, + architecture boundaries, provider routing, security, or cross-agent state. Use + the full Threadsmith Output Contract skeleton. + +Use boundary full output for `recover`, bootstrap, audit closeout, accepted +phases, phase-boundary reports, and any response that changes durable truth in a +way that affects route, scope, acceptance, release, or cross-agent state. ## Closeout Output Gate diff --git a/codex/skills/threadsmith/references/action-contracts.md b/codex/skills/threadsmith/references/action-contracts.md index 5900c7c..5e3198b 100644 --- a/codex/skills/threadsmith/references/action-contracts.md +++ b/codex/skills/threadsmith/references/action-contracts.md @@ -259,6 +259,71 @@ Use `下一内部 gate` for these transitions if they need to be named. Reserve `下一 phase 预览` for closeout or a genuine new phase. This avoids presenting reviewer, verifier, or closeout as a fresh user decision. +## Adaptive Work Session Rule + +A work session is a bounded group of related actions inside the current phase. +It keeps Threadsmith from turning every small implementation move into a new +micro-phase, while preserving planner, executor, reviewer, verifier, closeout, +and hygiene gates. + +Use work-session continuation when: + +- the operator accepted the current direction; +- the next 2-4 actions touch the same subsystem and accepted goal; +- no unapproved consumer surface, product semantics change, provider default, + credential, release, public sync, or destructive action appears; +- verification can remain `narrow` or `standard`; +- truth can be written at the session boundary without hiding a blocker. + +Do not bundle work into a work session when the next action changes product +semantics, exposes a new UI route / API endpoint / CLI command / public +integration, changes provider defaults, requires credentials, publishes, +merges, tags, deletes, resets, migrates data, contradicts committed truth, or +repairs a failed verification whose path is uncertain. + +Short approvals such as "同意,请使用 Threadsmith 推进" may continue an accepted +work session until a natural stop. If the user asks for one explicit role or one +explicit action, honor that narrower request. + +## Closeout Tier Rule + +Choose the smallest closeout tier that preserves orientation and safety: + +- `lite`: small or low-risk work. Include changed, verification, truth, next, + and risk only when material. +- `standard`: normal bounded implementation. Include result, changed + capability, verification, truth, remaining risk, and next phase. +- `audit`: release, PR / merge, public docs, destructive operations, + architecture boundaries, provider routing, security, or cross-agent state. + Use the full Threadsmith Output Contract skeleton. + +Full skeleton output is still mandatory for audit events and major phase +boundaries. Small accepted work may use `lite` when no stop gate or durable +route change exists. + +## Gap Check Budget Rule + +Gap checks prevent wrong work; they must not replace work. + +Default: one gap check per work session. If a gap check already selected an +implementation path, the next normal action is implementation, not another gap +check, unless verification failed, scope changed, product direction changed, +release / destructive / public risk appeared, provider routing changed, or +committed truth contradicts repo evidence. + +## Product / User-Value Heartbeat Rule + +After three consecutive governance-heavy accepted sessions without a heartbeat, +recommend one lightweight value check at the next phase boundary or +work-session closeout. Count `standard` and `audit` closeouts as +governance-heavy; `lite` closeouts do not increment the counter. + +The heartbeat is advisory. It asks whether the project became more usable, +understandable, reliable, playable, or closer to its stated goal, and whether +the next local engineering step is still highest value. It must not rewrite +acceptance, force a direction change, or interrupt an accepted implementation +path unless a real stop gate appears. + ## Full Governance Speed Rule Full governance means role-complete, not approval-heavy. diff --git a/codex/skills/threadsmith/references/runtime-contract.md b/codex/skills/threadsmith/references/runtime-contract.md index f378b7c..f94f60d 100644 --- a/codex/skills/threadsmith/references/runtime-contract.md +++ b/codex/skills/threadsmith/references/runtime-contract.md @@ -232,6 +232,29 @@ Role write boundaries: Unknown external agents must produce writeback proposals instead of direct committed-truth writes. +Work-session writeback shape: + +- start: do not create a new phase only to name a work session; record the + target in active work or the current packet only when it changes execution + behavior or prevents repeated recommendations +- during: keep internal notes as run evidence, command output, or local + reasoning unless a blocker, failed verification, scope change, or user + decision appears +- closeout: update acceptance when done-when evidence changed, active work when + role / blocker / next action changed, evidence summary or current packet when + the next operator turn needs the fact, and role packets when role-relevant + truth changed + +Concrete writeback files for v1: + +- active work target: `.threadsmith/active-work.json` +- accepted result or done-when evidence: `.threadsmith/acceptance-state.json` +- next operator fact: `.threadsmith/context/current-packet.json` or + `.threadsmith/context/evidence-summary.json` +- role-specific changed truth: `.threadsmith/context/role-packets/.json` +- historical accepted boundary: phase history / closeout report, not active + packet accumulation + Writeback proposals live at `.threadsmith/proposals/.json`. Proposal reviews live at `.threadsmith/proposal-reviews/.json`. They are runtime artifacts, not committed truth. A proposal may describe @@ -257,3 +280,46 @@ Do not silently continue with stale truth after a failed write. The control deck is a view over this state. Deck actions should map back to explicit Threadsmith actions, not free-form magic. + +## Governance Intensity Preference + +Governance intensity is a preference over ordinary Threadsmith behavior, not a +permission to bypass safety gates. + +Allowed values: + +- `light` +- `standard` +- `audit-heavy` + +Default: `standard`. + +Use the existing `.threadsmith/preferences.json` surface. Do not introduce a new +governance-only state file for v1. + +Priority: + +1. non-negotiable safety gates from the skill contract or AGENTS.md +2. explicit user instruction for the current invocation or work session +3. `.threadsmith/preferences.json` durable project default +4. non-conflicting soft defaults from AGENTS.md +5. fallback `standard` + +Invariant: audit stop gates cannot be downgraded by governance intensity. + +## Context Packet Current-State Budget + +The current Context Packet should be current-state oriented, not an ever-growing +history dump. + +Keep: + +- current goal +- recent 3-5 accepted slices or decisions +- current open risks and blockers +- next best step or current work-session target +- latest failed verification or high-signal evidence + +Move older history to archived evidence, phase history, reports, or handoff +packets. Compact only at work-session closeout or when the context budget is +exceeded, not in the middle of an accepted implementation path. diff --git a/docs/plans/threadsmith-adaptive-work-session-mode-v1.md b/docs/plans/threadsmith-adaptive-work-session-mode-v1.md new file mode 100644 index 0000000..46d4507 --- /dev/null +++ b/docs/plans/threadsmith-adaptive-work-session-mode-v1.md @@ -0,0 +1,281 @@ +# Threadsmith Adaptive Work Session Mode v1 Implementation Plan + +Goal: make Threadsmith useful as a general project governance layer without making solo or small-team development feel over-fragmented, over-documented, or phase-bound at every tiny action. + +Scope: introduce a generic adaptive work-session policy that can group related small actions, reduce unnecessary phase boundaries, tier closeout output, limit gap-check loops, and preserve truth/verification where risk actually justifies it. + +Non-goals: Asterlea-specific rules, project-specific hardcoding, frontend redesign, multi-provider routing, release automation, removal of review/verification gates, or weakening destructive-action and scope-change safety stops. + +Assumptions: Threadsmith should remain a safety belt and state store, not the product direction owner. The same mechanism should work for code projects, creative-engineering projects, docs-heavy projects, and later multi-agent projects. + +Verification: implementation should use the smallest verification tier that matches the touched surface, then run final contract/truth checks before completion. + +Verification should be tier-aware: + +- `lite`: focused tests, contract checks, parse checks, or `git diff --check` for low-risk local work. +- `standard`: package or workspace tests that cover the changed subsystem. +- `audit`: full project truth checks plus release, launcher, public docs, provider, security, or cross-agent checks when those surfaces are touched. + +This implementation plan final closeout still requires `npm run verify:skill-contract`, relevant package tests, `npm run verify:project-truth`, and `git diff --check`. Do not run the full suite after every `lite` work session unless the touched surface or failure evidence justifies it. + +Expected final commands for this implementation plan: `npm run verify:skill-contract`, `npm run test --workspace @threadsmith/domain`, `npm run test --workspace @threadsmith/runtime`, `npm run test --workspace @threadsmith/orchestrator`, `npm run verify:project-truth`, and `git diff --check`. + +Run `npm run verify:skill-sync` only when this implementation intentionally syncs the repository skill to the installed global skill. Otherwise, use `verify:skill-contract` to protect the repo skill contract without mutating or requiring global sync. + +## Problem Summary + +Threadsmith currently controls boundaries, verification, truth writeback, and closeout well, but it can over-apply full governance to small solo-development moves. The resulting failure mode is not incorrectness; it is operator fatigue: + +- phases become too small; +- closeouts become too frequent; +- gap checks can become action prelude loops; +- documents grow faster than product capability; +- the next step can become locally optimal rather than product-value optimal; +- context packets can become dense history containers instead of current operating packets. + +The fix should be a generic operating-mode adjustment, not an exception for one project. + +## Files + +- Modify: `codex/skills/threadsmith/SKILL.md` +- Modify: `codex/skills/threadsmith/references/action-contracts.md` +- Modify: `codex/skills/threadsmith/references/runtime-contract.md` +- Modify: `scripts/verify-threadsmith-skill-contract.mjs` +- Modify: `packages/runtime/src/nextBestStep.ts` +- Modify: `packages/runtime/src/nextBestStep.test.ts` +- Modify: `packages/runtime/src/contextBudget.ts` +- Modify: `packages/runtime/src/contextBudget.test.ts` +- Modify: `packages/domain/src/preferences.ts` +- Modify: `packages/domain/src/preferences.test.ts` +- Optional modify: `packages/orchestrator/src/plannerMode.ts` +- Optional modify: `packages/orchestrator/src/plannerMode.test.ts` +- Create: `docs/reports/adaptive-work-session-mode-v1-closeout.md` + +## Concepts + +### Work Session + +A work session is a bounded group of related actions inside the current phase. It does not replace the phase, and it must not silently expand the phase contract. + +Examples: + +- extract a module, add focused tests, update the direct consumer, and close out once; +- draft a plan, implement the narrow slice, verify, and summarize once; +- inspect a risk, fix the smallest confirmed issue, and record the result once. + +A work session is larger than a micro-phase and smaller than an open-ended epic. It may include multiple role gates or related actions, but durable phase/truth writeback should happen at the session boundary unless a real stop gate appears. + +Use a work session when all are true: + +- the operator has accepted the current direction; +- the next 2-4 actions affect the same subsystem and accepted goal; +- no unapproved new user-visible capability, consumer surface, product semantics change, provider default, credential, release, or destructive action is introduced; +- verification can remain focused or standard; +- truth can be written once at the session boundary without losing auditability. + +Do not use a work session when: + +- the next action changes product semantics; +- the next action exposes a new consumer surface; +- the next action changes provider, credential, release, or destructive behavior; +- repo evidence contradicts committed truth; +- verification failed and the repair path is uncertain. + +Related examples: + +- related: extract a module, add focused tests, update the existing direct consumer, and close out once; +- not related: extract a module, add an HTTP route, change frontend behavior, and change provider defaults in the same session. + +Strong stop examples: + +- new consumer surface: introducing a new UI route, API endpoint, CLI command, or public integration path; +- new product semantics: changing what the user sees, what a model response means, or how a workflow is interpreted; +- provider default: changing which model/provider runs by default, adding live credentials, or changing fallback behavior; +- release/destructive action: tag, publish, merge, public sync, delete, reset, migration, or irreversible file/data operation. + +### Truth Writeback Shape + +Work sessions should preserve durable truth without turning each internal action into a separate phase. + +At work-session start: + +- do not create a new phase only to name the session; +- record the selected work-session target in active work or the current packet only if it changes execution behavior or helps avoid repeated recommendations. + +During the session: + +- keep internal notes as run evidence, command output, or local reasoning unless a real stop gate appears; +- write durable truth immediately only for blockers, failed verification, scope changes, or user decisions. + +At work-session closeout: + +- update acceptance state when done-when evidence changed; +- update active work when role ownership, blocker state, or next action changed; +- update evidence summary or current packet when the next operator turn needs the new fact; +- refresh role packets or mark them stale when role-relevant truth changed; +- avoid preserving every internal sub-step as active truth when the final session result already captures it. + +Before coding, map these writeback rules to concrete runtime keys and files so implementation does not invent new truth surfaces implicitly. + +### Closeout Tiers + +Threadsmith should select the smallest closeout tier that preserves orientation and safety: + +- `lite`: small or low-risk work; summarize change, verification, and next natural stop only. +- `standard`: normal bounded implementation work; include phase result, changed capability, verification, risks, and next phase preview. +- `audit`: release, PR/merge, public docs, destructive operations, architecture boundaries, provider routing, security, or cross-agent state. + +Full skeleton output should become `audit` or major `standard`, not the default for every tiny phase. + +Closeout tier templates: + +`lite`: + +- required changed: +- required verification: +- required truth: `updated`, `unchanged`, or `skipped with reason` +- required next: +- optional risk: write `none` when no material risk remains + +`standard`: + +- required result: +- required changed capability: +- required verification: +- required truth: +- required remaining risk: +- required next phase: + +`audit`: + +- use the full Threadsmith Output Contract skeleton. + +### Gap Check Budget + +Gap checks should prevent wrong work, not replace work. A work session should not chain gap checks unless a new material unknown appears. + +Default rule: one gap check per work session unless: + +- committed truth contradicts repo evidence; +- verification fails; +- scope or product direction changes; +- the next step would affect release, destructive actions, provider routing, or public behavior. + +If a gap check has already selected the next implementation path, the next normal action should be implementation, not another gap check, unless verification failed, scope changed, release/destructive/public risk appeared, or committed truth contradicts repo evidence. + +### Product / User-Value Heartbeat + +Every few governance-heavy sessions, Threadsmith should recommend a lightweight value check: + +- did the project become more usable, playable, understandable, reliable, or closer to its stated product goal? +- is the next local engineering step still the highest-value direction? +- should the operator choose between engineering depth, product surface, architecture map, or creative/UX validation? + +This must be generic. For a code library, the heartbeat may ask about API usability. For a creative project, it may ask about product feel. For a CLI tool, it may ask about operator flow. It is a recommendation surface, not a mandatory ritual. + +The heartbeat only reminds the operator to reassess value. It must not rewrite current acceptance, force a direction change, or interrupt an accepted implementation path unless a real stop gate appears. + +Deterministic trigger for v1: after three consecutive governance-heavy accepted sessions without a value heartbeat, recommend one at the next phase boundary or work-session closeout. For v1, count accepted `standard` or `audit` closeouts from phase history or closeout tier evidence as governance-heavy; `lite` closeouts do not increment the counter. Reset the counter when the operator performs, accepts, or explicitly skips the heartbeat. + +### Governance Intensity Preference + +Governance intensity should be configurable without project-specific rules: + +- options: `light`, `standard`, `audit-heavy`; +- default: `standard`; +- source: explicit user instruction for the current operator invocation or work session, `.threadsmith/preferences.json`, and project `AGENTS.md`; +- lifecycle: invocation-level instructions expire after the current response or work session; `.threadsmith/preferences.json` persists across sessions; `AGENTS.md` applies as durable project constitution; +- priority: non-negotiable safety gates from the skill contract or `AGENTS.md` win first; explicit user instruction can override ordinary project defaults; `.threadsmith/preferences.json` provides the durable project default; `AGENTS.md` may provide additional soft defaults when they do not conflict with preferences; +- invariant: audit stop gates cannot be downgraded by preference. + +Use the existing preferences surface for v1. Do not introduce a new governance state file only for intensity. + +### Context Packet Current-State Budget + +The current Context Packet should stay current-state oriented by default: + +- current goal; +- recent 3-5 accepted slices or decisions; +- current open risks and blockers; +- next best step or current work-session target; +- latest failed verification or high-signal evidence. + +Older history should move to archived evidence, phase history, or reports instead of staying in the active packet. Compact only at work-session closeout or when the context budget is exceeded, not in the middle of an accepted implementation path. + +## Steps + +1. Add a `work-session` concept to the Threadsmith skill contract. + - Outcome: the global instructions distinguish `phase`, `slice`, `role gate`, and `work session`. + - Check: `verify-threadsmith-skill-contract` asserts the new concept exists and preserves stop gates. + +2. Add cadence rules for grouping related small actions. + - Outcome: short approvals such as "同意,请使用 Threadsmith 推进" can resolve to a work-session continuation until a natural stop, instead of a single micro-phase, unless the user explicitly requests one role or one action. + - Check: skill contract includes examples for grouped implementation, single-role drive, and stop-gate fallback. + +3. Add closeout tier selection. + - Outcome: Threadsmith can choose `lite`, `standard`, or `audit` output according to risk and boundary level. + - Check: contract tests verify full skeleton is still required for audit events, while small accepted work may use lite closeout; tier-specific templates are documented. + +4. Add gap-check budget rules. + - Outcome: Threadsmith avoids chaining gap checks by default and explains when another gap check is justified. + - Check: runtime or skill-contract tests include "gap check after gap check" and "gap check after failed verification" cases. + +5. Add product/user-value heartbeat guidance. + - Outcome: after repeated engineering-governance phases, Threadsmith recommends a lightweight value review rather than blindly continuing local technical depth. + - Check: tests or contract fixtures show the three-session trigger, and that the heartbeat is advisory and does not override explicit user direction. + +6. Add or update preferences for governance intensity. + - Outcome: projects can prefer `light`, `standard`, or `audit-heavy` governance without project-specific hardcoding. + - Check: domain preferences parse and default safely; audit stop gates cannot be downgraded by preferences. + +7. Update context budget guidance. + - Outcome: current packets should prioritize active facts, recent decisions, open risks, and next horizon rather than accumulating unlimited history. + - Check: context budget tests protect high-signal recent evidence and avoid forcing all historical facts into the current packet. + +8. Update runtime next-best-step behavior only where deterministic and testable. + - Outcome: runtime recommendations can label a next move as `work-session-continue`, `gap-check`, or `value-heartbeat` when evidence supports it, while output tier remains a separate presentation decision. + - Check: `nextBestStep` tests cover at least one low-risk continuation, one audit-required stop, one gap-check budget case, and one value-heartbeat recommendation. Runtime should only emit labels derivable from state/history; wording, closeout tier templates, and operator-facing phrasing stay in the skill contract. + +9. Keep autopilot and manual drive compatible. + - Outcome: adaptive work-session mode should work whether the chain is run through autopilot or manually driven by the current agent. + - Check: orchestrator tests do not require every adaptive session to produce `phase-runs`, but autopilot runs should still produce runtime timing artifacts. + +10. Write a required closeout report that explains the new operator-facing behavior. + - Outcome: future users can understand when Threadsmith is acting as safety belt, workflow driver, or audit gate. + - Check: closeout report includes non-goals and examples that are not tied to a single project. + +## Acceptance Criteria + +- Threadsmith can group 2-4 related small actions into one work session when risk is low and the operator has approved the direction. +- Threadsmith does not require full skeleton closeout for every small accepted slice. +- Threadsmith still stops for real gates: scope change, failed verification, destructive actions, release actions, stale truth, provider credentials, or cross-agent state risk. +- New consumer surfaces and product semantics changes cannot be bundled into a work session without explicit operator approval. +- Gap checks no longer chain by default. +- A generic value heartbeat exists and is not Asterlea-specific. +- Context packet guidance becomes shorter and more current-state oriented. +- Existing review, verification, closeout, and truth-writeback guarantees remain intact. + +## Risks + +- Making Threadsmith lighter could accidentally weaken safety boundaries. +- Closeout tiering could become vague if risk signals are not explicit. +- Work sessions could become too broad if "2-4 related actions" is treated as permission for open-ended implementation. +- Product/user-value heartbeat could become another ritual if it is too frequent or too verbose. +- Runtime behavior and skill prompt behavior could diverge unless tests cover both. +- Work sessions could incorrectly merge product-direction, design/research, and implementation decisions into one pass. Mitigation: product semantics, new user-visible capability, public interface, provider defaults, release, destructive actions, or uncertain repair paths must stop the session. + +## Stop Conditions + +- Any change would allow executor to skip reviewer/verifier/closeout gates. +- Any change would allow destructive git, release, provider, credential, or public-sync actions without an explicit stop. +- Any change would allow new consumer surfaces or product semantics changes to be bundled into a work session without explicit operator approval. +- The design starts adding project-specific examples as hard rules. +- The plan requires frontend changes or multi-provider routing to be useful. + +## Done When + +- The skill contract describes adaptive work sessions, closeout tiers, gap-check budget, and value heartbeat clearly. +- Contract verification protects the new behavior. +- Runtime/domain tests cover deterministic parts of the behavior. +- Existing tests and project truth verification pass. +- The final report explains how this changes day-to-day Threadsmith usage in plain language. diff --git a/docs/reports/adaptive-work-session-mode-v1-closeout.md b/docs/reports/adaptive-work-session-mode-v1-closeout.md new file mode 100644 index 0000000..499b7ce --- /dev/null +++ b/docs/reports/adaptive-work-session-mode-v1-closeout.md @@ -0,0 +1,70 @@ +# Adaptive Work Session Mode v1 Closeout + +## Result + +Adaptive Work Session Mode v1 is implemented as a generic Threadsmith workflow +improvement. It is not project-specific, does not change frontend behavior, and +does not sync the repository skill to the installed global skill. + +## Operator-Facing Behavior + +Threadsmith can now distinguish a normal phase from a smaller work session +inside that phase. In plain language: when a direction is already accepted and +the next few actions are tightly related, Threadsmith may keep moving until a +natural stop instead of turning every small action into a new phase. + +Closeout can now be tiered: + +- `lite`: small low-risk work, with changed / verification / truth / next. +- `standard`: normal bounded implementation, with result, changed capability, + verification, truth, remaining risk, and next phase. +- `audit`: release, PR / merge, public docs, destructive actions, architecture + boundaries, provider routing, security, or cross-agent state. This still uses + the full Threadsmith output contract. + +Gap checks now have a budget. If a gap check already selected an implementation +path, the next normal action should be implementation unless a real stop reason +appears. + +After three consecutive governance-heavy accepted sessions, Threadsmith can +recommend a lightweight value heartbeat. This is only an advisory route check; +it must not interrupt an already accepted implementation path. + +## Runtime And Truth Shape + +Runtime recommendations can carry deterministic labels: + +- `work-session-continue` +- `gap-check` +- `value-heartbeat` + +The labels are derived from state or explicit runtime signals. The skill +contract remains responsible for operator-facing wording and closeout templates. + +Truth writeback remains durable-boundary based. Work-session internals should +not create new phases only to name the session. Blockers, failed verification, +scope changes, and user decisions still write durable truth immediately. + +## Non-Goals Preserved + +- No Asterlea-specific behavior. +- No frontend work. +- No multi-provider routing work. +- No release automation. +- No global skill sync. +- No weakening of destructive-action, release, provider, credential, or + cross-agent stop gates. + +## Verification + +Expected verification for this implementation: + +- `npm run verify:skill-contract` +- `npm run test --workspace @threadsmith/domain` +- `npm run test --workspace @threadsmith/runtime` +- `npm run test --workspace @threadsmith/orchestrator` +- `npm run verify:project-truth` +- `git diff --check` + +`npm run verify:skill-sync` is intentionally omitted because this slice does +not sync the repository skill to the installed global skill. diff --git a/packages/domain/src/preferences.test.ts b/packages/domain/src/preferences.test.ts index 93f6c4a..e5141d1 100644 --- a/packages/domain/src/preferences.test.ts +++ b/packages/domain/src/preferences.test.ts @@ -1,5 +1,9 @@ import { describe, expect, it } from "vitest"; -import { createPreferences, resolveContinuationBehavior } from "./preferences.ts"; +import { + createPreferences, + resolveContinuationBehavior, + resolveGovernanceIntensity +} from "./preferences.ts"; describe("resolveContinuationBehavior", () => { it("prefers the project default over the global default", () => { @@ -17,5 +21,29 @@ describe("resolveContinuationBehavior", () => { expect(resolved.resolved.continuationBehavior).toBe("ask-every-time"); expect(resolved.resolved.continuationBehaviorSource).toBe("fallback"); + expect(resolved.resolvedGovernance?.governanceIntensity).toBe("standard"); + expect(resolved.resolvedGovernance?.governanceIntensitySource).toBe("fallback"); + }); + + it("resolves governance intensity from project default before AGENTS.md soft defaults", () => { + const resolved = resolveGovernanceIntensity("light", "audit-heavy"); + + expect(resolved.governanceIntensity).toBe("light"); + expect(resolved.governanceIntensitySource).toBe("project-default"); + }); + + it("uses AGENTS.md governance intensity only when project preference is absent", () => { + const resolved = createPreferences( + "smart-continuation", + null, + "ask-every-time", + null, + "audit-heavy" + ); + + expect(resolved.resolvedGovernance?.governanceIntensity).toBe("audit-heavy"); + expect(resolved.resolvedGovernance?.governanceIntensitySource).toBe( + "agents-md-default" + ); }); }); diff --git a/packages/domain/src/preferences.ts b/packages/domain/src/preferences.ts index 86d3b46..3ff7ef8 100644 --- a/packages/domain/src/preferences.ts +++ b/packages/domain/src/preferences.ts @@ -14,8 +14,22 @@ export const continuationBehaviorSourceSchema = z.enum([ "fallback" ]); +export const governanceIntensitySchema = z.enum([ + "light", + "standard", + "audit-heavy" +]); + +export const governanceIntensitySourceSchema = z.enum([ + "invocation", + "project-default", + "agents-md-default", + "fallback" +]); + export const storedPreferencesSchema = z.object({ continuationBehavior: continuationBehaviorSchema.optional(), + governanceIntensity: governanceIntensitySchema.optional(), projectCharterGate: z.object({ declinedSetup: z.boolean().default(false), declineReason: z.string().min(1).nullable().default(null), @@ -28,17 +42,29 @@ export const resolvedContinuationPreferenceSchema = z.object({ continuationBehaviorSource: continuationBehaviorSourceSchema }); +export const resolvedGovernanceIntensityPreferenceSchema = z.object({ + governanceIntensity: governanceIntensitySchema, + governanceIntensitySource: governanceIntensitySourceSchema +}); + export const preferencesSchema = z.object({ projectDefault: continuationBehaviorSchema.nullable(), globalDefault: continuationBehaviorSchema.nullable(), - resolved: resolvedContinuationPreferenceSchema + governanceIntensityDefault: governanceIntensitySchema.nullable().optional(), + agentsMdGovernanceDefault: governanceIntensitySchema.nullable().optional(), + resolved: resolvedContinuationPreferenceSchema, + resolvedGovernance: resolvedGovernanceIntensityPreferenceSchema.optional() }); export type ContinuationBehavior = z.infer; +export type GovernanceIntensity = z.infer; export type PreferenceScope = z.infer; export type ContinuationBehaviorSource = z.infer< typeof continuationBehaviorSourceSchema >; +export type GovernanceIntensitySource = z.infer< + typeof governanceIntensitySourceSchema +>; export type StoredPreferences = z.infer; export type Preferences = z.infer; @@ -67,14 +93,47 @@ export function resolveContinuationBehavior( }); } +export function resolveGovernanceIntensity( + projectDefault?: GovernanceIntensity | null, + agentsMdDefault?: GovernanceIntensity | null, + fallback: GovernanceIntensity = "standard" +) { + if (projectDefault) { + return resolvedGovernanceIntensityPreferenceSchema.parse({ + governanceIntensity: projectDefault, + governanceIntensitySource: "project-default" + }); + } + + if (agentsMdDefault) { + return resolvedGovernanceIntensityPreferenceSchema.parse({ + governanceIntensity: agentsMdDefault, + governanceIntensitySource: "agents-md-default" + }); + } + + return resolvedGovernanceIntensityPreferenceSchema.parse({ + governanceIntensity: fallback, + governanceIntensitySource: "fallback" + }); +} + export function createPreferences( projectDefault?: ContinuationBehavior | null, globalDefault?: ContinuationBehavior | null, - fallback: ContinuationBehavior = "ask-every-time" + fallback: ContinuationBehavior = "ask-every-time", + governanceIntensityDefault?: GovernanceIntensity | null, + agentsMdGovernanceDefault?: GovernanceIntensity | null ): Preferences { return preferencesSchema.parse({ projectDefault: projectDefault ?? null, globalDefault: globalDefault ?? null, - resolved: resolveContinuationBehavior(projectDefault, globalDefault, fallback) + governanceIntensityDefault: governanceIntensityDefault ?? null, + agentsMdGovernanceDefault: agentsMdGovernanceDefault ?? null, + resolved: resolveContinuationBehavior(projectDefault, globalDefault, fallback), + resolvedGovernance: resolveGovernanceIntensity( + governanceIntensityDefault, + agentsMdGovernanceDefault + ) }); } diff --git a/packages/runtime/src/contextBudget.test.ts b/packages/runtime/src/contextBudget.test.ts index a2d601a..597a855 100644 --- a/packages/runtime/src/contextBudget.test.ts +++ b/packages/runtime/src/contextBudget.test.ts @@ -115,4 +115,40 @@ describe("buildContextBudgetLedger", () => { "Lost-in-the-middle risk: acceptance is heavy in the middle of the packet; route only the role-specific subset." ); }); + + it("keeps current packets oriented around recent decisions instead of full history", () => { + const ledger = buildContextBudgetLedger({ + project: { label: "Threadsmith" }, + recentDecisions: Array.from({ length: 7 }, (_, index) => ({ + title: `Accepted slice ${index}`, + summary: "Historical accepted work." + })), + nextStep: { label: "Continue current work session" } + }); + + expect(ledger.warnings).toContain( + "recentDecisions has 7 items; current packets should keep only recent 3-5 accepted slices or decisions." + ); + expect(ledger.compressionAdvice).toEqual( + expect.arrayContaining([expect.stringContaining("recentDecisions:")]) + ); + }); + + it("preserves latest failed verification as high-signal current evidence", () => { + const ledger = buildContextBudgetLedger({ + evidence: { + latestFailedVerification: { + command: "npm run test --workspace @threadsmith/runtime", + result: "failed", + summary: "nextBestStep test failed on audit boundary expectation." + } + }, + nextStep: { label: "Repair failed verification" } + }); + + expect(ledger.budgetLevel).toBe("compact"); + expect(ledger.warnings).toEqual([]); + expect(ledger.sections.find((section) => section.section === "evidence")) + ?.toMatchObject({ level: "compact", itemCount: 1 }); + }); }); diff --git a/packages/runtime/src/contextBudget.ts b/packages/runtime/src/contextBudget.ts index cfdc46d..4445672 100644 --- a/packages/runtime/src/contextBudget.ts +++ b/packages/runtime/src/contextBudget.ts @@ -34,9 +34,14 @@ const SECTION_ADVICE: Record = { relevantFiles: "Prioritize changed files and entry points; avoid dumping whole repo maps.", recentDiff: "Summarize diff intent instead of copying file-by-file commentary.", evidence: "Keep command summaries and artifact refs; do not copy raw stdout or stderr.", - sourceRefs: "Reference durable files rather than repeating their contents." + sourceRefs: "Reference durable files rather than repeating their contents.", + recentDecisions: "Keep only the recent 3-5 accepted slices or decisions; move older history to phase history or reports.", + phaseHistory: "Do not keep full phase history in the current packet; keep refs and the latest relevant boundary.", + history: "Current packets are for current operating facts, not full history replay." }; +const HISTORY_SECTIONS = new Set(["recentDecisions", "phaseHistory", "history"]); + function estimateChars(value: unknown) { return JSON.stringify(value).length; } @@ -79,6 +84,10 @@ function sectionAdvice( items: number, limits: Required ) { + if (HISTORY_SECTIONS.has(section) && items > 5) { + return SECTION_ADVICE[section]; + } + if (level === "compact" && items < limits.sectionItemWatch) { return null; } @@ -144,6 +153,12 @@ function buildWarnings( if (section.level === "over-budget" || section.level === "heavy") { warnings.push(`${section.section} is ${section.level} (${section.estimatedChars} estimated chars).`); } + + if (HISTORY_SECTIONS.has(section.section) && section.itemCount > 5) { + warnings.push( + `${section.section} has ${section.itemCount} items; current packets should keep only recent 3-5 accepted slices or decisions.` + ); + } } const middleSections = sections.slice(1, -1); diff --git a/packages/runtime/src/nextBestStep.test.ts b/packages/runtime/src/nextBestStep.test.ts index 981fcb3..fe841aa 100644 --- a/packages/runtime/src/nextBestStep.test.ts +++ b/packages/runtime/src/nextBestStep.test.ts @@ -222,6 +222,7 @@ describe("selectNextBestStep", () => { }); expect(before.primary.actionId).toBe("advance-phase"); + expect(before.primary.nextStepKind).toBe("work-session-continue"); expect(after.primary.actionId).not.toBe("advance-phase"); expect(after.primary.actionId).toBe("open-current-phase"); }); @@ -478,4 +479,103 @@ describe("selectNextBestStep", () => { expect(result.primary.expectedRoles).toEqual(["hygiene", "reviewer"]); expect(result.primary.stopCondition).toContain("采纳、拒绝"); }); + + it("uses gap-check when a non-bootstrap user decision is pending", () => { + const result = selectNextBestStep({ + ...baseState, + activeWork: { + items: [ + { + role: "planner", + status: "waiting", + taskSummary: "判断这个 slice 是否会改变产品语义", + requiresUserDecision: true + } + ], + blockerSummary: null + } + }); + + expect(result.primary.actionId).toBe("open-current-phase"); + expect(result.primary.nextStepKind).toBe("gap-check"); + expect(result.primary.label).toBe("先做 gap check"); + }); + + it("continues implementation instead of chaining another gap check after a path was selected", () => { + const result = selectNextBestStep( + baseState, + undefined, + null, + undefined, + undefined, + null, + { + previousGapCheckSelectedImplementationPath: true + } + ); + + expect(result.primary.actionId).toBe("advance-phase"); + expect(result.primary.nextStepKind).toBe("work-session-continue"); + expect(result.primary.reason).toContain("上一轮 gap check 已经选出实现路径"); + }); + + it("does not let gap-check budget override failed verification repair", () => { + const result = selectNextBestStep( + { + ...baseState, + acceptanceState: { + ...baseState.acceptanceState, + verificationStatus: "failed" + } + }, + undefined, + null, + undefined, + undefined, + null, + { + previousGapCheckSelectedImplementationPath: true + } + ); + + expect(result.primary.actionId).toBe("advance-phase"); + expect(result.primary.label).toBe("修复 verification 缺口"); + expect(result.primary.nextStepKind).toBeUndefined(); + }); + + it("stops for audit-required consumer surfaces instead of bundling them into a work session", () => { + const result = selectNextBestStep( + baseState, + undefined, + null, + undefined, + undefined, + null, + { + introducesConsumerSurface: true + } + ); + + expect(result.primary.actionId).toBe("open-current-phase"); + expect(result.primary.label).toBe("先确认 audit 边界"); + expect(result.primary.reason).toContain("consumer surface"); + }); + + it("recommends a value heartbeat after three consecutive governance-heavy closeouts", () => { + const result = selectNextBestStep( + baseState, + undefined, + null, + undefined, + undefined, + null, + { + closeoutTiersSinceValueHeartbeat: ["standard", "audit", "standard"] + } + ); + + expect(result.primary.actionId).toBe("open-current-phase"); + expect(result.primary.nextStepKind).toBe("value-heartbeat"); + expect(result.primary.label).toBe("做一次价值 heartbeat"); + }); }); diff --git a/packages/runtime/src/nextBestStep.ts b/packages/runtime/src/nextBestStep.ts index ade82c6..32f36f1 100644 --- a/packages/runtime/src/nextBestStep.ts +++ b/packages/runtime/src/nextBestStep.ts @@ -13,10 +13,22 @@ import type { ContextRecoverySignal } from "./contextRecovery.ts"; export type { ActionRecommendation, + NextStepKind, NextBestStepDecision, RuntimeActionId } from "./nextBestStepModel.ts"; +export type CloseoutTier = "lite" | "standard" | "audit"; + +export interface AdaptiveWorkSessionSignals { + previousGapCheckSelectedImplementationPath?: boolean; + closeoutTiersSinceValueHeartbeat?: CloseoutTier[]; + requiresAuditStop?: boolean; + auditStopReason?: string; + introducesConsumerSurface?: boolean; + changesProductSemantics?: boolean; +} + function appendIfMissing(base: string, fragment: string) { return base.includes(fragment) ? base : `${base} ${fragment}`.trim(); } @@ -50,6 +62,51 @@ function findPendingUserDecision(state: ProjectState) { return state.activeWork.items.find((item) => item.requiresUserDecision); } +function isAuditStopRequired(signals: AdaptiveWorkSessionSignals) { + return Boolean( + signals.requiresAuditStop || + signals.introducesConsumerSurface || + signals.changesProductSemantics + ); +} + +function auditStopReason(signals: AdaptiveWorkSessionSignals) { + if (signals.auditStopReason?.trim()) { + return signals.auditStopReason.trim(); + } + + if (signals.introducesConsumerSurface) { + return "下一步会暴露新的 consumer surface,不能悄悄并入 work session。"; + } + + if (signals.changesProductSemantics) { + return "下一步会改变 product semantics,必须先停在 phase 边界确认。"; + } + + return "下一步触发 audit stop gate,必须先确认边界。"; +} + +function hasThreeConsecutiveGovernanceHeavyCloseouts( + closeoutTiers: CloseoutTier[] = [] +) { + const lastThree = closeoutTiers.slice(-3); + + return ( + lastThree.length === 3 && + lastThree.every((tier) => tier === "standard" || tier === "audit") + ); +} + +function canContinueWorkSession(state: ProjectState, pendingUserDecision: unknown) { + return ( + !pendingUserDecision && + state.currentPhase.blockedBy.length === 0 && + !state.activeWork.blockerSummary && + state.acceptanceState.verificationStatus !== "failed" && + state.acceptanceState.reviewStatus !== "review-blocked" + ); +} + function isBootstrapDecisionStage( state: ProjectState, latestRun: AgentRunRecord | null, @@ -80,7 +137,8 @@ export function selectNextBestStep( latestRun: AgentRunRecord | null = null, latestPhaseRun: PhaseRunSummary = createMissingPhaseRunSummary(), latestPhasePause: PhasePauseSummary = createMissingPhasePauseSummary(), - contextRecovery: ContextRecoverySignal | null = null + contextRecovery: ContextRecoverySignal | null = null, + adaptiveSignals: AdaptiveWorkSessionSignals = {} ): NextBestStepDecision { if (latestPhaseRun.status === "paused") { const pauseReason = latestPhasePause.summary @@ -370,6 +428,35 @@ export function selectNextBestStep( }; } + if (pendingUserDecision) { + return { + primary: recommendation( + "open-current-phase", + "先做 gap check", + `当前存在需要判断的开放决策:${pendingUserDecision.taskSummary}。先确认它是否改变范围、验收或产品语义,再决定能否进入 work session。`, + [pendingUserDecision.role], + "开放决策已被确认、排除或转成明确的实现路径。", + "gap-check" + ), + alternatives: [ + recommendation( + "run-hygiene", + "决策前重新锚定 truth", + "如果这个开放问题来自旧上下文或矛盾 packet,先运行 hygiene 会更稳。", + ["hygiene"], + "开放问题、committed truth 与下一步边界已经重新对齐。" + ), + recommendation( + "create-handoff", + "为当前判断创建 handoff", + "如果需要切到另一个线程判断这个问题,先把当前边界打包。", + ["hygiene"], + "已经存在一个可继续的判断边界。" + ) + ] + }; + } + const runningRole = state.activeWork.items.find( (item) => item.status === "running" && item.role !== "planner" ); @@ -431,6 +518,34 @@ export function selectNextBestStep( }; } + if (isAuditStopRequired(adaptiveSignals)) { + return { + primary: recommendation( + "open-current-phase", + "先确认 audit 边界", + `${auditStopReason(adaptiveSignals)} 这类动作不能降级成轻量 work session,需要先确认 phase contract、验收和 stop condition。`, + ["planner"], + "audit 边界已经确认,或者该动作被拆成安全的后续 phase。" + ), + alternatives: [ + recommendation( + "run-hygiene", + "audit 前重新锚定 truth", + "如果 audit stop 是由 stale truth 或 packet 冲突触发,先运行 hygiene。", + ["hygiene"], + "audit 触发原因与 committed truth 已重新一致。" + ), + recommendation( + "create-handoff", + "创建 audit handoff", + "如果这一步要交给另一个线程或 agent,先保存清晰边界。", + ["hygiene"], + "已经存在一个带 audit 边界的 continuation packet。" + ) + ] + }; + } + if (state.acceptanceState.reviewStatus === "review-blocked") { return { primary: recommendation( @@ -518,13 +633,54 @@ export function selectNextBestStep( }; } + if ( + hasThreeConsecutiveGovernanceHeavyCloseouts( + adaptiveSignals.closeoutTiersSinceValueHeartbeat + ) + ) { + return { + primary: recommendation( + "open-current-phase", + "做一次价值 heartbeat", + "最近已经连续完成三次 governance-heavy closeout。现在适合轻量确认:项目是否更可用、更可靠或更接近目标,以及下一步继续深挖工程是否仍然最高价值。", + ["planner"], + "操作者已接受、跳过或完成这次 value heartbeat,下一步方向重新对齐。", + "value-heartbeat" + ), + alternatives: [ + recommendation( + "advance-phase", + "继续当前 work session", + "如果操作者已经明确接受当前实现路径,heartbeat 不能打断已接受工作,可以继续推进。", + ["planner", "executor", "reviewer"], + "当前 work session 到达自然停点。", + "work-session-continue" + ), + recommendation( + "create-handoff", + "保存价值回看边界", + "如果需要把价值判断交给另一个线程,先打包当前事实。", + ["hygiene"], + "已经存在一个用于价值回看的 continuation packet。" + ) + ] + }; + } + return { primary: recommendation( "advance-phase", - "推进当前 phase", - "这是当前活跃项目里价值最高、且没有被阻塞的下一步。", + canContinueWorkSession(state, pendingUserDecision) + ? "继续当前 work session" + : "推进当前 phase", + adaptiveSignals.previousGapCheckSelectedImplementationPath + ? "上一轮 gap check 已经选出实现路径,且当前没有失败验证、范围变化或 audit stop;下一步应该进入实现,而不是再做一次 gap check。" + : "这是当前活跃项目里价值最高、且没有被阻塞的下一步。", ["planner", "executor", "reviewer"], - "当前 slice 到达待评审或待验证状态。" + "当前 slice 到达待评审或待验证状态。", + canContinueWorkSession(state, pendingUserDecision) + ? "work-session-continue" + : undefined ), alternatives: [ recommendation( diff --git a/packages/runtime/src/nextBestStepModel.ts b/packages/runtime/src/nextBestStepModel.ts index 1e3aacf..39d727c 100644 --- a/packages/runtime/src/nextBestStepModel.ts +++ b/packages/runtime/src/nextBestStepModel.ts @@ -7,8 +7,14 @@ export type RuntimeActionId = | "review-proposal" | "create-handoff"; +export type NextStepKind = + | "work-session-continue" + | "gap-check" + | "value-heartbeat"; + export interface ActionRecommendation { actionId: RuntimeActionId; + nextStepKind?: NextStepKind; label: string; reason: string; expectedRoles: string[]; @@ -25,10 +31,12 @@ export function recommendation( label: string, reason: string, expectedRoles: string[], - stopCondition: string + stopCondition: string, + nextStepKind?: NextStepKind ): ActionRecommendation { return { actionId, + nextStepKind, label, reason, expectedRoles, diff --git a/packages/runtime/src/supervisorState.test.ts b/packages/runtime/src/supervisorState.test.ts index 1d4370d..535b3df 100644 --- a/packages/runtime/src/supervisorState.test.ts +++ b/packages/runtime/src/supervisorState.test.ts @@ -440,7 +440,7 @@ describe("deriveSupervisorState", () => { "当前 phase:Build workflow loop" ); expect(supervisorState.commandBridge.recommendedRoute?.suggestedPrompt).toContain( - "当前建议动作:推进当前 phase" + "当前建议动作:继续当前 work session" ); expect(supervisorState.commandBridge.recommendedRoute?.suggestedPrompt).toContain( "当前指挥入口:Claude CLI" diff --git a/scripts/verify-threadsmith-skill-contract.mjs b/scripts/verify-threadsmith-skill-contract.mjs index 929d770..da37dad 100644 --- a/scripts/verify-threadsmith-skill-contract.mjs +++ b/scripts/verify-threadsmith-skill-contract.mjs @@ -43,11 +43,40 @@ const checks = [ /下一内部 gate/ ] }, + { + file: "SKILL.md", + label: "adaptive work session mode is documented", + patterns: [ + /## Adaptive Work Session Mode/, + /bounded group of related actions inside the current phase/, + /larger than a single role gate and smaller than a new phase/, + /next 2-4 actions/, + /consumer surface/, + /product semantics/, + /provider default/, + /Work-Session Truth Writeback/ + ] + }, + { + file: "SKILL.md", + label: "gap budget and value heartbeat are documented", + patterns: [ + /## Gap Check Budget/, + /one gap check by default/, + /next normal action should\s+be implementation/, + /## Product \/ User-Value Heartbeat/, + /three consecutive governance-heavy accepted sessions/, + /The heartbeat is advisory/ + ] + }, { file: "SKILL.md", label: "output matrix and orientation sections are required", patterns: [ /## Output Matrix/, + /`lite`/, + /`standard`/, + /`audit`/, /## Closeout Output Gate/, /If any two of these signals are present/, /commit, PR, merge, tag, release, durable truth writeback, packet update, or\s+closeout artifact/, @@ -212,6 +241,19 @@ const checks = [ /Reserve\s+`下一 phase 预览` for closeout/ ] }, + { + file: "references/action-contracts.md", + label: "adaptive work-session action rules are documented", + patterns: [ + /## Adaptive Work Session Rule/, + /2-4 actions/, + /new UI route \/ API endpoint \/ CLI command/, + /## Closeout Tier Rule/, + /## Gap Check Budget Rule/, + /## Product \/ User-Value Heartbeat Rule/, + /standard` and `audit` closeouts/ + ] + }, { file: "references/role-contracts.md", label: "role transition table is documented", @@ -369,6 +411,23 @@ const checks = [ /Hygiene may refresh derived packets/ ] }, + { + file: "references/runtime-contract.md", + label: "work-session writeback and context budget are documented", + patterns: [ + /Work-session writeback shape/, + /\.threadsmith\/active-work\.json/, + /\.threadsmith\/acceptance-state\.json/, + /\.threadsmith\/context\/current-packet\.json/, + /## Governance Intensity Preference/, + /`light`/, + /`standard`/, + /`audit-heavy`/, + /audit stop gates cannot be downgraded/, + /## Context Packet Current-State Budget/, + /recent 3-5 accepted slices/ + ] + }, { file: "references/external-agent-entry.md", label: "external agent entry contract is documented", From 0909acfb982fc2c43760289cbb2dd68248e34819 Mon Sep 17 00:00:00 2001 From: C1oud <120496180@qq.com> Date: Sat, 30 May 2026 14:25:39 +0800 Subject: [PATCH 2/2] fix: ignore stale pending decisions in next step routing --- packages/runtime/src/nextBestStep.test.ts | 24 +++++++++++++++++++++++ packages/runtime/src/nextBestStep.ts | 6 +++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/packages/runtime/src/nextBestStep.test.ts b/packages/runtime/src/nextBestStep.test.ts index fe841aa..2168e4a 100644 --- a/packages/runtime/src/nextBestStep.test.ts +++ b/packages/runtime/src/nextBestStep.test.ts @@ -501,6 +501,30 @@ describe("selectNextBestStep", () => { expect(result.primary.label).toBe("先做 gap check"); }); + it("ignores stale pending decisions from roles outside the current phase owners", () => { + const result = selectNextBestStep({ + ...baseState, + currentPhase: { + ...baseState.currentPhase, + activeOwners: ["executor"] + }, + activeWork: { + items: [ + { + role: "planner", + status: "waiting", + taskSummary: "旧的启动决策,不属于当前 executor-only phase", + requiresUserDecision: true + } + ], + blockerSummary: null + } + }); + + expect(result.primary.actionId).toBe("advance-phase"); + expect(result.primary.nextStepKind).toBe("work-session-continue"); + }); + it("continues implementation instead of chaining another gap check after a path was selected", () => { const result = selectNextBestStep( baseState, diff --git a/packages/runtime/src/nextBestStep.ts b/packages/runtime/src/nextBestStep.ts index 32f36f1..7946164 100644 --- a/packages/runtime/src/nextBestStep.ts +++ b/packages/runtime/src/nextBestStep.ts @@ -59,7 +59,11 @@ function isReportingFailureAfterSuccessfulTask(latestRun: AgentRunRecord | null) } function findPendingUserDecision(state: ProjectState) { - return state.activeWork.items.find((item) => item.requiresUserDecision); + return state.activeWork.items.find( + (item) => + item.requiresUserDecision && + state.currentPhase.activeOwners.includes(item.role) + ); } function isAuditStopRequired(signals: AdaptiveWorkSessionSignals) {