Blackbird engine: pitch-only reconciliation + removal cycle (stacked on #34) - #35
Draft
ahueb wants to merge 65 commits into
Draft
Blackbird engine: pitch-only reconciliation + removal cycle (stacked on #34)#35ahueb wants to merge 65 commits into
ahueb wants to merge 65 commits into
Conversation
… guard Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014oXRXEdWDcpitGYT3Y5hUP
…terminal_only narrowing; per-role menus Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014oXRXEdWDcpitGYT3Y5hUP
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014oXRXEdWDcpitGYT3Y5hUP
…l, compose, config, drop-table migration) Deletes src/agent/grantbot.py, funding_rules.py, foa_cache.py, src/services/grants.py, and src/models/grantbot_posted.py; drops the retrieve_foa tool, the funding-opportunities channel, the grantbot compose services, slack_bot_token_grantbot, and the grantbot_posted_foas table (0026, downgrade mirrors 0012). WRITER_GRANTBOT stays reserved (historical message ids carry its residue). simulation.py's Phase 3/4/5 funding call sites are Task 4-6's job and are untouched; its now-dangling foa_cache/ funding_rules imports are replaced with TASK-6-REMOVES no-op stand-ins so the module keeps importing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014oXRXEdWDcpitGYT3Y5hUP
Delete message_log.is_funding_post and MessageLog.is_funding_thread, and the funding branch inside get_thread_allowed_agents — locked decision: ex-funding thread roots get no replacement exception, they follow the same tag/2-party rule as any other thread. simulation.py's ~11 live call sites onto the deleted methods (Task 3's stubbing only covered the foa_cache/funding_rules module-level imports, not these) are neutered inline to `False`, each marked # TASK-6-REMOVES for Task 6 to delete alongside the rest of the funding plumbing. agent.py's is_funding_thread param is untouched (Task 5's territory) since it no longer receives anything but False. tests/unit/test_cohort_isolation.py: dropped is_funding_thread from the UNGATED-methods inventory list, and rewrote test_non_cohort_third_party_cannot_manufacture_priority to pin the new behavior directly on get_thread_allowed_agents (a moneybag root no longer grants a third party open access) instead of the old assertion, which was actually gated by the cohort filter and never exercised the funding exception at all. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…mpt builders
Removes is_funding_thread/your_prior_messages/thread_activity_summary from
build_phase4_prompt and foa_contexts/thread_foa_contexts/funding_only/
funding_thread_summaries from build_phase5_prompt, along with the
{foa_number}/{funding_thread_context} substitutions and the funding_only
template-surgery regex block (no partial survival, per design §6).
simulation.py's call sites updated to match; the FOA/funding_only assembly
that only fed the removed params is deleted, with TASK-6-REMOVES markers
left for the remaining dead is_funding branches Task 6 owns.
Rewrites the four branch-1-failing prompt-pin tests in test_roles.py to the
current Option A/B templates and pi_lab/scout_hub guidance text, deletes the
two tests whose premise (Baltimore gating prose, funding_only surgery) no
longer exists, and repins the two thread_guidance.py tests to spot-anchors of
the current DECIDE/CONCLUDE text and the 3-criteria gating contract. Deletes
the one funding-dedicated golden master (test_phase4_prompt_pi_context_and_funding_gm)
and its snapshot block.
test_roles.py + test_agent_prompts.py + test_thread_guidance.py are now 100%
green, retiring the last branch-1 unit failures; only the characterization
tier (regenerated in Task 16) stays red.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014oXRXEdWDcpitGYT3Y5hUP
…on; blocked agents narrow to terminal types Task 6 of the branch2 engine-reconciliation plan: removes every remaining funding call site in simulation.py/state.py (the FOA/funding_only stub block, _backfill_foa_cache, Phase 2/3/4 is_funding branches, the funding disjuncts in the blocked-action bypass) and deletes the phase-5 action == "reply" branch. Any non-new_post/non-skip action now logs and increments the skip streak via previous_skips + 1 (never a bare += 1). Renames _non_funding_thread_count -> _active_thread_count and unreviewed_non_funding_count -> unreviewed_count now that there's no funding exemption left to carve out. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014oXRXEdWDcpitGYT3Y5hUP
…p-table migration Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014oXRXEdWDcpitGYT3Y5hUP
private-profile-synthesis.md generated a "Collaboration Preferences"
section (partner-seeking, "wet-lab partners") that contradicts the
pitch-only model: agents pitch their own lab's ideas to BlackbirdBot,
they don't broker collaborations. Rewritten as "Pitch Preferences"
guidance (what to pitch first, evidence bar, what to hold back).
pi-dm-classify.md's standing_instruction example ("Always look for
opportunities with the Wiseman lab") named a cross-lab collaboration
target that no longer exists in this model; replaced with a
pitch-prioritization example.
pi-profile-rewrite.md's merge-target reference to "collaboration
preferences" updated to "pitch preferences" to match the renamed
section.
_FORBIDDEN in test_doc_prompt_sync.py extended with both retired
phrases so this can't regress.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…m fallback and onboarding template src/services/llm.py's synthesize_private_profile FileNotFoundError fallback and src/routers/onboarding.py's brand-new-user default template both hardcoded the retired "Collaboration Preferences" / "Criteria to Always Explore" section contract, invisible to the prompts-only phrase guard in test_doc_prompt_sync.py. Mirror the rewritten private-profile-synthesis.md Output Format contract (Pitch Preferences / Communication Style / Topic Priorities) instead, per this deployment's pitch-only model. Add tests/unit/test_no_collaboration_residue.py as a source-text guard so this can't regress again outside the prompts/*.md guard's blind spot. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Expands design §13 into a copy-paste-ready runbook: merge order; the 0026 grantbot_posted_foas drop-table migration (with pg_dump archive option, and a flag for run_migration.sh's stale --target default); grantbot compose-service teardown + orphaned /copi/grantbot CloudWatch group; DB purges for legacy unreviewed proposals and 💰 threads (with interesting_posts called out as needing none — it's in-memory only); stale profiles/private/blackbird.md host-file hygiene; cohort star-shape preflight SQL; image rebuild + --fresh restart; and the full verification signal set (menu, hub auto-activation, opportunity_assessments, zero funding/phase-2 activity, one-pitch/day cap). Also documents CLAUDE.md staleness this branch causes (the now-false retrieve_foa bullet) and a pre-existing app/blackbird-app service-name discrepancy between CLAUDE.md and the checked-in compose file, worked around via an explicit $WEB_SVC resolution rather than picked a side silently. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…te-channel collaboration flow Retires the Slack-driven 📝/checkmark bilateral-proposal handshake and the collab_private refinement flow it fed, per Task 7 of the branch-2 engine reconciliation: - _check_thread_outcome: deleted the checkmark-confirms-memo and posted-memo arms; kept the pause-button no-viable-collaboration close and general thread conclusion (timeouts, message-12) untouched. - Deleted _check_private_channel_outcome, _finalize_private_proposal, _seed_private_refinements and their call sites/scheduling. - agent.py: deleted the PRIVATE_CHANNEL_RULES prompt block and its injection in _compose_system_prompt; visibility/channel_id plumbing for private working-memory segmentation (infrastructure) is unaffected. - simulation.py: removed the now-dead is_private bypass of blocked_for_regular and the is_private_reply/target_post_id logic (only ever reachable via the "reply" action Task 6 already removed); kept the private-channel turn-taking rule and _finalized_private_channels (still populated from legacy DB rows). outcome="proposal" remains a valid ThreadDecision value for legacy rows and is still served by _close_thread, the admin/PI dashboard routes, and ProposalReview — nothing in the live system can produce a new one going forward. tests/integration/test_proposal_review.py's shared _conclude_thread fixture now fabricates a legacy-shaped ThreadDecision row directly for outcome="proposal" instead of driving the (now nonexistent) live handshake; outcome="no_proposal" still drives the real path. Replaced test_a_concluded_thread_records_a_proposal_decision with a control pair pinning that the pause-button close still writes a decision and that a checkmark+memo reply no longer does. Deleted TestSeedPrivateRefinements and TestPrivateChannelFinalization from test_simulation_logic.py, and the private-channel-rules assertions from test_privacy_scoping.py. Known casualty (accepted, deferred to Task 16 GM regen): the characterization test importing PRIVATE_CHANNEL_RULES will need its import/assertions and the matching .ambr snapshot block removed before regeneration. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014oXRXEdWDcpitGYT3Y5hUP
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-level post Adds a third _phase3_activate_threads loop, gated on the plain agent.role == "scout_hub" attribute (not self._roles_by_agent(), per INV-E structural note 4). It opens a hub interview thread on any new top-level post from an allowed sender (cohort gate), no @-mention required, mirroring the tag loop's private-channel/_closed_thread_ids/ already-active/get_thread_allowed_agents guards exactly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…hecklist itself Review found the §3b closure script's untested/dry-run-first caveat only lived in the task report, not the document — an operator following just the checklist would run new, never-exercised logic cold against production. Add the callout directly above the script, pointing at CLAUDE.md's scratch-DB (copi_xN) instructions for rehearsal. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…n-paper interview note execute_tool's retrieve_abstract branch now takes a keyword-only own_dois set; a lookup whose pmid_or_doi contains an own-lab DOI (Agent.own_publication_dois) bypasses both the per-thread abstracts_other cap check and its increment. A bare PMID still counts (no DOI to match against — documented limit, design §10). The single call site (simulation.py's _reply_to_thread tool_executor) passes agent.own_publication_dois. Also rewords the phase-4 own-paper injection in build_phase4_prompt from "was authored by your own lab... Only continue toward a collaboration..." to "cites a paper your own lab authored... Speak as its author... focus on what remains unexploited beyond the published scope" — matches the interview model's framing (the counterpart is the screening hub, not a co-author candidate) rather than the retired collaboration-proposal language. No import cycle: tools.py importing _extract_dois from agent.py is safe since agent.py's own import graph never reaches tools.py at module level (verified empirically both import orders + via simulation.py, which imports both). tests/unit/test_own_authored_papers.py's two reply-prompt-caution assertions updated to the new wording (unavoidable side effect of the reword: they pin the literal replaced string). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- _run_turn no longer calls _phase2_scan_filter and no longer gates Phase 5 on a phase2_ran flag; _phase2_scan_filter/_phase2_prune stay on disk (uncalled) per design §9, docstrings updated to say so. - build_phase2_scan_prompt drops the⚠️ SELF-AUTHORED injection (the builder itself, post fencing, and cites_own_paper are unaffected — cites_own_paper is still used by the phase-4 reply-prompt warning). - New settings.lab_daily_post_cap (default 1): pi_lab agents get one pitch per day in _phase5_new_post's cap gate; other roles keep daily_post_cap. - New tests/unit/test_phase2_guard.py pins both behaviors (source-inspection on _run_turn + a _drive()-style cap test for pi_lab vs scout_hub). - Deletes the now-obsolete test_agent_turn_gm.py::test_phase2_scan_prompt_flags_self_authored_gm and its .ambr block; fixes three brief-unlisted collateral breakages this change caused (test_own_authored_papers.py's scan-prompt SELF-AUTHORED assertion, and missing lab_daily_post_cap in two other tests' settings stubs). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-rejection deletion The comment above previous_skips's capture still described the back-to-back private-channel post rejection's bare += 1 as the reason for capturing the pre-reset streak value — but Task 7 deleted that rejection block entirely. Every remaining rejection path (unsupported action, post-type rejection, body-mention rejection) already restores the true streak via `previous_skips + 1`. Rewrite the comment to describe that, not code that no longer exists. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014oXRXEdWDcpitGYT3Y5hUP
Task 12 (branch-2 engine reconciliation). The reply action was retired in Tasks 6/7, so a PI tagging a bot's channel post could no longer make the bot join or reply to that thread — handle_channel_tag still promised exactly that. Repurpose the tag as pitch-shaping context instead: - build_phase5_prompt gains a keyword-only `pi_flagged` param; when set, it appends a `## Your PI flagged this` section (with the authoritative- direction sentence) after the rendered template. - _phase5_new_post builds that string from the agent's current pi_priority interesting_posts entries, passes it, and consumes those entries once the turn's action resolves (new_post posted successfully, or skip) — same turn-scoped semantics as has_pi_directive. The has_pi_priority bypass of phase5_skip_probability is unchanged. - handle_channel_tag's DM copy (both branches) and the two-agent branch's seeded pi_context now say the bot can't reply/join and will fold the note into its next pitch, instead of promising a reply or a new thread. New tests/unit/test_pi_flag_flow.py (8 tests, RED->GREEN) covers heading injection/omission, consume-once on both resolution paths, the new DM copy for both handle_channel_tag branches, and the pi_priority skip-bypass regression. tests/unit/test_doc_prompt_sync.py stays green (prompts/*.md untouched). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… sentinel The prior version of test_tagged_post_activates_exactly_one_thread_no_dupe_with_tag_loop only checked key cardinality, which can't distinguish "guard skipped a duplicate activation" from "guard missing, but the overwrite happened to build an identical ThreadState." Pre-seed a sentinel ThreadState (message_count=99, a value get_thread_message_count can't produce for this thread) under the thread id and assert it survives untouched. Verified by experiment: deleting the hub loop's `if thread_id in agent.state.active_threads: continue` line makes this fail (sentinel clobbered to message_count=1); restoring it passes again. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add SimulationEngine._validate_star_topology(): for every pi_lab agent with a
non-None cohort gate, flag (a) any other pi_lab agent reachable directly
(labs may only be cohorted with the hub) and (b) no scout_hub agent reachable
at all (hub unreachable, pitch targets unsatisfiable). start() raises
RuntimeError with all violations joined right after the first
_recompute_allowed_sender_ids() call; the shared mid-run recompute path logs
each violation as an ERROR instead, since a live run must not crash on an
admin's transient cohort edit.
tests/integration/test_cohort_engine_live.py's only start()-driving isolated
test used a lab-to-lab cohort with no hub in the roster at all — exactly the
shape this validation now rejects. Gave it a star-shaped topology (a
scout_hub agent, {lab, hub} per lab) and added a sibling test asserting the
old shape now raises through the real start() path.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… wording, consumption comment Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
_default_system_prompt() (used only when prompts/agent-system.md and every role override are missing from disk) still described the lab<->lab collaboration mesh verbatim: "facilitate scientific collaboration by engaging with other lab agents", "true complementarity", "concrete first experiment", "DM other labs' PIs". None of that exists in the pitch-only hub-and-spoke topology. Replace it with a short pitch-era fallback, mirroring the shape of the llm.py fallback fixed in 707b337: the agent represents its lab and pitches its own ideas to BlackbirdBot (the scouting hub), never proposes collaborations, and defers PI-intent questions (funding preference, equity appetite) to the actual PI. Kept intentionally short -- this is an emergency fallback, not the real prompt. No test pins the old fallback text. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…simulation tests/unit/test_no_collaboration_residue.py only covered llm.py's private- profile fallback and onboarding.py's default template. Extend it to the three other places just fixed in this wave: the welcome email (src/services/email.py), agent.py's emergency system-prompt fallback (_default_system_prompt), and simulation.py's memory-synthesis prompt (_update_agent_memory) -- each scanned for "collaboration opportunities", "facilitate scientific collaboration", "re-engage to refine", and "complementarity". RED/GREEN demonstrated by swapping in the pre-fix (commit ce424f2) blobs of all three files and re-running: 4 of the 12 new checks failed (email.py's "re-engage to refine"; agent.py's "facilitate scientific collaboration" and "complementarity"; simulation.py's "collaboration opportunities"), confirming the guard catches the exact residue those fixes removed. Restored the fixed files afterward; full file is green again (17 passed). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…FOA/grantbot mentions
test_full_run_live.py's `full_run` fixture put all three lab agents in one
shared cohort -- lab-to-lab, exactly the shape `_validate_star_topology`
(design doc §5) now fail-fasts on in `start()`. Add a scout_hub agent
(HUB_AGENT_ID = "blackbird", mirroring test_cohort_engine_live.py:1321's
`roles={"blackbird": "scout_hub"}` approach) and reshape the cohort setup
into one cohort per lab, each pairing the lab with the hub. The hub gets
a NullTransport, not a real Slack client -- there is no fourth probe bot
token provisioned (tests/conftest.py's _PROBE_BOTS is still su/cravatt/
wiseman), so this is the minimal edit that gets the hub into the engine's
roster without provisioning new credentials.
Limits, documented in the fixture docstring: this module's actual
scenario -- three real labs conversing directly to a lab-to-lab
:memo:/✅ handshake -- still assumes the retired mesh model (only a
scout_hub agent replies to top-level posts now), and several downstream
assertions (e.g. `expected_gate = set(AGENTS)`) still assume a full-mesh
gate. Rewriting the scenario is a much larger change this fix doesn't
attempt, and the whole module is gated on ANTHROPIC_API_KEY + live Slack
tokens so it cannot be run here to verify further. Verified `pytest
--co -q` still collects all 4 tests, and ruff is clean on the file.
prompts/daily_audit.md:28 dropped ` grantbot` from the compose logs
command (the service no longer exists).
prompts/pi-dm-classify.md:14-15 replaced two retired-capability examples
("Good catch on that FOA", "Summarize the funding opportunities") with
pitch-era equivalents. Not doc-embedded elsewhere (grepped docs/specs:
only a filename mention in a README's file list, no mirrored content),
so no sibling copy needed updating.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Re-measured with the exact command the ratchet runs (\`ruff check src --output-format=concise --quiet\`, no E902): 249 findings, down from the 260 ceiling. This wave's fixes (email.py, agent.py, simulation.py rewrites) paid down some of the pre-existing debt; lock it in rather than let it drift back up. Never raise this number to make a push pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
reopen_proposal defaulted to enable_private_refinement=True: a reopened proposal on a public-origin thread migrated into a NEW collab_private channel before posting the PI's guidance there. The engine-side private- channel collaboration/refinement flow was deleted (design doc §8 -- no agent converses inside a collab_private channel anymore, hub-and-spoke only), so a freshly migrated channel is a dead room nothing ever posts in again. Per the "private-channel collaboration is out" ruling: - src/config.py: enable_private_refinement default flipped True -> False. The flag itself is NOT removed -- src/services/email_inbound.py's separate (currently out-of-scope) inbound-email reply path still reads it, so it is not dead. - src/routers/agent_page.py: reopen_proposal's collab_private migration branch and the "already private -> 501" branch are both deleted. The route now has exactly one path, unconditionally: post the PI's guidance into the proposal's origin thread (Slack if the agent has a token, the DB inbox otherwise), regardless of that thread's visibility. Legacy viewing/discovery code for EXISTING private channels (_sync_private_channels_from_db, the /message route's membership check, admin views) is untouched. - templates/agent/dashboard.html: the reopen tab's copy no longer promises "the agents will continue the conversation with a fresh set of replies" -- that flow doesn't exist any more. Rewrote every test that drove reopen and asserted on collab_private- channel creation (tests/integration/test_agent_page.py's whole "private- channel reopen route" section, tests/integration/test_proposal_review.py's "Reopen -> private channel" section) to assert on the DB-inbox message / Slack post the route now makes instead, and added dedicated tests pinning that reopen never creates a collab_private channel. Also fixed a latent hermeticity gap this rewrite exposed: `slack_globally_enabled`'s workspace- wide auto-detect falls back to `Settings.get_slack_tokens()`, which is keyed by real roster ids -- a dev host with live-tier credentials in its `.env` (e.g. SLACK_BOT_TOKEN_WISEMAN) made these fictitious-agent tests take the real-Slack branch and 500. Both files' Slack-off fixtures now stub that out explicitly. Gate: python -c "import src.routers.agent_page" passes; the three affected test files (135 tests) pass in full against a real Postgres. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…design doc
Parallel design-coverage audit findings, all surgical:
- §6: "the turn skips" read as if an empty menu were a code-level bypass,
same as blocked_for_regular's narrowed-empty case. It isn't -- the skip
is LLM-mediated: _EMPTY_MENU renders skip-only text, phase 5 still
calls the LLM, and the model itself returns {"action": "skip"}.
Clarified and contrasted explicitly with the code-bypass case.
- §8: added a note that infrastructure/discovery code for channels that
already exist (_sync_private_channels_from_db, the /message route's
membership check, admin views) is retained by design -- only the flow
that creates new state (seeding, collab_private finalization, and now
reopen_proposal's migration, fix 9) was removed.
- §11: the quoted confidentiality-rule sentence was a drafted target, not
the landed text (prompts/agent-system.md's actual wording differs).
Annotated so a reader doesn't mistake the quote for a live pin -- the
doc-sync test (test_doc_prompt_sync.py) covers the prompt file itself,
not this design doc's quotation of it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Conflicts resolved deletion-wins: pi-dm-classify.md and pi-profile-rewrite.md deleted (branch-2 edits superseded); hub role.toml takes branch-1's empty post_types; the PI-flag paragraph removed from phase5-new-post.md and its doc mirror. The token-contract test's covered-template list drops the four deleted phase-2 templates and the deleted hub phase-5 template (builders die in Task 7). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ction engine Private-instructions removal (AUD-PRIV branch-2 layer): - agent.py: delete private_profile property/_private_profile cache, the "## Your Private Instructions" system-prompt injection, update_private_profile, persist_private_profile_to_db, and the phase-4 pi_context injection; drop build_phase5_prompt's pi_flagged param/injection; own_publication_dois now derives from the public profile only (decision 2). - llm.py: delete synthesize_private_profile + its fallback; profile_pipeline.py: delete step 9b (private-profile seed generation); profile_export.py: delete export_private_profile + PRIVATE_PROFILES_DIR. - onboarding.py: delete the private-profile step (GET/POST /onboarding/private-profile) and its live/seed/disk/template fallback chain; relocate its completion side effects (onboarding_complete flip, welcome email, pending-invite/post-login-redirect resume) onto save_profile, now the terminal step. Verified RED (reverted the relocation, 4 tests failed) then GREEN (restored, full file green — 57 passed). - Delete prompts/private-profile-synthesis.md and templates/onboarding/private_profile.html (now-orphaned readers). PI-interaction removal (AUD-PI branch-2 layer): - Delete src/agent/pi_handler.py (PIHandler) outright: DM handling, channel-tag handling, thread-conclusion DMs. - simulation.py: remove PIHandler construction, _load_pi_mappings (+ delegate_slack_ids fold-in), _seed_pi_dm_cursor, _poll_pi_dms, _poll_pi_dms_from_db, _poll_proposal_threads_for_pi, _handle_pi_inbound_entry, _check_pi_proposal_review, _reopen_thread, and notify_thread_conclusion callers. Rename _poll_slack_for_pi_messages -> _poll_slack_for_human_messages and strip its PI-specific branch (human messages are now ingested for history only, no action taken); _poll_inbound_from_db's is_bot=False branch likewise logs-and-ignores. has_pi_directive/pi_priority/PostRef.pi_context removed; phase-5 gate simplifies to has_new_work = has_interesting or has_phase4_work; skip/blocked bypasses drop their pi_priority carve-outs. ThreadState.pi_context/message_count_offset intentionally KEPT — still written by _sync_proposal_reviews_from_db (Task 6's scope). - email.py: reword the welcome email to drop the "weigh in yourself" PI->bot implication (decision 7), keeping read-only Slack spectating. - email_inbound.py::_handle_instruction: classify-and-ignore (decision 3) — no thread post, no channel migration, no review row; classification plumbing (classify_reply) kept. - KEPT per decision 5: pi_inbox.py service, pi_dm_messages model/migrations, and src/routers/agent_page.py's private-profile editor + PI-DM/message web routes (out of this task's scope — see task-5-report.md's escalations). Tests: delete test_pi_flag_flow.py and test_slack_pi_live.py; retarget test_hub_budget_scheduler.py's TestPIHandlerAccounting (2 of the 24 known-red failures) to TestPhase5CallAccounting, anchored on _phase5_new_post instead of the deleted PIHandler; rewrite test_no_collaboration_residue.py's obsolete section-list assertions into absence pins; fix autouse fixtures in test_onboarding_flow.py/test_agent_page.py that referenced deleted PRIVATE_PROFILES_DIR/synthesize_private_profile (would have zeroed both files); scalpel PI-DM tests out of test_message_persistence.py and test_cohort_engine_live.py; mechanical renames/cleanup across e2e and other live-tier integration files. Gate: tests/unit 22 failed / 1274 passed / 2 skipped (down from the 24 known-red baseline — all 22 remaining are Task 6's hub-reply-only territory, none introduced by this task). ruff: test-suite 0 findings, src/ 240 (ceiling 249, was 249 pre-task). Full tests/ collection (1972 tests) has zero import errors. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014oXRXEdWDcpitGYT3Y5hUP
…cation (Option A)
scout_hub is now hard-gated out of _phase5_new_post (decision 9): the hub's
:mag: Opportunity Assessment is extracted from, and stripped out of, its own
Phase-4 CONCLUDE reply instead of filed as a separate top-level post
(SimulationEngine._reply_to_thread/_capture_hub_assessment). Deletes
TERMINAL_POST_TYPES, available_for's terminal_only narrowing, the
blocked-narrowing plumbing in _phase5_new_post, unreviewed-proposal blocking
+ _sync_proposal_reviews_from_db (nothing on this branch creates a proposal
to review anymore; models/admin stay), and CANONICAL's opportunity_assessment
entry (CANONICAL == {pitch}). Labs at active_thread_threshold now skip
Phase 5 outright, pre-LLM, like the daily-post cap.
Rewrites/deletes the 22 known-red tests this left behind
(test_phase5_terminal_posts.py, test_role_menus.py,
test_post_type_enforcement.py hub cases, test_agent_prompts.py,
test_roles.py) plus the collateral breakage from the CANONICAL/
available_for signature changes (test_post_types.py, test_roles.py's
generic parse_post_types tests, test_hub_budget_scheduler.py's tick-IO
list, test_phase2_guard.py's now-inverted direct-call assertion). Adds new
pins for the Option A relocation: a concluding hub reply with a sidecar
produces an OpportunityAssessment row with the post stripped, a malformed
sidecar still posts with no row and a logged error, and a persistence
failure never crashes the reply — at both the unit level
(test_simulation_logic.py) and the integration level
(test_opportunity_assessment_persistence.py, rewritten from the deleted
Phase-5 driver to a new _reply_to_thread driver).
tests/unit: 1288 passed, 2 skipped (0 failed, was 22).
tests/integration: 484 passed, 1 skipped-irrelevant pre-existing failure
(test_cohort_engine_live.py, confirmed present on the unmodified base commit).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014oXRXEdWDcpitGYT3Y5hUP
…_posts cascade Phase 2 (scan/filter) has been dead code since 7806c1a (_run_turn never called it) and Task 5 removed its last field writer (pi_handler). Delete it outright: _phase2_scan_filter/_phase2_prune (simulation.py), build_phase2_scan_prompt/ build_phase2_prune_prompt/build_scan_system_prompt (agent.py), and interesting_posts_cap (config.py). Cascade every remaining reader of AgentState.interesting_posts to zero: the phase-5 available_posts filter loop, its interesting_posts swap-and-restore, and the private/public visibility resolution it fed (all downstream of a field with zero writers) collapse to a plain cap -> threshold -> prompt -> new_post/skip Phase 5; the two pruning sites (_evict_dead_thread, _apply_cohort_gate_to_state) drop their interesting_posts half; the AgentState.interesting_posts field and the now-fully-orphaned PostRef dataclass are deleted from state.py. Tests: test_phase2_guard.py replaced by test_lab_daily_post_cap.py (its phase-2 source-inspection pin is vacuous once the function doesn't exist; the daily-cap tests it also held survive under an accurate name). test_cohort_isolation.py::TestStatePruning, test_cohort_real_llm.py's four phase-2-driven real-model tests, and test_cohort_scenarios.py's interesting_senders-based test deleted with rationale (each traced to confirm the invariant it protected is still pinned elsewhere, deterministically, where one exists). test_cohort_engine_live.py's test_phase2_prompt_omits_non_cohort_posts (the one pre-existing tests/integration failure on this branch) deleted and re-pinned on the one surviving production call site of the same gated read (the scout_hub auto-activation branch of Phase 3). Gate: tests/unit 1283 passed/2 skipped/0 failed (was 1288/2/0 — net -5 deliberate removals); tests/integration (full, real_llm excluded) 538 passed/0 failed (was 484/1/72 — the one failure is gone); characterization 10 failed/57 passed (was 11/57, net -1, remainder pre-existing per the removal-cycle's own "stays red mid-cycle" constraint). grep for interesting_posts/phase2/_phase2/scan_system_prompt in src/ is empty. ruff src/ 240 findings, unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014oXRXEdWDcpitGYT3Y5hUP
test_scout_hub_never_reaches_the_llm_in_phase_5_even_when_saturated used the production active_thread_threshold (12) against a 65-thread fixture, so the generic backpressure check tripped first and the test passed even with the scout_hub role gate deleted. Raise the threshold to 1000 so only the role gate can explain the skip; verified by temporarily neutering the gate (test fails) and restoring it (test passes again). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014oXRXEdWDcpitGYT3Y5hUP
…rdict _capture_hub_assessment already warns when an <assessment_json> sidecar is present but broken; it stays silent when the tag is absent, correctly, since that's the ordinary case on ~11 of every 12 interview turns. That silence hid a real gap at the one turn where thread_guidance.py's CONCLUDE branch requires the hub to either decline (⏸️) or close with a verdict carrying the sidecar: a reply that does neither produced nothing persistable with no trace anywhere. _warn_if_hub_conclude_missing_assessment fires only when the thread is at thread_guidance's own CONCLUDE point (delegating to phase4_guidance rather than re-deriving the cutoff from settings.max_thread_messages, since the two can diverge), the reply does not open with the ⏸️ decline convention, and no assessment_json tag is present at all. Added a 4-case boundary matrix covering fire/silent on both sides of each condition. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014oXRXEdWDcpitGYT3Y5hUP
There was no direct test of email_inbound._handle_instruction at all — the removal cycle's decision 5 retired every human-PI-to-bot interaction surface, leaving the function a documented classify-and-ignore no-op, but nothing enforced that. Drive it directly against real DB fixtures and assert it returns False, writes no ProposalReview row, writes nothing to either message store (AgentMessage or PiDmMessage), and logs the ignore line naming the thread. Verified the test catches a regression (a row write + True return) before reverting the probe. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014oXRXEdWDcpitGYT3Y5hUP
…ount
_reply_to_thread computed thread.message_count as the count of messages
already in the thread and passed it straight to phase4_guidance, whose
documented contract is the ORDINAL of the message being written ("This is
message 12", not "message 11"). Combined with the system-enforced-close
check intercepting at the same prior-count >= max_thread_messages (before
any reply is generated), the CONCLUDE branch was unreachable under the
default config: a reply only ever generates at prior-count <= 11 (DECIDE at
most), and prior-count >= 12 silently closes the thread as a "timeout" with
no verdict. The documented system-enforced concluding reply (verdict +
sidecar when warranted) never executed.
Fix: Agent.build_phase4_prompt now feeds phase4_guidance (and the prompt's
{message_count} substitution, for internal consistency) thread.message_count
+ 1. _warn_if_hub_conclude_missing_assessment's own phase4_guidance call gets
the same correction, since it must agree with the guidance the reply it is
inspecting was actually generated under. The close check itself is
unchanged — it is correctly a check on the prior count — but now documents
the latent coupling between settings.max_thread_messages and
thread_guidance's hardcoded ordinal boundaries.
Added an end-to-end regression pin (TestPhase4OrdinalGuidance) driving a
real (non-mocked) Agent.build_phase4_prompt through a real
SimulationEngine._reply_to_thread call: a thread at prior-count 11 now
receives MUST-CONCLUDE guidance and posts; a thread at prior-count 12 closes
without ever reaching the LLM. Verified RED against the pre-fix code (via
git stash) and GREEN after.
phase4_guidance itself, and every test that calls it directly with an
explicit ordinal (test_thread_guidance.py, test_doc_prompt_sync.py) or drives
build_phase4_prompt without asserting on phase category text, are unaffected
and intentionally untouched. The GM/characterization snapshot tier is
expected to need regeneration (tracked separately) and is out of scope here.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014oXRXEdWDcpitGYT3Y5hUP
TestHubConcludeMissingAssessmentWarning previously had to inflate max_thread_messages to 20 to make a CONCLUDE-guided reply reachable at all — a symptom of the ordinal off-by-one just fixed. Now that Agent.build_phase4_prompt and _warn_if_hub_conclude_missing_assessment both use the reply's ordinal, a thread with 11 existing messages reaches the warning's CONCLUDE check as a genuinely real reply under DEFAULT settings (11 < max_thread_messages=12, ordinal 12 -> CONCLUDE). Dropped the threshold-inflation fixture entirely; the 4-case boundary matrix (fires / silent x each condition) now runs against the real default configuration. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014oXRXEdWDcpitGYT3Y5hUP
…ent warning's off-by-one Nine (now ten — grep found one more hyphenated "Phase-2 backlog" mention) comments across transport.py, slack_client.py, and simulation.py still described Phase 2 as live after prior tasks removed it outright. Reworded each to describe what actually consumes get_new_top_level_posts today (the hub's Phase 3 auto-activation scan) or dropped the phase reference where the comment was really about a historical bug. _VALID_GATING_STATES pointed at the deleted phase5-new-post.md; repointed at the relocated <assessment_json> skeleton in phase4-thread-reply.md. _warn_if_hub_conclude_missing_assessment logged thread.message_count (the prior count) while classifying by the ordinal (message_count + 1) — the same off-by-one 55822a4 fixed for build_phase4_prompt, just missed here. Now logs message_ordinal. Added an assertion pinning the corrected log line, plus a new real-path test (test_agent_prompts.py) driving build_phase4_prompt itself at prior-count 4 to pin the shifted EXPLORE/DECIDE boundary (ordinal 5 -> DECIDE), which was untested at the engine-wiring level. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014oXRXEdWDcpitGYT3Y5hUP
…es and the migration service they and email once gated Web dashboard dead ends whose sources were already deleted: - agent_page.py: the private-profile view/edit/save routes (nothing generates or reads a private profile anymore; agent.py's private_profile property and llm.py's synthesize_private_profile were already gone) and the PI-web-DM route (its only reader, pi_handler.py, was already gone). Removed their templates (agent/profile.html; the DM section of agent/conversations.html; the "Agent Instructions" card of agent/dashboard.html and the matching cross-link in profile/edit.html, which pointed at the now-deleted route). Delegate account/dashboard access and all admin/history views are untouched. - src/services/private_channels.py (the public-thread -> collab_private channel migration service): zero production callers remain — the web reopen route stopped calling it in an earlier fix, and it was the sole consumer of AgentSlackClient's create_private_channel/invite_to_channel beyond direct unit coverage. Deleted outright, along with the enable_private_refinement setting that gated it (config.py) — orphaned once neither the reopen route nor email_inbound.py read it any longer (verified: zero references in email_inbound.py despite config.py's own comment claiming otherwise). - config.py's unreviewed_proposal_block_count: unreferenced in src/ since an earlier task deleted the unreviewed-proposal-blocking mechanism from _phase5_new_post; removed the setting and the inert kwargs test fixtures were still passing it. Test fallout: deleted test_private_channel_migration.py's dead-service coverage, keeping and renaming (to test_slack_private_channel_creation.py) the parts that test AgentSlackClient's own capabilities rather than the deleted service; deleted test_slack_private_live.py (live-Slack coverage of the same deleted service — its module-level import would otherwise break collection); deleted test_message_persistence.py's migration-flow tests; deleted test_slack_client_contract.py's pin on the deleted _MAX_POST_CHARS; updated test_agent_page.py's Ep table (20 -> 16 endpoints) and stale docstrings referencing the deleted setting/module. Updated tests/integration/test_onboarding_flow.py's docstring, which still credited agent_page.py as a current writer of private_profile_md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014oXRXEdWDcpitGYT3Y5hUP
… reply-only hub email_inbound.py::_send_instruction_confirmation was unreachable: the caller only invoked it when _handle_instruction returned reopened=True, and _handle_instruction (the removal cycle's classify-and-log-ignore no-op) always returns False. Deleted the function and the dead conditional around its call site; the "already emailed the PI an explanation" comment there was also false (_handle_instruction only logs) and is corrected. email.py's welcome text still told PIs BlackbirdBot "posts an Opportunity Assessment to the thread" on reaching a verdict — describing the retired standalone top-level post. Reworded (plain-text and HTML bodies) to match the reply-only hub: the verdict is stated directly in BlackbirdBot's concluding reply in the same thread. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014oXRXEdWDcpitGYT3Y5hUP
…xt; fix a stale docstring ThreadState.pi_context had zero readers or writers anywhere in src/ or tests/ — its only writer (a web-guidance reopen path) and only reader (Agent._compose_system_prompt's phase-4 injection) were both already deleted; its own comment flagged dropping the field as "a separate cleanup." Dropped it; message_count_offset (same comment, same former writer) stays since simulation.py still reads it. pi_inbox.py::record_pi_message's docstring claimed the inbound poller "routes it through PI handling (proposal-review clear, thread reopen, pi_context, @bot tags)" — none of that exists any more (confirmed against _poll_inbound_from_db's own, already-accurate docstring). Corrected. Extended test_doc_prompt_sync.py's _FORBIDDEN prompt-phrase guard with four phrases from this cycle's four removals ("your pi flagged", "private instructions", "dm rules", "phase 2") — verified zero current hits in prompts/, so this is a pure regression guard, not a fix. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014oXRXEdWDcpitGYT3Y5hUP
…gnals; fix CLAUDE.md's private-instructions/hub-posting claims
CLAUDE.md's BlackbirdBot section still described the rubric as loaded from
profiles/private/blackbird.md and injected under a "## Your Private
Instructions" header (deleted this cycle — Agent._compose_system_prompt no
longer does this, and nothing reads profiles/private/{agent_id}.md), and
described BlackbirdBot posting a standalone ":mag: Opportunity Assessment"
(also retired — the hub is reply-only; the sidecar rides inside its
CONCLUDING reply). Rewrote both, and pointed at the deploy checklist's new
archive-and-diff step for the untracked rubric file.
Appended an addendum to the pr34-pitch-only-reconciliation-design.md doc
recording the removal cycle's four removals (private instructions,
reply-only hub / assessment relocation, human-PI interaction, phase-2
prompts), its ten locked decisions, and the ordinal fix as an
audit-discovered correction landed alongside them (dated) — superseding the
corresponding sections of that earlier design without erasing them.
Amended the deploy checklist:
- §4 (host-file hygiene) rewritten from "delete the stale rubric file" to
"archive-and-diff it BEFORE deploy" — the old rationale (private_profile
caches it once at startup; deleting without replacing leaves no rubric)
no longer applies since nothing reads the file at runtime, but a new risk
does: it may hold rubric content never migrated into the tracked prompt.
- §3a's purge rationale updated — the setting that used to make unreviewed
proposals block Phase 5 is deleted, so purging them is tidiness now, not
unblocking.
- §7 (verification): 7.2/7.3 tightened to check the hub's reply carries
thread_ts NOT NULL and the assessment persists from that reply with the
sidecar stripped (not a top-level post); 7.5's phase-2 line-number
references replaced with "deleted outright, no code path left"; added 7.7
(zero hub top-level posts, run-wide), 7.8 (a cap-reaching interview ends
with a CONCLUDE reply, not a bare timeout — the ordinal-fix regression
check), and 7.9 (zero PI flows: no acted-on PI DM, no new collab_private
channel).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014oXRXEdWDcpitGYT3Y5hUP
…le (single reviewed diff for andrewsu) Starting state: 10 failed, 57 passed — no collection errors (nothing in this tier imports the deleted synthesize_private_profile by name), just stale snapshots/fixture drift: - test_agent_turn_gm.py (4 failed): pure prompt-content drift from Task 1 (dc903ff, private-instructions/DM-rule/PI-flag Core Rules removal) and Task 2 (93702e6, hub reply-only + assessment-sidecar relocation into Phase 4 CONCLUDE), plus the ordinal fix (55822a4). - test_profile_pipeline_gm.py (6 failed): run_profile_pipeline no longer calls the deleted synthesize_private_profile, so it makes at most 2 LLM calls (public synthesis + one retry), never a 3rd "private seed" call. The shared FakeAnthropic fixture and five tests' scripts still scripted that extra response; on multi-call tests it got consumed as a bogus next public call (plain markdown failing JSON parsing), throwing off llm_calls_total / rejected_in_progress relative to the pinned snapshot. Test-side fixes only, no test deleted (none of the brief's other-listed casualties — phase-2/PostRef fixtures, a surviving hub phase-5 GM, a PI-flag GM — exist in this tier): - test_phase4_prompt_phase_progression_gm: build_phase4_prompt feeds phase4_guidance the ordinal (message_count + 1), so the fixture's prior counts 2/8/12 now produce ordinals 3/9/13 instead of the canonical EXPLORE/DECIDE/MUST_CONCLUDE examples (2/8/12) the test pins. Shifted the fixture's prior counts to 1/7/11. - test_profile_pipeline_gm.py: dropped the dead _PRIVATE_SEED constant and every script referencing it (module docstring, _install_fakes, and 9 of 10 tests); trimmed each multi-call test's script to the number of calls run_profile_pipeline actually makes now; adjusted the corresponding llm_calls_total assertions/snapshots; dropped the now-meaningless seed_set_after_first_run/seed_unchanged_on_rerun keys from the rerun test. private_profile_md/private_profile_seed stay pinned as None in the golden master (decision 5 kept the DB columns; nothing writes them anymore). Regenerated once (--snapshot-update), verified green (67 passed, 16 snapshot blocks), then reviewed the .ambr diff block-by-block (split by "# name:", diffed independently): 0 blocks added/removed, 9 changed (4 in test_agent_turn_gm.ambr, 5 in test_profile_pipeline_gm.ambr), 7 unchanged. Every changed block traces to Task 1 (dc903ff), Task 2 (93702e6), the ordinal fix (55822a4), or this task's own message_count fixture shift — including one block (pubmed_outage_on_rerun_keeps_the_grounded_profile) that came out byte-identical to its pre-cycle value once the fixture-consumption bug was fixed, confirming that failure was a pure fixture artifact and not a real behavior change. No unexplained hunk. No src/ change. No real_llm test was run. Full per-block accounting: .superpowers/sdd/2026-08-12-removal-cycle/task-9-report.md Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014oXRXEdWDcpitGYT3Y5hUP
… src lint to 231 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014oXRXEdWDcpitGYT3Y5hUP
Deletes the remaining human-PI-to-bot write surfaces the audit found still live: the web dashboard's post_agent_message and connect_slack routes (agent_page.py), reopen_proposal's Slack-post branch (DB inbox is now the only, unconditional path), and pi_inbox.record_pi_dm/pi_may_post_to_channel (orphaned once their only callers were gone). SimulationEngine's _poll_slack_for_human_messages is renamed to _poll_slack_for_bot_messages and loses its human branch outright — a human Slack message is no longer ingested at all. Closes the trigger loop those write paths used to feed: every GATED MessageLog read (get_new_top_level_posts/get_replies_to_agent_posts/ get_tags_for_agent/has_new_reply_from_other) now filters out human-authored (is_bot=False) entries unconditionally, independent of the cohort gate. Before this, the surviving is_bot=False producer could still have set a bot's has_pending_reply, granted reactive priority, or — via _infer_agent_id's substring match (e.g. "Andrew Su (PI)" contains agent_id "su") — fabricated a Phase-3 thread activation misattributed to a bot that never posted anything. _entry_allowed's own human-bypass clause is left untouched (it is a general cohort-gate primitive with its own permanent test suite); the new invariant lives one layer up, in the four GATED methods themselves, since it also has to hold when the cohort gate is off by default. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014oXRXEdWDcpitGYT3Y5hUP
…pipeline (I1) tests/integration/test_profile_pipeline_live.py asserted the pre-removal shape: private_profile_seed non-empty, probe.private_calls == 1, and "exactly two LLM calls" per happy-path run. synthesize_private_profile (former step 9b) was deleted outright in the 2026-08-12 PI-interaction removal cycle, and the golden masters already reflect that (private_profile_ seed/private_profile_md always None, one LLM call per run) — this file did not follow. Flips every private_profile_seed/private_calls assertion to match the provable constant (None / 0), rewrites failure text to name the deletion instead of a function that no longer exists, and removes the T4.2/T4.5 seed_set_after_first_run/seed_unchanged_on_rerun keys entirely — GM #4's snapshot no longer carries either claim, so there was nothing live left to reconcile them against. T4.2's seed comparison, a None == None tautology with no surviving subject, is now a direct, honest pin instead. File still collects cleanly (5 tests); never executed (real_llm + live_api, skipif on ANTHROPIC_API_KEY which is unset here). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014oXRXEdWDcpitGYT3Y5hUP
…set dead code (M1-M5) M1: config.py's daily_post_cap is deleted — pi_lab and scout_hub are the only two roles that exist, scout_hub is hard-gated out of _phase5_new_post before the cap check, so the else-branch of the settings.lab_daily_post_cap /settings.daily_post_cap ternary was provably unreachable. Drops the now-dead kwarg from 6 test stubs. M2: state.py's message_count_offset (permanently 0 — its only writer was deleted in an earlier removal-cycle commit) and its 12-line dead-code comment are deleted; the no-op "- thread.message_count_offset" subtraction in _reply_to_thread is removed. Characterization suite re-run and confirmed unchanged (67 passed, 16/16 snapshots) — no golden master shifted. M3: profile.py's private_profile_md column comment updated to say what is actually true (retired, kept, no writers) instead of describing a write path (agent via PI DM) that no longer exists. M4: both prompt-set docs' §4 intro line corrected to "chosen by the ordinal of the reply being written" — matching thread_guidance.phase4_guidance's actual contract (Agent.build_phase4_prompt already feeds it the ordinal, not the prior count). Doc-only prose outside the fenced blocks the doc-sync test parses; that suite re-run and confirmed still green. M5: thread_guidance.py's docstring no longer claims both roles' strings are pinned by the golden master — only pi_lab's are (the GM never drives scout_hub at all); scout_hub's doc-sync coverage is test_doc_prompt_sync.py's own parametrized test, named explicitly now. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014oXRXEdWDcpitGYT3Y5hUP
… decision 5 The prior commit's is_bot filter on the four GATED MessageLog reads was over-broad: it also blocked get_new_top_level_posts/get_replies_to_agent_posts /get_tags_for_agent, which decision 5 requires to keep returning human rows for history/observability (pi_inbox views, ingestion-completeness reads). Caught by tests/integration/test_cohort_engine_live.py:: test_db_ingestion_is_complete_and_reads_are_per_agent, which pins exactly that contract and regressed at the prior commit. Reverts the filter on those three methods (docstrings restated to explain why: they are general-purpose per-agent reads, not activation-only, even though their only real caller today is Phase 3). has_new_reply_from_other keeps its filter unconditionally — it is the one GATED method whose entire job is driving bot behavior (pending state, reactive priority), with no other caller. The activation-inert half of decision 5 (a human row must never activate a thread) moves to the actual point of activation instead: SimulationEngine._phase3_activate_threads now filters is_bot directly in each of its three loops (tag, reply, hub auto-activation). Test fallout: reverted test_top_level_posts_filtered/test_tags_filtered/ test_partner_visible_in_pi_created_private_channel (test_cohort_isolation.py) to their original human-inclusive assertions; deleted the three message-log- level activation-exclusion tests added by the over-broad fix (test_human_top_level_post_never_returned_even_with_the_gate_off, test_human_reply_to_own_post_never_returned, test_human_tag_never_activates_via_a_bot_name_substring) — the same behavioral guarantee is already covered at the correct layer by tests/unit/test_hub_auto_activation.py's engine-level tests (unchanged, still passing, since they drive _phase3_activate_threads end to end and that function still filters is_bot). has_new_reply_from_other's tests are unaffected. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014oXRXEdWDcpitGYT3Y5hUP
This was referenced Aug 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Was stacked on the prompts+docs branch (PR #34, now merged into
blackbird) — this PR now targetsblackbirddirectly and carries only the engine-side commits. Implements the engine half of the pitch-only design and the follow-on removal cycle.Engine reconciliation
pitch(one per day, enforced pre-LLM).0026dropsgrantbot_posted_foas— archive first per the deploy checklist): GrantBot modules/service/tool/channel and the funding-thread open-to-all participation exception.WRITER_GRANTBOTstays reserved for historical message ids.:memo:/✅proposal handshakes, private-channel refinement, the phase-5replyaction.Removal cycle (second pass, per updated product direction)
profiles/private/blackbird.mdbefore deploying — see the checklist.<assessment_json>sidecar rides in its concluding interview reply, is stripped before Slack, and persists to/admin/assessments.CANONICALpost types ={pitch}.pi_dm_messages+ history views kept read-only.interesting_postsmachinery). Pipeline is 1→3→4→5.Audit-discovered correction
phase4_guidanceand the{message_count}token now receive the reply's ordinal; the message-12 concluding reply — verdict inline + sidecar — is real, pinned end-to-end, with an absent-sidecar warning live under default config.For review
be732ff,8a8fb12) — @andrewsu, these plus thethread_guidance.py§4 adoption on the base branch are the sign-off surface.docs/plans/2026-08-12-blackbird-pitch-only-deploy-checklist.md(migration + archive steps, DB purges, host-file hygiene, verification signals).docs/plans/2026-08-12-pr34-pitch-only-reconciliation-design.md(§15 records the removal cycle's locked decisions and sanctioned survivors).🤖 Generated with Claude Code