Persona-Sati Host Bootstrap Phases 1-2 Implementation Plan¶
For agentic workers: REQUIRED SUB-SKILL: Use
superpowers:subagent-driven-development(recommended) orsuperpowers:executing-plansto implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.
Goal: Make persona-sati session orientation reliable across host clients by first putting the bootstrap rule where hosts actually inject it, then replacing the fragile three-call startup ritual with a single bootstrap_session() MCP tool.
Architecture: Phase 1 is the host-visible bridge: entraclaw documents and tests the instruction surfaces that actually reach Claude Code, Copilot CLI, and GitHub Copilot. Phase 2 is the persona-sati API change: persona-sati returns one bootstrap packet that includes the assembled mind contract, active context, memory catalog summary, cognition protocol, and degraded-mode state. Phases 1 and 2 should land together because Phase 1 should teach hosts the new bootstrap_session() entry point, not preserve the old three-call ritual as the long-term shape.
Tech Stack: Python 3.12, FastMCP, pytest, ruff, Markdown host instruction files, shell setup scripts.
Problem statement¶
prompts/agent_system.md, prompts/anatomy/*, and persona-sati's prompt hemispheres are treated as load-bearing operating context, but MCP instructions= is not reliably injected into the model context by Claude Code or Copilot CLI. The host LLM only reliably sees host-injected files (CLAUDE.md, AGENTS.md, .github/copilot-instructions.md), MCP tool descriptions, explicit tool results, and hook-provided additional context.
The result is that the model can have body and mind tools available while missing the protocol that tells it when to use them. Efferent-copy partially mitigates this by mechanically firing observe() around entraclaw tools, but it does not solve session start, reflect(), recall(), thinking placeholders, memory reads, or non-tool replies.
Boundary decisions¶
- Do not collapse body and mind back together.
- Do not try to load full memory into every session.
- Do not rely on FastMCP
instructions=for behavior contracts. - Do not make persona-sati know Teams, email, chat IDs, or entraclaw-specific state.
- Do define a stable bootstrap packet that any body can request.
- Do mirror the bootstrap rule into every host-visible instruction surface until a broker runtime exists.
File map¶
Entraclaw repo: /path/to/entraclaw-identity-research¶
- Modify:
docs/TODO-persona-sati-host-bootstrap.md— update the tracker from "three-call session-start" to "bootstrap_session()plus host bootstrap." - Create:
docs/clients/persona-sati-host-bootstrap.md— canonical pasteable host instruction snippet. - Modify:
CLAUDE.md— replace the three-call ritual withbootstrap_session()and keep degraded-mode rules. - Modify:
AGENTS.md— same asCLAUDE.md, shorter but complete. - Modify:
.github/copilot-instructions.md— add a compact Copilot-visible bootstrap rule. - Modify:
scripts/hooks/require_body_prompt.py— recognizebootstrap_session()as a valid body/mind prompt sentinel. - Modify:
tests/hooks/test_require_body_prompt.py— prove high-blast-radius tools pass afterbootstrap_session(). - Modify:
tests/test_prompt_doctrine.py— pin the bootstrap doctrine across host-visible files. - Modify:
README.md— add a short "Host bootstrap" section. - Modify:
scripts/setup.sh— print a final action block telling operators where to install the snippet.
Persona-sati repo: /path/to/persona-sati¶
- Create:
src/persona_mcp/active/bootstrap.py— pure function that builds the bootstrap packet. - Modify:
src/persona_mcp/server.py— registerbootstrap_session()as an MCP tool. - Create:
tests/active/test_bootstrap.py— unit tests for the packet builder. - Modify:
tests/test_server.py— in-process FastMCP test for the new tool. - Create:
docs/reference/bootstrap-session-schema.md— packet compatibility contract for body clients. - Modify:
prompts/hemispheres/cognition-protocol.md— update the public startup rule fromcontext()tobootstrap_session(). - Modify:
CLAUDE.mdandAGENTS.md— update contributor instructions to usebootstrap_session(). - Modify:
README.md— document host-bootstrap requirements for users wiring persona-sati into a body. - Modify:
scripts/setup.sh— print the host-bootstrap action block after--with-entraclawwiring.
Execution order¶
Implement Phase 2 before Phase 1 in practice, even though this document
describes the host bridge first. The callable bootstrap_session() API must
exist before host-visible instructions start telling agents to call it. Keep
the older three-call startup tools compatible throughout the rollout.
Safe order:
- Land persona-sati Phase 2:
bootstrap_session(), schema contract, tests, and persona-sati docs/setup handoff. - Land entraclaw Phase 1: host-visible doctrine, setup handoff, and
require_body_prompt.pysupport for successful bootstrap results. - Run cross-repo validation from a fresh host session.
Phase 1: Host-visible bootstrap doctrine¶
Task 1: Write entraclaw doctrine tests first¶
Files:
- Modify: /path/to/entraclaw-identity-research/tests/test_prompt_doctrine.py
- [ ] Step 1: Add bootstrap doctrine files and markers
Add these constants near the existing DOCTRINE_FILES constant:
BOOTSTRAP_DOCTRINE_FILES = [
"AGENTS.md",
"CLAUDE.md",
".github/copilot-instructions.md",
"docs/clients/persona-sati-host-bootstrap.md",
"README.md",
"scripts/setup.sh",
]
BOOTSTRAP_MARKERS = [
"bootstrap_session",
"reflect",
"recall",
"observe",
"FastMCP instructions",
"mind_contract_available",
]
- [ ] Step 2: Add failing test for host bootstrap doctrine
Append this test:
@pytest.mark.parametrize("relpath", BOOTSTRAP_DOCTRINE_FILES)
def test_host_bootstrap_doctrine_mentions_required_mind_protocol(relpath: str) -> None:
path = REPO_ROOT / relpath
assert path.exists(), f"Bootstrap doctrine file missing: {relpath}"
text = path.read_text(encoding="utf-8")
missing = [marker for marker in BOOTSTRAP_MARKERS if marker not in text]
assert not missing, (
f"{relpath} must mention {missing} so persona-sati bootstrap "
"reaches host LLMs that ignore FastMCP instructions=."
)
- [ ] Step 3: Run the failing test
Run:
cd "/path/to/entraclaw-identity-research"
pytest tests/test_prompt_doctrine.py::test_host_bootstrap_doctrine_mentions_required_mind_protocol -v
Expected: FAIL because docs/clients/persona-sati-host-bootstrap.md does not exist and the other host files do not yet mention bootstrap_session.
Task 2: Create the canonical host bootstrap snippet¶
Files:
- Create: /path/to/entraclaw-identity-research/docs/clients/persona-sati-host-bootstrap.md
- [ ] Step 1: Create the clients docs directory if missing
Run:
- [ ] Step 2: Write the snippet
Create docs/clients/persona-sati-host-bootstrap.md with this content:
# Persona-Sati Host Bootstrap Snippet
Paste this into the host-level instruction file for any client that uses
persona-sati. This is required because Claude Code and Copilot CLI do not
inject FastMCP instructions into the model context; they reliably expose
host instruction files, tool descriptions, and tool results.
## Required session bootstrap
When a `persona-sati` MCP server is available, call
`bootstrap_session()` before the first substantive answer or external
tool call. Treat its result as the active mind contract for the session.
Decision tree:
1. If `bootstrap_session()` succeeds and `mind_contract_available` is
true, proceed with the returned mind contract.
2. If `bootstrap_session()` is unavailable but the older tools exist,
fall back to this order:
1. `get_system_prompt()`
2. `context()`
3. `list_memory_files()`
3. If the mind contract is unavailable or malformed, say persona-sati is
degraded and do not impersonate the persona.
4. If persona-sati is unreachable, say you are running in degraded
body-only mode before using external tools that depend on memory,
personality, or cognition discipline.
## Per-turn cognition
- Use `observe(tool_name, args)` before external tool calls when the
body has not mechanically wrapped them.
- Use `observe(tool_name, args, result=...)` after external tool calls
when the body has not mechanically wrapped them.
- Use `reflect(observation, kind="user_said")` when the user gives
behavior feedback, changes project direction, reveals durable
personal context, or asks a question about the agent's own cognition.
- Use `recall(query, k, facet)` when the bootstrap packet or observe
result indicates relevant memory exists but the excerpt is not enough.
## Body versus mind routing
The body owns tools, channel discipline, security, audit, and external
state. The mind owns voice, memory, continuity, cognition discipline,
and relationship context. Body rules win for security and tool use.
Mind rules win for continuity and voice when they do not conflict with
body safety.
- [ ] Step 3: Run the doctrine test again
Run:
cd "/path/to/entraclaw-identity-research"
pytest tests/test_prompt_doctrine.py::test_host_bootstrap_doctrine_mentions_required_mind_protocol -v
Expected: still FAIL until host files are updated.
Task 3: Update entraclaw host-visible instruction files¶
Files:
- Modify: /path/to/entraclaw-identity-research/CLAUDE.md
- Modify: /path/to/entraclaw-identity-research/AGENTS.md
- Modify: /path/to/entraclaw-identity-research/.github/copilot-instructions.md
- [ ] Step 1: Replace the CLAUDE.md session-start section
In CLAUDE.md, replace the current "Session-Start Protocol" call list with this wording:
On every new session where persona-sati is available, **before answering
the user's first substantive question or using external tools**, call:
1. `mcp__persona-sati__bootstrap_session()` — returns the assembled mind
contract, active context, memory catalog summary, available mind tools,
cognition rules, and degraded-mode flags.
If `bootstrap_session()` is unavailable but persona-sati exposes the
older tools, fall back to:
1. `mcp__persona-sati__get_system_prompt()`
2. `mcp__persona-sati__context()`
3. `mcp__persona-sati__list_memory_files()`
FastMCP instructions are not enough: Claude Code and Copilot CLI do not
reliably inject MCP server `instructions=` into the LLM system prompt.
Use the tool result as live context.
If `bootstrap_session()` returns `mind_contract_available: false`, or if
the fallback `get_system_prompt()` returns an error, say persona-sati is
degraded and do not impersonate the persona. If persona-sati is
unreachable, say you are running in degraded body-only mode.
- [ ] Step 2: Update the CLAUDE.md per-turn bullet list
Ensure the per-turn list includes these exact tool names:
- `observe(...)` — before and after external tool calls when the body
has not mechanically wrapped the call through efferent-copy.
- `reflect(...)` — for user statements, time passing, ambient
observations, persona drift, or behavioral feedback.
- `recall(...)` — when bootstrap/observe indicates relevant memory but
the excerpt is not enough.
- [ ] Step 3: Make the same shorter update in AGENTS.md
In AGENTS.md, replace the existing three-call session-start list with:
On every new session, call `mcp__persona-sati__bootstrap_session()`
before the first substantive answer or external tool call. If that tool
is unavailable, fall back to `get_system_prompt()`, `context()`, then
`list_memory_files()`. FastMCP instructions are not enough; hosts may
show them only in debug UI.
If `bootstrap_session()` returns `mind_contract_available: false`, or if
the fallback `get_system_prompt()` returns an error, say persona-sati is
degraded and do not impersonate the persona. If persona-sati is
unreachable, say you are running in degraded body-only mode.
Per-turn: use `observe`, `reflect`, and `recall` per the bootstrap
packet. Efferent-copy may mechanically cover body-tool `observe()`, but
it does not cover session bootstrap, `reflect()`, or `recall()`.
- [ ] Step 4: Add compact Copilot instruction
Append this section to .github/copilot-instructions.md:
## Persona-Sati Bootstrap
When the persona-sati MCP server is available, call
`bootstrap_session()` before the first substantive answer or external
tool call. This is required because FastMCP instructions are not
reliably injected into host LLM context. If `bootstrap_session()` is not
available, call `get_system_prompt()`, `context()`, then
`list_memory_files()`.
If `bootstrap_session()` returns `mind_contract_available: false`, or if
the fallback `get_system_prompt()` returns an error, say persona-sati is
degraded and do not impersonate the persona. If persona-sati is
unreachable, say you are running in degraded body-only mode.
Use `observe`, `reflect`, and `recall` according to the bootstrap
packet. Efferent-copy can mechanically cover `observe()` around
entraclaw body tools, but it does not cover session bootstrap,
`reflect()`, `recall()`, or ordinary text replies.
- [ ] Step 5: Run doctrine tests
Run:
Expected: PASS.
Task 4: Update entraclaw setup/docs handoff¶
Files:
- Modify: /path/to/entraclaw-identity-research/README.md
- Modify: /path/to/entraclaw-identity-research/scripts/setup.sh
- Modify: /path/to/entraclaw-identity-research/scripts/hooks/require_body_prompt.py
- Modify: /path/to/entraclaw-identity-research/tests/hooks/test_require_body_prompt.py
- Modify: /path/to/entraclaw-identity-research/docs/TODO-persona-sati-host-bootstrap.md
- [ ] Step 1: Add README host bootstrap section
Add this section near MCP setup instructions:
## Host bootstrap for persona-sati
If this worktree uses persona-sati, install the host bootstrap snippet
from `docs/clients/persona-sati-host-bootstrap.md` into your host's
global instruction file. This is required because Claude Code and
Copilot CLI do not reliably inject FastMCP `instructions=` into the
model context.
Minimum behavior: the agent must call `bootstrap_session()` before the
first substantive answer or external tool call. If the tool is not yet
available, fall back to `get_system_prompt()`, `context()`, and
`list_memory_files()`. If `mind_contract_available` is false, do not
impersonate the persona.
- [ ] Step 2: Add setup.sh final message
Near the end of scripts/setup.sh, after MCP config output, print:
cat <<'EOF'
Persona-sati host bootstrap:
If this host uses persona-sati, copy docs/clients/persona-sati-host-bootstrap.md
into the host's global instruction file.
Claude Code: ~/.claude/CLAUDE.md
Copilot CLI: use the configured global Copilot instruction location for this install
Repo-local fallback: CLAUDE.md, AGENTS.md, and .github/copilot-instructions.md
Required first call in new sessions: bootstrap_session()
If mind_contract_available=false: body-only mode; do not impersonate persona.
EOF
Do not silently edit a user's global file in this task. Printing the action is safer until the exact Copilot CLI global path is confirmed.
- [ ] Step 3: Update the Claude Code body-prompt gate
Update scripts/hooks/require_body_prompt.py so a successful
mcp__persona-sati__bootstrap_session result is accepted as a body/mind
prompt sentinel alongside mcp__persona-sati__get_system_prompt. The
bootstrap result must parse as JSON and include mind_contract_available:
true; a failed tool call or a packet with mind_contract_available: false
must not unlock high-blast-radius tools. Keep the gate fail-closed for the
same high-blast-radius entraclaw tools.
Add or update hook tests so a transcript containing a successful
bootstrap_session tool result allows mcp__entraclaw__send_teams_message.
Also test that a transcript containing only the tool call, a malformed result,
or mind_contract_available: false still blocks.
- [ ] Step 4: Update the TODO tracker
In docs/TODO-persona-sati-host-bootstrap.md, update the recommendation so it says Phase 1+2 now target bootstrap_session() as the primary entry point and the old three-call sequence is compatibility fallback.
- [ ] Step 5: Run targeted tests and lint
Run:
cd "/path/to/entraclaw-identity-research"
pytest tests/test_prompt_doctrine.py tests/hooks/test_require_body_prompt.py -v
ruff check tests/test_prompt_doctrine.py tests/hooks/test_require_body_prompt.py scripts/hooks/require_body_prompt.py
Expected: PASS.
- [ ] Step 6: Commit Phase 1
Run:
cd "/path/to/entraclaw-identity-research"
git add docs/clients/persona-sati-host-bootstrap.md \
docs/TODO-persona-sati-host-bootstrap.md \
README.md \
CLAUDE.md \
AGENTS.md \
.github/copilot-instructions.md \
scripts/setup.sh \
scripts/hooks/require_body_prompt.py \
tests/hooks/test_require_body_prompt.py \
tests/test_prompt_doctrine.py
git commit -m "docs: add persona-sati host bootstrap doctrine" \
-m "Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>"
Phase 2: Persona-sati bootstrap_session() tool¶
Task 5: Write pure bootstrap packet tests¶
Files:
- Create: /path/to/persona-sati/tests/active/test_bootstrap.py
- Modify: /path/to/persona-sati/tests/test_server.py
- [ ] Step 1: Create the failing test file
Create tests/active/test_bootstrap.py with:
from __future__ import annotations
from pathlib import Path
from persona_mcp.active.bootstrap import build_bootstrap_session
def _write_memory(path: Path, name: str, body: str) -> None:
path.joinpath(name).write_text(body, encoding="utf-8")
def test_build_bootstrap_session_returns_operating_packet(tmp_path: Path) -> None:
mem = tmp_path / "memory"
mem.mkdir()
_write_memory(mem, "MEMORY.md", "# Index\n- [Brandon](user_brandon.md) - sponsor context\n")
_write_memory(mem, "user_brandon.md", "---\nname: Brandon\ntype: user\n---\nUser profile")
_write_memory(mem, "running_commitments.md", "---\nname: Commitments\ntype: project\n---\n- Ship bootstrap")
_write_memory(mem, "carry_forward.md", "---\nname: Carry\ntype: session\n---\n- Continue context work")
packet = build_bootstrap_session(
mem,
session_id="s1",
mind_contract="FULL VOICE CONTRACT",
mind_contract_available=True,
available_mind_tools=["bootstrap_session", "observe", "reflect", "recall"],
)
assert packet["schema_version"] == 1
assert packet["session_id"] == "s1"
assert packet["mind_contract"] == "FULL VOICE CONTRACT"
assert packet["mind_contract_available"] is True
assert packet["degraded_mode"]["mind_contract_available"] is True
assert packet["required_first_call"] == "bootstrap_session"
assert "observe" in packet["available_mind_tools"]
assert "reflect" in packet["available_mind_tools"]
assert "recall" in packet["available_mind_tools"]
assert packet["context"]["open_commitments"] == ["Ship bootstrap"]
assert packet["context"]["recent_carry_forward"] == ["Continue context work"]
assert packet["memory_catalog"]["total_count"] == 4
assert packet["memory_catalog"]["index_present"] is True
assert packet["memory_catalog"]["category_counts"]["user"] == 1
assert "FastMCP instructions" in packet["host_limitations"][0]
def test_build_bootstrap_session_marks_missing_mind_contract_as_degraded(tmp_path: Path) -> None:
mem = tmp_path / "memory"
mem.mkdir()
_write_memory(mem, "MEMORY.md", "# Index\n")
packet = build_bootstrap_session(
mem,
session_id=None,
mind_contract="ERROR: persona-sati voice contract failed to assemble.",
mind_contract_available=False,
available_mind_tools=["bootstrap_session"],
)
assert packet["mind_contract_available"] is False
assert packet["degraded_mode"]["mind_contract_available"] is False
assert "mind contract unavailable" in packet["degraded_mode"]["reasons"]
def test_persona_sati_setup_mentions_host_bootstrap() -> None:
text = Path("scripts/setup.sh").read_text(encoding="utf-8")
assert "bootstrap_session" in text
assert "host bootstrap" in text.lower()
def test_memory_catalog_omits_filenames_and_reports_category_counts(tmp_path: Path) -> None:
mem = tmp_path / "memory"
mem.mkdir()
for i in range(205):
_write_memory(mem, f"memory_{i:03d}.md", "---\nname: M\ntype: note\n---\nbody")
_write_memory(mem, "user_brandon.md", "---\nname: Brandon\ntype: user\n---\nbody")
packet = build_bootstrap_session(
mem,
session_id="s1",
mind_contract="FULL VOICE CONTRACT",
mind_contract_available=True,
available_mind_tools=["bootstrap_session", "observe"],
)
assert packet["memory_catalog"]["total_count"] == 206
assert "files" not in packet["memory_catalog"]
assert packet["memory_catalog"]["category_counts"]["note"] == 205
assert packet["memory_catalog"]["category_counts"]["user"] == 1
- [ ] Step 2: Run the failing tests
Run:
Expected: FAIL because persona_mcp.active.bootstrap does not exist.
Task 6: Implement the pure bootstrap packet builder¶
Files:
- Create: /path/to/persona-sati/src/persona_mcp/active/bootstrap.py
- [ ] Step 1: Create
bootstrap.py
Create src/persona_mcp/active/bootstrap.py with:
"""Session bootstrap packet for host LLMs.
This module is deliberately body-agnostic. It knows about persona-sati's
mind tools and memory layout, not Teams, email, shells, or chat IDs.
"""
from __future__ import annotations
from pathlib import Path
from typing import TypedDict
from persona_mcp.active.context import Context, build_context
from persona_mcp.memory import list_memories
class MemoryCatalog(TypedDict):
total_count: int
index_present: bool
category_counts: dict[str, int]
class DegradedMode(TypedDict):
mind_contract_available: bool
reasons: list[str]
class BootstrapPacket(TypedDict):
schema_version: int
required_first_call: str
session_id: str | None
mind_contract: str
mind_contract_available: bool
available_mind_tools: list[str]
cognition_protocol: list[str]
context: Context
memory_catalog: MemoryCatalog
degraded_mode: DegradedMode
host_limitations: list[str]
def _memory_catalog(memory_dir: Path) -> MemoryCatalog:
files = list_memories(memory_dir)
category_counts: dict[str, int] = {}
for name in files:
try:
raw = (memory_dir / name).read_text(encoding="utf-8")
except OSError:
category = "unknown"
else:
category = "unknown"
for line in raw.splitlines()[:20]:
if line.startswith("type:"):
category = line.partition(":")[2].strip() or "unknown"
break
category_counts[category] = category_counts.get(category, 0) + 1
return {
"total_count": len(files),
"index_present": "MEMORY.md" in files,
"category_counts": category_counts,
}
def build_bootstrap_session(
memory_dir: Path,
*,
session_id: str | None,
mind_contract: str,
mind_contract_available: bool,
available_mind_tools: list[str],
) -> BootstrapPacket:
"""Return the operating packet a host should load first."""
reasons: list[str] = []
if not mind_contract_available:
reasons.append("mind contract unavailable")
return {
"schema_version": 1,
"required_first_call": "bootstrap_session",
"session_id": session_id,
"mind_contract": mind_contract,
"mind_contract_available": mind_contract_available,
"available_mind_tools": sorted(available_mind_tools),
"cognition_protocol": [
"Call bootstrap_session before the first substantive answer or external tool call.",
"Use observe before and after external tool calls unless the body mechanically wraps them.",
"Use reflect for user_said, time_passed, ambient, and internal observations.",
"Use recall when observe/bootstrap indicates relevant memory but the excerpt is insufficient.",
"Surface cautionary_flags and stop for prediction_error greater than 0.7.",
],
"context": build_context(
memory_dir,
binding_info={},
session_id=session_id,
),
"memory_catalog": _memory_catalog(memory_dir),
"degraded_mode": {
"mind_contract_available": mind_contract_available,
"reasons": reasons,
},
"host_limitations": [
"FastMCP instructions are not reliable model context in Claude Code or Copilot CLI.",
"Tool descriptions and tool results are more reliable than server instructions.",
"Efferent-copy can cover observe for body tools but not reflect, recall, or text-only replies.",
],
}
- [ ] Step 2: Run unit tests
Run:
Expected: PASS.
Task 6b: Define bootstrap packet schema contract¶
Files:
- Create: /path/to/persona-sati/docs/reference/bootstrap-session-schema.md
- Modify: /path/to/persona-sati/tests/active/test_bootstrap.py
- [ ] Step 1: Document schema v1
Create docs/reference/bootstrap-session-schema.md with:
# bootstrap_session() schema
`bootstrap_session()` returns JSON. `schema_version` is currently `1`.
## Compatibility rules
- Clients must require `schema_version`, `required_first_call`,
`mind_contract`, `mind_contract_available`, `available_mind_tools`,
`cognition_protocol`, `context`, `memory_catalog`, `degraded_mode`, and
`host_limitations`.
- Servers may add fields in schema v1.
- Clients must ignore unknown fields.
- Removing or renaming required fields requires `schema_version = 2`.
- If `mind_contract_available` is false, clients must not impersonate the
persona; body-only behavior is allowed if body safety rules are loaded.
- `memory_catalog` must not expose memory filenames. Use `recall()` or
`read_memory_file()` for intentional deeper access.
- [ ] Step 2: Add a required-field test
Add a unit test that builds a packet and asserts the exact required top-level
keys are present. Also assert schema_version == 1 and that unknown future
fields are permitted by the docs contract rather than rejected in code.
Run:
Expected: PASS.
Task 7: Register bootstrap_session() as an MCP tool¶
Files:
- Modify: /path/to/persona-sati/src/persona_mcp/server.py
- Modify: /path/to/persona-sati/tests/test_server.py
- [ ] Step 1: Add failing FastMCP test
Append to tests/test_server.py:
@pytest.mark.anyio
async def test_bootstrap_session_tool_returns_operating_packet(server):
result = await server.call_tool("bootstrap_session", {"session_id": "test-session"})
packet = json.loads(result[0][0].text)
assert packet["schema_version"] == 1
assert packet["required_first_call"] == "bootstrap_session"
assert packet["session_id"] == "test-session"
assert packet["mind_contract_available"] is True
assert "test agent" in packet["mind_contract"]
assert "observe" in packet["available_mind_tools"]
assert "reflect" in packet["available_mind_tools"]
assert "recall" in packet["available_mind_tools"]
assert packet["memory_catalog"]["index_present"] is True
@pytest.mark.anyio
async def test_bootstrap_session_reports_broken_mind_contract(memory_dir, tmp_path, monkeypatch):
monkeypatch.setenv("ENTRACLAW_PERSONA_SYNC", "off")
prompt = tmp_path / "agent_system.md"
prompt.write_text("@include missing.md\n", encoding="utf-8")
srv = create_server(memory_dir=memory_dir, prompt_path=prompt)
result = await srv.call_tool("bootstrap_session", {})
packet = json.loads(result[0][0].text)
assert packet["mind_contract_available"] is False
assert packet["degraded_mode"]["mind_contract_available"] is False
assert "ERROR: persona-sati voice contract failed to assemble" in packet["mind_contract"]
@pytest.mark.anyio
async def test_get_system_prompt_and_bootstrap_share_broken_prompt_error(memory_dir, tmp_path, monkeypatch):
monkeypatch.setenv("ENTRACLAW_PERSONA_SYNC", "off")
prompt = tmp_path / "agent_system.md"
prompt.write_text("@include missing.md\n", encoding="utf-8")
srv = create_server(memory_dir=memory_dir, prompt_path=prompt)
prompt_result = await srv.call_tool("get_system_prompt", {})
bootstrap_result = await srv.call_tool("bootstrap_session", {})
packet = json.loads(bootstrap_result[0][0].text)
assert prompt_result[0][0].text == packet["mind_contract"]
- [ ] Step 2: Run the failing FastMCP test
Run:
cd "/path/to/persona-sati"
pytest tests/test_server.py::test_bootstrap_session_tool_returns_operating_packet -v
Expected: FAIL because the tool is not registered.
- [ ] Step 3: Import the builder in
server.py
Add near the other active imports:
- [ ] Step 4: Register the MCP tool
First add a private helper near the existing get_system_prompt() tool so both
startup surfaces share one fail-loud prompt-loading path:
def _load_mind_contract() -> tuple[str, bool]:
if not p_path.is_file():
return f"Error: system prompt not found at {p_path}", False
try:
return load_prompt(p_path).text, True
except PromptAssemblyError as exc:
return format_body_error(exc), False
Then update get_system_prompt() to return only the first tuple element from
_load_mind_contract().
Add a small runtime introspection helper so available_mind_tools reflects the
registered FastMCP tool surface instead of a hardcoded list:
def _available_mind_tools() -> list[str]:
return sorted(tool.name for tool in server._tool_manager.list_tools())
Add this tool after refresh_persona() and before observe():
@server.tool()
def bootstrap_session(session_id: str | None = None) -> str:
"""Return the mind bootstrap packet for a new host session.
Call this before the first substantive answer or external tool
call. It replaces the older three-call startup ritual
(`get_system_prompt`, `context`, `list_memory_files`) with one
operating packet that includes the assembled mind contract.
"""
if hook:
hook("bootstrap_session", {"session_id": session_id})
mind_contract, mind_contract_available = _load_mind_contract()
packet = build_bootstrap_session(
mem_dir,
session_id=session_id,
mind_contract=mind_contract,
mind_contract_available=mind_contract_available,
available_mind_tools=_available_mind_tools(),
)
return json.dumps(packet)
Add a server test that asserts packet["available_mind_tools"] matches
await server.list_tools() so the packet cannot drift from registered tools.
- [ ] Step 5: Run server tests
Run:
cd "/path/to/persona-sati"
pytest tests/test_server.py::test_bootstrap_session_tool_returns_operating_packet -v
Expected: PASS.
Task 8: Update persona-sati prompt and host docs¶
Files:
- Modify: /path/to/persona-sati/prompts/hemispheres/cognition-protocol.md
- Modify: /path/to/persona-sati/CLAUDE.md
- Modify: /path/to/persona-sati/AGENTS.md
- Modify: /path/to/persona-sati/README.md
- Modify: /path/to/persona-sati/scripts/setup.sh
- [ ] Step 1: Update cognition protocol startup wording
In prompts/hemispheres/cognition-protocol.md, replace the session-start bullet:
- **At session start:** I call `context()` once to orient — who's
here, what's active, what I was in the middle of.
with:
- **At session start:** I call `bootstrap_session()` once before the
first substantive answer or external tool call. It returns the assembled
mind contract, active context, memory catalog summary, available mind
tools, cognition protocol, and degraded-mode state. If an older client
lacks `bootstrap_session`, I fall back to `context()` plus
`list_memory_files()`.
- [ ] Step 2: Update "What the four calls are for"
Replace that subsection with:
### What the active cognition calls are for
- `bootstrap_session` — the first call. It orients the host to the
mind contract, active context, memory catalog, tool surface, and
degraded-mode state.
- `observe` — the heartbeat. Every tool call (as defined above)
passes through it unless the body mechanically wraps the call.
- `recall` — when observe's excerpts aren't enough. Rich semantic
retrieval with optional facets (`relationship:bob`, `project:portal`,
`type:session_digest`).
- `context` — lower-level session snapshot used by bootstrap and older
clients.
- `reflect` — for signals that aren't tool calls. Same pipeline as
observe but the input is free text.
- [ ] Step 3: Update persona-sati CLAUDE.md and AGENTS.md
In both files, add a short note near the MCP/tool description:
`bootstrap_session()` is the primary startup tool for host LLMs. It
replaces the old instruction to call `get_system_prompt()`, `context()`,
and `list_memory_files()` separately. Keep the older tools for
compatibility, but teach new bodies to call `bootstrap_session()` first.
- [ ] Step 4: Update persona-sati README and setup handoff
Add a short README section explaining that hosts using persona-sati must
load the host bootstrap rule because FastMCP instructions= is not reliable
model context.
In scripts/setup.sh, after successful --with-entraclaw MCP wiring, print
the same host-bootstrap action block as entraclaw setup: install the canonical
snippet into the host-global instruction file, and ensure new sessions call
bootstrap_session() first.
- [ ] Step 5: Run prompt/server tests
Run:
cd "/path/to/persona-sati"
pytest tests/test_server.py tests/active/test_bootstrap.py tests/test_prompt_loader.py -v
ruff check src/persona_mcp/active/bootstrap.py src/persona_mcp/server.py tests/active/test_bootstrap.py tests/test_server.py
Expected: PASS.
- [ ] Step 6: Commit Phase 2
Run:
cd "/path/to/persona-sati"
git add src/persona_mcp/active/bootstrap.py \
src/persona_mcp/server.py \
tests/active/test_bootstrap.py \
tests/test_server.py \
docs/reference/bootstrap-session-schema.md \
prompts/hemispheres/cognition-protocol.md \
CLAUDE.md \
AGENTS.md \
README.md \
scripts/setup.sh
git commit -m "feat: add persona-sati bootstrap_session tool" \
-m "Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>"
Cross-repo validation¶
Task 9: Validate fresh-session behavior¶
Files: - No code changes.
- [ ] Step 1: Install both repos in their own virtual environments
Run:
cd "/path/to/persona-sati"
source .venv/bin/activate
pip install -e ".[dev,prediction,snn,blob,provisioning]"
cd "/path/to/entraclaw-identity-research"
source .venv/bin/activate
pip install -e ".[dev]"
- [ ] Step 2: Run targeted test suites
Run:
cd "/path/to/persona-sati"
pytest tests/test_server.py tests/active/test_bootstrap.py -v && ruff check .
cd "/path/to/entraclaw-identity-research"
pytest tests/test_prompt_doctrine.py tests/hooks/test_inject_body_prompt.py -v && ruff check .
Expected: PASS in both repos.
- [ ] Step 3: Manual smoke test in a non-entraclaw repo
Start a fresh host session from a directory that is not the entraclaw repo and has the .mcp.json entries for entraclaw and persona-sati. Ask:
Expected: the agent calls bootstrap_session() before answering. If it does not, update the host-global snippet installation instructions before merging.
- [ ] Step 4: Close issue tracker only after smoke test
After the smoke test passes, update GitHub issue #71 with:
Phase 1+2 landed. bootstrap_session() is now the primary persona-sati startup contract; host-visible docs and doctrine tests point to it. Manual non-entraclaw session smoke test passed.
Do not close #71 until the non-entraclaw smoke test confirms the host actually calls bootstrap_session().
GSTACK REVIEW REPORT¶
| Review | Trigger | Why | Runs | Status | Findings |
|---|---|---|---|---|---|
| CEO Review | /plan-ceo-review |
Scope & strategy | 1 | clean | Historical run, not specific to this Phase 1+2 plan |
| Codex Review | /codex review |
Independent 2nd opinion | 4 | issues_found | Outside voice found sequencing, schema, gate-result, metadata, and fallback gaps; accepted fixes are folded into this plan |
| Eng Review | /plan-eng-review |
Architecture & tests (required) | 4 | clean | 8 section findings, 0 critical gaps |
| Design Review | /plan-design-review |
UI/UX gaps | 0 | — | Not applicable: no UI changes |
| DX Review | /plan-devex-review |
Developer experience gaps | 0 | — | Not run |
- CODEX: Accepted material findings: implement Phase 2 before Phase 1, add schema contract, require successful bootstrap result for gate unlock, remove memory filenames from bootstrap, add deterministic fallback semantics, and derive
available_mind_toolsfrom registered tools. - UNRESOLVED: 0
- VERDICT: ENG CLEARED — ready to implement Phase 2 first, then Phase 1.