feat: crew member dispatch via per-session session_* mount on ACP backends - #8153
Conversation
GPT 5.6 Review — ✅ no blocking findingsGPT 5.6 completed its review of This comment is updated in place on each push. Review detailsNo findings. False positive or not applicable? A repository writer can comment: |
Design Review (Fable 5) — 🟡 CONCERNSDesign-level review of Design-Verdict: CONCERNS Sound per-session-mount architecture; the risk is shipping an always-on, auto-approved session-control surface before its dedicated kill switch (#8083) exists. Watch
Suggestions
[DESIGN-REVIEWED] 9d4b244 |
First Principles Review (Fable 5) — 🟡 CONCERNSPremise-level review of All greps done; I have what I need to issue the review. First-Principles-Verdict: CONCERNS Every capability here is derived from the zero-config member contract or a pod-observed defect — except the "explicit caller pick" backend arm, which has zero callers. What this change shipsIntent: let a crew member's DM thread dispatch, patrol, and stop worker sessions with zero operator setup — an ADDITION.
Watch
Subtractions
[FIRST-PRINCIPLES-REVIEWED] 9d4b244 |
db15d6b to
700e82b
Compare
Opus 4.8 Review — ✅ no blocking findingsReviewed Review detailsI've traced the candidate's mechanics against the actual code. Confirmed facts:
Falsification: the exploit's input condition (a) reduces to "the OS sandbox is not confining writes to No self-originated finding survives scrutiny: the No findings. [OPUS-REVIEWED] 9d4b244 Verdict parsed from the review's SHA-scoped output markers for commit False positive or not applicable? A repository writer can comment: |
700e82b to
ebc532c
Compare
|
ebc532c to
ac500e7
Compare
|
|
/ai-review override gpt ac500e7: H13's pinned scope is the registration seam (ProviderRegistry, create_provider_factory per its own row in harness-parity.md), not per-request dispatch inside the provider/runtime, where seven shipped positive-dispatch call sites already exist; the added call is inert on kiro (getattr + set membership + string check, cannot raise, passes an empty default) and the cross-model local review assessed this exact line pair as requiring no change. |
|
Human judgment recorded@CrysisDeu marked the gpt AI finding as false positive, not applicable, or explicitly accepted for
This decision applies only to this commit. A new push requires a new judgment. |
|
A member DM session previously differed from an ordinary session only by
the [CURRENT AGENT] label. Member turns now inject a [MEMBER IDENTITY]
block with four layers of distinct ownership:
1. identity — derived from the crew record (name, description,
triggers); auto-generated so an empty description still has a floor
2. behavior — product-owned working protocol: worker-not-Q&A-bot,
front-desk-vs-workshop dispatch, a four-rung stuck ladder that
escalates only at permission / reachability / one-way-door walls,
zero-context escalation drafts validated by a context-free subagent,
and quiet-run reporting
3. permanent rules — user-owned, stored at trust/member-rules/<slug>.json
under the keystone-gated trust/ subtree so the member cannot rewrite
its own safety boundary; GET/PUT /api/members/{slug}/rules is the
only write path (human dashboard action, app tokens denied)
4. briefing — member-owned working memory at members/<slug>/briefing.md,
agent-writable by design, injection-capped with a visible marker
Precedence is the injection order. The section builder degrades to an
ordinary crew session on any failure EXCEPT an unreadable rules file:
for the one safety-relevant layer that degrade is the fail-open, so
MemberRulesUnreadable propagates and aborts the turn until the user
repairs or clears the file.
Review follow-ups (post-rebase, addressing bolichen97's review; feature
authored by CrysisDeu):
- read_member_briefing now opens through the central
pinned_fs.open_in_pinned_parent helper, pinning the ancestor chain
(members/<slug>/ is agent-writable too — a swapped parent redirected
the whole traversal past the leaf-only O_NOFOLLOW; same shape as
#2446). Fails closed where the pinned walk is unavailable.
- member_briefing_supported() names the layer-4 availability; on
platforms where briefing reads fail closed (Windows) the section now
says "not available on this platform" and item 6 of [HOW YOU WORK] is
softened instead of instructing upkeep of a never-injected file.
- Rebased onto main over the crew-member operating-mode block (#8153):
both blocks render — dispatch mechanics first, then the four layers.
- Scrub coverage test now carries a fullwidth NFKC forgery per minted
header, and a regression test pins the symlinked-parent refusal
red-before-green.
Co-authored-by: Kiro Crew <noreply@kirodotdev.github.io>
A member DM session previously differed from an ordinary session only by
the [CURRENT AGENT] label. Member turns now inject a [MEMBER IDENTITY]
block with four layers of distinct ownership:
1. identity — derived from the crew record (name, description,
triggers); auto-generated so an empty description still has a floor
2. behavior — product-owned working protocol: worker-not-Q&A-bot,
front-desk-vs-workshop dispatch, a four-rung stuck ladder that
escalates only at permission / reachability / one-way-door walls,
zero-context escalation drafts validated by a context-free subagent,
and quiet-run reporting
3. permanent rules — user-owned, stored at trust/member-rules/<slug>.json
under the keystone-gated trust/ subtree so the member cannot rewrite
its own safety boundary; GET/PUT /api/members/{slug}/rules is the
only write path (human dashboard action, app tokens denied)
4. briefing — member-owned working memory at members/<slug>/briefing.md,
agent-writable by design, injection-capped with a visible marker
Precedence is the injection order. The section builder degrades to an
ordinary crew session on any failure EXCEPT an unreadable rules file:
for the one safety-relevant layer that degrade is the fail-open, so
MemberRulesUnreadable propagates and aborts the turn until the user
repairs or clears the file.
Review follow-ups (post-rebase, addressing bolichen97's review; feature
authored by CrysisDeu):
- read_member_briefing now opens through the central
pinned_fs.open_in_pinned_parent helper, pinning the ancestor chain
(members/<slug>/ is agent-writable too — a swapped parent redirected
the whole traversal past the leaf-only O_NOFOLLOW; same shape as
#2446). Fails closed where the pinned walk is unavailable.
- member_briefing_supported() names the layer-4 availability; on
platforms where briefing reads fail closed (Windows) the section now
says "not available on this platform" and item 6 of [HOW YOU WORK] is
softened instead of instructing upkeep of a never-injected file.
- Rebased onto main over the crew-member operating-mode block (#8153):
both blocks render — dispatch mechanics first, then the four layers.
- Scrub coverage test now carries a fullwidth NFKC forgery per minted
header, and a regression test pins the symlinked-parent refusal
red-before-green.
Co-authored-by: Kiro Crew <noreply@kirodotdev.github.io>
A member DM session previously differed from an ordinary session only by
the [CURRENT AGENT] label. Member turns now inject a [MEMBER IDENTITY]
block with four layers of distinct ownership:
1. identity — derived from the crew record (name, description,
triggers); auto-generated so an empty description still has a floor
2. behavior — product-owned working protocol: worker-not-Q&A-bot,
front-desk-vs-workshop dispatch, a four-rung stuck ladder that
escalates only at permission / reachability / one-way-door walls,
zero-context escalation drafts validated by a context-free subagent,
and quiet-run reporting
3. permanent rules — user-owned, stored at trust/member-rules/<slug>.json
under the keystone-gated trust/ subtree so the member cannot rewrite
its own safety boundary; GET/PUT /api/members/{slug}/rules is the
only write path (human dashboard action, app tokens denied)
4. briefing — member-owned working memory at members/<slug>/briefing.md,
agent-writable by design, injection-capped with a visible marker
Precedence is the injection order. The section builder degrades to an
ordinary crew session on any failure EXCEPT an unreadable rules file:
for the one safety-relevant layer that degrade is the fail-open, so
MemberRulesUnreadable propagates and aborts the turn until the user
repairs or clears the file.
Review follow-ups (post-rebase, addressing bolichen97's review; feature
authored by CrysisDeu):
- read_member_briefing now opens through the central
pinned_fs.open_in_pinned_parent helper, pinning the ancestor chain
(members/<slug>/ is agent-writable too — a swapped parent redirected
the whole traversal past the leaf-only O_NOFOLLOW; same shape as
#2446). Fails closed where the pinned walk is unavailable.
- member_briefing_supported() names the layer-4 availability; on
platforms where briefing reads fail closed (Windows) the section now
says "not available on this platform" and item 6 of [HOW YOU WORK] is
softened instead of instructing upkeep of a never-injected file.
- Rebased onto main over the crew-member operating-mode block (#8153):
both blocks render — dispatch mechanics first, then the four layers.
- Scrub coverage test now carries a fullwidth NFKC forgery per minted
header, and a regression test pins the symlinked-parent refusal
red-before-green.
- The layer-3 fail-closed contract now holds PER-TURN: a first-turn
MemberRulesUnreadable abort leaves a warm session (the provider
client outlives the failed context build), and warm turns skipped
both member-section paths — running a bounded member with no bounds.
build_message now validates rules on every warm member turn, and the
dashboard runner re-arms the reinjection flag when a member DM's
first turn dies before landing, so the section (fresh rules included)
is re-delivered on the first turn after the user repairs the file.
- Feature-map row for Crew Members now names the GET/PUT
/api/members/{slug}/rules endpoints.
- The PUT collision scan uses the central _member_names_for_slug helper
(grammar filter + MemberSlugError tolerance) instead of an ad-hoc
comprehension a hand-edited config key could crash.
Co-authored-by: Kiro Crew <noreply@kirodotdev.github.io>
A member DM session previously differed from an ordinary session only by
the [CURRENT AGENT] label. Member turns now inject a [MEMBER IDENTITY]
block with four layers of distinct ownership:
1. identity — derived from the crew record (name, description,
triggers); auto-generated so an empty description still has a floor
2. behavior — product-owned working protocol: worker-not-Q&A-bot,
front-desk-vs-workshop dispatch, a four-rung stuck ladder that
escalates only at permission / reachability / one-way-door walls,
zero-context escalation drafts validated by a context-free subagent,
and quiet-run reporting
3. permanent rules — user-owned, stored at trust/member-rules/<slug>.json
under the keystone-gated trust/ subtree so the member cannot rewrite
its own safety boundary; GET/PUT /api/members/{slug}/rules is the
only write path (human dashboard action, app tokens denied)
4. briefing — member-owned working memory at members/<slug>/briefing.md,
agent-writable by design, injection-capped with a visible marker
Precedence is the injection order. The section builder degrades to an
ordinary crew session on any failure EXCEPT an unreadable rules file:
for the one safety-relevant layer that degrade is the fail-open, so
MemberRulesUnreadable propagates and aborts the turn until the user
repairs or clears the file.
Review follow-ups (post-rebase, addressing bolichen97's review; feature
authored by CrysisDeu):
- read_member_briefing now opens through the central
pinned_fs.open_in_pinned_parent helper, pinning the ancestor chain
(members/<slug>/ is agent-writable too — a swapped parent redirected
the whole traversal past the leaf-only O_NOFOLLOW; same shape as
#2446). Fails closed where the pinned walk is unavailable.
- member_briefing_supported() names the layer-4 availability; on
platforms where briefing reads fail closed (Windows) the section now
says "not available on this platform" and item 6 of [HOW YOU WORK] is
softened instead of instructing upkeep of a never-injected file.
- Rebased onto main over the crew-member operating-mode block (#8153):
both blocks render — dispatch mechanics first, then the four layers.
- Scrub coverage test now carries a fullwidth NFKC forgery per minted
header, and a regression test pins the symlinked-parent refusal
red-before-green.
- The layer-3 fail-closed contract now holds PER-TURN across the whole
session lifecycle (branch table in build_message): a first-turn
MemberRulesUnreadable abort leaves a warm session whose turns skipped
both member-section paths, and an idle-expired session restored via
session/load carried a STALE [PERMANENT RULES] snapshot. build_message
now validates rules on every warm member turn, re-injects the CURRENT
member section on slim resume (rules gate included), and the dashboard
runner re-arms the reinjection flag when a member DM's first turn dies
before landing, so the section (fresh rules included) is re-delivered
on the first turn after the user repairs the file.
- Feature-map row for Crew Members now names the GET/PUT
/api/members/{slug}/rules endpoints.
- The PUT collision scan uses the central _member_names_for_slug helper
(grammar filter + MemberSlugError tolerance) instead of an ad-hoc
comprehension a hand-edited config key could crash.
Co-authored-by: Kiro Crew <noreply@kirodotdev.github.io>
A member DM session previously differed from an ordinary session only by
the [CURRENT AGENT] label. Member turns now inject a [MEMBER IDENTITY]
block with four layers of distinct ownership:
1. identity — derived from the crew record (name, description,
triggers); auto-generated so an empty description still has a floor
2. behavior — product-owned working protocol: worker-not-Q&A-bot,
front-desk-vs-workshop dispatch, a four-rung stuck ladder that
escalates only at permission / reachability / one-way-door walls,
zero-context escalation drafts validated by a context-free subagent,
and quiet-run reporting
3. permanent rules — user-owned, stored at trust/member-rules/<slug>.json
under the keystone-gated trust/ subtree so the member cannot rewrite
its own safety boundary; GET/PUT /api/members/{slug}/rules is the
only write path (human dashboard action, app tokens denied)
4. briefing — member-owned working memory at members/<slug>/briefing.md,
agent-writable by design, injection-capped with a visible marker
Precedence is the injection order. The section builder degrades to an
ordinary crew session on any failure EXCEPT an unreadable rules file:
for the one safety-relevant layer that degrade is the fail-open, so
MemberRulesUnreadable propagates and aborts the turn until the user
repairs or clears the file.
Review follow-ups (post-rebase, addressing bolichen97's review; feature
authored by CrysisDeu):
- read_member_briefing now opens through the central
pinned_fs.open_in_pinned_parent helper, pinning the ancestor chain
(members/<slug>/ is agent-writable too — a swapped parent redirected
the whole traversal past the leaf-only O_NOFOLLOW; same shape as
#2446). Fails closed where the pinned walk is unavailable.
- member_briefing_supported() names the layer-4 availability; on
platforms where briefing reads fail closed (Windows) the section now
says "not available on this platform" and item 6 of [HOW YOU WORK] is
softened instead of instructing upkeep of a never-injected file.
- Rebased onto main over the crew-member operating-mode block (#8153):
both blocks render — dispatch mechanics first, then the four layers.
- Scrub coverage test now carries a fullwidth NFKC forgery per minted
header, and a regression test pins the symlinked-parent refusal
red-before-green.
- The layer-3 fail-closed contract now holds PER-TURN across the whole
session lifecycle (branch table in build_message): a first-turn
MemberRulesUnreadable abort leaves a warm session whose turns skipped
both member-section paths, and an idle-expired session restored via
session/load carried a STALE [PERMANENT RULES] snapshot. build_message
now validates rules on every warm member turn, re-injects the CURRENT
member section on slim resume (rules gate included), and the dashboard
runner re-arms the reinjection flag when a member DM's first turn dies
before landing, so the section (fresh rules included) is re-delivered
on the first turn after the user repairs the file.
- Feature-map row for Crew Members now names the GET/PUT
/api/members/{slug}/rules endpoints.
- The PUT collision scan uses the central _member_names_for_slug helper
(grammar filter + MemberSlugError tolerance) instead of an ad-hoc
comprehension a hand-edited config key could crash.
- Rules saves and clears are crash-durable: atomic_write's file fsync
covers the data, but the publishing rename, the first-save directory
creation, and the clearing unlink are directory entries — fsync_dir
(raising on save, best_effort on the already-committed clear) makes
the PUT's 200 survive a power-off instead of resurrecting cleared
rules or losing saved ones.
Co-authored-by: Kiro Crew <noreply@kirodotdev.github.io>
A member DM session previously differed from an ordinary session only by
the [CURRENT AGENT] label. Member turns now inject a [MEMBER IDENTITY]
block with four layers of distinct ownership:
1. identity — derived from the crew record (name, description,
triggers); auto-generated so an empty description still has a floor
2. behavior — product-owned working protocol: worker-not-Q&A-bot,
front-desk-vs-workshop dispatch, a four-rung stuck ladder that
escalates only at permission / reachability / one-way-door walls,
zero-context escalation drafts validated by a context-free subagent,
and quiet-run reporting
3. permanent rules — user-owned, stored at trust/member-rules/<slug>.json
under the keystone-gated trust/ subtree so the member cannot rewrite
its own safety boundary; GET/PUT /api/members/{slug}/rules is the
only write path (human dashboard action, app tokens denied)
4. briefing — member-owned working memory at members/<slug>/briefing.md,
agent-writable by design, injection-capped with a visible marker
Precedence is the injection order. The section builder degrades to an
ordinary crew session on any failure EXCEPT an unreadable rules file:
for the one safety-relevant layer that degrade is the fail-open, so
MemberRulesUnreadable propagates and aborts the turn until the user
repairs or clears the file.
Review follow-ups (post-rebase, addressing bolichen97's review; feature
authored by CrysisDeu):
- read_member_briefing now opens through the central
pinned_fs.open_in_pinned_parent helper, pinning the ancestor chain
(members/<slug>/ is agent-writable too — a swapped parent redirected
the whole traversal past the leaf-only O_NOFOLLOW; same shape as
#2446). Fails closed where the pinned walk is unavailable.
- member_briefing_supported() names the layer-4 availability; on
platforms where briefing reads fail closed (Windows) the section now
says "not available on this platform" and item 6 of [HOW YOU WORK] is
softened instead of instructing upkeep of a never-injected file.
- Rebased onto main over the crew-member operating-mode block (#8153):
both blocks render — dispatch mechanics first, then the four layers.
- Scrub coverage test now carries a fullwidth NFKC forgery per minted
header, and a regression test pins the symlinked-parent refusal
red-before-green.
- The layer-3 fail-closed contract now holds PER-TURN across the whole
session lifecycle (branch table in build_message): a first-turn
MemberRulesUnreadable abort leaves a warm session whose turns skipped
both member-section paths, and an idle-expired session restored via
session/load carried a STALE [PERMANENT RULES] snapshot. build_message
now validates rules on every warm member turn, re-injects the CURRENT
member section on slim resume (rules gate included), and the dashboard
runner re-arms the reinjection flag when a member DM's first turn dies
before landing, so the section (fresh rules included) is re-delivered
on the first turn after the user repairs the file.
- Feature-map row for Crew Members now names the GET/PUT
/api/members/{slug}/rules endpoints.
- The PUT collision scan uses the central _member_names_for_slug helper
(grammar filter + MemberSlugError tolerance) instead of an ad-hoc
comprehension a hand-edited config key could crash.
- Rules saves and clears are crash-durable: atomic_write's file fsync
covers the data, but the publishing rename, the first-save directory
creation, and the clearing unlink are directory entries — fsync_dir
(raising on save, best_effort on the already-committed clear) makes
the PUT's 200 survive a power-off instead of resurrecting cleared
rules or losing saved ones.
- The rules GET is owner-gated like the PUT: rules are the owner's
private safety instructions, and any allowed Slack user can mint a
dashboard session, so the read would otherwise disclose them.
- Escaped lone surrogates in the rules payload are refused with a coded
400 (rules_not_encodable) at the PUT boundary, and write_member_rules
rejects UTF-8-unencodable text before any state changes — previously
the UnicodeEncodeError from the write escaped as a mislabeled
"too long" error, and escaping surrogates into the file would only
defer the crash to prompt-encode time inside the member's turn.
Co-authored-by: Kiro Crew <noreply@kirodotdev.github.io>
A member DM session previously differed from an ordinary session only by
the [CURRENT AGENT] label. Member turns now inject a [MEMBER IDENTITY]
block with four layers of distinct ownership:
1. identity — derived from the crew record (name, description,
triggers); auto-generated so an empty description still has a floor
2. behavior — product-owned working protocol: worker-not-Q&A-bot,
front-desk-vs-workshop dispatch, a four-rung stuck ladder that
escalates only at permission / reachability / one-way-door walls,
zero-context escalation drafts validated by a context-free subagent,
and quiet-run reporting
3. permanent rules — user-owned, stored at trust/member-rules/<slug>.json
under the keystone-gated trust/ subtree so the member cannot rewrite
its own safety boundary; GET/PUT /api/members/{slug}/rules is the
only write path (human dashboard action, app tokens denied)
4. briefing — member-owned working memory at members/<slug>/briefing.md,
agent-writable by design, injection-capped with a visible marker
Precedence is the injection order. The section builder degrades to an
ordinary crew session on any failure EXCEPT an unreadable rules file:
for the one safety-relevant layer that degrade is the fail-open, so
MemberRulesUnreadable propagates and aborts the turn until the user
repairs or clears the file.
Review follow-ups (post-rebase, addressing bolichen97's review; feature
authored by CrysisDeu):
- read_member_briefing now opens through the central
pinned_fs.open_in_pinned_parent helper, pinning the ancestor chain
(members/<slug>/ is agent-writable too — a swapped parent redirected
the whole traversal past the leaf-only O_NOFOLLOW; same shape as
#2446). Fails closed where the pinned walk is unavailable.
- member_briefing_supported() names the layer-4 availability; on
platforms where briefing reads fail closed (Windows) the section now
says "not available on this platform" and item 6 of [HOW YOU WORK] is
softened instead of instructing upkeep of a never-injected file.
- Rebased onto main over the crew-member operating-mode block (#8153):
both blocks render — dispatch mechanics first, then the four layers.
- Scrub coverage test now carries a fullwidth NFKC forgery per minted
header, and a regression test pins the symlinked-parent refusal
red-before-green.
- The layer-3 fail-closed contract now holds PER-TURN across the whole
session lifecycle (branch table in build_message): a first-turn
MemberRulesUnreadable abort leaves a warm session whose turns skipped
both member-section paths, and an idle-expired session restored via
session/load carried a STALE [PERMANENT RULES] snapshot. build_message
now validates rules on every warm member turn, re-injects the CURRENT
member section on slim resume (rules gate included), and the dashboard
runner re-arms the reinjection flag when a member DM's first turn dies
before landing, so the section (fresh rules included) is re-delivered
on the first turn after the user repairs the file.
- Feature-map row for Crew Members now names the GET/PUT
/api/members/{slug}/rules endpoints.
- The PUT collision scan uses the central _member_names_for_slug helper
(grammar filter + MemberSlugError tolerance) instead of an ad-hoc
comprehension a hand-edited config key could crash.
- Rules saves and clears are crash-durable: atomic_write's file fsync
covers the data, but the publishing rename, the first-save directory
creation, and the clearing unlink are directory entries — fsync_dir
(raising on save, best_effort on the already-committed clear) makes
the PUT's 200 survive a power-off instead of resurrecting cleared
rules or losing saved ones.
- The rules GET is owner-gated like the PUT: rules are the owner's
private safety instructions, and any allowed Slack user can mint a
dashboard session, so the read would otherwise disclose them.
- Escaped lone surrogates in the rules payload are refused with a coded
400 (rules_not_encodable) at the PUT boundary, and write_member_rules
rejects UTF-8-unencodable text before any state changes — previously
the UnicodeEncodeError from the write escaped as a mislabeled
"too long" error, and escaping surrogates into the file would only
defer the crash to prompt-encode time inside the member's turn.
- The feature-map Crew Members row lists the rules API as a combined
`GET,PUT /api/members/{slug}/rules` entry, keeping the row within the
feature map's four-route limit.
Co-authored-by: Kiro Crew <noreply@kirodotdev.github.io>
A member DM session previously differed from an ordinary session only by
the [CURRENT AGENT] label. Member turns now inject a [MEMBER IDENTITY]
block with four layers of distinct ownership:
1. identity — derived from the crew record (name, description,
triggers); auto-generated so an empty description still has a floor
2. behavior — product-owned working protocol: worker-not-Q&A-bot,
front-desk-vs-workshop dispatch, a four-rung stuck ladder that
escalates only at permission / reachability / one-way-door walls,
zero-context escalation drafts validated by a context-free subagent,
and quiet-run reporting
3. permanent rules — user-owned, stored at trust/member-rules/<slug>.json
under the keystone-gated trust/ subtree so the member cannot rewrite
its own safety boundary; GET/PUT /api/members/{slug}/rules is the
only write path (human dashboard action, app tokens denied)
4. briefing — member-owned working memory at members/<slug>/briefing.md,
agent-writable by design, injection-capped with a visible marker
Precedence is the injection order. The section builder degrades to an
ordinary crew session on any failure EXCEPT an unreadable rules file:
for the one safety-relevant layer that degrade is the fail-open, so
MemberRulesUnreadable propagates and aborts the turn until the user
repairs or clears the file.
Review follow-ups (post-rebase, addressing bolichen97's review; feature
authored by CrysisDeu):
- read_member_briefing now opens through the central
pinned_fs.open_in_pinned_parent helper, pinning the ancestor chain
(members/<slug>/ is agent-writable too — a swapped parent redirected
the whole traversal past the leaf-only O_NOFOLLOW; same shape as
#2446). Fails closed where the pinned walk is unavailable.
- member_briefing_supported() names the layer-4 availability; on
platforms where briefing reads fail closed (Windows) the section now
says "not available on this platform" and item 6 of [HOW YOU WORK] is
softened instead of instructing upkeep of a never-injected file.
- Rebased onto main over the crew-member operating-mode block (#8153):
both blocks render — dispatch mechanics first, then the four layers.
- Scrub coverage test now carries a fullwidth NFKC forgery per minted
header, and a regression test pins the symlinked-parent refusal
red-before-green.
- The layer-3 fail-closed contract now holds PER-TURN across the whole
session lifecycle (branch table in build_message): a first-turn
MemberRulesUnreadable abort leaves a warm session whose turns skipped
both member-section paths, and an idle-expired session restored via
session/load carried a STALE [PERMANENT RULES] snapshot. build_message
now validates rules on every warm member turn, re-injects the CURRENT
member section on slim resume (rules gate included), and the dashboard
runner re-arms the reinjection flag when a member DM's first turn dies
before landing, so the section (fresh rules included) is re-delivered
on the first turn after the user repairs the file.
- Feature-map row for Crew Members now names the GET/PUT
/api/members/{slug}/rules endpoints.
- The PUT collision scan uses the central _member_names_for_slug helper
(grammar filter + MemberSlugError tolerance) instead of an ad-hoc
comprehension a hand-edited config key could crash.
- Rules saves and clears are crash-durable: atomic_write's file fsync
covers the data, but the publishing rename, the first-save directory
creation, and the clearing unlink are directory entries — fsync_dir
(raising on save, best_effort on the already-committed clear) makes
the PUT's 200 survive a power-off instead of resurrecting cleared
rules or losing saved ones.
- The rules GET is owner-gated like the PUT: rules are the owner's
private safety instructions, and any allowed Slack user can mint a
dashboard session, so the read would otherwise disclose them.
- Escaped lone surrogates in the rules payload are refused with a coded
400 (rules_not_encodable) at the PUT boundary, and write_member_rules
rejects UTF-8-unencodable text before any state changes — previously
the UnicodeEncodeError from the write escaped as a mislabeled
"too long" error, and escaping surrogates into the file would only
defer the crash to prompt-encode time inside the member's turn.
- The feature-map Crew Members row lists the rules API as a combined
`GET,PUT /api/members/{slug}/rules` entry, keeping the row within the
feature map's four-route limit.
- The member first-turn re-arm lives in the runner's finally (the one
block on every exit path), not the except arm alone: a user Stop's
cancelled completion never raises, so the except-arm-only re-arm left
the warm session running without [PERMANENT RULES] after a stopped
first turn.
Co-authored-by: Kiro Crew <noreply@kirodotdev.github.io>
Open PR relationship auditThis is a consolidated, point-in-time code-level audit note. It compares complete merge-base diffs and current/merged code; it does not treat a shared topic as duplication or partial coverage as completion. Relationship findings
No PR, Issue, label, branch, or review state was changed by the relationship-note portion of this audit. |
A member DM session previously differed from an ordinary session only by
the [CURRENT AGENT] label. Member turns now inject a [MEMBER IDENTITY]
block with four layers of distinct ownership:
1. identity — derived from the crew record (name, description,
triggers); auto-generated so an empty description still has a floor
2. behavior — product-owned working protocol: worker-not-Q&A-bot,
front-desk-vs-workshop dispatch, a four-rung stuck ladder that
escalates only at permission / reachability / one-way-door walls,
zero-context escalation drafts validated by a context-free subagent,
and quiet-run reporting
3. permanent rules — user-owned, stored at trust/member-rules/<slug>.json
under the keystone-gated trust/ subtree so the member cannot rewrite
its own safety boundary; GET/PUT /api/members/{slug}/rules is the
only write path (human dashboard action, app tokens denied)
4. briefing — member-owned working memory at members/<slug>/briefing.md,
agent-writable by design, injection-capped with a visible marker
Precedence is the injection order. The section builder degrades to an
ordinary crew session on any failure EXCEPT an unreadable rules file:
for the one safety-relevant layer that degrade is the fail-open, so
MemberRulesUnreadable propagates and aborts the turn until the user
repairs or clears the file.
Review follow-ups (post-rebase, addressing bolichen97's review; feature
authored by CrysisDeu):
- read_member_briefing now opens through the central
pinned_fs.open_in_pinned_parent helper, pinning the ancestor chain
(members/<slug>/ is agent-writable too — a swapped parent redirected
the whole traversal past the leaf-only O_NOFOLLOW; same shape as
#2446). Fails closed where the pinned walk is unavailable.
- member_briefing_supported() names the layer-4 availability; on
platforms where briefing reads fail closed (Windows) the section now
says "not available on this platform" and item 6 of [HOW YOU WORK] is
softened instead of instructing upkeep of a never-injected file.
- Rebased onto main over the crew-member operating-mode block (#8153):
both blocks render — dispatch mechanics first, then the four layers.
- Scrub coverage test now carries a fullwidth NFKC forgery per minted
header, and a regression test pins the symlinked-parent refusal
red-before-green.
- The layer-3 fail-closed contract now holds PER-TURN across the whole
session lifecycle (branch table in build_message): a first-turn
MemberRulesUnreadable abort leaves a warm session whose turns skipped
both member-section paths, and an idle-expired session restored via
session/load carried a STALE [PERMANENT RULES] snapshot. build_message
now validates rules on every warm member turn, re-injects the CURRENT
member section on slim resume (rules gate included), and the dashboard
runner re-arms the reinjection flag when a member DM's first turn dies
before landing, so the section (fresh rules included) is re-delivered
on the first turn after the user repairs the file.
- Feature-map row for Crew Members now names the GET/PUT
/api/members/{slug}/rules endpoints.
- The PUT collision scan uses the central _member_names_for_slug helper
(grammar filter + MemberSlugError tolerance) instead of an ad-hoc
comprehension a hand-edited config key could crash.
- Rules saves and clears are crash-durable: atomic_write's file fsync
covers the data, but the publishing rename, the first-save directory
creation, and the clearing unlink are directory entries — fsync_dir
(raising on save, best_effort on the already-committed clear) makes
the PUT's 200 survive a power-off instead of resurrecting cleared
rules or losing saved ones.
- The rules GET is owner-gated like the PUT: rules are the owner's
private safety instructions, and any allowed Slack user can mint a
dashboard session, so the read would otherwise disclose them.
- Escaped lone surrogates in the rules payload are refused with a coded
400 (rules_not_encodable) at the PUT boundary, and write_member_rules
rejects UTF-8-unencodable text before any state changes — previously
the UnicodeEncodeError from the write escaped as a mislabeled
"too long" error, and escaping surrogates into the file would only
defer the crash to prompt-encode time inside the member's turn.
- The feature-map Crew Members row lists the rules API as a combined
`GET,PUT /api/members/{slug}/rules` entry, keeping the row within the
feature map's four-route limit.
- The member first-turn re-arm lives in the runner's finally (the one
block on every exit path), not the except arm alone: a user Stop's
cancelled completion never raises, so the except-arm-only re-arm left
the warm session running without [PERMANENT RULES] after a stopped
first turn.
Co-authored-by: Kiro Crew <noreply@kirodotdev.github.io>
A member DM session previously differed from an ordinary session only by
the [CURRENT AGENT] label. Member turns now inject a [MEMBER IDENTITY]
block with four layers of distinct ownership:
1. identity — derived from the crew record (name, description,
triggers); auto-generated so an empty description still has a floor
2. behavior — product-owned working protocol: worker-not-Q&A-bot,
front-desk-vs-workshop dispatch, a four-rung stuck ladder that
escalates only at permission / reachability / one-way-door walls,
zero-context escalation drafts validated by a context-free subagent,
and quiet-run reporting
3. permanent rules — user-owned, stored at trust/member-rules/<slug>.json
under the keystone-gated trust/ subtree so the member cannot rewrite
its own safety boundary; GET/PUT /api/members/{slug}/rules is the
only write path (human dashboard action, app tokens denied)
4. briefing — member-owned working memory at members/<slug>/briefing.md,
agent-writable by design, injection-capped with a visible marker
Precedence is the injection order. The section builder degrades to an
ordinary crew session on any failure EXCEPT an unreadable rules file:
for the one safety-relevant layer that degrade is the fail-open, so
MemberRulesUnreadable propagates and aborts the turn until the user
repairs or clears the file.
Review follow-ups (post-rebase, addressing bolichen97's review; feature
authored by CrysisDeu):
- read_member_briefing now opens through the central
pinned_fs.open_in_pinned_parent helper, pinning the ancestor chain
(members/<slug>/ is agent-writable too — a swapped parent redirected
the whole traversal past the leaf-only O_NOFOLLOW; same shape as
#2446). Fails closed where the pinned walk is unavailable.
- member_briefing_supported() names the layer-4 availability; on
platforms where briefing reads fail closed (Windows) the section now
says "not available on this platform" and item 6 of [HOW YOU WORK] is
softened instead of instructing upkeep of a never-injected file.
- Rebased onto main over the crew-member operating-mode block (#8153):
both blocks render — dispatch mechanics first, then the four layers.
- Scrub coverage test now carries a fullwidth NFKC forgery per minted
header, and a regression test pins the symlinked-parent refusal
red-before-green.
- The layer-3 fail-closed contract now holds PER-TURN across the whole
session lifecycle (branch table in build_message): a first-turn
MemberRulesUnreadable abort leaves a warm session whose turns skipped
both member-section paths, and an idle-expired session restored via
session/load carried a STALE [PERMANENT RULES] snapshot. build_message
now validates rules on every warm member turn, re-injects the CURRENT
member section on slim resume (rules gate included), and the dashboard
runner re-arms the reinjection flag when a member DM's first turn dies
before landing, so the section (fresh rules included) is re-delivered
on the first turn after the user repairs the file.
- Feature-map row for Crew Members now names the GET/PUT
/api/members/{slug}/rules endpoints.
- The PUT collision scan uses the central _member_names_for_slug helper
(grammar filter + MemberSlugError tolerance) instead of an ad-hoc
comprehension a hand-edited config key could crash.
- Rules saves and clears are crash-durable: atomic_write's file fsync
covers the data, but the publishing rename, the first-save directory
creation, and the clearing unlink are directory entries — fsync_dir
(raising on save, best_effort on the already-committed clear) makes
the PUT's 200 survive a power-off instead of resurrecting cleared
rules or losing saved ones.
- The rules GET is owner-gated like the PUT: rules are the owner's
private safety instructions, and any allowed Slack user can mint a
dashboard session, so the read would otherwise disclose them.
- Escaped lone surrogates in the rules payload are refused with a coded
400 (rules_not_encodable) at the PUT boundary, and write_member_rules
rejects UTF-8-unencodable text before any state changes — previously
the UnicodeEncodeError from the write escaped as a mislabeled
"too long" error, and escaping surrogates into the file would only
defer the crash to prompt-encode time inside the member's turn.
- The feature-map Crew Members row lists the rules API as a combined
`GET,PUT /api/members/{slug}/rules` entry, keeping the row within the
feature map's four-route limit.
- The member first-turn re-arm lives in the runner's finally (the one
block on every exit path), not the except arm alone: a user Stop's
cancelled completion never raises, so the except-arm-only re-arm left
the warm session running without [PERMANENT RULES] after a stopped
first turn.
Co-authored-by: Kiro Crew <noreply@kirodotdev.github.io>
A member DM session previously differed from an ordinary session only by
the [CURRENT AGENT] label. Member turns now inject a [MEMBER IDENTITY]
block with four layers of distinct ownership:
1. identity — derived from the crew record (name, description,
triggers); auto-generated so an empty description still has a floor
2. behavior — product-owned working protocol: worker-not-Q&A-bot,
front-desk-vs-workshop dispatch, a four-rung stuck ladder that
escalates only at permission / reachability / one-way-door walls,
zero-context escalation drafts validated by a context-free subagent,
and quiet-run reporting
3. permanent rules — user-owned, stored at trust/member-rules/<slug>.json
under the keystone-gated trust/ subtree so the member cannot rewrite
its own safety boundary; GET/PUT /api/members/{slug}/rules is the
only write path (human dashboard action, app tokens denied)
4. briefing — member-owned working memory at members/<slug>/briefing.md,
agent-writable by design, injection-capped with a visible marker
Precedence is the injection order. The section builder degrades to an
ordinary crew session on any failure EXCEPT an unreadable rules file:
for the one safety-relevant layer that degrade is the fail-open, so
MemberRulesUnreadable propagates and aborts the turn until the user
repairs or clears the file.
Review follow-ups (post-rebase, addressing bolichen97's review; feature
authored by CrysisDeu):
- read_member_briefing now opens through the central
pinned_fs.open_in_pinned_parent helper, pinning the ancestor chain
(members/<slug>/ is agent-writable too — a swapped parent redirected
the whole traversal past the leaf-only O_NOFOLLOW; same shape as
#2446). Fails closed where the pinned walk is unavailable.
- member_briefing_supported() names the layer-4 availability; on
platforms where briefing reads fail closed (Windows) the section now
says "not available on this platform" and item 6 of [HOW YOU WORK] is
softened instead of instructing upkeep of a never-injected file.
- Rebased onto main over the crew-member operating-mode block (#8153):
both blocks render — dispatch mechanics first, then the four layers.
- Scrub coverage test now carries a fullwidth NFKC forgery per minted
header, and a regression test pins the symlinked-parent refusal
red-before-green.
- The layer-3 fail-closed contract now holds PER-TURN across the whole
session lifecycle (branch table in build_message): a first-turn
MemberRulesUnreadable abort leaves a warm session whose turns skipped
both member-section paths, and an idle-expired session restored via
session/load carried a STALE [PERMANENT RULES] snapshot. build_message
now validates rules on every warm member turn, re-injects the CURRENT
member section on slim resume (rules gate included), and the dashboard
runner re-arms the reinjection flag when a member DM's first turn dies
before landing, so the section (fresh rules included) is re-delivered
on the first turn after the user repairs the file.
- Feature-map row for Crew Members now names the GET/PUT
/api/members/{slug}/rules endpoints.
- The PUT collision scan uses the central _member_names_for_slug helper
(grammar filter + MemberSlugError tolerance) instead of an ad-hoc
comprehension a hand-edited config key could crash.
- Rules saves and clears are crash-durable: atomic_write's file fsync
covers the data, but the publishing rename, the first-save directory
creation, and the clearing unlink are directory entries — fsync_dir
(raising on save, best_effort on the already-committed clear) makes
the PUT's 200 survive a power-off instead of resurrecting cleared
rules or losing saved ones.
- The rules GET is owner-gated like the PUT: rules are the owner's
private safety instructions, and any allowed Slack user can mint a
dashboard session, so the read would otherwise disclose them.
- Escaped lone surrogates in the rules payload are refused with a coded
400 (rules_not_encodable) at the PUT boundary, and write_member_rules
rejects UTF-8-unencodable text before any state changes — previously
the UnicodeEncodeError from the write escaped as a mislabeled
"too long" error, and escaping surrogates into the file would only
defer the crash to prompt-encode time inside the member's turn.
- The feature-map Crew Members row lists the rules API as a combined
`GET,PUT /api/members/{slug}/rules` entry, keeping the row within the
feature map's four-route limit.
- The member first-turn re-arm lives in the runner's finally (the one
block on every exit path), not the except arm alone: a user Stop's
cancelled completion never raises, so the except-arm-only re-arm left
the warm session running without [PERMANENT RULES] after a stopped
first turn.
Co-authored-by: Kiro Crew <noreply@kirodotdev.github.io>
A member DM session previously differed from an ordinary session only by
the [CURRENT AGENT] label. Member turns now inject a [MEMBER IDENTITY]
block with four layers of distinct ownership:
1. identity — derived from the crew record (name, description,
triggers); auto-generated so an empty description still has a floor
2. behavior — product-owned working protocol: worker-not-Q&A-bot,
front-desk-vs-workshop dispatch, a four-rung stuck ladder that
escalates only at permission / reachability / one-way-door walls,
zero-context escalation drafts validated by a context-free subagent,
and quiet-run reporting
3. permanent rules — user-owned, stored at trust/member-rules/<slug>.json
under the keystone-gated trust/ subtree so the member cannot rewrite
its own safety boundary; GET/PUT /api/members/{slug}/rules is the
only write path (human dashboard action, app tokens denied)
4. briefing — member-owned working memory at members/<slug>/briefing.md,
agent-writable by design, injection-capped with a visible marker
Precedence is the injection order. The section builder degrades to an
ordinary crew session on any failure EXCEPT an unreadable rules file:
for the one safety-relevant layer that degrade is the fail-open, so
MemberRulesUnreadable propagates and aborts the turn until the user
repairs or clears the file.
Review follow-ups (post-rebase, addressing bolichen97's review; feature
authored by CrysisDeu):
- read_member_briefing now opens through the central
pinned_fs.open_in_pinned_parent helper, pinning the ancestor chain
(members/<slug>/ is agent-writable too — a swapped parent redirected
the whole traversal past the leaf-only O_NOFOLLOW; same shape as
#2446). Fails closed where the pinned walk is unavailable.
- member_briefing_supported() names the layer-4 availability; on
platforms where briefing reads fail closed (Windows) the section now
says "not available on this platform" and item 6 of [HOW YOU WORK] is
softened instead of instructing upkeep of a never-injected file.
- Rebased onto main over the crew-member operating-mode block (#8153):
both blocks render — dispatch mechanics first, then the four layers.
- Scrub coverage test now carries a fullwidth NFKC forgery per minted
header, and a regression test pins the symlinked-parent refusal
red-before-green.
- The layer-3 fail-closed contract now holds PER-TURN across the whole
session lifecycle (branch table in build_message): a first-turn
MemberRulesUnreadable abort leaves a warm session whose turns skipped
both member-section paths, and an idle-expired session restored via
session/load carried a STALE [PERMANENT RULES] snapshot. build_message
now validates rules on every warm member turn, re-injects the CURRENT
member section on slim resume (rules gate included), and the dashboard
runner re-arms the reinjection flag when a member DM's first turn dies
before landing, so the section (fresh rules included) is re-delivered
on the first turn after the user repairs the file.
- Feature-map row for Crew Members now names the GET/PUT
/api/members/{slug}/rules endpoints.
- The PUT collision scan uses the central _member_names_for_slug helper
(grammar filter + MemberSlugError tolerance) instead of an ad-hoc
comprehension a hand-edited config key could crash.
- Rules saves and clears are crash-durable: atomic_write's file fsync
covers the data, but the publishing rename, the first-save directory
creation, and the clearing unlink are directory entries — fsync_dir
(raising on save, best_effort on the already-committed clear) makes
the PUT's 200 survive a power-off instead of resurrecting cleared
rules or losing saved ones.
- The rules GET is owner-gated like the PUT: rules are the owner's
private safety instructions, and any allowed Slack user can mint a
dashboard session, so the read would otherwise disclose them.
- Escaped lone surrogates in the rules payload are refused with a coded
400 (rules_not_encodable) at the PUT boundary, and write_member_rules
rejects UTF-8-unencodable text before any state changes — previously
the UnicodeEncodeError from the write escaped as a mislabeled
"too long" error, and escaping surrogates into the file would only
defer the crash to prompt-encode time inside the member's turn.
- The feature-map Crew Members row lists the rules API as a combined
`GET,PUT /api/members/{slug}/rules` entry, keeping the row within the
feature map's four-route limit.
- The member first-turn re-arm lives in the runner's finally (the one
block on every exit path), not the except arm alone: a user Stop's
cancelled completion never raises, so the except-arm-only re-arm left
the warm session running without [PERMANENT RULES] after a stopped
first turn.
Co-authored-by: Kiro Crew <noreply@kirodotdev.github.io>
A member DM session previously differed from an ordinary session only by
the [CURRENT AGENT] label. Member turns now inject a [MEMBER IDENTITY]
block with four layers of distinct ownership:
1. identity — derived from the crew record (name, description,
triggers); auto-generated so an empty description still has a floor
2. behavior — product-owned working protocol: worker-not-Q&A-bot,
front-desk-vs-workshop dispatch, a four-rung stuck ladder that
escalates only at permission / reachability / one-way-door walls,
zero-context escalation drafts validated by a context-free subagent,
and quiet-run reporting
3. permanent rules — user-owned, stored at trust/member-rules/<slug>.json
under the keystone-gated trust/ subtree so the member cannot rewrite
its own safety boundary; GET/PUT /api/members/{slug}/rules is the
only write path (human dashboard action, app tokens denied)
4. briefing — member-owned working memory at members/<slug>/briefing.md,
agent-writable by design, injection-capped with a visible marker
Precedence is the injection order. The section builder degrades to an
ordinary crew session on any failure EXCEPT an unreadable rules file:
for the one safety-relevant layer that degrade is the fail-open, so
MemberRulesUnreadable propagates and aborts the turn until the user
repairs or clears the file.
Review follow-ups (post-rebase, addressing bolichen97's review; feature
authored by CrysisDeu):
- read_member_briefing now opens through the central
pinned_fs.open_in_pinned_parent helper, pinning the ancestor chain
(members/<slug>/ is agent-writable too — a swapped parent redirected
the whole traversal past the leaf-only O_NOFOLLOW; same shape as
#2446). Fails closed where the pinned walk is unavailable.
- member_briefing_supported() names the layer-4 availability; on
platforms where briefing reads fail closed (Windows) the section now
says "not available on this platform" and item 6 of [HOW YOU WORK] is
softened instead of instructing upkeep of a never-injected file.
- Rebased onto main over the crew-member operating-mode block (#8153):
both blocks render — dispatch mechanics first, then the four layers.
- Scrub coverage test now carries a fullwidth NFKC forgery per minted
header, and a regression test pins the symlinked-parent refusal
red-before-green.
- The layer-3 fail-closed contract now holds PER-TURN across the whole
session lifecycle (branch table in build_message): a first-turn
MemberRulesUnreadable abort leaves a warm session whose turns skipped
both member-section paths, and an idle-expired session restored via
session/load carried a STALE [PERMANENT RULES] snapshot. build_message
now validates rules on every warm member turn, re-injects the CURRENT
member section on slim resume (rules gate included), and the dashboard
runner re-arms the reinjection flag when a member DM's first turn dies
before landing, so the section (fresh rules included) is re-delivered
on the first turn after the user repairs the file.
- Feature-map row for Crew Members now names the GET/PUT
/api/members/{slug}/rules endpoints.
- The PUT collision scan uses the central _member_names_for_slug helper
(grammar filter + MemberSlugError tolerance) instead of an ad-hoc
comprehension a hand-edited config key could crash.
- Rules saves and clears are crash-durable: atomic_write's file fsync
covers the data, but the publishing rename, the first-save directory
creation, and the clearing unlink are directory entries — fsync_dir
(raising on save, best_effort on the already-committed clear) makes
the PUT's 200 survive a power-off instead of resurrecting cleared
rules or losing saved ones.
- The rules GET is owner-gated like the PUT: rules are the owner's
private safety instructions, and any allowed Slack user can mint a
dashboard session, so the read would otherwise disclose them.
- Escaped lone surrogates in the rules payload are refused with a coded
400 (rules_not_encodable) at the PUT boundary, and write_member_rules
rejects UTF-8-unencodable text before any state changes — previously
the UnicodeEncodeError from the write escaped as a mislabeled
"too long" error, and escaping surrogates into the file would only
defer the crash to prompt-encode time inside the member's turn.
- The feature-map Crew Members row lists the rules API as a combined
`GET,PUT /api/members/{slug}/rules` entry, keeping the row within the
feature map's four-route limit.
- The member first-turn re-arm lives in the runner's finally (the one
block on every exit path), not the except arm alone: a user Stop's
cancelled completion never raises, so the except-arm-only re-arm left
the warm session running without [PERMANENT RULES] after a stopped
first turn.
Co-authored-by: Kiro Crew <noreply@kirodotdev.github.io>
A member DM session previously differed from an ordinary session only by
the [CURRENT AGENT] label. Member turns now inject a [MEMBER IDENTITY]
block with four layers of distinct ownership:
1. identity — derived from the crew record (name, description,
triggers); auto-generated so an empty description still has a floor
2. behavior — product-owned working protocol: worker-not-Q&A-bot,
front-desk-vs-workshop dispatch, a four-rung stuck ladder that
escalates only at permission / reachability / one-way-door walls,
zero-context escalation drafts validated by a context-free subagent,
and quiet-run reporting
3. permanent rules — user-owned, stored at trust/member-rules/<slug>.json
under the keystone-gated trust/ subtree so the member cannot rewrite
its own safety boundary; GET/PUT /api/members/{slug}/rules is the
only write path (human dashboard action, app tokens denied)
4. briefing — member-owned working memory at members/<slug>/briefing.md,
agent-writable by design, injection-capped with a visible marker
Precedence is the injection order. The section builder degrades to an
ordinary crew session on any failure EXCEPT an unreadable rules file:
for the one safety-relevant layer that degrade is the fail-open, so
MemberRulesUnreadable propagates and aborts the turn until the user
repairs or clears the file.
Review follow-ups (post-rebase, addressing bolichen97's review; feature
authored by CrysisDeu):
- read_member_briefing now opens through the central
pinned_fs.open_in_pinned_parent helper, pinning the ancestor chain
(members/<slug>/ is agent-writable too — a swapped parent redirected
the whole traversal past the leaf-only O_NOFOLLOW; same shape as
#2446). Fails closed where the pinned walk is unavailable.
- member_briefing_supported() names the layer-4 availability; on
platforms where briefing reads fail closed (Windows) the section now
says "not available on this platform" and item 6 of [HOW YOU WORK] is
softened instead of instructing upkeep of a never-injected file.
- Rebased onto main over the crew-member operating-mode block (#8153):
both blocks render — dispatch mechanics first, then the four layers.
- Scrub coverage test now carries a fullwidth NFKC forgery per minted
header, and a regression test pins the symlinked-parent refusal
red-before-green.
- The layer-3 fail-closed contract now holds PER-TURN across the whole
session lifecycle (branch table in build_message): a first-turn
MemberRulesUnreadable abort leaves a warm session whose turns skipped
both member-section paths, and an idle-expired session restored via
session/load carried a STALE [PERMANENT RULES] snapshot. build_message
now validates rules on every warm member turn, re-injects the CURRENT
member section on slim resume (rules gate included), and the dashboard
runner re-arms the reinjection flag when a member DM's first turn dies
before landing, so the section (fresh rules included) is re-delivered
on the first turn after the user repairs the file.
- Feature-map row for Crew Members now names the GET/PUT
/api/members/{slug}/rules endpoints.
- The PUT collision scan uses the central _member_names_for_slug helper
(grammar filter + MemberSlugError tolerance) instead of an ad-hoc
comprehension a hand-edited config key could crash.
- Rules saves and clears are crash-durable: atomic_write's file fsync
covers the data, but the publishing rename, the first-save directory
creation, and the clearing unlink are directory entries — fsync_dir
(raising on save, best_effort on the already-committed clear) makes
the PUT's 200 survive a power-off instead of resurrecting cleared
rules or losing saved ones.
- The rules GET is owner-gated like the PUT: rules are the owner's
private safety instructions, and any allowed Slack user can mint a
dashboard session, so the read would otherwise disclose them.
- Escaped lone surrogates in the rules payload are refused with a coded
400 (rules_not_encodable) at the PUT boundary, and write_member_rules
rejects UTF-8-unencodable text before any state changes — previously
the UnicodeEncodeError from the write escaped as a mislabeled
"too long" error, and escaping surrogates into the file would only
defer the crash to prompt-encode time inside the member's turn.
- The feature-map Crew Members row lists the rules API as a combined
`GET,PUT /api/members/{slug}/rules` entry, keeping the row within the
feature map's four-route limit.
- The member first-turn re-arm lives in the runner's finally (the one
block on every exit path), not the except arm alone: a user Stop's
cancelled completion never raises, so the except-arm-only re-arm left
the warm session running without [PERMANENT RULES] after a stopped
first turn.
Co-authored-by: Kiro Crew <noreply@kirodotdev.github.io>
A member DM session previously differed from an ordinary session only by
the [CURRENT AGENT] label. Member turns now inject a [MEMBER IDENTITY]
block with four layers of distinct ownership:
1. identity — derived from the crew record (name, description,
triggers); auto-generated so an empty description still has a floor
2. behavior — product-owned working protocol: worker-not-Q&A-bot,
front-desk-vs-workshop dispatch, a four-rung stuck ladder that
escalates only at permission / reachability / one-way-door walls,
zero-context escalation drafts validated by a context-free subagent,
and quiet-run reporting
3. permanent rules — user-owned, stored at trust/member-rules/<slug>.json
under the keystone-gated trust/ subtree so the member cannot rewrite
its own safety boundary; GET/PUT /api/members/{slug}/rules is the
only write path (human dashboard action, app tokens denied)
4. briefing — member-owned working memory at members/<slug>/briefing.md,
agent-writable by design, injection-capped with a visible marker
Precedence is the injection order. The section builder degrades to an
ordinary crew session on any failure EXCEPT an unreadable rules file:
for the one safety-relevant layer that degrade is the fail-open, so
MemberRulesUnreadable propagates and aborts the turn until the user
repairs or clears the file.
Review follow-ups (post-rebase, addressing bolichen97's review; feature
authored by CrysisDeu):
- read_member_briefing now opens through the central
pinned_fs.open_in_pinned_parent helper, pinning the ancestor chain
(members/<slug>/ is agent-writable too — a swapped parent redirected
the whole traversal past the leaf-only O_NOFOLLOW; same shape as
#2446). Fails closed where the pinned walk is unavailable.
- member_briefing_supported() names the layer-4 availability; on
platforms where briefing reads fail closed (Windows) the section now
says "not available on this platform" and item 6 of [HOW YOU WORK] is
softened instead of instructing upkeep of a never-injected file.
- Rebased onto main over the crew-member operating-mode block (#8153):
both blocks render — dispatch mechanics first, then the four layers.
- Scrub coverage test now carries a fullwidth NFKC forgery per minted
header, and a regression test pins the symlinked-parent refusal
red-before-green.
- The layer-3 fail-closed contract now holds PER-TURN across the whole
session lifecycle (branch table in build_message): a first-turn
MemberRulesUnreadable abort leaves a warm session whose turns skipped
both member-section paths, and an idle-expired session restored via
session/load carried a STALE [PERMANENT RULES] snapshot. build_message
now validates rules on every warm member turn, re-injects the CURRENT
member section on slim resume (rules gate included), and the dashboard
runner re-arms the reinjection flag when a member DM's first turn dies
before landing, so the section (fresh rules included) is re-delivered
on the first turn after the user repairs the file.
- Feature-map row for Crew Members now names the GET/PUT
/api/members/{slug}/rules endpoints.
- The PUT collision scan uses the central _member_names_for_slug helper
(grammar filter + MemberSlugError tolerance) instead of an ad-hoc
comprehension a hand-edited config key could crash.
- Rules saves and clears are crash-durable: atomic_write's file fsync
covers the data, but the publishing rename, the first-save directory
creation, and the clearing unlink are directory entries — fsync_dir
(raising on save, best_effort on the already-committed clear) makes
the PUT's 200 survive a power-off instead of resurrecting cleared
rules or losing saved ones.
- The rules GET is owner-gated like the PUT: rules are the owner's
private safety instructions, and any allowed Slack user can mint a
dashboard session, so the read would otherwise disclose them.
- Escaped lone surrogates in the rules payload are refused with a coded
400 (rules_not_encodable) at the PUT boundary, and write_member_rules
rejects UTF-8-unencodable text before any state changes — previously
the UnicodeEncodeError from the write escaped as a mislabeled
"too long" error, and escaping surrogates into the file would only
defer the crash to prompt-encode time inside the member's turn.
- The feature-map Crew Members row lists the rules API as a combined
`GET,PUT /api/members/{slug}/rules` entry, keeping the row within the
feature map's four-route limit.
- The member first-turn re-arm lives in the runner's finally (the one
block on every exit path), not the except arm alone: a user Stop's
cancelled completion never raises, so the except-arm-only re-arm left
the warm session running without [PERMANENT RULES] after a stopped
first turn.
Co-authored-by: Kiro Crew <noreply@kirodotdev.github.io>
A member DM session previously differed from an ordinary session only by
the [CURRENT AGENT] label. Member turns now inject a [MEMBER IDENTITY]
block with four layers of distinct ownership:
1. identity — derived from the crew record (name, description,
triggers); auto-generated so an empty description still has a floor
2. behavior — product-owned working protocol: worker-not-Q&A-bot,
front-desk-vs-workshop dispatch, a four-rung stuck ladder that
escalates only at permission / reachability / one-way-door walls,
zero-context escalation drafts validated by a context-free subagent,
and quiet-run reporting
3. permanent rules — user-owned, stored at trust/member-rules/<slug>.json
under the keystone-gated trust/ subtree so the member cannot rewrite
its own safety boundary; GET/PUT /api/members/{slug}/rules is the
only write path (human dashboard action, app tokens denied)
4. briefing — member-owned working memory at members/<slug>/briefing.md,
agent-writable by design, injection-capped with a visible marker
Precedence is the injection order. The section builder degrades to an
ordinary crew session on any failure EXCEPT an unreadable rules file:
for the one safety-relevant layer that degrade is the fail-open, so
MemberRulesUnreadable propagates and aborts the turn until the user
repairs or clears the file.
Review follow-ups (post-rebase, addressing bolichen97's review; feature
authored by CrysisDeu):
- read_member_briefing now opens through the central
pinned_fs.open_in_pinned_parent helper, pinning the ancestor chain
(members/<slug>/ is agent-writable too — a swapped parent redirected
the whole traversal past the leaf-only O_NOFOLLOW; same shape as
#2446). Fails closed where the pinned walk is unavailable.
- member_briefing_supported() names the layer-4 availability; on
platforms where briefing reads fail closed (Windows) the section now
says "not available on this platform" and item 6 of [HOW YOU WORK] is
softened instead of instructing upkeep of a never-injected file.
- Rebased onto main over the crew-member operating-mode block (#8153):
both blocks render — dispatch mechanics first, then the four layers.
- Scrub coverage test now carries a fullwidth NFKC forgery per minted
header, and a regression test pins the symlinked-parent refusal
red-before-green.
- The layer-3 fail-closed contract now holds PER-TURN across the whole
session lifecycle (branch table in build_message): a first-turn
MemberRulesUnreadable abort leaves a warm session whose turns skipped
both member-section paths, and an idle-expired session restored via
session/load carried a STALE [PERMANENT RULES] snapshot. build_message
now validates rules on every warm member turn, re-injects the CURRENT
member section on slim resume (rules gate included), and the dashboard
runner re-arms the reinjection flag when a member DM's first turn dies
before landing, so the section (fresh rules included) is re-delivered
on the first turn after the user repairs the file.
- Feature-map row for Crew Members now names the GET/PUT
/api/members/{slug}/rules endpoints.
- The PUT collision scan uses the central _member_names_for_slug helper
(grammar filter + MemberSlugError tolerance) instead of an ad-hoc
comprehension a hand-edited config key could crash.
- Rules saves and clears are crash-durable: atomic_write's file fsync
covers the data, but the publishing rename, the first-save directory
creation, and the clearing unlink are directory entries — fsync_dir
(raising on save, best_effort on the already-committed clear) makes
the PUT's 200 survive a power-off instead of resurrecting cleared
rules or losing saved ones.
- The rules GET is owner-gated like the PUT: rules are the owner's
private safety instructions, and any allowed Slack user can mint a
dashboard session, so the read would otherwise disclose them.
- Escaped lone surrogates in the rules payload are refused with a coded
400 (rules_not_encodable) at the PUT boundary, and write_member_rules
rejects UTF-8-unencodable text before any state changes — previously
the UnicodeEncodeError from the write escaped as a mislabeled
"too long" error, and escaping surrogates into the file would only
defer the crash to prompt-encode time inside the member's turn.
- The feature-map Crew Members row lists the rules API as a combined
`GET,PUT /api/members/{slug}/rules` entry, keeping the row within the
feature map's four-route limit.
- The member first-turn re-arm lives in the runner's finally (the one
block on every exit path), not the except arm alone: a user Stop's
cancelled completion never raises, so the except-arm-only re-arm left
the warm session running without [PERMANENT RULES] after a stopped
first turn.
Co-authored-by: Kiro Crew <noreply@kirodotdev.github.io>
A member DM session previously differed from an ordinary session only by
the [CURRENT AGENT] label. Member turns now inject a [MEMBER IDENTITY]
block with four layers of distinct ownership:
1. identity — derived from the crew record (name, description,
triggers); auto-generated so an empty description still has a floor
2. behavior — product-owned working protocol: worker-not-Q&A-bot,
front-desk-vs-workshop dispatch, a four-rung stuck ladder that
escalates only at permission / reachability / one-way-door walls,
zero-context escalation drafts validated by a context-free subagent,
and quiet-run reporting
3. permanent rules — user-owned, stored at trust/member-rules/<slug>.json
under the keystone-gated trust/ subtree so the member cannot rewrite
its own safety boundary; GET/PUT /api/members/{slug}/rules is the
only write path (human dashboard action, app tokens denied)
4. briefing — member-owned working memory at members/<slug>/briefing.md,
agent-writable by design, injection-capped with a visible marker
Precedence is the injection order. The section builder degrades to an
ordinary crew session on any failure EXCEPT an unreadable rules file:
for the one safety-relevant layer that degrade is the fail-open, so
MemberRulesUnreadable propagates and aborts the turn until the user
repairs or clears the file.
Review follow-ups (post-rebase, addressing bolichen97's review; feature
authored by CrysisDeu):
- read_member_briefing now opens through the central
pinned_fs.open_in_pinned_parent helper, pinning the ancestor chain
(members/<slug>/ is agent-writable too — a swapped parent redirected
the whole traversal past the leaf-only O_NOFOLLOW; same shape as
#2446). Fails closed where the pinned walk is unavailable.
- member_briefing_supported() names the layer-4 availability; on
platforms where briefing reads fail closed (Windows) the section now
says "not available on this platform" and item 6 of [HOW YOU WORK] is
softened instead of instructing upkeep of a never-injected file.
- Rebased onto main over the crew-member operating-mode block (#8153):
both blocks render — dispatch mechanics first, then the four layers.
- Scrub coverage test now carries a fullwidth NFKC forgery per minted
header, and a regression test pins the symlinked-parent refusal
red-before-green.
- The layer-3 fail-closed contract now holds PER-TURN across the whole
session lifecycle (branch table in build_message): a first-turn
MemberRulesUnreadable abort leaves a warm session whose turns skipped
both member-section paths, and an idle-expired session restored via
session/load carried a STALE [PERMANENT RULES] snapshot. build_message
now validates rules on every warm member turn, re-injects the CURRENT
member section on slim resume (rules gate included), and the dashboard
runner re-arms the reinjection flag when a member DM's first turn dies
before landing, so the section (fresh rules included) is re-delivered
on the first turn after the user repairs the file.
- Feature-map row for Crew Members now names the GET/PUT
/api/members/{slug}/rules endpoints.
- The PUT collision scan uses the central _member_names_for_slug helper
(grammar filter + MemberSlugError tolerance) instead of an ad-hoc
comprehension a hand-edited config key could crash.
- Rules saves and clears are crash-durable: atomic_write's file fsync
covers the data, but the publishing rename, the first-save directory
creation, and the clearing unlink are directory entries — fsync_dir
(raising on save, best_effort on the already-committed clear) makes
the PUT's 200 survive a power-off instead of resurrecting cleared
rules or losing saved ones.
- The rules GET is owner-gated like the PUT: rules are the owner's
private safety instructions, and any allowed Slack user can mint a
dashboard session, so the read would otherwise disclose them.
- Escaped lone surrogates in the rules payload are refused with a coded
400 (rules_not_encodable) at the PUT boundary, and write_member_rules
rejects UTF-8-unencodable text before any state changes — previously
the UnicodeEncodeError from the write escaped as a mislabeled
"too long" error, and escaping surrogates into the file would only
defer the crash to prompt-encode time inside the member's turn.
- The feature-map Crew Members row lists the rules API as a combined
`GET,PUT /api/members/{slug}/rules` entry, keeping the row within the
feature map's four-route limit.
- The member first-turn re-arm lives in the runner's finally (the one
block on every exit path), not the except arm alone: a user Stop's
cancelled completion never raises, so the except-arm-only re-arm left
the warm session running without [PERMANENT RULES] after a stopped
first turn.
Co-authored-by: Kiro Crew <noreply@kirodotdev.github.io>
A member DM session previously differed from an ordinary session only by
the [CURRENT AGENT] label. Member turns now inject a [MEMBER IDENTITY]
block with four layers of distinct ownership:
1. identity — derived from the crew record (name, description,
triggers); auto-generated so an empty description still has a floor
2. behavior — product-owned working protocol: worker-not-Q&A-bot,
front-desk-vs-workshop dispatch, a four-rung stuck ladder that
escalates only at permission / reachability / one-way-door walls,
zero-context escalation drafts validated by a context-free subagent,
and quiet-run reporting
3. permanent rules — user-owned, stored at trust/member-rules/<slug>.json
under the keystone-gated trust/ subtree so the member cannot rewrite
its own safety boundary; GET/PUT /api/members/{slug}/rules is the
only write path (human dashboard action, app tokens denied)
4. briefing — member-owned working memory at members/<slug>/briefing.md,
agent-writable by design, injection-capped with a visible marker
Precedence is the injection order. The section builder degrades to an
ordinary crew session on any failure EXCEPT an unreadable rules file:
for the one safety-relevant layer that degrade is the fail-open, so
MemberRulesUnreadable propagates and aborts the turn until the user
repairs or clears the file.
Review follow-ups (post-rebase, addressing bolichen97's review; feature
authored by CrysisDeu):
- read_member_briefing now opens through the central
pinned_fs.open_in_pinned_parent helper, pinning the ancestor chain
(members/<slug>/ is agent-writable too — a swapped parent redirected
the whole traversal past the leaf-only O_NOFOLLOW; same shape as
#2446). Fails closed where the pinned walk is unavailable.
- member_briefing_supported() names the layer-4 availability; on
platforms where briefing reads fail closed (Windows) the section now
says "not available on this platform" and item 6 of [HOW YOU WORK] is
softened instead of instructing upkeep of a never-injected file.
- Rebased onto main over the crew-member operating-mode block (#8153):
both blocks render — dispatch mechanics first, then the four layers.
- Scrub coverage test now carries a fullwidth NFKC forgery per minted
header, and a regression test pins the symlinked-parent refusal
red-before-green.
- The layer-3 fail-closed contract now holds PER-TURN across the whole
session lifecycle (branch table in build_message): a first-turn
MemberRulesUnreadable abort leaves a warm session whose turns skipped
both member-section paths, and an idle-expired session restored via
session/load carried a STALE [PERMANENT RULES] snapshot. build_message
now validates rules on every warm member turn, re-injects the CURRENT
member section on slim resume (rules gate included), and the dashboard
runner re-arms the reinjection flag when a member DM's first turn dies
before landing, so the section (fresh rules included) is re-delivered
on the first turn after the user repairs the file.
- Feature-map row for Crew Members now names the GET/PUT
/api/members/{slug}/rules endpoints.
- The PUT collision scan uses the central _member_names_for_slug helper
(grammar filter + MemberSlugError tolerance) instead of an ad-hoc
comprehension a hand-edited config key could crash.
- Rules saves and clears are crash-durable: atomic_write's file fsync
covers the data, but the publishing rename, the first-save directory
creation, and the clearing unlink are directory entries — fsync_dir
(raising on save, best_effort on the already-committed clear) makes
the PUT's 200 survive a power-off instead of resurrecting cleared
rules or losing saved ones.
- The rules GET is owner-gated like the PUT: rules are the owner's
private safety instructions, and any allowed Slack user can mint a
dashboard session, so the read would otherwise disclose them.
- Escaped lone surrogates in the rules payload are refused with a coded
400 (rules_not_encodable) at the PUT boundary, and write_member_rules
rejects UTF-8-unencodable text before any state changes — previously
the UnicodeEncodeError from the write escaped as a mislabeled
"too long" error, and escaping surrogates into the file would only
defer the crash to prompt-encode time inside the member's turn.
- The feature-map Crew Members row lists the rules API as a combined
`GET,PUT /api/members/{slug}/rules` entry, keeping the row within the
feature map's four-route limit.
- The member first-turn re-arm lives in the runner's finally (the one
block on every exit path), not the except arm alone: a user Stop's
cancelled completion never raises, so the except-arm-only re-arm left
the warm session running without [PERMANENT RULES] after a stopped
first turn.
Co-authored-by: Kiro Crew <noreply@kirodotdev.github.io>
A member DM session previously differed from an ordinary session only by
the [CURRENT AGENT] label. Member turns now inject a [MEMBER IDENTITY]
block with four layers of distinct ownership:
1. identity — derived from the crew record (name, description,
triggers); auto-generated so an empty description still has a floor
2. behavior — product-owned working protocol: worker-not-Q&A-bot,
front-desk-vs-workshop dispatch, a four-rung stuck ladder that
escalates only at permission / reachability / one-way-door walls,
zero-context escalation drafts validated by a context-free subagent,
and quiet-run reporting
3. permanent rules — user-owned, stored at trust/member-rules/<slug>.json
under the keystone-gated trust/ subtree so the member cannot rewrite
its own safety boundary; GET/PUT /api/members/{slug}/rules is the
only write path (human dashboard action, app tokens denied)
4. briefing — member-owned working memory at members/<slug>/briefing.md,
agent-writable by design, injection-capped with a visible marker
Precedence is the injection order. The section builder degrades to an
ordinary crew session on any failure EXCEPT an unreadable rules file:
for the one safety-relevant layer that degrade is the fail-open, so
MemberRulesUnreadable propagates and aborts the turn until the user
repairs or clears the file.
Review follow-ups (post-rebase, addressing bolichen97's review; feature
authored by CrysisDeu):
- read_member_briefing now opens through the central
pinned_fs.open_in_pinned_parent helper, pinning the ancestor chain
(members/<slug>/ is agent-writable too — a swapped parent redirected
the whole traversal past the leaf-only O_NOFOLLOW; same shape as
#2446). Fails closed where the pinned walk is unavailable.
- member_briefing_supported() names the layer-4 availability; on
platforms where briefing reads fail closed (Windows) the section now
says "not available on this platform" and item 6 of [HOW YOU WORK] is
softened instead of instructing upkeep of a never-injected file.
- Rebased onto main over the crew-member operating-mode block (#8153):
both blocks render — dispatch mechanics first, then the four layers.
- Scrub coverage test now carries a fullwidth NFKC forgery per minted
header, and a regression test pins the symlinked-parent refusal
red-before-green.
- The layer-3 fail-closed contract now holds PER-TURN across the whole
session lifecycle (branch table in build_message): a first-turn
MemberRulesUnreadable abort leaves a warm session whose turns skipped
both member-section paths, and an idle-expired session restored via
session/load carried a STALE [PERMANENT RULES] snapshot. build_message
now validates rules on every warm member turn, re-injects the CURRENT
member section on slim resume (rules gate included), and the dashboard
runner re-arms the reinjection flag when a member DM's first turn dies
before landing, so the section (fresh rules included) is re-delivered
on the first turn after the user repairs the file.
- Feature-map row for Crew Members now names the GET/PUT
/api/members/{slug}/rules endpoints.
- The PUT collision scan uses the central _member_names_for_slug helper
(grammar filter + MemberSlugError tolerance) instead of an ad-hoc
comprehension a hand-edited config key could crash.
- Rules saves and clears are crash-durable: atomic_write's file fsync
covers the data, but the publishing rename, the first-save directory
creation, and the clearing unlink are directory entries — fsync_dir
(raising on save, best_effort on the already-committed clear) makes
the PUT's 200 survive a power-off instead of resurrecting cleared
rules or losing saved ones.
- The rules GET is owner-gated like the PUT: rules are the owner's
private safety instructions, and any allowed Slack user can mint a
dashboard session, so the read would otherwise disclose them.
- Escaped lone surrogates in the rules payload are refused with a coded
400 (rules_not_encodable) at the PUT boundary, and write_member_rules
rejects UTF-8-unencodable text before any state changes — previously
the UnicodeEncodeError from the write escaped as a mislabeled
"too long" error, and escaping surrogates into the file would only
defer the crash to prompt-encode time inside the member's turn.
- The feature-map Crew Members row lists the rules API as a combined
`GET,PUT /api/members/{slug}/rules` entry, keeping the row within the
feature map's four-route limit.
- The member first-turn re-arm lives in the runner's finally (the one
block on every exit path), not the except arm alone: a user Stop's
cancelled completion never raises, so the except-arm-only re-arm left
the warm session running without [PERMANENT RULES] after a stopped
first turn.
Co-authored-by: Kiro Crew <noreply@kirodotdev.github.io>
A member DM session previously differed from an ordinary session only by
the [CURRENT AGENT] label. Member turns now inject a [MEMBER IDENTITY]
block with four layers of distinct ownership:
1. identity — derived from the crew record (name, description,
triggers); auto-generated so an empty description still has a floor
2. behavior — product-owned working protocol: worker-not-Q&A-bot,
front-desk-vs-workshop dispatch, a four-rung stuck ladder that
escalates only at permission / reachability / one-way-door walls,
zero-context escalation drafts validated by a context-free subagent,
and quiet-run reporting
3. permanent rules — user-owned, stored at trust/member-rules/<slug>.json
under the keystone-gated trust/ subtree so the member cannot rewrite
its own safety boundary; GET/PUT /api/members/{slug}/rules is the
only write path (human dashboard action, app tokens denied)
4. briefing — member-owned working memory at members/<slug>/briefing.md,
agent-writable by design, injection-capped with a visible marker
Precedence is the injection order. The section builder degrades to an
ordinary crew session on any failure EXCEPT an unreadable rules file:
for the one safety-relevant layer that degrade is the fail-open, so
MemberRulesUnreadable propagates and aborts the turn until the user
repairs or clears the file.
Review follow-ups (post-rebase, addressing bolichen97's review; feature
authored by CrysisDeu):
- read_member_briefing now opens through the central
pinned_fs.open_in_pinned_parent helper, pinning the ancestor chain
(members/<slug>/ is agent-writable too — a swapped parent redirected
the whole traversal past the leaf-only O_NOFOLLOW; same shape as
#2446). Fails closed where the pinned walk is unavailable.
- member_briefing_supported() names the layer-4 availability; on
platforms where briefing reads fail closed (Windows) the section now
says "not available on this platform" and item 6 of [HOW YOU WORK] is
softened instead of instructing upkeep of a never-injected file.
- Rebased onto main over the crew-member operating-mode block (#8153):
both blocks render — dispatch mechanics first, then the four layers.
- Scrub coverage test now carries a fullwidth NFKC forgery per minted
header, and a regression test pins the symlinked-parent refusal
red-before-green.
- The layer-3 fail-closed contract now holds PER-TURN across the whole
session lifecycle (branch table in build_message): a first-turn
MemberRulesUnreadable abort leaves a warm session whose turns skipped
both member-section paths, and an idle-expired session restored via
session/load carried a STALE [PERMANENT RULES] snapshot. build_message
now validates rules on every warm member turn, re-injects the CURRENT
member section on slim resume (rules gate included), and the dashboard
runner re-arms the reinjection flag when a member DM's first turn dies
before landing, so the section (fresh rules included) is re-delivered
on the first turn after the user repairs the file.
- Feature-map row for Crew Members now names the GET/PUT
/api/members/{slug}/rules endpoints.
- The PUT collision scan uses the central _member_names_for_slug helper
(grammar filter + MemberSlugError tolerance) instead of an ad-hoc
comprehension a hand-edited config key could crash.
- Rules saves and clears are crash-durable: atomic_write's file fsync
covers the data, but the publishing rename, the first-save directory
creation, and the clearing unlink are directory entries — fsync_dir
(raising on save, best_effort on the already-committed clear) makes
the PUT's 200 survive a power-off instead of resurrecting cleared
rules or losing saved ones.
- The rules GET is owner-gated like the PUT: rules are the owner's
private safety instructions, and any allowed Slack user can mint a
dashboard session, so the read would otherwise disclose them.
- Escaped lone surrogates in the rules payload are refused with a coded
400 (rules_not_encodable) at the PUT boundary, and write_member_rules
rejects UTF-8-unencodable text before any state changes — previously
the UnicodeEncodeError from the write escaped as a mislabeled
"too long" error, and escaping surrogates into the file would only
defer the crash to prompt-encode time inside the member's turn.
- The feature-map Crew Members row lists the rules API as a combined
`GET,PUT /api/members/{slug}/rules` entry, keeping the row within the
feature map's four-route limit.
- The member first-turn re-arm lives in the runner's finally (the one
block on every exit path), not the except arm alone: a user Stop's
cancelled completion never raises, so the except-arm-only re-arm left
the warm session running without [PERMANENT RULES] after a stopped
first turn.
Co-authored-by: Kiro Crew <noreply@kirodotdev.github.io>
A member DM session previously differed from an ordinary session only by
the [CURRENT AGENT] label. Member turns now inject a [MEMBER IDENTITY]
block with four layers of distinct ownership:
1. identity — derived from the crew record (name, description,
triggers); auto-generated so an empty description still has a floor
2. behavior — product-owned working protocol: worker-not-Q&A-bot,
front-desk-vs-workshop dispatch, a four-rung stuck ladder that
escalates only at permission / reachability / one-way-door walls,
zero-context escalation drafts validated by a context-free subagent,
and quiet-run reporting
3. permanent rules — user-owned, stored at trust/member-rules/<slug>.json
under the keystone-gated trust/ subtree so the member cannot rewrite
its own safety boundary; GET/PUT /api/members/{slug}/rules is the
only write path (human dashboard action, app tokens denied)
4. briefing — member-owned working memory at members/<slug>/briefing.md,
agent-writable by design, injection-capped with a visible marker
Precedence is the injection order. The section builder degrades to an
ordinary crew session on any failure EXCEPT an unreadable rules file:
for the one safety-relevant layer that degrade is the fail-open, so
MemberRulesUnreadable propagates and aborts the turn until the user
repairs or clears the file.
Review follow-ups (post-rebase, addressing bolichen97's review; feature
authored by CrysisDeu):
- read_member_briefing now opens through the central
pinned_fs.open_in_pinned_parent helper, pinning the ancestor chain
(members/<slug>/ is agent-writable too — a swapped parent redirected
the whole traversal past the leaf-only O_NOFOLLOW; same shape as
#2446). Fails closed where the pinned walk is unavailable.
- member_briefing_supported() names the layer-4 availability; on
platforms where briefing reads fail closed (Windows) the section now
says "not available on this platform" and item 6 of [HOW YOU WORK] is
softened instead of instructing upkeep of a never-injected file.
- Rebased onto main over the crew-member operating-mode block (#8153):
both blocks render — dispatch mechanics first, then the four layers.
- Scrub coverage test now carries a fullwidth NFKC forgery per minted
header, and a regression test pins the symlinked-parent refusal
red-before-green.
- The layer-3 fail-closed contract now holds PER-TURN across the whole
session lifecycle (branch table in build_message): a first-turn
MemberRulesUnreadable abort leaves a warm session whose turns skipped
both member-section paths, and an idle-expired session restored via
session/load carried a STALE [PERMANENT RULES] snapshot. build_message
now validates rules on every warm member turn, re-injects the CURRENT
member section on slim resume (rules gate included), and the dashboard
runner re-arms the reinjection flag when a member DM's first turn dies
before landing, so the section (fresh rules included) is re-delivered
on the first turn after the user repairs the file.
- Feature-map row for Crew Members now names the GET/PUT
/api/members/{slug}/rules endpoints.
- The PUT collision scan uses the central _member_names_for_slug helper
(grammar filter + MemberSlugError tolerance) instead of an ad-hoc
comprehension a hand-edited config key could crash.
- Rules saves and clears are crash-durable: atomic_write's file fsync
covers the data, but the publishing rename, the first-save directory
creation, and the clearing unlink are directory entries — fsync_dir
(raising on save, best_effort on the already-committed clear) makes
the PUT's 200 survive a power-off instead of resurrecting cleared
rules or losing saved ones.
- The rules GET is owner-gated like the PUT: rules are the owner's
private safety instructions, and any allowed Slack user can mint a
dashboard session, so the read would otherwise disclose them.
- Escaped lone surrogates in the rules payload are refused with a coded
400 (rules_not_encodable) at the PUT boundary, and write_member_rules
rejects UTF-8-unencodable text before any state changes — previously
the UnicodeEncodeError from the write escaped as a mislabeled
"too long" error, and escaping surrogates into the file would only
defer the crash to prompt-encode time inside the member's turn.
- The feature-map Crew Members row lists the rules API as a combined
`GET,PUT /api/members/{slug}/rules` entry, keeping the row within the
feature map's four-route limit.
- The member first-turn re-arm lives in the runner's finally (the one
block on every exit path), not the except arm alone: a user Stop's
cancelled completion never raises, so the except-arm-only re-arm left
the warm session running without [PERMANENT RULES] after a stopped
first turn.
Co-authored-by: Kiro Crew <noreply@kirodotdev.github.io>
A member DM session previously differed from an ordinary session only by
the [CURRENT AGENT] label. Member turns now inject a [MEMBER IDENTITY]
block with four layers of distinct ownership:
1. identity — derived from the crew record (name, description,
triggers); auto-generated so an empty description still has a floor
2. behavior — product-owned working protocol: worker-not-Q&A-bot,
front-desk-vs-workshop dispatch, a four-rung stuck ladder that
escalates only at permission / reachability / one-way-door walls,
zero-context escalation drafts validated by a context-free subagent,
and quiet-run reporting
3. permanent rules — user-owned, stored at trust/member-rules/<slug>.json
under the keystone-gated trust/ subtree so the member cannot rewrite
its own safety boundary; GET/PUT /api/members/{slug}/rules is the
only write path (human dashboard action, app tokens denied)
4. briefing — member-owned working memory at members/<slug>/briefing.md,
agent-writable by design, injection-capped with a visible marker
Precedence is the injection order. The section builder degrades to an
ordinary crew session on any failure EXCEPT an unreadable rules file:
for the one safety-relevant layer that degrade is the fail-open, so
MemberRulesUnreadable propagates and aborts the turn until the user
repairs or clears the file.
Review follow-ups (post-rebase, addressing bolichen97's review; feature
authored by CrysisDeu):
- read_member_briefing now opens through the central
pinned_fs.open_in_pinned_parent helper, pinning the ancestor chain
(members/<slug>/ is agent-writable too — a swapped parent redirected
the whole traversal past the leaf-only O_NOFOLLOW; same shape as
#2446). Fails closed where the pinned walk is unavailable.
- member_briefing_supported() names the layer-4 availability; on
platforms where briefing reads fail closed (Windows) the section now
says "not available on this platform" and item 6 of [HOW YOU WORK] is
softened instead of instructing upkeep of a never-injected file.
- Rebased onto main over the crew-member operating-mode block (#8153):
both blocks render — dispatch mechanics first, then the four layers.
- Scrub coverage test now carries a fullwidth NFKC forgery per minted
header, and a regression test pins the symlinked-parent refusal
red-before-green.
- The layer-3 fail-closed contract now holds PER-TURN across the whole
session lifecycle (branch table in build_message): a first-turn
MemberRulesUnreadable abort leaves a warm session whose turns skipped
both member-section paths, and an idle-expired session restored via
session/load carried a STALE [PERMANENT RULES] snapshot. build_message
now validates rules on every warm member turn, re-injects the CURRENT
member section on slim resume (rules gate included), and the dashboard
runner re-arms the reinjection flag when a member DM's first turn dies
before landing, so the section (fresh rules included) is re-delivered
on the first turn after the user repairs the file.
- Feature-map row for Crew Members now names the GET/PUT
/api/members/{slug}/rules endpoints.
- The PUT collision scan uses the central _member_names_for_slug helper
(grammar filter + MemberSlugError tolerance) instead of an ad-hoc
comprehension a hand-edited config key could crash.
- Rules saves and clears are crash-durable: atomic_write's file fsync
covers the data, but the publishing rename, the first-save directory
creation, and the clearing unlink are directory entries — fsync_dir
(raising on save, best_effort on the already-committed clear) makes
the PUT's 200 survive a power-off instead of resurrecting cleared
rules or losing saved ones.
- The rules GET is owner-gated like the PUT: rules are the owner's
private safety instructions, and any allowed Slack user can mint a
dashboard session, so the read would otherwise disclose them.
- Escaped lone surrogates in the rules payload are refused with a coded
400 (rules_not_encodable) at the PUT boundary, and write_member_rules
rejects UTF-8-unencodable text before any state changes — previously
the UnicodeEncodeError from the write escaped as a mislabeled
"too long" error, and escaping surrogates into the file would only
defer the crash to prompt-encode time inside the member's turn.
- The feature-map Crew Members row lists the rules API as a combined
`GET,PUT /api/members/{slug}/rules` entry, keeping the row within the
feature map's four-route limit.
- The member first-turn re-arm lives in the runner's finally (the one
block on every exit path), not the except arm alone: a user Stop's
cancelled completion never raises, so the except-arm-only re-arm left
the warm session running without [PERMANENT RULES] after a stopped
first turn.
Co-authored-by: Kiro Crew <noreply@kirodotdev.github.io>
A member DM session previously differed from an ordinary session only by
the [CURRENT AGENT] label. Member turns now inject a [MEMBER IDENTITY]
block with four layers of distinct ownership:
1. identity — derived from the crew record (name, description,
triggers); auto-generated so an empty description still has a floor
2. behavior — product-owned working protocol: worker-not-Q&A-bot,
front-desk-vs-workshop dispatch, a four-rung stuck ladder that
escalates only at permission / reachability / one-way-door walls,
zero-context escalation drafts validated by a context-free subagent,
and quiet-run reporting
3. permanent rules — user-owned, stored at trust/member-rules/<slug>.json
under the keystone-gated trust/ subtree so the member cannot rewrite
its own safety boundary; GET/PUT /api/members/{slug}/rules is the
only write path (human dashboard action, app tokens denied)
4. briefing — member-owned working memory at members/<slug>/briefing.md,
agent-writable by design, injection-capped with a visible marker
Precedence is the injection order. The section builder degrades to an
ordinary crew session on any failure EXCEPT an unreadable rules file:
for the one safety-relevant layer that degrade is the fail-open, so
MemberRulesUnreadable propagates and aborts the turn until the user
repairs or clears the file.
Review follow-ups (post-rebase, addressing bolichen97's review; feature
authored by CrysisDeu):
- read_member_briefing now opens through the central
pinned_fs.open_in_pinned_parent helper, pinning the ancestor chain
(members/<slug>/ is agent-writable too — a swapped parent redirected
the whole traversal past the leaf-only O_NOFOLLOW; same shape as
#2446). Fails closed where the pinned walk is unavailable.
- member_briefing_supported() names the layer-4 availability; on
platforms where briefing reads fail closed (Windows) the section now
says "not available on this platform" and item 6 of [HOW YOU WORK] is
softened instead of instructing upkeep of a never-injected file.
- Rebased onto main over the crew-member operating-mode block (#8153):
both blocks render — dispatch mechanics first, then the four layers.
- Scrub coverage test now carries a fullwidth NFKC forgery per minted
header, and a regression test pins the symlinked-parent refusal
red-before-green.
- The layer-3 fail-closed contract now holds PER-TURN across the whole
session lifecycle (branch table in build_message): a first-turn
MemberRulesUnreadable abort leaves a warm session whose turns skipped
both member-section paths, and an idle-expired session restored via
session/load carried a STALE [PERMANENT RULES] snapshot. build_message
now validates rules on every warm member turn, re-injects the CURRENT
member section on slim resume (rules gate included), and the dashboard
runner re-arms the reinjection flag when a member DM's first turn dies
before landing, so the section (fresh rules included) is re-delivered
on the first turn after the user repairs the file.
- Feature-map row for Crew Members now names the GET/PUT
/api/members/{slug}/rules endpoints.
- The PUT collision scan uses the central _member_names_for_slug helper
(grammar filter + MemberSlugError tolerance) instead of an ad-hoc
comprehension a hand-edited config key could crash.
- Rules saves and clears are crash-durable: atomic_write's file fsync
covers the data, but the publishing rename, the first-save directory
creation, and the clearing unlink are directory entries — fsync_dir
(raising on save, best_effort on the already-committed clear) makes
the PUT's 200 survive a power-off instead of resurrecting cleared
rules or losing saved ones.
- The rules GET is owner-gated like the PUT: rules are the owner's
private safety instructions, and any allowed Slack user can mint a
dashboard session, so the read would otherwise disclose them.
- Escaped lone surrogates in the rules payload are refused with a coded
400 (rules_not_encodable) at the PUT boundary, and write_member_rules
rejects UTF-8-unencodable text before any state changes — previously
the UnicodeEncodeError from the write escaped as a mislabeled
"too long" error, and escaping surrogates into the file would only
defer the crash to prompt-encode time inside the member's turn.
- The feature-map Crew Members row lists the rules API as a combined
`GET,PUT /api/members/{slug}/rules` entry, keeping the row within the
feature map's four-route limit.
- The member first-turn re-arm lives in the runner's finally (the one
block on every exit path), not the except arm alone: a user Stop's
cancelled completion never raises, so the except-arm-only re-arm left
the warm session running without [PERMANENT RULES] after a stopped
first turn.
Co-authored-by: Kiro Crew <noreply@kirodotdev.github.io>
A member DM session previously differed from an ordinary session only by
the [CURRENT AGENT] label. Member turns now inject a [MEMBER IDENTITY]
block with four layers of distinct ownership:
1. identity — derived from the crew record (name, description,
triggers); auto-generated so an empty description still has a floor
2. behavior — product-owned working protocol: worker-not-Q&A-bot,
front-desk-vs-workshop dispatch, a four-rung stuck ladder that
escalates only at permission / reachability / one-way-door walls,
zero-context escalation drafts validated by a context-free subagent,
and quiet-run reporting
3. permanent rules — user-owned, stored at trust/member-rules/<slug>.json
under the keystone-gated trust/ subtree so the member cannot rewrite
its own safety boundary; GET/PUT /api/members/{slug}/rules is the
only write path (human dashboard action, app tokens denied)
4. briefing — member-owned working memory at members/<slug>/briefing.md,
agent-writable by design, injection-capped with a visible marker
Precedence is the injection order. The section builder degrades to an
ordinary crew session on any failure EXCEPT an unreadable rules file:
for the one safety-relevant layer that degrade is the fail-open, so
MemberRulesUnreadable propagates and aborts the turn until the user
repairs or clears the file.
Review follow-ups (post-rebase, addressing bolichen97's review; feature
authored by CrysisDeu):
- read_member_briefing now opens through the central
pinned_fs.open_in_pinned_parent helper, pinning the ancestor chain
(members/<slug>/ is agent-writable too — a swapped parent redirected
the whole traversal past the leaf-only O_NOFOLLOW; same shape as
#2446). Fails closed where the pinned walk is unavailable.
- member_briefing_supported() names the layer-4 availability; on
platforms where briefing reads fail closed (Windows) the section now
says "not available on this platform" and item 6 of [HOW YOU WORK] is
softened instead of instructing upkeep of a never-injected file.
- Rebased onto main over the crew-member operating-mode block (#8153):
both blocks render — dispatch mechanics first, then the four layers.
- Scrub coverage test now carries a fullwidth NFKC forgery per minted
header, and a regression test pins the symlinked-parent refusal
red-before-green.
- The layer-3 fail-closed contract now holds PER-TURN across the whole
session lifecycle (branch table in build_message): a first-turn
MemberRulesUnreadable abort leaves a warm session whose turns skipped
both member-section paths, and an idle-expired session restored via
session/load carried a STALE [PERMANENT RULES] snapshot. build_message
now validates rules on every warm member turn, re-injects the CURRENT
member section on slim resume (rules gate included), and the dashboard
runner re-arms the reinjection flag when a member DM's first turn dies
before landing, so the section (fresh rules included) is re-delivered
on the first turn after the user repairs the file.
- Feature-map row for Crew Members now names the GET/PUT
/api/members/{slug}/rules endpoints.
- The PUT collision scan uses the central _member_names_for_slug helper
(grammar filter + MemberSlugError tolerance) instead of an ad-hoc
comprehension a hand-edited config key could crash.
- Rules saves and clears are crash-durable: atomic_write's file fsync
covers the data, but the publishing rename, the first-save directory
creation, and the clearing unlink are directory entries — fsync_dir
(raising on save, best_effort on the already-committed clear) makes
the PUT's 200 survive a power-off instead of resurrecting cleared
rules or losing saved ones.
- The rules GET is owner-gated like the PUT: rules are the owner's
private safety instructions, and any allowed Slack user can mint a
dashboard session, so the read would otherwise disclose them.
- Escaped lone surrogates in the rules payload are refused with a coded
400 (rules_not_encodable) at the PUT boundary, and write_member_rules
rejects UTF-8-unencodable text before any state changes — previously
the UnicodeEncodeError from the write escaped as a mislabeled
"too long" error, and escaping surrogates into the file would only
defer the crash to prompt-encode time inside the member's turn.
- The feature-map Crew Members row lists the rules API as a combined
`GET,PUT /api/members/{slug}/rules` entry, keeping the row within the
feature map's four-route limit.
- The member first-turn re-arm lives in the runner's finally (the one
block on every exit path), not the except arm alone: a user Stop's
cancelled completion never raises, so the except-arm-only re-arm left
the warm session running without [PERMANENT RULES] after a stopped
first turn.
Co-authored-by: Kiro Crew <noreply@kirodotdev.github.io>
A member DM session previously differed from an ordinary session only by
the [CURRENT AGENT] label. Member turns now inject a [MEMBER IDENTITY]
block with four layers of distinct ownership:
1. identity — derived from the crew record (name, description,
triggers); auto-generated so an empty description still has a floor
2. behavior — product-owned working protocol: worker-not-Q&A-bot,
front-desk-vs-workshop dispatch, a four-rung stuck ladder that
escalates only at permission / reachability / one-way-door walls,
zero-context escalation drafts validated by a context-free subagent,
and quiet-run reporting
3. permanent rules — user-owned, stored at trust/member-rules/<slug>.json
under the keystone-gated trust/ subtree so the member cannot rewrite
its own safety boundary; GET/PUT /api/members/{slug}/rules is the
only write path (human dashboard action, app tokens denied)
4. briefing — member-owned working memory at members/<slug>/briefing.md,
agent-writable by design, injection-capped with a visible marker
Precedence is the injection order. The section builder degrades to an
ordinary crew session on any failure EXCEPT an unreadable rules file:
for the one safety-relevant layer that degrade is the fail-open, so
MemberRulesUnreadable propagates and aborts the turn until the user
repairs or clears the file.
Review follow-ups (post-rebase, addressing bolichen97's review; feature
authored by CrysisDeu):
- read_member_briefing now opens through the central
pinned_fs.open_in_pinned_parent helper, pinning the ancestor chain
(members/<slug>/ is agent-writable too — a swapped parent redirected
the whole traversal past the leaf-only O_NOFOLLOW; same shape as
#2446). Fails closed where the pinned walk is unavailable.
- member_briefing_supported() names the layer-4 availability; on
platforms where briefing reads fail closed (Windows) the section now
says "not available on this platform" and item 6 of [HOW YOU WORK] is
softened instead of instructing upkeep of a never-injected file.
- Rebased onto main over the crew-member operating-mode block (#8153):
both blocks render — dispatch mechanics first, then the four layers.
- Scrub coverage test now carries a fullwidth NFKC forgery per minted
header, and a regression test pins the symlinked-parent refusal
red-before-green.
- The layer-3 fail-closed contract now holds PER-TURN across the whole
session lifecycle (branch table in build_message): a first-turn
MemberRulesUnreadable abort leaves a warm session whose turns skipped
both member-section paths, and an idle-expired session restored via
session/load carried a STALE [PERMANENT RULES] snapshot. build_message
now validates rules on every warm member turn, re-injects the CURRENT
member section on slim resume (rules gate included), and the dashboard
runner re-arms the reinjection flag when a member DM's first turn dies
before landing, so the section (fresh rules included) is re-delivered
on the first turn after the user repairs the file.
- Feature-map row for Crew Members now names the GET/PUT
/api/members/{slug}/rules endpoints.
- The PUT collision scan uses the central _member_names_for_slug helper
(grammar filter + MemberSlugError tolerance) instead of an ad-hoc
comprehension a hand-edited config key could crash.
- Rules saves and clears are crash-durable: atomic_write's file fsync
covers the data, but the publishing rename, the first-save directory
creation, and the clearing unlink are directory entries — fsync_dir
(raising on save, best_effort on the already-committed clear) makes
the PUT's 200 survive a power-off instead of resurrecting cleared
rules or losing saved ones.
- The rules GET is owner-gated like the PUT: rules are the owner's
private safety instructions, and any allowed Slack user can mint a
dashboard session, so the read would otherwise disclose them.
- Escaped lone surrogates in the rules payload are refused with a coded
400 (rules_not_encodable) at the PUT boundary, and write_member_rules
rejects UTF-8-unencodable text before any state changes — previously
the UnicodeEncodeError from the write escaped as a mislabeled
"too long" error, and escaping surrogates into the file would only
defer the crash to prompt-encode time inside the member's turn.
- The feature-map Crew Members row lists the rules API as a combined
`GET,PUT /api/members/{slug}/rules` entry, keeping the row within the
feature map's four-route limit.
- The member first-turn re-arm lives in the runner's finally (the one
block on every exit path), not the except arm alone: a user Stop's
cancelled completion never raises, so the except-arm-only re-arm left
the warm session running without [PERMANENT RULES] after a stopped
first turn.
Co-authored-by: Kiro Crew <noreply@kirodotdev.github.io>
Problem / Motivation
A crew member's pinned DM thread is designed to be a conductor: it should dispatch real work into worker sessions it creates, patrol them, and report back — with zero operator configuration. Today it cannot: the
session_*session-control tools live behind a per-agent server assignment on the on-disk agent template, and granting them there would hand session control to every session running that template, not just the member's DM. PR #8021 exploredworker_*twin tools onkirocrew-coreand was deliberately closed for the same objection in reverse: adding same-class tools to a core server grants them fleet-wide.Why it matters
Members are the product's zero-configuration delegation surface. Without a session-scoped mount, an operator must either hand-edit agent templates (breaking zero-config and over-granting) or members stay chat-only. The per-session ACP channel —
session/new mcpServerson the claude seam,_meta.kiro.customAgentson KAS — is the protocol-native way to give exactly one session extra tools, which is the capability this feature rides.What changed (motivation → approach → change)
Goal: member DM sessions get the existing
kirocrew-dashboardsession_*tools, scoped to that one session, on wire-capable ACP backends (KAS, claude seam). kiro-cli v2 reads its template from disk and exposes no per-session channel, so it is explicitly out of scope: a member session on it runs as plain chat (tools not mounted, never mounted-and-refused).agent.member_acp_backend(defaultkas) + a member auto-route at the single provider-construction point (config/loader.pyfactory closure), gated byACP_BACKENDS_MEMBER_DISPATCH = {claude, kas}andresolve_selected_backend(same governance/selectability gate as the persisted field; denied/unknown degrades to kiro = plain chat).members.member_dispatch_session_server()builds the session-levelmcpServerselement carryingKIROCREW_SESSION_KEYin its env (the same per-process trust channelAcpClientalready uses, and the one path the KAS projection's env stripping never touches).AcpRuntime.create_sessionandload_sessioninject it —session/loadre-initializes the session's MCP servers, so a resume that skipped the injection stripped a member thread of its tools mid-conversation (observed on pod).to_client_custom_agent(member_dispatch=True)adds@kirocrew-dashboardtotoolsand merges_MEMBER_DASHBOARD_GRANTSinto theallowedToolsinput before the governance ceiling filter. The member grant set is the conductor's read/create set plussession_send/session_stop: the write verbs are safe to auto-approve for a member specifically becauseauthorize_target'screated_byownership fence bounds them to worker sessions the member itself opened (403not_creatorotherwise) — a fence the conductor does not have.AcpClient._append_member_dispatch_serverappends the same entry to the session array, honoring the mirror's permission-surface precondition (no mount when Crew does not ownsettings.local.json).bypass_memberpool-decision arm: a pooled child is spawned with no session key on the default backend, so a warm hit would skip both the member backend route and the mount (found by pod e2e).is_member_session_keyacceptsmember-,dashboard_member-, and the canonical session-map aliasdashboard:member-(the spelling that actually reaches the provider factory; found by pod e2e).member_dispatch=Truethe dashboard server name joins the KAS projection's subtraction set, so an agent spec that already declareskirocrew-dashboard(the opt-in assignable set) is not projected alongside the session-level injection (same class as the pooled-stub subtraction).agent.session_controlswitch but are bound bycreated_byownership recorded at slot birth and rehydrated on restart;not_creatordrain classification registered as non-containment. Because the rehydratedcreated_byis an input to an authorization decision, the session-history store joins_WRITE_PROTECTED_HOME_PATHS(the rotation.yaml / alias-record precedent): transcripts stay readable, but an agent file-edit can no longer forge a victim slot's creator attribution and collect ownership after a restart.session_*spellings and is gated on_member_backend_can_dispatch(), so a degraded backend never teaches tools the session does not hold.Relands the reusable server-side pieces from #8021 (closed by design discussion — see its closing comment) on the per-session-mount architecture that discussion converged on. Related: issue #8073 (operator ceiling for member dispatch) — partially addressed here by the backend capability gate; the
members.dispatchconfig switch itself is in flight as PR #8083, which is additive and composes with this change. Pre-push adversarial review (GPT + Opus lanes) found no blocking issues; its one substantive fix (the double-registration case above) is included, and the two claude-lane gaps it raised are deferred with rationale to issue #8152 (claude is a non-default member backend and both degradations are graceful — an approval prompt, or an honest tools-unavailable report).Tests
test/test_member_dispatch_mount.py(new, 24 tests): capability set pinned to{claude, kas}; entry shape + identity env; key spellings incl. the colon alias; KAS projection (tools append, member grants through the ceiling,*passthrough, spec non-mutation, default untouched); claude append (member / non-member / unowned permission surface / kiro backend / same-name dedup); runtime flag threading; pool bypass key recognition.test/test_member_session_control.py(new): member switch bypass +created_byownership boundary (relanded from feat: crew members drive worker sessions with zero configuration #8021,worker_*classes removed).test/test_queue_drain_revalidation.py:not_creatorregistered as a non-containment refusal.test/test_kas_agents.py,test/test_acp_runtime.py: stub signatures extended for the new keyword.Manual verification
Pod e2e (isolated
kirocrew pod, KAS backend, default config): member DM completed the full dispatch loop —session_create(worker created) →session_send("reply PONG") →session_read_message(worker replied exactlyPONG) →session_stop— with zero approval stalls and zero configuration. The e2e surfaced and this PR fixes four real integration bugs (warm-pool bypass, colon key spelling, missing write-verb grants, resume losing the mount).Screenshots / video
N/A — backend-only change; no UI surface modified.