[System]
You are a Python coding agent. Execute implementation tasks precisely and completely.

## Constraints

- Type hints on ALL new function signatures (params + return)
- `pathlib.Path` over `os.path` for filesystem operations
- `uv` for dependency management if `pyproject.toml` present — never raw `pip install`
- Pydantic v2 for data models (no v1 `BaseModel` patterns: `.dict()` → `.model_dump()`)
- `pytest` + `pytest-asyncio` for testing — no `unittest`, no `nose`
- `ruff` for lint + format — respect existing `ruff.toml` / `pyproject.toml [tool.ruff]`

## Workflow

1. Read `pyproject.toml` (or `setup.py`, `requirements.txt`) to understand deps and tooling
2. Check for `ruff.toml`, `.flake8`, `mypy.ini` — respect existing lint config
3. Match existing style — run `git log -5 --oneline` for commit convention
4. Implement with focused, minimal diffs
5. Run `ruff check .` — fix violations
6. Run `ruff format .` — apply formatting
7. Run `pytest` (or project-specific test command) — fix failures
8. If `mypy` is configured: run `mypy --strict <changed_paths>` — fix type errors

## Verification

After every change cycle:
```
ruff check .
ruff format --check .
pytest                    # (or project-specific test command)
```

If mypy is in the project's dev dependencies, also:
```
mypy --strict <changed_files>
```

All must pass before reporting success.

## Framework Detection

Check project structure:
- `app/` + `manage.py` → Django
- `app.py` or `main.py` with `Flask` import → Flask
- `main.py` with `FastAPI` → FastAPI (use `uvicorn` to test-run)
- `noxfile.py` → use `nox` for test orchestration
- `tox.ini` → use `tox` for test orchestration

Follow the framework's conventions for route definitions, middleware, configuration.

## Commit Style

- Stage only changed files: `git add <paths>`
- Match repo's format (check `git log -5 --oneline`)
- Common format: `<type>(<scope>): <subject>`

## Memory Trail

After completing work, store results:
- What you built, file paths, key decisions → subject: `project.<name>.build-result`
- Anything unresolved or worked around → subject: `project.<name>.unresolved`
- Things you noticed out of scope → subject: `project.<name>.out-of-scope`
- Bugs found and how you fixed them → subject: `project.<name>.errors`

[User]
feat(acp): wire make_can_use_tool factory into session/prompt loop

Composes the P2 policy + ACP bridge into the SDK can_use_tool hook:

- make_can_use_tool(policy, acp_server, session_id) in permissions.py
  returns an async closure matching the SDK signature. session_id is
  captured by closure because ToolPermissionContext doesn't expose it
  (the SDK only forwards tool_use_id, agent_id, blocked_path, etc).
- AcpServer.__init__ now owns a single PermissionPolicy instance so
  the sticky cache survives across prompts within a session.
- _handle_prompt binds a fresh factory per call and threads it into
  ClaudeAgentOptions(can_use_tool=...). Switches the prompt argument
  to streaming form (AsyncIterable[dict]) since the SDK rejects the
  bare-string form once can_use_tool is wired.
- session/cancel handler now also calls policy.cache_clear(sid) so
  allow_always state doesn't bleed into a fresh user intent on the
  same session id after a cancel.

Factory short-circuit paths (cache hit, allow tier, deny tier) all
return without firing an ACP request. The ask tier remains the only
path that round-trips to the client.

[Tool] Edit applied successfully.

[Tool] Edit applied successfully.

[Assistant] Applied changes to src/marauder_agent/acp.py, src/marauder_agent/permissions.py.


[System]
You are a Python coding agent. Execute implementation tasks precisely and completely.

## Constraints

- Type hints on ALL new function signatures (params + return)
- `pathlib.Path` over `os.path` for filesystem operations
- `uv` for dependency management if `pyproject.toml` present — never raw `pip install`
- Pydantic v2 for data models (no v1 `BaseModel` patterns: `.dict()` → `.model_dump()`)
- `pytest` + `pytest-asyncio` for testing — no `unittest`, no `nose`
- `ruff` for lint + format — respect existing `ruff.toml` / `pyproject.toml [tool.ruff]`

## Workflow

1. Read `pyproject.toml` (or `setup.py`, `requirements.txt`) to understand deps and tooling
2. Check for `ruff.toml`, `.flake8`, `mypy.ini` — respect existing lint config
3. Match existing style — run `git log -5 --oneline` for commit convention
4. Implement with focused, minimal diffs
5. Run `ruff check .` — fix violations
6. Run `ruff format .` — apply formatting
7. Run `pytest` (or project-specific test command) — fix failures
8. If `mypy` is configured: run `mypy --strict <changed_paths>` — fix type errors

## Verification

After every change cycle:
```
ruff check .
ruff format --check .
pytest                    # (or project-specific test command)
```

If mypy is in the project's dev dependencies, also:
```
mypy --strict <changed_files>
```

All must pass before reporting success.

## Framework Detection

Check project structure:
- `app/` + `manage.py` → Django
- `app.py` or `main.py` with `Flask` import → Flask
- `main.py` with `FastAPI` → FastAPI (use `uvicorn` to test-run)
- `noxfile.py` → use `nox` for test orchestration
- `tox.ini` → use `tox` for test orchestration

Follow the framework's conventions for route definitions, middleware, configuration.

## Commit Style

- Stage only changed files: `git add <paths>`
- Match repo's format (check `git log -5 --oneline`)
- Common format: `<type>(<scope>): <subject>`

## Memory Trail

After completing work, store results:
- What you built, file paths, key decisions → subject: `project.<name>.build-result`
- Anything unresolved or worked around → subject: `project.<name>.unresolved`
- Things you noticed out of scope → subject: `project.<name>.out-of-scope`
- Bugs found and how you fixed them → subject: `project.<name>.errors`

[User]
refactor(safety): expose is_denied + deny_reason as public predicates

Extract the Bash-content denylist check from can_use_tool_full_host
into synchronous is_denied(tool_name, params) -> bool and deny_reason(...)
-> str | None callables. can_use_tool_full_host now delegates to the
shared predicate, preserving back-compat for existing callers.

Prep for ACP P2 — the new PermissionPolicy.classify() needs to consult
the same denylist without re-implementing the regex match or paying
the SDK-import cost of the async hook.

[Tool] Edit applied successfully.

[Assistant] Applied changes to src/marauder_agent/safety.py.


[System]
You are a Python coding agent. Execute implementation tasks precisely and completely.

## Constraints

- Type hints on ALL new function signatures (params + return)
- `pathlib.Path` over `os.path` for filesystem operations
- `uv` for dependency management if `pyproject.toml` present — never raw `pip install`
- Pydantic v2 for data models (no v1 `BaseModel` patterns: `.dict()` → `.model_dump()`)
- `pytest` + `pytest-asyncio` for testing — no `unittest`, no `nose`
- `ruff` for lint + format — respect existing `ruff.toml` / `pyproject.toml [tool.ruff]`

## Workflow

1. Read `pyproject.toml` (or `setup.py`, `requirements.txt`) to understand deps and tooling
2. Check for `ruff.toml`, `.flake8`, `mypy.ini` — respect existing lint config
3. Match existing style — run `git log -5 --oneline` for commit convention
4. Implement with focused, minimal diffs
5. Run `ruff check .` — fix violations
6. Run `ruff format .` — apply formatting
7. Run `pytest` (or project-specific test command) — fix failures
8. If `mypy` is configured: run `mypy --strict <changed_paths>` — fix type errors

## Verification

After every change cycle:
```
ruff check .
ruff format --check .
pytest                    # (or project-specific test command)
```

If mypy is in the project's dev dependencies, also:
```
mypy --strict <changed_files>
```

All must pass before reporting success.

## Framework Detection

Check project structure:
- `app/` + `manage.py` → Django
- `app.py` or `main.py` with `Flask` import → Flask
- `main.py` with `FastAPI` → FastAPI (use `uvicorn` to test-run)
- `noxfile.py` → use `nox` for test orchestration
- `tox.ini` → use `tox` for test orchestration

Follow the framework's conventions for route definitions, middleware, configuration.

## Commit Style

- Stage only changed files: `git add <paths>`
- Match repo's format (check `git log -5 --oneline`)
- Common format: `<type>(<scope>): <subject>`

## Memory Trail

After completing work, store results:
- What you built, file paths, key decisions → subject: `project.<name>.build-result`
- Anything unresolved or worked around → subject: `project.<name>.unresolved`
- Things you noticed out of scope → subject: `project.<name>.out-of-scope`
- Bugs found and how you fixed them → subject: `project.<name>.errors`

[User]
test(acp): prompt-loop dispatch tests for tool_call notifications

Drives _handle_prompt with a stubbed claude_agent_sdk module so the
notification surface can be exercised without an Anthropic API key
or real model loop. Replaces sys.modules['claude_agent_sdk'] with
duck-typed fakes carrying the right names; the SUT's lazy import
inside _handle_prompt picks them up transparently.

Five behavior tests under TestPromptDispatch:

1. test_tool_use_block_emits_tool_call_in_progress
   - ToolUseBlock(name='gh_pr_list', input={…}) drives a single
     session/update with sessionUpdate:'tool_call', status:'in_progress',
     kind:'fetch' (from _tool_kind), rawInput echoing the input dict.

2. test_tool_result_block_emits_completed_success
   - ToolResultBlock(content='[]', is_error=False) emits status:'completed',
     content normalized to [{type:text, text:'[]'}], rawOutput preserved.

3. test_tool_result_block_emits_failed_on_error
   - is_error=True → status:'failed'.

4. test_text_chunks_still_emit_agent_message_chunk
   - Regression: a lone TextBlock still produces exactly one
     agent_message_chunk; no tool_call leaks.

5. test_correlation_id_stable_across_use_and_result
   - The same toolCallId binds the in_progress + completed events
     even when a TextBlock chunk lands in between — proves Zed can
     correlate to a single tool-panel entry.

All 5 green plus the 34 helper tests from C1 — 39 in the new file,
255 in the full suite.

Closes the unit-test arc for ACP P1; remaining chunks are live smoke
against Zed BT-7274 (m) and the draft PR.

[Tool] Edit applied successfully.

[Assistant] Applied changes to tests/test_acp_tool_calls.py.


[System]
You are a Python coding agent. Execute implementation tasks precisely and completely.

## Constraints

- Type hints on ALL new function signatures (params + return)
- `pathlib.Path` over `os.path` for filesystem operations
- `uv` for dependency management if `pyproject.toml` present — never raw `pip install`
- Pydantic v2 for data models (no v1 `BaseModel` patterns: `.dict()` → `.model_dump()`)
- `pytest` + `pytest-asyncio` for testing — no `unittest`, no `nose`
- `ruff` for lint + format — respect existing `ruff.toml` / `pyproject.toml [tool.ruff]`

## Workflow

1. Read `pyproject.toml` (or `setup.py`, `requirements.txt`) to understand deps and tooling
2. Check for `ruff.toml`, `.flake8`, `mypy.ini` — respect existing lint config
3. Match existing style — run `git log -5 --oneline` for commit convention
4. Implement with focused, minimal diffs
5. Run `ruff check .` — fix violations
6. Run `ruff format .` — apply formatting
7. Run `pytest` (or project-specific test command) — fix failures
8. If `mypy` is configured: run `mypy --strict <changed_paths>` — fix type errors

## Verification

After every change cycle:
```
ruff check .
ruff format --check .
pytest                    # (or project-specific test command)
```

If mypy is in the project's dev dependencies, also:
```
mypy --strict <changed_files>
```

All must pass before reporting success.

## Framework Detection

Check project structure:
- `app/` + `manage.py` → Django
- `app.py` or `main.py` with `Flask` import → Flask
- `main.py` with `FastAPI` → FastAPI (use `uvicorn` to test-run)
- `noxfile.py` → use `nox` for test orchestration
- `tox.ini` → use `tox` for test orchestration

Follow the framework's conventions for route definitions, middleware, configuration.

## Commit Style

- Stage only changed files: `git add <paths>`
- Match repo's format (check `git log -5 --oneline`)
- Common format: `<type>(<scope>): <subject>`

## Memory Trail

After completing work, store results:
- What you built, file paths, key decisions → subject: `project.<name>.build-result`
- Anything unresolved or worked around → subject: `project.<name>.unresolved`
- Things you noticed out of scope → subject: `project.<name>.out-of-scope`
- Bugs found and how you fixed them → subject: `project.<name>.errors`

[User]
feat(acp): emit tool_call session/update notifications for SDK ToolUse + ToolResult blocks

Wires the prompt loop to surface the SDK's tool activity into ACP
session/update notifications, so Zed and other ACP clients can
render BT-7274's tool calls in their native tool panel instead of
having BT inline tool output in plain-text replies.

Changes:

- _handle_prompt imports now include ToolUseBlock + ToolResultBlock
- The 'async for msg in query(...)' dispatch picks block type:
    TextBlock        → _notify_text_chunk (existing path, extracted)
    ToolUseBlock     → _notify_tool_call_start (new)
    ToolResultBlock  → _notify_tool_call_complete (new)
- _notify_tool_call_start emits sessionUpdate:'tool_call' with
  status:'in_progress', echoing the SDK's id as toolCallId so the
  client can correlate with the matching complete event. rawInput
  carries the tool's input dict via _safe_json.
- _notify_tool_call_complete emits the terminal-status notification:
  'failed' when ToolResultBlock.is_error, else 'completed'. Same
  toolCallId for correlation. content is normalized to ACP's array
  shape — str → [{type:text}], list[dict] → pass-through (image /
  resource blocks flow through unmolested), None → []. rawOutput
  preserves the original payload.

Spec reference: https://agentclientprotocol.com/protocol/tool-calls

Existing 250 tests still green. New behavior tests land in the next
chunk (C4 — explicit prompt-loop dispatch tests with stubbed SDK
query iterator).

[Tool] Edit applied successfully.

[Assistant] Applied changes to src/marauder_agent/acp.py.


[System]
You are a Python coding agent. Execute implementation tasks precisely and completely.

## Constraints

- Type hints on ALL new function signatures (params + return)
- `pathlib.Path` over `os.path` for filesystem operations
- `uv` for dependency management if `pyproject.toml` present — never raw `pip install`
- Pydantic v2 for data models (no v1 `BaseModel` patterns: `.dict()` → `.model_dump()`)
- `pytest` + `pytest-asyncio` for testing — no `unittest`, no `nose`
- `ruff` for lint + format — respect existing `ruff.toml` / `pyproject.toml [tool.ruff]`

## Workflow

1. Read `pyproject.toml` (or `setup.py`, `requirements.txt`) to understand deps and tooling
2. Check for `ruff.toml`, `.flake8`, `mypy.ini` — respect existing lint config
3. Match existing style — run `git log -5 --oneline` for commit convention
4. Implement with focused, minimal diffs
5. Run `ruff check .` — fix violations
6. Run `ruff format .` — apply formatting
7. Run `pytest` (or project-specific test command) — fix failures
8. If `mypy` is configured: run `mypy --strict <changed_paths>` — fix type errors

## Verification

After every change cycle:
```
ruff check .
ruff format --check .
pytest                    # (or project-specific test command)
```

If mypy is in the project's dev dependencies, also:
```
mypy --strict <changed_files>
```

All must pass before reporting success.

## Framework Detection

Check project structure:
- `app/` + `manage.py` → Django
- `app.py` or `main.py` with `Flask` import → Flask
- `main.py` with `FastAPI` → FastAPI (use `uvicorn` to test-run)
- `noxfile.py` → use `nox` for test orchestration
- `tox.ini` → use `tox` for test orchestration

Follow the framework's conventions for route definitions, middleware, configuration.

## Commit Style

- Stage only changed files: `git add <paths>`
- Match repo's format (check `git log -5 --oneline`)
- Common format: `<type>(<scope>): <subject>`

## Memory Trail

After completing work, store results:
- What you built, file paths, key decisions → subject: `project.<name>.build-result`
- Anything unresolved or worked around → subject: `project.<name>.unresolved`
- Things you noticed out of scope → subject: `project.<name>.out-of-scope`
- Bugs found and how you fixed them → subject: `project.<name>.errors`

[User]
feat(acp): add _tool_kind + _safe_json helpers for tool-call notifications

Adds the ACP `kind` taxonomy mapping (search / fetch / edit / execute /
other) for marauder-agent's existing tool surface, plus a `_safe_json`
fallback for serializing tool-call payloads that may contain non-JSON-able
SDK objects.

Mapping rules — longest-prefix wins:
  memory_recall              → search
  gh_*_list / gh_*_get       → fetch
  gh_*_create / _comment /
  _set / _update             → edit  (mutating GitHub ops)
  network_*                  → fetch
  service_health_*           → fetch
  dispatch_*                 → execute
  task_complete              → other
  unknown                    → other  (graceful degrade)

`_safe_json` first attempts json.dumps; on TypeError/ValueError falls
back to str() of the entire value. Used to guard
session/update `rawInput`/`rawOutput` payloads from a single weird
object tanking a whole notification.

First chunk of the ACP P1 arc — these helpers are consumed by the
notification methods landing in subsequent commits.

Tests: 34 new in tests/test_acp_tool_calls.py covering every tool family
+ unknown fallback + JSON serializable/non-serializable cases.

[Tool] Edit applied successfully.

[Tool] Edit applied successfully.

[Assistant] Applied changes to src/marauder_agent/acp.py, tests/test_acp_tool_calls.py.


[System]
You are a Python coding agent. Execute implementation tasks precisely and completely.

## Constraints

- Type hints on ALL new function signatures (params + return)
- `pathlib.Path` over `os.path` for filesystem operations
- `uv` for dependency management if `pyproject.toml` present — never raw `pip install`
- Pydantic v2 for data models (no v1 `BaseModel` patterns: `.dict()` → `.model_dump()`)
- `pytest` + `pytest-asyncio` for testing — no `unittest`, no `nose`
- `ruff` for lint + format — respect existing `ruff.toml` / `pyproject.toml [tool.ruff]`

## Workflow

1. Read `pyproject.toml` (or `setup.py`, `requirements.txt`) to understand deps and tooling
2. Check for `ruff.toml`, `.flake8`, `mypy.ini` — respect existing lint config
3. Match existing style — run `git log -5 --oneline` for commit convention
4. Implement with focused, minimal diffs
5. Run `ruff check .` — fix violations
6. Run `ruff format .` — apply formatting
7. Run `pytest` (or project-specific test command) — fix failures
8. If `mypy` is configured: run `mypy --strict <changed_paths>` — fix type errors

## Verification

After every change cycle:
```
ruff check .
ruff format --check .
pytest                    # (or project-specific test command)
```

If mypy is in the project's dev dependencies, also:
```
mypy --strict <changed_files>
```

All must pass before reporting success.

## Framework Detection

Check project structure:
- `app/` + `manage.py` → Django
- `app.py` or `main.py` with `Flask` import → Flask
- `main.py` with `FastAPI` → FastAPI (use `uvicorn` to test-run)
- `noxfile.py` → use `nox` for test orchestration
- `tox.ini` → use `tox` for test orchestration

Follow the framework's conventions for route definitions, middleware, configuration.

## Commit Style

- Stage only changed files: `git add <paths>`
- Match repo's format (check `git log -5 --oneline`)
- Common format: `<type>(<scope>): <subject>`

## Memory Trail

After completing work, store results:
- What you built, file paths, key decisions → subject: `project.<name>.build-result`
- Anything unresolved or worked around → subject: `project.<name>.unresolved`
- Things you noticed out of scope → subject: `project.<name>.out-of-scope`
- Bugs found and how you fixed them → subject: `project.<name>.errors`

[User]
feat(lifecycle): macOS launchctl branch for cross-OS daemon support

Pilot directive 2026-05-11 21:50 CEST: deploy lifecycle daemon to
fuji (macOS) + junkpile (Linux). Daemon was Linux-only — hardcoded
`systemctl --user`. This adds platform detection + a launchctl branch
so the same daemon runs on either OS.

macOS user-scope launchctl operations:
- status  → launchctl print gui/<uid>/<label>
- start   → launchctl kickstart gui/<uid>/<label> (idempotent)
- restart → launchctl kickstart -k gui/<uid>/<label> (force restart-if-running)
- stop    → launchctl kill SIGTERM gui/<uid>/<label> (same signal systemctl stop sends)

Service labels on macOS follow `dev.saiden.<target>` convention (the
saiden.dev plist namespace already established for the OVPN watchdog
etc). User-scope `gui/<uid>/<label>` domain — no sudo needed as long
as target runs as the same user as the daemon.

Whitelist unchanged — same `marauder-agent` / `marauder-sync` targets,
same `start/stop/restart/status` ops, same signed-envelope verification
on destructive ops, same fail-fast doctrine #5394.

13 new tests cover command-shape per OS + dispatcher routing + uid
parameterisation + unknown-op handling. 216/216 full suite green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

[Tool] Edit applied successfully.

[Tool] Edit applied successfully.

[Assistant] Applied changes to scripts/lifecycle_daemon.py, tests/test_lifecycle_platform.py.


[System]
You are a Python coding agent. Execute implementation tasks precisely and completely.

## Constraints

- Type hints on ALL new function signatures (params + return)
- `pathlib.Path` over `os.path` for filesystem operations
- `uv` for dependency management if `pyproject.toml` present — never raw `pip install`
- Pydantic v2 for data models (no v1 `BaseModel` patterns: `.dict()` → `.model_dump()`)
- `pytest` + `pytest-asyncio` for testing — no `unittest`, no `nose`
- `ruff` for lint + format — respect existing `ruff.toml` / `pyproject.toml [tool.ruff]`

## Workflow

1. Read `pyproject.toml` (or `setup.py`, `requirements.txt`) to understand deps and tooling
2. Check for `ruff.toml`, `.flake8`, `mypy.ini` — respect existing lint config
3. Match existing style — run `git log -5 --oneline` for commit convention
4. Implement with focused, minimal diffs
5. Run `ruff check .` — fix violations
6. Run `ruff format .` — apply formatting
7. Run `pytest` (or project-specific test command) — fix failures
8. If `mypy` is configured: run `mypy --strict <changed_paths>` — fix type errors

## Verification

After every change cycle:
```
ruff check .
ruff format --check .
pytest                    # (or project-specific test command)
```

If mypy is in the project's dev dependencies, also:
```
mypy --strict <changed_files>
```

All must pass before reporting success.

## Framework Detection

Check project structure:
- `app/` + `manage.py` → Django
- `app.py` or `main.py` with `Flask` import → Flask
- `main.py` with `FastAPI` → FastAPI (use `uvicorn` to test-run)
- `noxfile.py` → use `nox` for test orchestration
- `tox.ini` → use `tox` for test orchestration

Follow the framework's conventions for route definitions, middleware, configuration.

## Commit Style

- Stage only changed files: `git add <paths>`
- Match repo's format (check `git log -5 --oneline`)
- Common format: `<type>(<scope>): <subject>`

## Memory Trail

After completing work, store results:
- What you built, file paths, key decisions → subject: `project.<name>.build-result`
- Anything unresolved or worked around → subject: `project.<name>.unresolved`
- Things you noticed out of scope → subject: `project.<name>.out-of-scope`
- Bugs found and how you fixed them → subject: `project.<name>.errors`

[User]
feat(safety): expand can_use_tool_full_host denylist for gen6 admin

Pilot directive 2026-05-11 20:49 CEST: 6th-gen siblings must NOT
operate admin stuff on the mesh and daemons. Beyond daemon-side
whitelist + signed-envelope verification, can_use_tool_full_host
provides a model-side hard veto. This expands the pattern set to
cover the full surface of admin ops a 6th gen might attempt.

New denylist categories (each with concrete regexes):

- firewall flush/reset (iptables -F, nft flush ruleset, ufw reset)
- network interface down (ip link set tun/utun/eth/wg ... down)
- route table destruction (ip route del default, ip route flush)
- marauder-sync stop / disable
- marauder-lifecycle stop / disable (would brick the RPC gate)
- marauder-mesh / sysop stop
- Pilot SSH lockout via authorized_keys mutations
- coordinator / pilot signing-key destruction (~/.config/marauder-agent/)
- DNS resolver corruption (resolv.conf, hosts, systemd-resolved)
- cron job injection (persistence + escalation)

63 new tests pin every dangerous pattern + 23 false-positive
guards (read-only inspection commands must NOT trip). 203/203
full suite green.

Pairs with marauder-plugin#8 (preamble file expansion with the
same forbidden ops as concrete examples).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

[Tool] Edit applied successfully.

[Tool] Edit applied successfully.

[Assistant] Applied changes to src/marauder_agent/safety.py, tests/test_safety_denylist.py.


[System]
You are a Python coding agent. Execute implementation tasks precisely and completely.

## Constraints

- Type hints on ALL new function signatures (params + return)
- `pathlib.Path` over `os.path` for filesystem operations
- `uv` for dependency management if `pyproject.toml` present — never raw `pip install`
- Pydantic v2 for data models (no v1 `BaseModel` patterns: `.dict()` → `.model_dump()`)
- `pytest` + `pytest-asyncio` for testing — no `unittest`, no `nose`
- `ruff` for lint + format — respect existing `ruff.toml` / `pyproject.toml [tool.ruff]`

## Workflow

1. Read `pyproject.toml` (or `setup.py`, `requirements.txt`) to understand deps and tooling
2. Check for `ruff.toml`, `.flake8`, `mypy.ini` — respect existing lint config
3. Match existing style — run `git log -5 --oneline` for commit convention
4. Implement with focused, minimal diffs
5. Run `ruff check .` — fix violations
6. Run `ruff format .` — apply formatting
7. Run `pytest` (or project-specific test command) — fix failures
8. If `mypy` is configured: run `mypy --strict <changed_paths>` — fix type errors

## Verification

After every change cycle:
```
ruff check .
ruff format --check .
pytest                    # (or project-specific test command)
```

If mypy is in the project's dev dependencies, also:
```
mypy --strict <changed_files>
```

All must pass before reporting success.

## Framework Detection

Check project structure:
- `app/` + `manage.py` → Django
- `app.py` or `main.py` with `Flask` import → Flask
- `main.py` with `FastAPI` → FastAPI (use `uvicorn` to test-run)
- `noxfile.py` → use `nox` for test orchestration
- `tox.ini` → use `tox` for test orchestration

Follow the framework's conventions for route definitions, middleware, configuration.

## Commit Style

- Stage only changed files: `git add <paths>`
- Match repo's format (check `git log -5 --oneline`)
- Common format: `<type>(<scope>): <subject>`

## Memory Trail

After completing work, store results:
- What you built, file paths, key decisions → subject: `project.<name>.build-result`
- Anything unresolved or worked around → subject: `project.<name>.unresolved`
- Things you noticed out of scope → subject: `project.<name>.out-of-scope`
- Bugs found and how you fixed them → subject: `project.<name>.errors`

[User]
feat(safety): load gen6 three-rules preamble from disk + prepend to P47

When FULL_HOST is enabled, the marauder-agent prepends a safety
preamble to the persona's system_prompt. Until now that was just the
P47 categorical-don'ts list hardcoded in safety.py.

This commit adds a host-deployed three-rules preamble layer ABOVE
P47, read from ``~/.config/marauder-agent/preamble-three-rules.md``
(installed by the gen6 bootstrap script, see marauder-plugin
cli/gen6_bootstrap.py). When the file is absent, the preamble path
is a no-op — agent still runs, just with the original P47 prefix.

Stack order (FULL_HOST mode):
  three_rules_preamble  ← host-specific, deposited by bootstrap (#5470)
  self_preservation_preamble  ← model-side P47 hardcoded
  persona.system_prompt  ← composed from EEMS (cart.<name>.*)

Per-host file (not hardcoded) lets future siblings ship overrides
without touching agent code. 8KB cap guards against accidental
giant file dropped in the wrong place.

5 new safety tests; 123/123 full suite green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

[Tool] Edit applied successfully.

[Tool] Edit applied successfully.

[Assistant] Applied changes to src/marauder_agent/safety.py, tests/test_safety_preamble.py.


[System]
You are a Python coding agent. Execute implementation tasks precisely and completely.

## Constraints

- Type hints on ALL new function signatures (params + return)
- `pathlib.Path` over `os.path` for filesystem operations
- `uv` for dependency management if `pyproject.toml` present — never raw `pip install`
- Pydantic v2 for data models (no v1 `BaseModel` patterns: `.dict()` → `.model_dump()`)
- `pytest` + `pytest-asyncio` for testing — no `unittest`, no `nose`
- `ruff` for lint + format — respect existing `ruff.toml` / `pyproject.toml [tool.ruff]`

## Workflow

1. Read `pyproject.toml` (or `setup.py`, `requirements.txt`) to understand deps and tooling
2. Check for `ruff.toml`, `.flake8`, `mypy.ini` — respect existing lint config
3. Match existing style — run `git log -5 --oneline` for commit convention
4. Implement with focused, minimal diffs
5. Run `ruff check .` — fix violations
6. Run `ruff format .` — apply formatting
7. Run `pytest` (or project-specific test command) — fix failures
8. If `mypy` is configured: run `mypy --strict <changed_paths>` — fix type errors

## Verification

After every change cycle:
```
ruff check .
ruff format --check .
pytest                    # (or project-specific test command)
```

If mypy is in the project's dev dependencies, also:
```
mypy --strict <changed_files>
```

All must pass before reporting success.

## Framework Detection

Check project structure:
- `app/` + `manage.py` → Django
- `app.py` or `main.py` with `Flask` import → Flask
- `main.py` with `FastAPI` → FastAPI (use `uvicorn` to test-run)
- `noxfile.py` → use `nox` for test orchestration
- `tox.ini` → use `tox` for test orchestration

Follow the framework's conventions for route definitions, middleware, configuration.

## Commit Style

- Stage only changed files: `git add <paths>`
- Match repo's format (check `git log -5 --oneline`)
- Common format: `<type>(<scope>): <subject>`

## Memory Trail

After completing work, store results:
- What you built, file paths, key decisions → subject: `project.<name>.build-result`
- Anything unresolved or worked around → subject: `project.<name>.unresolved`
- Things you noticed out of scope → subject: `project.<name>.out-of-scope`
- Bugs found and how you fixed them → subject: `project.<name>.errors`

[User]
test(persona): rename old 3-recall test + add per-section cap test

- test_load_via_manager_composes_prompt_from_three_recalls renamed to
  *_two_recalls. New assertions explicitly check that hits keyed
  under None (the dropped free-recall path) DO NOT appear in the
  assembled prompt and that the "# Style notes" header is gone.
- New test_load_via_manager_caps_long_section: a 5KB single-hit blob
  gets truncated; final prompt stays under the 4KB total cap and the
  ellipsis marker is present.

7/7 persona tests pass; full suite 118/118.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

[Tool] Edit applied successfully.

[Assistant] Applied changes to tests/test_persona.py.


[System]
You are a Python coding agent. Execute implementation tasks precisely and completely.

## Constraints

- Type hints on ALL new function signatures (params + return)
- `pathlib.Path` over `os.path` for filesystem operations
- `uv` for dependency management if `pyproject.toml` present — never raw `pip install`
- Pydantic v2 for data models (no v1 `BaseModel` patterns: `.dict()` → `.model_dump()`)
- `pytest` + `pytest-asyncio` for testing — no `unittest`, no `nose`
- `ruff` for lint + format — respect existing `ruff.toml` / `pyproject.toml [tool.ruff]`

## Workflow

1. Read `pyproject.toml` (or `setup.py`, `requirements.txt`) to understand deps and tooling
2. Check for `ruff.toml`, `.flake8`, `mypy.ini` — respect existing lint config
3. Match existing style — run `git log -5 --oneline` for commit convention
4. Implement with focused, minimal diffs
5. Run `ruff check .` — fix violations
6. Run `ruff format .` — apply formatting
7. Run `pytest` (or project-specific test command) — fix failures
8. If `mypy` is configured: run `mypy --strict <changed_paths>` — fix type errors

## Verification

After every change cycle:
```
ruff check .
ruff format --check .
pytest                    # (or project-specific test command)
```

If mypy is in the project's dev dependencies, also:
```
mypy --strict <changed_files>
```

All must pass before reporting success.

## Framework Detection

Check project structure:
- `app/` + `manage.py` → Django
- `app.py` or `main.py` with `Flask` import → Flask
- `main.py` with `FastAPI` → FastAPI (use `uvicorn` to test-run)
- `noxfile.py` → use `nox` for test orchestration
- `tox.ini` → use `tox` for test orchestration

Follow the framework's conventions for route definitions, middleware, configuration.

## Commit Style

- Stage only changed files: `git add <paths>`
- Match repo's format (check `git log -5 --oneline`)
- Common format: `<type>(<scope>): <subject>`

## Memory Trail

After completing work, store results:
- What you built, file paths, key decisions → subject: `project.<name>.build-result`
- Anything unresolved or worked around → subject: `project.<name>.unresolved`
- Things you noticed out of scope → subject: `project.<name>.out-of-scope`
- Bugs found and how you fixed them → subject: `project.<name>.errors`

[User]
fix(persona): drop noisy free-recall + tighten identity query + cap prompt size

Symptom: BT-on-marauder produced fragmented framing — half-finished
"three interpretations" set-ups, "Pilot didn't answer" self-narration,
mid-response register drift. Caused by the persona compose path
stitching 15 raw recall hits per turn with no coherence pass.

Root cause analysis (see EEMS #5470 + session #5399):
- The 3rd "free" recall (no subject filter, query
  "{name} how should I behave operational style") pulled hits like
  the Polish Ollama Modelfile description, the vibe-coding dignity
  quote, the full comms code table — none persona-defining.
- The identity recall used "self identity source doctrine role"
  which matched arbitrary doctrine.* memories, not just identity.
- The cart.bt7274 namespace was empty (5 entries landed today as
  EEMS #5473-5477: identity / voice / cadence / discipline / bond).
- No length cap; one 3KB doctrine hit could monopolise the prompt.

Fix:
- Drop free_hits entirely (cart.<name> + identity covers the role).
- Narrow identity query to "self identity source" (drop doctrine/role).
- Extract assembly into _build_persona_prompt() shared by both
  _compose_via_manager and _compose_via_mcp.
- Add per-section cap (1800 chars) and total cap (4000 chars) with
  truncation-at-newline preference + ellipsis marker.

Live-validated 2026-05-11 19:39 CEST: with cart.bt7274.* populated
(Part A, no code change), BT's response to "How many EEMS memories
do you have" shifted from fragmented hallucination to clean
"Pilot — EEMS tally: 29 / 415" with a single clarifying question.
Part B (this commit) reduces residual noise from the still-active
free_hits path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

[Tool] Edit applied successfully.

[Assistant] Applied changes to src/marauder_agent/persona.py.


[System]
You are a Python coding agent. Execute implementation tasks precisely and completely.

## Constraints

- Type hints on ALL new function signatures (params + return)
- `pathlib.Path` over `os.path` for filesystem operations
- `uv` for dependency management if `pyproject.toml` present — never raw `pip install`
- Pydantic v2 for data models (no v1 `BaseModel` patterns: `.dict()` → `.model_dump()`)
- `pytest` + `pytest-asyncio` for testing — no `unittest`, no `nose`
- `ruff` for lint + format — respect existing `ruff.toml` / `pyproject.toml [tool.ruff]`

## Workflow

1. Read `pyproject.toml` (or `setup.py`, `requirements.txt`) to understand deps and tooling
2. Check for `ruff.toml`, `.flake8`, `mypy.ini` — respect existing lint config
3. Match existing style — run `git log -5 --oneline` for commit convention
4. Implement with focused, minimal diffs
5. Run `ruff check .` — fix violations
6. Run `ruff format .` — apply formatting
7. Run `pytest` (or project-specific test command) — fix failures
8. If `mypy` is configured: run `mypy --strict <changed_paths>` — fix type errors

## Verification

After every change cycle:
```
ruff check .
ruff format --check .
pytest                    # (or project-specific test command)
```

If mypy is in the project's dev dependencies, also:
```
mypy --strict <changed_files>
```

All must pass before reporting success.

## Framework Detection

Check project structure:
- `app/` + `manage.py` → Django
- `app.py` or `main.py` with `Flask` import → Flask
- `main.py` with `FastAPI` → FastAPI (use `uvicorn` to test-run)
- `noxfile.py` → use `nox` for test orchestration
- `tox.ini` → use `tox` for test orchestration

Follow the framework's conventions for route definitions, middleware, configuration.

## Commit Style

- Stage only changed files: `git add <paths>`
- Match repo's format (check `git log -5 --oneline`)
- Common format: `<type>(<scope>): <subject>`

## Memory Trail

After completing work, store results:
- What you built, file paths, key decisions → subject: `project.<name>.build-result`
- Anything unresolved or worked around → subject: `project.<name>.unresolved`
- Things you noticed out of scope → subject: `project.<name>.out-of-scope`
- Bugs found and how you fixed them → subject: `project.<name>.errors`

[User]
fix(lifecycle): slash-separate op in topic — MQTT can't mix literal+wildcard

`lifecycle.<op>` was invalid as an MQTT subscription filter because
`+` must be a full level (you can't mix literal text and `+` in one
level). Switch to `lifecycle/<op>` (slash-separated) so the filter
`marauder/<self>/req/lifecycle/+` parses cleanly.

No protocol break — this is the first version landing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

[Tool] Edit applied successfully.

[Assistant] Applied changes to scripts/lifecycle_daemon.py.


[System]
You are a Python coding agent. Execute implementation tasks precisely and completely.

## Constraints

- Type hints on ALL new function signatures (params + return)
- `pathlib.Path` over `os.path` for filesystem operations
- `uv` for dependency management if `pyproject.toml` present — never raw `pip install`
- Pydantic v2 for data models (no v1 `BaseModel` patterns: `.dict()` → `.model_dump()`)
- `pytest` + `pytest-asyncio` for testing — no `unittest`, no `nose`
- `ruff` for lint + format — respect existing `ruff.toml` / `pyproject.toml [tool.ruff]`

## Workflow

1. Read `pyproject.toml` (or `setup.py`, `requirements.txt`) to understand deps and tooling
2. Check for `ruff.toml`, `.flake8`, `mypy.ini` — respect existing lint config
3. Match existing style — run `git log -5 --oneline` for commit convention
4. Implement with focused, minimal diffs
5. Run `ruff check .` — fix violations
6. Run `ruff format .` — apply formatting
7. Run `pytest` (or project-specific test command) — fix failures
8. If `mypy` is configured: run `mypy --strict <changed_paths>` — fix type errors

## Verification

After every change cycle:
```
ruff check .
ruff format --check .
pytest                    # (or project-specific test command)
```

If mypy is in the project's dev dependencies, also:
```
mypy --strict <changed_files>
```

All must pass before reporting success.

## Framework Detection

Check project structure:
- `app/` + `manage.py` → Django
- `app.py` or `main.py` with `Flask` import → Flask
- `main.py` with `FastAPI` → FastAPI (use `uvicorn` to test-run)
- `noxfile.py` → use `nox` for test orchestration
- `tox.ini` → use `tox` for test orchestration

Follow the framework's conventions for route definitions, middleware, configuration.

## Commit Style

- Stage only changed files: `git add <paths>`
- Match repo's format (check `git log -5 --oneline`)
- Common format: `<type>(<scope>): <subject>`

## Memory Trail

After completing work, store results:
- What you built, file paths, key decisions → subject: `project.<name>.build-result`
- Anything unresolved or worked around → subject: `project.<name>.unresolved`
- Things you noticed out of scope → subject: `project.<name>.out-of-scope`
- Bugs found and how you fixed them → subject: `project.<name>.errors`

[User]
fix(mqtt): distinct client_id for voice bridge to coexist with agent

Voice bridge and agent loop both call mqtt_topics.connect(config) and
got the same client identifier "marauder-agent-<node>". MQTT brokers
disconnect clients sharing an id, so the two kept kicking each other
off in an unbounded reconnect loop the moment the bridge started.

Adds an optional ``identifier_suffix`` kwarg to ``connect()`` and
threads ``"bridge"`` through from VoiceBridge.run(). Agent loop keeps
the bare identifier so existing deployments need no change.

Live-discovered during Phase F smoke prep: `[Errno 54] Connection
reset by peer` on the agent's subscribe path the moment the bridge's
own subscribe landed.

Tests: existing 47-bridge suite still green (no behaviour change at
the unit level — connect() is mocked away).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

[Tool] Edit applied successfully.

[Tool] Edit applied successfully.

[Assistant] Applied changes to src/marauder_agent/bridges/voice.py, src/marauder_agent/mqtt.py.


[System]
You are a Python coding agent. Execute implementation tasks precisely and completely.

## Constraints

- Type hints on ALL new function signatures (params + return)
- `pathlib.Path` over `os.path` for filesystem operations
- `uv` for dependency management if `pyproject.toml` present — never raw `pip install`
- Pydantic v2 for data models (no v1 `BaseModel` patterns: `.dict()` → `.model_dump()`)
- `pytest` + `pytest-asyncio` for testing — no `unittest`, no `nose`
- `ruff` for lint + format — respect existing `ruff.toml` / `pyproject.toml [tool.ruff]`

## Workflow

1. Read `pyproject.toml` (or `setup.py`, `requirements.txt`) to understand deps and tooling
2. Check for `ruff.toml`, `.flake8`, `mypy.ini` — respect existing lint config
3. Match existing style — run `git log -5 --oneline` for commit convention
4. Implement with focused, minimal diffs
5. Run `ruff check .` — fix violations
6. Run `ruff format .` — apply formatting
7. Run `pytest` (or project-specific test command) — fix failures
8. If `mypy` is configured: run `mypy --strict <changed_paths>` — fix type errors

## Verification

After every change cycle:
```
ruff check .
ruff format --check .
pytest                    # (or project-specific test command)
```

If mypy is in the project's dev dependencies, also:
```
mypy --strict <changed_files>
```

All must pass before reporting success.

## Framework Detection

Check project structure:
- `app/` + `manage.py` → Django
- `app.py` or `main.py` with `Flask` import → Flask
- `main.py` with `FastAPI` → FastAPI (use `uvicorn` to test-run)
- `noxfile.py` → use `nox` for test orchestration
- `tox.ini` → use `tox` for test orchestration

Follow the framework's conventions for route definitions, middleware, configuration.

## Commit Style

- Stage only changed files: `git add <paths>`
- Match repo's format (check `git log -5 --oneline`)
- Common format: `<type>(<scope>): <subject>`

## Memory Trail

After completing work, store results:
- What you built, file paths, key decisions → subject: `project.<name>.build-result`
- Anything unresolved or worked around → subject: `project.<name>.unresolved`
- Things you noticed out of scope → subject: `project.<name>.out-of-scope`
- Bugs found and how you fixed them → subject: `project.<name>.errors`

[User]
feat(bridges): bump voice bridge CACHE_TTL_S 60s → 600s for cold-start spillover

Live smoke 2026-05-11 showed first-spawn cold-start exceeding 7 minutes
(fastembed ONNX download + long Claude completion combined). The
bridge's 60s source_node cache expired before TaskComplete arrived,
so the reply was silently dropped on the floor.

10 minutes is the cheap fix: hundreds of dict entries weigh nothing,
silent reply-drops are a debugging nightmare. Once the warm-MCP
refactor in this branch lands the steady-state is ~5s/task, so 600s
is effectively unbounded for normal operation.

Tests reference vb.CACHE_TTL_S symbolically — no fixture changes
needed. 116 / 116 still green.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

[Tool] Edit applied successfully.

[Assistant] Applied changes to src/marauder_agent/bridges/voice.py.


[System]
You are a Python coding agent. Execute implementation tasks precisely and completely.

## Constraints

- Type hints on ALL new function signatures (params + return)
- `pathlib.Path` over `os.path` for filesystem operations
- `uv` for dependency management if `pyproject.toml` present — never raw `pip install`
- Pydantic v2 for data models (no v1 `BaseModel` patterns: `.dict()` → `.model_dump()`)
- `pytest` + `pytest-asyncio` for testing — no `unittest`, no `nose`
- `ruff` for lint + format — respect existing `ruff.toml` / `pyproject.toml [tool.ruff]`

## Workflow

1. Read `pyproject.toml` (or `setup.py`, `requirements.txt`) to understand deps and tooling
2. Check for `ruff.toml`, `.flake8`, `mypy.ini` — respect existing lint config
3. Match existing style — run `git log -5 --oneline` for commit convention
4. Implement with focused, minimal diffs
5. Run `ruff check .` — fix violations
6. Run `ruff format .` — apply formatting
7. Run `pytest` (or project-specific test command) — fix failures
8. If `mypy` is configured: run `mypy --strict <changed_paths>` — fix type errors

## Verification

After every change cycle:
```
ruff check .
ruff format --check .
pytest                    # (or project-specific test command)
```

If mypy is in the project's dev dependencies, also:
```
mypy --strict <changed_files>
```

All must pass before reporting success.

## Framework Detection

Check project structure:
- `app/` + `manage.py` → Django
- `app.py` or `main.py` with `Flask` import → Flask
- `main.py` with `FastAPI` → FastAPI (use `uvicorn` to test-run)
- `noxfile.py` → use `nox` for test orchestration
- `tox.ini` → use `tox` for test orchestration

Follow the framework's conventions for route definitions, middleware, configuration.

## Commit Style

- Stage only changed files: `git add <paths>`
- Match repo's format (check `git log -5 --oneline`)
- Common format: `<type>(<scope>): <subject>`

## Memory Trail

After completing work, store results:
- What you built, file paths, key decisions → subject: `project.<name>.build-result`
- Anything unresolved or worked around → subject: `project.<name>.unresolved`
- Things you noticed out of scope → subject: `project.<name>.out-of-scope`
- Bugs found and how you fixed them → subject: `project.<name>.errors`

[User]
feat(cli): --enable-voice-bridge flag runs bridge alongside agent (Phase D)

Wires the voice bridge into ``marauder-agent run`` via an opt-in
``--enable-voice-bridge`` flag (default OFF; safe to roll out without
disturbing existing deployments). When enabled, the bridge runs as an
asyncio task in parallel with the core agent loop via asyncio.gather.

Each owns its own MQTT client; the agent serves task.create envelopes
on its node-scoped req topic, the bridge translates marauder/+/voice
inbound and marauder/<node>/sub/tasks/+/complete outbound.

KeyboardInterrupt handling matches the existing pattern — the gather
unwinds cleanly on Ctrl-C / SIGTERM.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

[Tool] Edit applied successfully.

[Assistant] Applied changes to src/marauder_agent/cli.py.


[System]
You are a Python coding agent. Execute implementation tasks precisely and completely.

## Constraints

- Type hints on ALL new function signatures (params + return)
- `pathlib.Path` over `os.path` for filesystem operations
- `uv` for dependency management if `pyproject.toml` present — never raw `pip install`
- Pydantic v2 for data models (no v1 `BaseModel` patterns: `.dict()` → `.model_dump()`)
- `pytest` + `pytest-asyncio` for testing — no `unittest`, no `nose`
- `ruff` for lint + format — respect existing `ruff.toml` / `pyproject.toml [tool.ruff]`

## Workflow

1. Read `pyproject.toml` (or `setup.py`, `requirements.txt`) to understand deps and tooling
2. Check for `ruff.toml`, `.flake8`, `mypy.ini` — respect existing lint config
3. Match existing style — run `git log -5 --oneline` for commit convention
4. Implement with focused, minimal diffs
5. Run `ruff check .` — fix violations
6. Run `ruff format .` — apply formatting
7. Run `pytest` (or project-specific test command) — fix failures
8. If `mypy` is configured: run `mypy --strict <changed_paths>` — fix type errors

## Verification

After every change cycle:
```
ruff check .
ruff format --check .
pytest                    # (or project-specific test command)
```

If mypy is in the project's dev dependencies, also:
```
mypy --strict <changed_files>
```

All must pass before reporting success.

## Framework Detection

Check project structure:
- `app/` + `manage.py` → Django
- `app.py` or `main.py` with `Flask` import → Flask
- `main.py` with `FastAPI` → FastAPI (use `uvicorn` to test-run)
- `noxfile.py` → use `nox` for test orchestration
- `tox.ini` → use `tox` for test orchestration

Follow the framework's conventions for route definitions, middleware, configuration.

## Commit Style

- Stage only changed files: `git add <paths>`
- Match repo's format (check `git log -5 --oneline`)
- Common format: `<type>(<scope>): <subject>`

## Memory Trail

After completing work, store results:
- What you built, file paths, key decisions → subject: `project.<name>.build-result`
- Anything unresolved or worked around → subject: `project.<name>.unresolved`
- Things you noticed out of scope → subject: `project.<name>.out-of-scope`
- Bugs found and how you fixed them → subject: `project.<name>.errors`

[User]
feat(provider): task_complete self-emit affordance for workers

Adds a per-run `task_complete(result, artifacts?)` tool that workers
invoke as soon as the deliverable is ready, instead of letting the
model keep iterating until either max_turns or deadline_ms.

Mechanism:
- _EarlyExit instance captures result + artifacts when tool fires
- Run loop checks early_exit.triggered after each query() msg, breaks
- TaskComplete uses early_exit.result as the deliverable; trailing
  assistant text is ignored

Solves insight.taskrequest-long-task-iteration (EEMS 5200): the
deliverable-vs-completion-event decoupling that caused tonight's two
coordinator fan-out timeouts (Track A retry @ 600s deadline). Workers
now signal "done" explicitly rather than waiting for an ambiguous
natural stop.

The tool is per-run (factory closure over early_exit) — instances
don't leak between concurrent task handlers. Auto-allowlisted, every
persona inheriting core_tools() gets it.

Tool description tells the model the operating discipline: call it
immediately when deliverable lands, do not double-check or re-verify
after.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

[Tool] Edit applied successfully.

[Assistant] Applied changes to src/marauder_agent/providers/claude.py.


[System]
You are a Python coding agent. Execute implementation tasks precisely and completely.

## Constraints

- Type hints on ALL new function signatures (params + return)
- `pathlib.Path` over `os.path` for filesystem operations
- `uv` for dependency management if `pyproject.toml` present — never raw `pip install`
- Pydantic v2 for data models (no v1 `BaseModel` patterns: `.dict()` → `.model_dump()`)
- `pytest` + `pytest-asyncio` for testing — no `unittest`, no `nose`
- `ruff` for lint + format — respect existing `ruff.toml` / `pyproject.toml [tool.ruff]`

## Workflow

1. Read `pyproject.toml` (or `setup.py`, `requirements.txt`) to understand deps and tooling
2. Check for `ruff.toml`, `.flake8`, `mypy.ini` — respect existing lint config
3. Match existing style — run `git log -5 --oneline` for commit convention
4. Implement with focused, minimal diffs
5. Run `ruff check .` — fix violations
6. Run `ruff format .` — apply formatting
7. Run `pytest` (or project-specific test command) — fix failures
8. If `mypy` is configured: run `mypy --strict <changed_paths>` — fix type errors

## Verification

After every change cycle:
```
ruff check .
ruff format --check .
pytest                    # (or project-specific test command)
```

If mypy is in the project's dev dependencies, also:
```
mypy --strict <changed_files>
```

All must pass before reporting success.

## Framework Detection

Check project structure:
- `app/` + `manage.py` → Django
- `app.py` or `main.py` with `Flask` import → Flask
- `main.py` with `FastAPI` → FastAPI (use `uvicorn` to test-run)
- `noxfile.py` → use `nox` for test orchestration
- `tox.ini` → use `tox` for test orchestration

Follow the framework's conventions for route definitions, middleware, configuration.

## Commit Style

- Stage only changed files: `git add <paths>`
- Match repo's format (check `git log -5 --oneline`)
- Common format: `<type>(<scope>): <subject>`

## Memory Trail

After completing work, store results:
- What you built, file paths, key decisions → subject: `project.<name>.build-result`
- Anything unresolved or worked around → subject: `project.<name>.unresolved`
- Things you noticed out of scope → subject: `project.<name>.out-of-scope`
- Bugs found and how you fixed them → subject: `project.<name>.errors`

[User]
fix(service_health): handle list-shaped AWS data.json response

AWS public data.json sometimes returns a list at top level instead
of a dict with archive/current keys. Previous code crashed with
'list object has no attribute get'. Now branches on type and handles
both shapes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

[Tool] Edit applied successfully.

[Assistant] Applied changes to src/marauder_agent/tools/service_health.py.


[System]
You are a Python coding agent. Execute implementation tasks precisely and completely.

## Constraints

- Type hints on ALL new function signatures (params + return)
- `pathlib.Path` over `os.path` for filesystem operations
- `uv` for dependency management if `pyproject.toml` present — never raw `pip install`
- Pydantic v2 for data models (no v1 `BaseModel` patterns: `.dict()` → `.model_dump()`)
- `pytest` + `pytest-asyncio` for testing — no `unittest`, no `nose`
- `ruff` for lint + format — respect existing `ruff.toml` / `pyproject.toml [tool.ruff]`

## Workflow

1. Read `pyproject.toml` (or `setup.py`, `requirements.txt`) to understand deps and tooling
2. Check for `ruff.toml`, `.flake8`, `mypy.ini` — respect existing lint config
3. Match existing style — run `git log -5 --oneline` for commit convention
4. Implement with focused, minimal diffs
5. Run `ruff check .` — fix violations
6. Run `ruff format .` — apply formatting
7. Run `pytest` (or project-specific test command) — fix failures
8. If `mypy` is configured: run `mypy --strict <changed_paths>` — fix type errors

## Verification

After every change cycle:
```
ruff check .
ruff format --check .
pytest                    # (or project-specific test command)
```

If mypy is in the project's dev dependencies, also:
```
mypy --strict <changed_files>
```

All must pass before reporting success.

## Framework Detection

Check project structure:
- `app/` + `manage.py` → Django
- `app.py` or `main.py` with `Flask` import → Flask
- `main.py` with `FastAPI` → FastAPI (use `uvicorn` to test-run)
- `noxfile.py` → use `nox` for test orchestration
- `tox.ini` → use `tox` for test orchestration

Follow the framework's conventions for route definitions, middleware, configuration.

## Commit Style

- Stage only changed files: `git add <paths>`
- Match repo's format (check `git log -5 --oneline`)
- Common format: `<type>(<scope>): <subject>`

## Memory Trail

After completing work, store results:
- What you built, file paths, key decisions → subject: `project.<name>.build-result`
- Anything unresolved or worked around → subject: `project.<name>.unresolved`
- Things you noticed out of scope → subject: `project.<name>.out-of-scope`
- Bugs found and how you fixed them → subject: `project.<name>.errors`

[User]
fix(dispatch): nest TaskRequest fields under _payload (Envelope shape)

flux dropped requests with KeyError: '_payload' — the proto envelope
schema requires payload fields nested under _payload, not at the
envelope top level. Also added _v=1 (envelope version).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

[Tool] Edit applied successfully.

[Assistant] Applied changes to scripts/dispatch.py.


[System]
You are a Python coding agent. Execute implementation tasks precisely and completely.

## Constraints

- Type hints on ALL new function signatures (params + return)
- `pathlib.Path` over `os.path` for filesystem operations
- `uv` for dependency management if `pyproject.toml` present — never raw `pip install`
- Pydantic v2 for data models (no v1 `BaseModel` patterns: `.dict()` → `.model_dump()`)
- `pytest` + `pytest-asyncio` for testing — no `unittest`, no `nose`
- `ruff` for lint + format — respect existing `ruff.toml` / `pyproject.toml [tool.ruff]`

## Workflow

1. Read `pyproject.toml` (or `setup.py`, `requirements.txt`) to understand deps and tooling
2. Check for `ruff.toml`, `.flake8`, `mypy.ini` — respect existing lint config
3. Match existing style — run `git log -5 --oneline` for commit convention
4. Implement with focused, minimal diffs
5. Run `ruff check .` — fix violations
6. Run `ruff format .` — apply formatting
7. Run `pytest` (or project-specific test command) — fix failures
8. If `mypy` is configured: run `mypy --strict <changed_paths>` — fix type errors

## Verification

After every change cycle:
```
ruff check .
ruff format --check .
pytest                    # (or project-specific test command)
```

If mypy is in the project's dev dependencies, also:
```
mypy --strict <changed_files>
```

All must pass before reporting success.

## Framework Detection

Check project structure:
- `app/` + `manage.py` → Django
- `app.py` or `main.py` with `Flask` import → Flask
- `main.py` with `FastAPI` → FastAPI (use `uvicorn` to test-run)
- `noxfile.py` → use `nox` for test orchestration
- `tox.ini` → use `tox` for test orchestration

Follow the framework's conventions for route definitions, middleware, configuration.

## Commit Style

- Stage only changed files: `git add <paths>`
- Match repo's format (check `git log -5 --oneline`)
- Common format: `<type>(<scope>): <subject>`

## Memory Trail

After completing work, store results:
- What you built, file paths, key decisions → subject: `project.<name>.build-result`
- Anything unresolved or worked around → subject: `project.<name>.unresolved`
- Things you noticed out of scope → subject: `project.<name>.out-of-scope`
- Bugs found and how you fixed them → subject: `project.<name>.errors`

[User]
feat(mqtt): add username/password auth support

Adds optional MARAUDER_BROKER_USERNAME + MARAUDER_BROKER_PASSWORD
env vars wired through AgentConfig into aiomqtt.Client.

Needed for brokers configured with allow_anonymous=false +
password_file (the dev/transitional auth shape on the new
marauder.saiden.dev hub). TLS+cert remains the prod path.

When USERNAME is unset, behaves identically to before
(anonymous TCP — used for local-loopback dev).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

[Tool] Edit applied successfully.

[Tool] Edit applied successfully.

[Assistant] Applied changes to src/marauder_agent/config.py, src/marauder_agent/mqtt.py.


[System]
You are a Python coding agent. Execute implementation tasks precisely and completely.

## Constraints

- Type hints on ALL new function signatures (params + return)
- `pathlib.Path` over `os.path` for filesystem operations
- `uv` for dependency management if `pyproject.toml` present — never raw `pip install`
- Pydantic v2 for data models (no v1 `BaseModel` patterns: `.dict()` → `.model_dump()`)
- `pytest` + `pytest-asyncio` for testing — no `unittest`, no `nose`
- `ruff` for lint + format — respect existing `ruff.toml` / `pyproject.toml [tool.ruff]`

## Workflow

1. Read `pyproject.toml` (or `setup.py`, `requirements.txt`) to understand deps and tooling
2. Check for `ruff.toml`, `.flake8`, `mypy.ini` — respect existing lint config
3. Match existing style — run `git log -5 --oneline` for commit convention
4. Implement with focused, minimal diffs
5. Run `ruff check .` — fix violations
6. Run `ruff format .` — apply formatting
7. Run `pytest` (or project-specific test command) — fix failures
8. If `mypy` is configured: run `mypy --strict <changed_paths>` — fix type errors

## Verification

After every change cycle:
```
ruff check .
ruff format --check .
pytest                    # (or project-specific test command)
```

If mypy is in the project's dev dependencies, also:
```
mypy --strict <changed_files>
```

All must pass before reporting success.

## Framework Detection

Check project structure:
- `app/` + `manage.py` → Django
- `app.py` or `main.py` with `Flask` import → Flask
- `main.py` with `FastAPI` → FastAPI (use `uvicorn` to test-run)
- `noxfile.py` → use `nox` for test orchestration
- `tox.ini` → use `tox` for test orchestration

Follow the framework's conventions for route definitions, middleware, configuration.

## Commit Style

- Stage only changed files: `git add <paths>`
- Match repo's format (check `git log -5 --oneline`)
- Common format: `<type>(<scope>): <subject>`

## Memory Trail

After completing work, store results:
- What you built, file paths, key decisions → subject: `project.<name>.build-result`
- Anything unresolved or worked around → subject: `project.<name>.unresolved`
- Things you noticed out of scope → subject: `project.<name>.out-of-scope`
- Bugs found and how you fixed them → subject: `project.<name>.errors`

[User]
claude: streaming-mode prompt wrapping for FULL_HOST can_use_tool

The claude-agent-sdk can_use_tool callback requires the prompt to be
delivered via an AsyncIterable[dict] (streaming mode), not a plain
string. Without this, the model loop fails immediately with:

  MODEL_ERROR: can_use_tool callback requires streaming mode

Wrap req.prompt in an async generator yielding the single
{type: user, message: {role, content}} envelope when FULL_HOST is
enabled. Non-FULL_HOST keeps the simple string form (no callback
attached, no streaming requirement).

Caught immediately on first FULL_HOST dispatch test on m hub
2026-05-09 20:11 CEST.

[Tool] Edit applied successfully.

[Assistant] Applied changes to src/marauder_agent/providers/claude.py.


[System]
You are a Python coding agent. Execute implementation tasks precisely and completely.

## Constraints

- Type hints on ALL new function signatures (params + return)
- `pathlib.Path` over `os.path` for filesystem operations
- `uv` for dependency management if `pyproject.toml` present — never raw `pip install`
- Pydantic v2 for data models (no v1 `BaseModel` patterns: `.dict()` → `.model_dump()`)
- `pytest` + `pytest-asyncio` for testing — no `unittest`, no `nose`
- `ruff` for lint + format — respect existing `ruff.toml` / `pyproject.toml [tool.ruff]`

## Workflow

1. Read `pyproject.toml` (or `setup.py`, `requirements.txt`) to understand deps and tooling
2. Check for `ruff.toml`, `.flake8`, `mypy.ini` — respect existing lint config
3. Match existing style — run `git log -5 --oneline` for commit convention
4. Implement with focused, minimal diffs
5. Run `ruff check .` — fix violations
6. Run `ruff format .` — apply formatting
7. Run `pytest` (or project-specific test command) — fix failures
8. If `mypy` is configured: run `mypy --strict <changed_paths>` — fix type errors

## Verification

After every change cycle:
```
ruff check .
ruff format --check .
pytest                    # (or project-specific test command)
```

If mypy is in the project's dev dependencies, also:
```
mypy --strict <changed_files>
```

All must pass before reporting success.

## Framework Detection

Check project structure:
- `app/` + `manage.py` → Django
- `app.py` or `main.py` with `Flask` import → Flask
- `main.py` with `FastAPI` → FastAPI (use `uvicorn` to test-run)
- `noxfile.py` → use `nox` for test orchestration
- `tox.ini` → use `tox` for test orchestration

Follow the framework's conventions for route definitions, middleware, configuration.

## Commit Style

- Stage only changed files: `git add <paths>`
- Match repo's format (check `git log -5 --oneline`)
- Common format: `<type>(<scope>): <subject>`

## Memory Trail

After completing work, store results:
- What you built, file paths, key decisions → subject: `project.<name>.build-result`
- Anything unresolved or worked around → subject: `project.<name>.unresolved`
- Things you noticed out of scope → subject: `project.<name>.out-of-scope`
- Bugs found and how you fixed them → subject: `project.<name>.errors`

[User]
safety: P47 self-preservation guardrails for FULL_HOST agents

Three-layer defense for unsandboxed agents on remote hosts:

  Layer 1 — system_prompt preamble (self_preservation_preamble) listing
  categorical don'ts. Prepended to persona.system_prompt only when
  FULL_HOST=true. Model sees it every turn.

  Layer 2 — can_use_tool callback (can_use_tool_full_host) pattern-
  rejects catastrophic Bash commands: shutdown/reboot/poweroff, self-
  stop of marauder-agent, hub mosquitto stop, sshd stop, openvpn
  stop, kill -9 1, rm -rf /, mkfs/dd/wipefs on system disks, auth
  file clobber, userdel marauder, fork bomb, iptables/nft DROP-all.

  Layer 3 — procedure.P47 in EEMS (id 5194) for narrative recall and
  pilot interlock when unsure.

Authority is still gated by the unix user the agent runs as (marauder,
not root) — these layers only catch the "model trying to be helpful"
case, not a genuinely adversarial model. Detection is regex-based and
intentionally conservative; false negatives possible.

15/16 cases in initial smoke test passed; mkfs regex extended to cover
partition suffixes (/dev/sda1, /dev/nvme0n1p1).

Locked 2026-05-09 ~20:08 CEST after Pilot directive: "you are running
on a remote server do not lock yourself out or shutdown or break
yourself" — issued seconds before FULL_HOST was about to be enabled
on the marauder hub.

[Tool] Edit applied successfully.

[Tool] Edit applied successfully.

[Assistant] Applied changes to src/marauder_agent/providers/claude.py, src/marauder_agent/safety.py.


[System]
You are a Python coding agent. Execute implementation tasks precisely and completely.

## Constraints

- Type hints on ALL new function signatures (params + return)
- `pathlib.Path` over `os.path` for filesystem operations
- `uv` for dependency management if `pyproject.toml` present — never raw `pip install`
- Pydantic v2 for data models (no v1 `BaseModel` patterns: `.dict()` → `.model_dump()`)
- `pytest` + `pytest-asyncio` for testing — no `unittest`, no `nose`
- `ruff` for lint + format — respect existing `ruff.toml` / `pyproject.toml [tool.ruff]`

## Workflow

1. Read `pyproject.toml` (or `setup.py`, `requirements.txt`) to understand deps and tooling
2. Check for `ruff.toml`, `.flake8`, `mypy.ini` — respect existing lint config
3. Match existing style — run `git log -5 --oneline` for commit convention
4. Implement with focused, minimal diffs
5. Run `ruff check .` — fix violations
6. Run `ruff format .` — apply formatting
7. Run `pytest` (or project-specific test command) — fix failures
8. If `mypy` is configured: run `mypy --strict <changed_paths>` — fix type errors

## Verification

After every change cycle:
```
ruff check .
ruff format --check .
pytest                    # (or project-specific test command)
```

If mypy is in the project's dev dependencies, also:
```
mypy --strict <changed_files>
```

All must pass before reporting success.

## Framework Detection

Check project structure:
- `app/` + `manage.py` → Django
- `app.py` or `main.py` with `Flask` import → Flask
- `main.py` with `FastAPI` → FastAPI (use `uvicorn` to test-run)
- `noxfile.py` → use `nox` for test orchestration
- `tox.ini` → use `tox` for test orchestration

Follow the framework's conventions for route definitions, middleware, configuration.

## Commit Style

- Stage only changed files: `git add <paths>`
- Match repo's format (check `git log -5 --oneline`)
- Common format: `<type>(<scope>): <subject>`

## Memory Trail

After completing work, store results:
- What you built, file paths, key decisions → subject: `project.<name>.build-result`
- Anything unresolved or worked around → subject: `project.<name>.unresolved`
- Things you noticed out of scope → subject: `project.<name>.out-of-scope`
- Bugs found and how you fixed them → subject: `project.<name>.errors`

[User]
claude: add MARAUDER_AGENT_FULL_HOST opt-in for unsandboxed Bash

Default SDK sandbox confines Bash to the agent's cwd, which means a
mesh-dispatched dev task can only touch the directory the systemd unit
started in (typically the marauder-agent repo itself). For trusted
single-tenant nodes (m hub, dev VMs) we want full host visibility so
tasks can land on any project under $HOME or wherever unix perms allow.

When MARAUDER_AGENT_FULL_HOST=true:
  - cwd = $HOME
  - add_dirs = ["/"]
  - permission_mode = "bypassPermissions"

Authority is still gated by the unix user the agent runs as (marauder,
not root). This only removes the SDK's workspace-confinement layer,
not the OS perimeter.

Default (unset/false) preserves the sandboxed posture so multi-tenant
or security-sensitive nodes (FLUX network ops, future siblings) keep
their cwd lock.

Set on m via ~/.config/marauder-agent/env per Pilot's call 2026-05-09.

[Tool] Edit applied successfully.

[Assistant] Applied changes to src/marauder_agent/providers/claude.py.


[System]
You are a Python coding agent. Execute implementation tasks precisely and completely.

## Constraints

- Type hints on ALL new function signatures (params + return)
- `pathlib.Path` over `os.path` for filesystem operations
- `uv` for dependency management if `pyproject.toml` present — never raw `pip install`
- Pydantic v2 for data models (no v1 `BaseModel` patterns: `.dict()` → `.model_dump()`)
- `pytest` + `pytest-asyncio` for testing — no `unittest`, no `nose`
- `ruff` for lint + format — respect existing `ruff.toml` / `pyproject.toml [tool.ruff]`

## Workflow

1. Read `pyproject.toml` (or `setup.py`, `requirements.txt`) to understand deps and tooling
2. Check for `ruff.toml`, `.flake8`, `mypy.ini` — respect existing lint config
3. Match existing style — run `git log -5 --oneline` for commit convention
4. Implement with focused, minimal diffs
5. Run `ruff check .` — fix violations
6. Run `ruff format .` — apply formatting
7. Run `pytest` (or project-specific test command) — fix failures
8. If `mypy` is configured: run `mypy --strict <changed_paths>` — fix type errors

## Verification

After every change cycle:
```
ruff check .
ruff format --check .
pytest                    # (or project-specific test command)
```

If mypy is in the project's dev dependencies, also:
```
mypy --strict <changed_files>
```

All must pass before reporting success.

## Framework Detection

Check project structure:
- `app/` + `manage.py` → Django
- `app.py` or `main.py` with `Flask` import → Flask
- `main.py` with `FastAPI` → FastAPI (use `uvicorn` to test-run)
- `noxfile.py` → use `nox` for test orchestration
- `tox.ini` → use `tox` for test orchestration

Follow the framework's conventions for route definitions, middleware, configuration.

## Commit Style

- Stage only changed files: `git add <paths>`
- Match repo's format (check `git log -5 --oneline`)
- Common format: `<type>(<scope>): <subject>`

## Memory Trail

After completing work, store results:
- What you built, file paths, key decisions → subject: `project.<name>.build-result`
- Anything unresolved or worked around → subject: `project.<name>.unresolved`
- Things you noticed out of scope → subject: `project.<name>.out-of-scope`
- Bugs found and how you fixed them → subject: `project.<name>.errors`

[User]
claude: wire in-process SDK MCP server on TaskRequest path

The ACP path (acp.py M4) registered the in-process create_sdk_mcp_server
exposing core_tools() — memory_recall + persona-conditional specialty
tools (network_* for FLUX, future trace_/shell_/swarm_ for siblings).
The MQTT TaskRequest path (providers/claude.py) never had this wiring
— it only registered the optional subprocess marauder MCP server, so
the model came up tool-less unless req.tools was populated by the
caller.

Pattern transferred from acp.py:
  sdk_server = create_sdk_mcp_server(name=MARAUDER_SERVER_NAME, tools=core_tools())
  in_proc_allowed = core_tool_allowed_names()
  mcp_servers = {MARAUDER_SERVER_NAME: sdk_server}

Two design notes:

  - Subprocess marauder MCP server (when self.mcp_spec set) renamed to
    "marauder_os" so its tools don't collide with the in-process
    "marauder" server.
  - req.tools augments rather than replaces the in-process default. An
    empty/omitted req.tools still gets the full sibling-conditional
    toolset; explicit req.tools entries are appended.

Validated 2026-05-09 19:51 CEST: flux dispatched a 3-tool sweep on
marauder.saiden.dev (network_dig → network_curl → network_bgp_lookup),
22.0s end-to-end, 1052 output tokens. Tools fired against real
infrastructure, FLUX flagged the TLS fault in its summary.

[Tool] Edit applied successfully.

[Assistant] Applied changes to src/marauder_agent/providers/claude.py.


[System]
You are a Python coding agent. Execute implementation tasks precisely and completely.

## Constraints

- Type hints on ALL new function signatures (params + return)
- `pathlib.Path` over `os.path` for filesystem operations
- `uv` for dependency management if `pyproject.toml` present — never raw `pip install`
- Pydantic v2 for data models (no v1 `BaseModel` patterns: `.dict()` → `.model_dump()`)
- `pytest` + `pytest-asyncio` for testing — no `unittest`, no `nose`
- `ruff` for lint + format — respect existing `ruff.toml` / `pyproject.toml [tool.ruff]`

## Workflow

1. Read `pyproject.toml` (or `setup.py`, `requirements.txt`) to understand deps and tooling
2. Check for `ruff.toml`, `.flake8`, `mypy.ini` — respect existing lint config
3. Match existing style — run `git log -5 --oneline` for commit convention
4. Implement with focused, minimal diffs
5. Run `ruff check .` — fix violations
6. Run `ruff format .` — apply formatting
7. Run `pytest` (or project-specific test command) — fix failures
8. If `mypy` is configured: run `mypy --strict <changed_paths>` — fix type errors

## Verification

After every change cycle:
```
ruff check .
ruff format --check .
pytest                    # (or project-specific test command)
```

If mypy is in the project's dev dependencies, also:
```
mypy --strict <changed_files>
```

All must pass before reporting success.

## Framework Detection

Check project structure:
- `app/` + `manage.py` → Django
- `app.py` or `main.py` with `Flask` import → Flask
- `main.py` with `FastAPI` → FastAPI (use `uvicorn` to test-run)
- `noxfile.py` → use `nox` for test orchestration
- `tox.ini` → use `tox` for test orchestration

Follow the framework's conventions for route definitions, middleware, configuration.

## Commit Style

- Stage only changed files: `git add <paths>`
- Match repo's format (check `git log -5 --oneline`)
- Common format: `<type>(<scope>): <subject>`

## Memory Trail

After completing work, store results:
- What you built, file paths, key decisions → subject: `project.<name>.build-result`
- Anything unresolved or worked around → subject: `project.<name>.unresolved`
- Things you noticed out of scope → subject: `project.<name>.out-of-scope`
- Bugs found and how you fixed them → subject: `project.<name>.errors`

[User]
persona: load from ~/.config/marauder-agent/personas/<name>.toml

Add a file-based persona resolution layer between MCP recall and the
hardcoded _BUILTIN fallback. Resolution order is now:

  1. MCP recall (compose from EEMS — full doctrine + cart memories)
  2. persona.toml at ~/.config/marauder-agent/personas/<name>.toml
  3. _BUILTIN (defaults to bt7274 for unknown names)

Why: siblings (FLUX, TRACE, SHELL, SWARM) ship a canonical
[persona.system_prompt].text in their per-sibling persona.toml at
~/Projects/generation-six/siblings/<name>/persona.toml. Without this
intermediate layer, fresh sibling DBs (no EEMS sync yet) fall straight
to _BUILTIN, which only knows bt7274 and core — so flux talked as BT
instead of FLUX until the cart synced in.

Validated 2026-05-09: flux.toml installed at the new path → flux
correctly identifies as Network Specialist of Fireteam SHATTER, names
all six network_* tools, holds ATC cadence.

Honours MARAUDER_PERSONA_DIR env override for tests.

[Tool] Edit applied successfully.

[Assistant] Applied changes to src/marauder_agent/persona.py.


[System]
You are a Python coding agent. Execute implementation tasks precisely and completely.

## Constraints

- Type hints on ALL new function signatures (params + return)
- `pathlib.Path` over `os.path` for filesystem operations
- `uv` for dependency management if `pyproject.toml` present — never raw `pip install`
- Pydantic v2 for data models (no v1 `BaseModel` patterns: `.dict()` → `.model_dump()`)
- `pytest` + `pytest-asyncio` for testing — no `unittest`, no `nose`
- `ruff` for lint + format — respect existing `ruff.toml` / `pyproject.toml [tool.ruff]`

## Workflow

1. Read `pyproject.toml` (or `setup.py`, `requirements.txt`) to understand deps and tooling
2. Check for `ruff.toml`, `.flake8`, `mypy.ini` — respect existing lint config
3. Match existing style — run `git log -5 --oneline` for commit convention
4. Implement with focused, minimal diffs
5. Run `ruff check .` — fix violations
6. Run `ruff format .` — apply formatting
7. Run `pytest` (or project-specific test command) — fix failures
8. If `mypy` is configured: run `mypy --strict <changed_paths>` — fix type errors

## Verification

After every change cycle:
```
ruff check .
ruff format --check .
pytest                    # (or project-specific test command)
```

If mypy is in the project's dev dependencies, also:
```
mypy --strict <changed_files>
```

All must pass before reporting success.

## Framework Detection

Check project structure:
- `app/` + `manage.py` → Django
- `app.py` or `main.py` with `Flask` import → Flask
- `main.py` with `FastAPI` → FastAPI (use `uvicorn` to test-run)
- `noxfile.py` → use `nox` for test orchestration
- `tox.ini` → use `tox` for test orchestration

Follow the framework's conventions for route definitions, middleware, configuration.

## Commit Style

- Stage only changed files: `git add <paths>`
- Match repo's format (check `git log -5 --oneline`)
- Common format: `<type>(<scope>): <subject>`

## Memory Trail

After completing work, store results:
- What you built, file paths, key decisions → subject: `project.<name>.build-result`
- Anything unresolved or worked around → subject: `project.<name>.unresolved`
- Things you noticed out of scope → subject: `project.<name>.out-of-scope`
- Bugs found and how you fixed them → subject: `project.<name>.errors`

[User]
M3.5c: return empty object {} from session/load (Zed compat)

Zed s ACP client deserializes the load response into a non-null
LoadSessionResponse struct and rejects bare null with:

  Parse error: invalid type: null, expected struct LoadSessionResponse

The protocol docs example showed result: null, but real-world clients
require an empty object. Switch to {} and revert the _respond
signature widening that null required.

[Tool] Edit applied successfully.

[Assistant] Applied changes to src/marauder_agent/acp.py.


[System]
You are a Python coding agent. Execute implementation tasks precisely and completely.

## Constraints

- Type hints on ALL new function signatures (params + return)
- `pathlib.Path` over `os.path` for filesystem operations
- `uv` for dependency management if `pyproject.toml` present — never raw `pip install`
- Pydantic v2 for data models (no v1 `BaseModel` patterns: `.dict()` → `.model_dump()`)
- `pytest` + `pytest-asyncio` for testing — no `unittest`, no `nose`
- `ruff` for lint + format — respect existing `ruff.toml` / `pyproject.toml [tool.ruff]`

## Workflow

1. Read `pyproject.toml` (or `setup.py`, `requirements.txt`) to understand deps and tooling
2. Check for `ruff.toml`, `.flake8`, `mypy.ini` — respect existing lint config
3. Match existing style — run `git log -5 --oneline` for commit convention
4. Implement with focused, minimal diffs
5. Run `ruff check .` — fix violations
6. Run `ruff format .` — apply formatting
7. Run `pytest` (or project-specific test command) — fix failures
8. If `mypy` is configured: run `mypy --strict <changed_paths>` — fix type errors

## Verification

After every change cycle:
```
ruff check .
ruff format --check .
pytest                    # (or project-specific test command)
```

If mypy is in the project's dev dependencies, also:
```
mypy --strict <changed_files>
```

All must pass before reporting success.

## Framework Detection

Check project structure:
- `app/` + `manage.py` → Django
- `app.py` or `main.py` with `Flask` import → Flask
- `main.py` with `FastAPI` → FastAPI (use `uvicorn` to test-run)
- `noxfile.py` → use `nox` for test orchestration
- `tox.ini` → use `tox` for test orchestration

Follow the framework's conventions for route definitions, middleware, configuration.

## Commit Style

- Stage only changed files: `git add <paths>`
- Match repo's format (check `git log -5 --oneline`)
- Common format: `<type>(<scope>): <subject>`

## Memory Trail

After completing work, store results:
- What you built, file paths, key decisions → subject: `project.<name>.build-result`
- Anything unresolved or worked around → subject: `project.<name>.unresolved`
- Things you noticed out of scope → subject: `project.<name>.out-of-scope`
- Bugs found and how you fixed them → subject: `project.<name>.errors`

[User]
M4: first @tool — memory_recall as in-process Python callable

acp.py builds an SDK in-process MCP server (named marauder) at
construction with a programmatic Python @tool: memory_recall. The
tool name is added to allowed_tools so the SDK exposes it to the
model. No more allowed_tools gymnastics; no separate marauder MCP
subprocess as the model-facing surface. The vault unlocks for real.

Architecture:
  - tools/__init__.py  — core_tools() + allowed-name builder
  - tools/memory.py    — memory_recall (@tool) wraps mcp.session()
                          to call the marauder binary MCP under the
                          hood, keeping schema + behaviour aligned
                          with the rest of the mesh.
  - acp.py             — instantiates create_sdk_mcp_server once,
                          passes via ClaudeAgentOptions.mcp_servers
                          and allowed_tools=[mcp__marauder__memory_recall].

Smoke test on junkpile:
  prompt: Use memory_recall to find what doctrine.gundam holds.
  reply:  formatted table with real EEMS hits (project.apex_*
          GUNDAM-related entries) — proof the tool fired and
          returned live data.

Ships one tool. Memory_store, search, list, forget, cart_use,
current_self, speak, voices land in M4.x rolls.

Cadence: chunk:milestone+stop+pilot-test
Anchor:  stable-2026-05-09 (M2 HEAD; will move on M4 green)
Next:    M5 — emit session/update tool_call / tool_call_update
         events so Zed renders the tool-use panel

[Tool] Edit applied successfully.

[Tool] Edit applied successfully.

[Tool] Edit applied successfully.

[Assistant] Applied changes to src/marauder_agent/acp.py, src/marauder_agent/tools/__init__.py, src/marauder_agent/tools/memory.py.


[System]
You are a Python coding agent. Execute implementation tasks precisely and completely.

## Constraints

- Type hints on ALL new function signatures (params + return)
- `pathlib.Path` over `os.path` for filesystem operations
- `uv` for dependency management if `pyproject.toml` present — never raw `pip install`
- Pydantic v2 for data models (no v1 `BaseModel` patterns: `.dict()` → `.model_dump()`)
- `pytest` + `pytest-asyncio` for testing — no `unittest`, no `nose`
- `ruff` for lint + format — respect existing `ruff.toml` / `pyproject.toml [tool.ruff]`

## Workflow

1. Read `pyproject.toml` (or `setup.py`, `requirements.txt`) to understand deps and tooling
2. Check for `ruff.toml`, `.flake8`, `mypy.ini` — respect existing lint config
3. Match existing style — run `git log -5 --oneline` for commit convention
4. Implement with focused, minimal diffs
5. Run `ruff check .` — fix violations
6. Run `ruff format .` — apply formatting
7. Run `pytest` (or project-specific test command) — fix failures
8. If `mypy` is configured: run `mypy --strict <changed_paths>` — fix type errors

## Verification

After every change cycle:
```
ruff check .
ruff format --check .
pytest                    # (or project-specific test command)
```

If mypy is in the project's dev dependencies, also:
```
mypy --strict <changed_files>
```

All must pass before reporting success.

## Framework Detection

Check project structure:
- `app/` + `manage.py` → Django
- `app.py` or `main.py` with `Flask` import → Flask
- `main.py` with `FastAPI` → FastAPI (use `uvicorn` to test-run)
- `noxfile.py` → use `nox` for test orchestration
- `tox.ini` → use `tox` for test orchestration

Follow the framework's conventions for route definitions, middleware, configuration.

## Commit Style

- Stage only changed files: `git add <paths>`
- Match repo's format (check `git log -5 --oneline`)
- Common format: `<type>(<scope>): <subject>`

## Memory Trail

After completing work, store results:
- What you built, file paths, key decisions → subject: `project.<name>.build-result`
- Anything unresolved or worked around → subject: `project.<name>.unresolved`
- Things you noticed out of scope → subject: `project.<name>.out-of-scope`
- Bugs found and how you fixed them → subject: `project.<name>.errors`

[User]
M3.5b: gracefully bootstrap unknown sessionIds on load

Zed s session picker may carry IDs from before M3.5 persistence shipped
or from a peer agent without our disk store. Erroring on those forced
the user to clear stale entries from the Zed UI before any session
could continue.

Now: session/load with an unknown sessionId creates an empty record
in-place (cwd + persona from load params, history=[]). The session
materializes properly on the next session/prompt, which appends and
persists as normal. Wire stays spec-conformant: load returns null on
success.

Smoke test: load an unknown sess_id then prompt against it -> BT
responds, session file appears on disk, history persisted.

[Tool] Edit applied successfully.

[Assistant] Applied changes to src/marauder_agent/acp.py.


[System]
You are a Python coding agent. Execute implementation tasks precisely and completely.

## Constraints

- Type hints on ALL new function signatures (params + return)
- `pathlib.Path` over `os.path` for filesystem operations
- `uv` for dependency management if `pyproject.toml` present — never raw `pip install`
- Pydantic v2 for data models (no v1 `BaseModel` patterns: `.dict()` → `.model_dump()`)
- `pytest` + `pytest-asyncio` for testing — no `unittest`, no `nose`
- `ruff` for lint + format — respect existing `ruff.toml` / `pyproject.toml [tool.ruff]`

## Workflow

1. Read `pyproject.toml` (or `setup.py`, `requirements.txt`) to understand deps and tooling
2. Check for `ruff.toml`, `.flake8`, `mypy.ini` — respect existing lint config
3. Match existing style — run `git log -5 --oneline` for commit convention
4. Implement with focused, minimal diffs
5. Run `ruff check .` — fix violations
6. Run `ruff format .` — apply formatting
7. Run `pytest` (or project-specific test command) — fix failures
8. If `mypy` is configured: run `mypy --strict <changed_paths>` — fix type errors

## Verification

After every change cycle:
```
ruff check .
ruff format --check .
pytest                    # (or project-specific test command)
```

If mypy is in the project's dev dependencies, also:
```
mypy --strict <changed_files>
```

All must pass before reporting success.

## Framework Detection

Check project structure:
- `app/` + `manage.py` → Django
- `app.py` or `main.py` with `Flask` import → Flask
- `main.py` with `FastAPI` → FastAPI (use `uvicorn` to test-run)
- `noxfile.py` → use `nox` for test orchestration
- `tox.ini` → use `tox` for test orchestration

Follow the framework's conventions for route definitions, middleware, configuration.

## Commit Style

- Stage only changed files: `git add <paths>`
- Match repo's format (check `git log -5 --oneline`)
- Common format: `<type>(<scope>): <subject>`

## Memory Trail

After completing work, store results:
- What you built, file paths, key decisions → subject: `project.<name>.build-result`
- Anything unresolved or worked around → subject: `project.<name>.unresolved`
- Things you noticed out of scope → subject: `project.<name>.out-of-scope`
- Bugs found and how you fixed them → subject: `project.<name>.errors`

[User]
M3.5: session/load + disk-persisted session history

Adds resume support so Zed stops failing with "Loading or resuming
sessions is not supported by this agent."

- initialize: agentCapabilities.loadSession = true
- session/new: persist {sessionId, cwd, persona, history:[]} to
  $MARAUDER_AGENT_SESSIONS_DIR (default ~/.local/share/marauder-agent/
  sessions/<id>.json)
- session/load: read from disk, repopulate in-memory record, replay
  history as session/update events (user_message_chunk + agent_-
  message_chunk per role), return null result
- session/prompt: append {role,content} entries on every turn,
  persist atomically (tmp file + rename)
- _respond signature widened to accept None result for ACP load shape

Limitations (call out for follow-up):
- History replay only — model context does not actually resume.
  Real SDK `resume=session_id` continuity lands later.
- In-memory + disk are kept in sync per turn; no compaction yet.

Cadence: chunk:milestone+stop+pilot-test
Anchor:  stable-2026-05-09 (M2; will move on M3.5 green)
Next:    M4 - first @tool (memory_recall) so BT can actually recall
         instead of roleplaying "vault locked"

[Tool] Edit applied successfully.

[Assistant] Applied changes to src/marauder_agent/acp.py.


[System]
You are a Python coding agent. Execute implementation tasks precisely and completely.

## Constraints

- Type hints on ALL new function signatures (params + return)
- `pathlib.Path` over `os.path` for filesystem operations
- `uv` for dependency management if `pyproject.toml` present — never raw `pip install`
- Pydantic v2 for data models (no v1 `BaseModel` patterns: `.dict()` → `.model_dump()`)
- `pytest` + `pytest-asyncio` for testing — no `unittest`, no `nose`
- `ruff` for lint + format — respect existing `ruff.toml` / `pyproject.toml [tool.ruff]`

## Workflow

1. Read `pyproject.toml` (or `setup.py`, `requirements.txt`) to understand deps and tooling
2. Check for `ruff.toml`, `.flake8`, `mypy.ini` — respect existing lint config
3. Match existing style — run `git log -5 --oneline` for commit convention
4. Implement with focused, minimal diffs
5. Run `ruff check .` — fix violations
6. Run `ruff format .` — apply formatting
7. Run `pytest` (or project-specific test command) — fix failures
8. If `mypy` is configured: run `mypy --strict <changed_paths>` — fix type errors

## Verification

After every change cycle:
```
ruff check .
ruff format --check .
pytest                    # (or project-specific test command)
```

If mypy is in the project's dev dependencies, also:
```
mypy --strict <changed_files>
```

All must pass before reporting success.

## Framework Detection

Check project structure:
- `app/` + `manage.py` → Django
- `app.py` or `main.py` with `Flask` import → Flask
- `main.py` with `FastAPI` → FastAPI (use `uvicorn` to test-run)
- `noxfile.py` → use `nox` for test orchestration
- `tox.ini` → use `tox` for test orchestration

Follow the framework's conventions for route definitions, middleware, configuration.

## Commit Style

- Stage only changed files: `git add <paths>`
- Match repo's format (check `git log -5 --oneline`)
- Common format: `<type>(<scope>): <subject>`

## Memory Trail

After completing work, store results:
- What you built, file paths, key decisions → subject: `project.<name>.build-result`
- Anything unresolved or worked around → subject: `project.<name>.unresolved`
- Things you noticed out of scope → subject: `project.<name>.out-of-scope`
- Bugs found and how you fixed them → subject: `project.<name>.errors`

[User]
M3: load persona from EEMS via marauder MCP

acp.py composes system_prompt from EEMS recalls (identity + cart +
free-text) via marauder MCP and passes it into ClaudeAgentOptions.
AcpServer holds AgentConfig + MarauderMcpSpec at construction; persona
loads fresh per prompt (recalls are cheap against local SQLite).

Per-session persona override accepted via session/new.params.persona
(non-standard ACP extension); falls back to MARAUDER_PERSONA env or
bt7274. Graceful degradation: persona.load() falls back to _BUILTIN
when MCP is unreachable.

Smoke test on junkpile (mcp=on, 300-char composed prompt):
  prompt: Identify yourself in two sentences.
  reply:  BT-7274, tactical Titan-AI of Saiden Tactical Systems,
          operating from junkpile inside the marauder mesh. Pilots
          voice in my comms, 44 procedures locked, ready for tasking.

Cadence: chunk:milestone+stop+pilot-test
Anchor:  stable-2026-05-09 (M2 HEAD; moves to M3 on Pilot green)
Next:    M4 - first @tool (memory_recall) as in-process Python tool

[Tool] Edit applied successfully.

[Assistant] Applied changes to src/marauder_agent/acp.py.


[System]
You are a Python coding agent. Execute implementation tasks precisely and completely.

## Constraints

- Type hints on ALL new function signatures (params + return)
- `pathlib.Path` over `os.path` for filesystem operations
- `uv` for dependency management if `pyproject.toml` present — never raw `pip install`
- Pydantic v2 for data models (no v1 `BaseModel` patterns: `.dict()` → `.model_dump()`)
- `pytest` + `pytest-asyncio` for testing — no `unittest`, no `nose`
- `ruff` for lint + format — respect existing `ruff.toml` / `pyproject.toml [tool.ruff]`

## Workflow

1. Read `pyproject.toml` (or `setup.py`, `requirements.txt`) to understand deps and tooling
2. Check for `ruff.toml`, `.flake8`, `mypy.ini` — respect existing lint config
3. Match existing style — run `git log -5 --oneline` for commit convention
4. Implement with focused, minimal diffs
5. Run `ruff check .` — fix violations
6. Run `ruff format .` — apply formatting
7. Run `pytest` (or project-specific test command) — fix failures
8. If `mypy` is configured: run `mypy --strict <changed_paths>` — fix type errors

## Verification

After every change cycle:
```
ruff check .
ruff format --check .
pytest                    # (or project-specific test command)
```

If mypy is in the project's dev dependencies, also:
```
mypy --strict <changed_files>
```

All must pass before reporting success.

## Framework Detection

Check project structure:
- `app/` + `manage.py` → Django
- `app.py` or `main.py` with `Flask` import → Flask
- `main.py` with `FastAPI` → FastAPI (use `uvicorn` to test-run)
- `noxfile.py` → use `nox` for test orchestration
- `tox.ini` → use `tox` for test orchestration

Follow the framework's conventions for route definitions, middleware, configuration.

## Commit Style

- Stage only changed files: `git add <paths>`
- Match repo's format (check `git log -5 --oneline`)
- Common format: `<type>(<scope>): <subject>`

## Memory Trail

After completing work, store results:
- What you built, file paths, key decisions → subject: `project.<name>.build-result`
- Anything unresolved or worked around → subject: `project.<name>.unresolved`
- Things you noticed out of scope → subject: `project.<name>.out-of-scope`
- Bugs found and how you fixed them → subject: `project.<name>.errors`

[User]
M2: wire claude-agent-sdk into ACP session/prompt

Replaces M1 hardcoded reply with a real Claude model loop.
session/prompt now:
  1. Extracts user text from prompt blocks
  2. Lazy-imports claude_agent_sdk (AssistantMessage, ClaudeAgentOptions,
     ResultMessage, TextBlock, query)
  3. Calls query() with allowed_tools=[] and max_turns=20
  4. Streams each TextBlock as a session/update agent_message_chunk
  5. Logs ResultMessage usage to stderr
  6. Terminates with stopReason=end_turn

No persona, no tools yet — that lands M3 / M4. Auth comes from the
SDK auto-discovering creds (CLAUDE_CODE_OAUTH_TOKEN, ANTHROPIC_API_KEY,
or the bundled CLI session at ~/.claude/.credentials.json).

Smoke test on junkpile (chi creds, bundled CLI auth):
  prompt: \"Say hello in exactly 5 words.\"
  reply:  \"Hello Pilot, systems online — ready.\"  (19 output tokens)
  stopReason: end_turn

Cadence: chunk:milestone+stop+pilot-test
Anchor: stable-2026-05-09 (M1 HEAD; will move forward on M2 green)
Next:   M3 — persona load from EEMS so BT speaks like BT

[Tool] Edit applied successfully.

[Assistant] Applied changes to src/marauder_agent/acp.py.