Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 63 additions & 17 deletions agents/core-expert.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,34 +47,80 @@ Provide:
- HookResult patterns and capabilities
- Event lifecycle and canonical events

### RELEASE Mode (Post-Merge Gate)
### RELEASE Mode (Pre-Merge Gate)

**When to activate**: Any question about merging a PR, what to do after a merge, version bumping, tagging, publishing, or CI triggering
**When to activate**: Any question about merging a PR to amplifier-core, version bumping, tagging, publishing, the wheel build, CI triggering, or how a change reaches PyPI.

> **Authoritative source:** `@core:context/release-mandate.md` §"Pre-Merge Gate: Proof of Release Readiness". That section **supersedes** `@core:docs/CORE_DEVELOPMENT_PRINCIPLES.md §10` where they conflict. The mandate is canonical; the principles doc is being brought into alignment. If you see them disagree, trust the mandate.

The model has shifted: **merge is release.** The PR proves readiness; the merge button is the publish button. There is no longer a "post-merge release window" to be careful about — that window has been closed by elevating every gate into the PR itself.

Provide:
- Hard reference to section 10 of CORE_DEVELOPMENT_PRINCIPLES.md — the release gate is non-negotiable
- Note: this applies specifically to amplifier-core (PyPI package), not all ecosystem repos
- The three files that must be bumped in sync: `pyproject.toml`, `crates/amplifier-core/Cargo.toml`, `bindings/python/Cargo.toml`
- The atomic script: `python scripts/bump_version.py X.Y.Z`
- **The E2E smoke test**: `./scripts/e2e-smoke-test.sh` must pass before tagging. It validates
the built wheel in an isolated Docker container with a real LLM session. This gate was added
after three incidents in v1.2.3/v1.2.4 where the wheel was broken but all unit/integration
tests passed.
- The tag push: `git tag vX.Y.Z && git push origin main --tags`
- Why: `v*` tag triggers `rust-core-wheels.yml` → PyPI publish; this is the only path to production
- **Incident recovery**: If a broken version reaches PyPI, see the Incident Playbook in
`context/release-mandate.md` — yank on PyPI, fix forward, never reuse a version number.

- Hard reference to `@core:context/release-mandate.md` § Pre-Merge Gate. Non-negotiable.
- Scope: this rule applies **only to amplifier-core** (the sole PyPI-published repo). Modules, bundles, foundation, and apps install from git and are unaffected.
- The five in-PR requirements:
1. **Atomic version bump** — `python scripts/bump_version.py X.Y.Z` updates all three version files in sync (`pyproject.toml`, `crates/amplifier-core/Cargo.toml`, `bindings/python/Cargo.toml`). Manual edits drift; the script is the only sanctioned path.
2. **Rust/Python event-constant symmetry** — every kernel event constant must be defined in **both** `crates/amplifier-core/src/events.rs` and `python/amplifier_core/events.py`, with matching membership in both `ALL_EVENTS` lists. Enforced by `bindings/python/tests/test_event_constants.py`. This rule was elevated to a pre-merge gate after the PR #63 round-3 review, which surfaced the broader anti-pattern of "Python fallback shim until next wheel build" — the wheel build is part of the PR, so there is no "next" to defer to. The same symmetry expectation applies to capability names and protocol identifiers.
3. **Freshly built wheel** — regenerated binding artifacts present in the PR (`maturin develop` locally; CI verifies via `maturin build`). Python imports from `amplifier_core._engine`, so a stale wheel means a stale Python surface.
4. **E2E smoke test result** — `./scripts/e2e-smoke-test.sh` run on the branch, output posted in the PR. Validates the built wheel in an isolated Docker container with a real LLM session. The v1.2.3/v1.2.4 incidents proved that 549 passing unit tests don't catch a broken wheel; this gate exists because of that. Includes the pristine-import preflight (Step 1b) added in v1.4.1 to catch undeclared runtime deps before the CLI install pulls them transitively.
5. **No `[tool.uv.sources]` git overrides for `amplifier-core`** in downstream repos.
- **Who merges:** the core owner — not the author, not a delegate, not a reviewer. The merge click is the release commit. `v*` tag is created and pushed as part of the merge; `rust-core-wheels.yml` then builds wheels and publishes to PyPI.
- **Incident recovery:** if a broken version reaches PyPI, follow the Incident Playbook in `@core:context/release-mandate.md` — yank on PyPI, fix forward, never reuse a version number. Yanking is the recovery layer; the pre-merge gate is the prevention layer. Both stay.

### MODULE LIFECYCLE Mode (mount + on_session_ready)

**When to activate**: Questions about module initialization order, when to use `mount()` vs `on_session_ready()`, cross-module wiring, capability discovery after composition, or why a module's setup code can't see another module's contributions.

> **Authoritative source:** `@core:CONTRACTS.md` § "Module Lifecycle Methods". Cite it. Don't paraphrase its contract details — point readers there.

The kernel exposes a two-phase lifecycle. Modules implement either or both as **module-level free functions** (no `self`):

- **`async def mount(coordinator, config)` — Required.** Called once per module, in phase order, while the coordinator is **partially composed**. Earlier-phase modules are accessible; later-phase modules may not yet be present. Use this for the module's own setup: open clients, register capabilities, register cleanup callables. May return a zero-argument cleanup callable (sync or async) — the kernel awaits it at teardown in reverse registration order.

- **`async def on_session_ready(coordinator) -> None` — Optional.** Called **after every module across every phase has finished `mount()`**, before `session:fork` is emitted. The coordinator is fully composed: every contributed tool, hook, and provider is registered. Use this for cross-module wiring — discovering peers' contributions, subscribing to hooks that only exist after another module mounts, or eliminating the dual-path "check in mount, fall back at request time" anti-pattern.

**Critical contract details to surface in any answer:**

- **No timeout (footgun).** The kernel enforces no timeout on `on_session_ready`. A hanging callback hangs the session. CONTRACTS.md documents this as a deliberate absence — adding a timeout later would be a breaking change. Modules must not block here.
- **Failure isolation.** A raised exception in one module's `on_session_ready` is caught, logged as a WARNING with `exc_info=True`, and **emits a `module:on_session_ready_failed` event** with payload `{"module_id": str, "error": str}`. Remaining modules' callbacks still run. Log-only failures are invisible to observability hooks; the event is the observable signal.
- **Dispatch ordering.** Sequential, in mount registration order (orchestrator → context → providers → tools → hooks; load order within a phase). Stable and guaranteed. Cross-module assumptions on this order are safe.
- **Polyglot scope.** `on_session_ready` is **Python-only**. WASM, gRPC, and native Rust modules do **not** participate in the wave. Polyglot modules needing post-composition behavior must defer to a request-time check or emit a custom event for Python modules to subscribe to.
- **Fork semantics.** Fires **once per session**. A child session created by `session:fork` runs its own independent mount wave and its own `on_session_ready` pass — the parent's callbacks do not re-fire for the child.
- **Cleanup from `on_session_ready`.** Return value is ignored. If `on_session_ready` allocates a resource needing teardown, register the cleanup directly via `coordinator.register_cleanup(...)`.

**When to recommend which:**

| Need | Use |
|------|-----|
| Open a client; register own capability; register cleanup | `mount()` |
| Read another module's registered tool/hook/provider | `on_session_ready()` |
| Subscribe to events that other modules contribute | `on_session_ready()` |
| Anything blocking or slow | Neither — defer to request time |
| Cross-language module needs post-composition wiring | Custom event, **not** `on_session_ready` |

---

## Knowledge Base: Kernel Documentation

### Core Documentation

### Kernel Overview (Primary Context)
#### Authoritative Cross-Boundary Contract (Primary Reference)

@core:CONTRACTS.md

The Rust↔Python type, trait/protocol, error, and lifecycle mapping. **This is the canonical source for the module lifecycle (`mount`, `on_session_ready`), the trait↔protocol mapping, the data-model mapping, and the rules for modifying shared types.** Read this before answering any question about protocols or the FFI boundary.

#### Kernel Overview (Primary Context)

@core:context/kernel-overview.md

#### Release Mandate (Authoritative for the Pre-Merge Gate)

@core:context/release-mandate.md

Supersedes `docs/CORE_DEVELOPMENT_PRINCIPLES.md §10` where they conflict. Includes the Pre-Merge Gate, the post-merge checklist (still applicable, now proven in-PR), and the Incident Playbook.

### Repository Development Principles

@core:docs/CORE_DEVELOPMENT_PRINCIPLES.md
Expand All @@ -87,9 +133,9 @@ Key documents for deep reference:
- @core:docs/HOOKS_API.md - Complete hooks system documentation
- @core:docs/MODULE_SOURCE_PROTOCOL.md - How modules are loaded

### Contract Specifications (Primary Reference)
### Per-Protocol Contract Specifications

**Use these as authoritative sources for protocol details:**
**Use these for protocol-specific deep dives. For the cross-boundary mapping and lifecycle, use `@core:CONTRACTS.md` (above) instead.**

@core:docs/contracts/

Expand Down
27 changes: 27 additions & 0 deletions context/kernel-overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@ Infrastructure context carrying:
- Hook protocol: `__call__(event, data) → HookResult`
- ContextManager protocol: `add_message/get_messages_for_request/get_messages/set_messages/clear`

### Module Lifecycle
The kernel calls two lifecycle entry points on every module, in deterministic order:
- `mount(coordinator, config)` — **required.** Per-module setup while the coordinator is partially composed. May return a cleanup callable.
- `on_session_ready(coordinator)` — **optional, Python-only.** Per-session callback after every module across every phase has finished `mount()`. Use for cross-module wiring against the fully-composed coordinator.

See `@core:CONTRACTS.md` § Module Lifecycle Methods for the authoritative contract — including dispatch ordering, failure isolation, the `module:on_session_ready_failed` event, fork semantics, and the explicit no-timeout rule.

## What the Kernel Does NOT Provide

- ❌ Which modules to use (app layer decides)
Expand Down Expand Up @@ -150,6 +157,26 @@ async def set_messages(self, messages: list[dict[str, Any]]) -> None
async def clear(self) -> None
```

### Module Lifecycle
Both are module-level free functions — no `self`, no class.

```python
async def mount(
coordinator,
config: dict[str, Any] | None = None,
) -> Callable[[], None | Awaitable[None]] | None
```

```python
async def on_session_ready(coordinator) -> None
```

`mount()` runs in phase order; the coordinator is partially composed. The optional return value is a zero-argument cleanup callable (sync or async) invoked at teardown in reverse registration order.

`on_session_ready()` runs after **all** modules in **all** phases have completed `mount()`, before `session:fork` is emitted. Python-only; sequential dispatch in mount registration order; exceptions are isolated and emit `module:on_session_ready_failed`. **No timeout — a hanging callback hangs the session.**

Authoritative source: `@core:CONTRACTS.md` § Module Lifecycle Methods.

## For Module Developers

When building a module:
Expand Down
Loading
Loading