Skip to content

fix: fail-closed intent-birth, scope-runner new-work path, fresh-session handoff (2.5.48) - #676

Merged
apackeer merged 7 commits into
v2from
fix/intent-birth-fail-closed
Aug 9, 2026
Merged

fix: fail-closed intent-birth, scope-runner new-work path, fresh-session handoff (2.5.48)#676
apackeer merged 7 commits into
v2from
fix/intent-birth-fail-closed

Conversation

@apackeer

@apackeer apackeer commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Problem

Three compounding defects affect starting and finishing intents:

  1. A truly-bare intent-birth (no --scope, no --arguments, no --label) silently minted a garbage intent: default scope, empty [Project description], an intents.json row, and a repointed active-intent cursor - manual cleanup required. Verified empirically on upstream before fixing: exit 0, a minted default-scope intent from a bare call.
  2. Scope runners shipped only the bare forwarding loop. With a completed intent active, next correctly returns done - and the runner stopped there, with no instruction that unrelated new work goes through next --new-intent. Dead end.
  3. A second intent born mid-session inherited the completed intent's transcript - stale context carried into unrelated work.

What changes

  1. Fail-closed birth: handleIntentBirth refuses the truly-bare invocation with actionable usage guidance. Every blessed path still births - the engine's birth print directive always names --scope, and the init runner forwards an --arguments description; the init-runner prose now asks instead of firing a bare birth.
  2. New-work path: renderRunner (single source, every generated runner stays in sync) gains a "Starting unrelated new work?" section - recognize continuation vs new work (default continuation), OFFER via the harness's structured question (never auto-birth), then relay next --new-intent --scope <the runner's baked scope>. Both completed-workflow done directives now carry a NEW_WORK_HINT so the escape hatch surfaces at exactly the moment it is needed.
  3. Fresh-session handoff: birthPrintDirective splits on birth kind. Fresh-start births are byte-identical (existing pins stay green). --new-intent births STOP and tell the user to start a fresh session (/clear on Claude Code) then /aidlc - safe because the SessionStart hook treats source:clear as a first-class SESSION_STARTED and re-injects the workflow context from disk; the engine string stays harness-neutral.

Testing

  • t165-intent-birth-p4 +6 cases (bare birth refused exit-1 and mutates nothing; each of --scope/--arguments/--label independently still births; the two birth-directive tails), t130-scope-runners +4 (every runner carries the offer, names its baked scope, names /clear).
  • smoke+unit 177/1 (the 1 = t84 git flake, passes green-alone); integration 100/3 (t89 + t66 pre-existing base reds reproduced on pristine 9f91454; t72 live-agent timeout flake). Fresh-start pins t118/t143 green.
  • package.ts --check, typecheck clean.

Note for maintainers: version/CHANGELOG re-bump expected when landing after sibling PRs.

@leandrodamascena leandrodamascena left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change introduces the intended fresh-session boundary, but the implementation is inconsistent across harnesses and corrupts session audit attribution on the exact handoff path. It also leaves two mutation paths able to create undescribed intents. Please address the inline findings before merge.

// concrete command (Claude: `/clear`).
if (flags.newIntent) {
return printDirective(
`${runCmd} to start the new intent${cost}.${labelHint} Then STOP, do NOT re-run \`next\` in this session. ` +

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Keep the fresh-session handoff consistent across harnesses

The shared directive now tells every harness to stop after intent-birth, but only Claude’s orchestrator skill was updated. Codex, Kiro, Kiro IDE, and OpenCode still instruct the conductor to re-run next in the same session, preserving the stale-context bug this change intends to fix. The shared message also says /aidlc, while Codex uses $aidlc. Please update every harness-specific skill and tailor the resume command.

Comment thread core/tools/aidlc-orchestrate.ts Outdated
return printDirective(
`${runCmd} to start the new intent${cost}.${labelHint} Then STOP, do NOT re-run \`next\` in this session. ` +
`This is a NEW, unrelated intent, and the current session still carries the previous intent's context. ` +
`Tell the user to start a fresh session (in Claude Code, \`/clear\`; or restart the CLI), then run \`/aidlc\` to begin the new intent with a clean slate. ` +

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Attribute SESSION_ENDED to the session’s original intent

intent-birth switches the active-intent cursor before the user runs /clear. The SessionEnd hook resolves its audit destination from that cursor, so the old session’s SESSION_ENDED event is written to the newly created intent. I reproduced an old intent containing SESSION_STARTED while the new intent received SESSION_ENDED: clear. Please preserve the original session attribution or clear before switching the cursor, with an integration test covering the complete handoff.

// current stage". The freeform new-work text rides in flags.intent (the same
// slot Branch 9a threads as the description).
// birth print that performs it. Unlike the fresh-start tail, the new-intent
// directive tells the conductor to STOP after birth and hand off to a fresh

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Reject --new-intent without a description

next --new-intent currently emits a valid birth command even when no new-work text was supplied. Following it creates a generic scope-named intent and repoints the active cursor, despite this branch claiming that distinct work was confirmed. Require a nonblank description before emitting the mutation.

Comment thread core/tools/aidlc-utility.ts Outdated
// print directive (`next` / `next --new-intent`) always names `--scope`, and
// the init runner forwards a `--arguments` description. This never trips a
// legitimate call.
if (!flags.scope && !flags.arguments && !flags.label) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Treat missing flag values as absent

The new fail-closed guard checks only truthiness, but parseArgs() represents a valueless option as "true". Therefore, intent-birth --arguments bypasses the guard and creates an intent such as 260803-true; --label behaves similarly. Validate that value-bearing flags have real values before allowing the mutation.

@apackeer

apackeer commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

Addressed all four review findings in ef26ad77:

  • Updated every harness conductor and generated scope runner to stop after a second-intent birth, use the harness-specific fresh-session flow, and invoke $aidlc on Codex versus /aidlc elsewhere.
  • Preserved session ownership across the handoff so SESSION_ENDED and its heartbeat land on the ending session’s original intent, including sessions that began before the first workflow birth and Codex’s inferred SessionEnd path.
  • Made next --new-intent reject missing or blank new-work descriptions before emitting a birth command.
  • Made every value-bearing intent-birth option reject valueless or blank occurrences before project-path resolution or mutation.

Verification:

  • bun scripts/package.ts --check
  • bun run typecheck
  • bun run lint
  • Focused smoke/unit/integration: 13 files, 1,789 assertions, 0 failures
  • Final session regression slice: 7 files, 146 assertions, 0 failures

@apackeer
apackeer force-pushed the fix/intent-birth-fail-closed branch from ef26ad7 to fadf5d1 Compare August 6, 2026 10:56
@apackeer apackeer changed the title fix: fail-closed intent-birth, scope-runner new-work path, fresh-session handoff (2.5.21) fix: fail-closed intent-birth, scope-runner new-work path, fresh-session handoff (2.5.48) Aug 6, 2026

@leandrodamascena leandrodamascena left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two correctness issues remain:

  1. core/tools/aidlc-utility.ts:202
    .current-session identifies the most recently started session, not the session invoking intent-birth. With concurrent pre-workflow sessions A and B, a birth initiated by A can stamp B, causing later session events to be attributed to the wrong intent.

  2. core/hooks/aidlc-session-start.ts:178
    /aidlc and $aidlc are skill invocations, not shell commands. Joining them with && makes the second invocation trailing arguments, so cross-space rebind may switch spaces without selecting the intended intent. Emit two sequential instructions instead.

Validation: 73 focused tests, package parity, typecheck, and lint passed.

@apackeer

apackeer commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

Addressed both new review findings in 7b5124d9:

  • Replaced intent-birth's .current-session guess with exact PostToolUse binding: the successful birth result is paired with that tool event's host session_id, existing ownership is never overwritten, and an unstamped UUID-backed SessionEnd now fails closed instead of using another conversation's active cursor. Kiro CLI, Kiro IDE, Codex, and OpenCode all forward the required identity/result fields; Kiro no longer executes intent birth off-band before the session-aware shell boundary.
  • Replaced cross-space /aidlc / $aidlc command chaining with two explicit sequential skill invocations, so the space switch completes before the intent selection.

Verification:

  • bun scripts/package.ts --check
  • bun run typecheck
  • bun run lint
  • Focused smoke/unit/integration: 8 files, 194 assertions, 0 failures
  • Migration-path integration regression: 42 tests, 0 failures
  • Outgoing secret scan: 48 files, clean

@leandrodamascena leandrodamascena left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The latest commit addresses the two previous findings, but two fresh-session correctness issues remain, and CI is currently red.

1. P1: Stop enforcement defeats the fresh-session handoff

After --new-intent creates the second intent, the directive tells the current session to stop. However, aidlc-stop.ts resolves the newly active intent and runs next against it. Because that intent has pending work, the hook returns decision: block and forces the old, context-polluted session back into the workflow.

References:

  • core/tools/aidlc-orchestrate.ts:540-545
  • core/hooks/aidlc-stop.ts:875-882
  • core/hooks/aidlc-stop.ts:953-966
  • core/hooks/aidlc-stop.ts:1074-1095

I reproduced this end-to-end after two successful births: the second intent became active, then Stop returned a forwarding-loop block instead of allowing the requested reset.

Please make Stop resolve the invoking session’s stamped intent or recognize the explicit post-birth handoff boundary. Add a regression test that performs the second birth and invokes the real Stop hook before firing SessionEnd.

2. P1: modern Kiro IDE sessions cannot emit SESSION_ENDED

Modern SessionStart forwards the host’s real session_id, but the payload-free Kiro IDE SessionEnd path always forwards kiro-ide-legacy-current. For a UUID-backed workflow, the core hook refuses fallback from that unstamped synthetic ID, so the modern session’s SESSION_ENDED event and heartbeat are silently dropped.

References:

  • harness/kiro-ide/hooks/aidlc-kiro-adapter.ts:346-355
  • harness/kiro-ide/hooks/aidlc-kiro-adapter.ts:538-545
  • core/hooks/aidlc-session-end.ts:48-67

The existing test covers only the legacy synthetic-session path. Please add a modern SessionStart → birth binding → payload-free agentStop test and preserve a session identity that the SessionEnd hook can resolve.

3. P2: the committed unit suite is red

classifyTerminalCommand() intentionally returns null for intent birth, but the workspace parser parity test still requires a non-null classification.

Reference: tests/unit/t229-workspace-parser.test.ts:250-255

This is also the failure reported by the current smoke+unit CI job.

Verification

  • Focused unit slice: 175 passed, 1 failed (t229-workspace-parser).
  • Intent-birth integration: 42 passed.
  • bun scripts/package.ts --check: passed for all harnesses.
  • git diff --check: passed.
  • The PR remains conflicting with current v2, including authored conflicts in aidlc-orchestrate.ts and t165-intent-birth-p4.test.ts.

I recommend keeping CHANGES_REQUESTED until both handoff paths are corrected, the unit suite is green, and the branch is rebased.

@apackeer
apackeer force-pushed the fix/intent-birth-fail-closed branch from 7b5124d to de77a19 Compare August 7, 2026 23:20
@apackeer

apackeer commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the latest review findings and rebased onto current v2 in de77a196:

  • Added an exact, one-shot per-session handoff receipt for second-intent creation. The real Stop hook now allows the requested fresh-session boundary instead of consulting the newly active intent and forcing the old conversation back into the workflow.
  • Preserved modern Kiro IDE session identity across payload-free Stop/SessionEnd calls, with the legacy synthetic ID retained as fallback. Added a modern SessionStart -> second intent -> real Stop -> SessionEnd regression.
  • Kept intent create on the session-aware engine/conductor path and corrected the stale workspace-parser parity expectation.
  • Rebased on PR fix: AI-DLC speaks in your project terms, not its own (#682) (2.5.57) #707's merge commit and audited its large voice/rename/scaffolding change across the overlapping authored files. The voice contract, narration, hook renames, and scope-aware phase directories are intact. The audit found and fixed one carryover regression where generated runners had reintroduced the retired intent-birth name; all generated guidance now uses intent-create.
  • Regenerated every harness distribution and refreshed the coverage registry.

Verification:

  • bun scripts/package.ts --check
  • bun run typecheck
  • bun run lint
  • Full smoke + unit: 204 files, 5,011 assertions, 0 failures
  • Fresh-session integration regression: 42 tests, 0 failures
  • PR fix: AI-DLC speaks in your project terms, not its own (#682) (2.5.57) #707 focused parity: 13 files, 1,737 assertions, 0 failures
  • Local merge-tree against v2: clean
  • Outgoing secret scan: clean

@apackeer
apackeer force-pushed the fix/intent-birth-fail-closed branch from de77a19 to bfb7a26 Compare August 8, 2026 11:40

@leandrodamascena leandrodamascena left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The latest commit resolves the previously blocking findings:

  • The Stop hook now recognizes the exact post-intent-create fresh-session handoff using a one-shot session receipt.
  • Kiro IDE no longer maps its modern per-turn Stop event to SESSION_ENDED.
  • The workspace parser regression is fixed.
  • The branch is current with v2, mergeable, and package output is synchronized.

I am comfortable approving this revision.

Two non-blocking P2 follow-ups remain for Kiro IDE session identity.

P2 follow-up: preserve the legacy synthetic session during intent creation

harness/kiro-ide/hooks/aidlc-kiro-adapter.ts:491

The legacy Kiro IDE channel has no host-provided session_id, so SessionStart records the synthetic identity kiro-ide-legacy-current. However, the rebuild-stage-graph forwarding path includes a session ID only when ide.sessionId exists. Consequently, a successful legacy intent-create reaches bindCreatedIntentToInvokingSession() without any session identity, so the hook cannot record initial ownership or a second-intent handoff receipt.

I reproduced the first-create path and confirmed that the expected synthetic session stamp was missing.

Suggested follow-up: forward ide.sessionId when available and otherwise use the remembered synthetic session identity.

P2 follow-up: use the Stop event session identity

harness/kiro-ide/hooks/aidlc-kiro-adapter.ts:106,616

Modern Kiro IDE Stop payloads contain session_id, but continue-workflow is excluded from INPUT_TARGETS. The adapter therefore ignores the event-local identity and forwards the workspace-global last-started session instead. With two chats in the same workspace, the most recent SessionStart can overwrite that shared marker. A Stop from S1 can then be evaluated as S2, leaving the exact S1 handoff receipt unconsumed.

I reproduced this with two modern sessions: the receipt correctly described the S1 intent boundary, but it remained after a Stop carrying session_id: "S1".

Suggested follow-up: parse and prefer the Stop payload session_id, retaining the remembered identity only as the legacy fallback.

These issues are limited to Kiro IDE compatibility and session-accounting behavior. They do not corrupt the durable intent record, and modern Kiro IDE Stop is advisory rather than blocking, so I do not consider them merge blockers.

Validation

  • 145 focused tests passed across six files.
  • bun scripts/package.ts --check passed for every harness.
  • bun run typecheck passed.
  • bun run lint exited successfully.
  • git diff --check origin/v2...HEAD passed.
  • The worktree remained clean at bfb7a261.

@apackeer
apackeer force-pushed the fix/intent-birth-fail-closed branch from bfb7a26 to d2c0c86 Compare August 9, 2026 22:29
@apackeer
apackeer merged commit 18bcc46 into v2 Aug 9, 2026
5 checks passed
@apackeer
apackeer deleted the fix/intent-birth-fail-closed branch August 9, 2026 22:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants