Citation refusal at propose-time
A proposal citing a slug the writing manifest doesn't resolve is rejected before any row lands. The most common hallucination shape — fabricated source attribution — is refused by construction.
The platform already enforces every primitive an AI research agent's outputs would have to honour — bitemporal discipline, citation refusal, content-addressed feature hashes, a named-human promotion gate. The agent work is therefore not "bolt an LLM on": it's name agents as a second operator kindalongside the human operator, give them a typed tool surface, and let the existing gates do their job.
The six ADRs below cover the substrate. Three runnable examples exercise it end-to-end — thepropose example(happy-path proposal pipeline), therefusal-recovery example(what happens when a tool's backend is structurally absent), and thefeature-search example(bounded discovery through the agent surface). All three are contract-tested in CI.
Operator(kind='human'|'agent') with a typed identifier. Agents must reference a registered model_artifact; the validator refuses construction otherwise.
Operator type + cited_artifacts: agent:<model>/<run_id> typed prefix
JSON-RPC 2.0 / MCP server over stdio. Read + bounded-discovery tools only. Write tools (register, promote, record_order) return 403 by construction.
McpServer + ToolRefusedError typed refusal contract
Three-axis score per agent run: citation quality (must be 1.0), reproducibility (Jaccard floor), backtest IC/Sharpe. Lands on BOOK_PROVENANCE_SCHEMA.verification_score.
AgentRunScore + classify_run_outcome → eligible/flagged/not_promotable/refused
Per-run token / wall / tool-call ceilings. Per-phase model routing (planning → Opus, tool_calls → Haiku, synthesis → Sonnet). Daily operator-level spend cap.
AgentRunBudget + project_run_cost_usd worst-case projector
Jaccard similarity between an agent's original and re-run drafts on three axes (features, citations, config). Below the floor → flagged for operator review.
compute_self_consistency + classify_prompt_injection
Backend-state absence is a typed refusal (no_state_root), never an empty result. Every tool publishes its refusal_reasons slugs in the catalogue — drift-tested against the handler source.
_open_book_state() + ToolDefinition.refusal_reasons
A proposal citing a slug the writing manifest doesn't resolve is rejected before any row lands. The most common hallucination shape — fabricated source attribution — is refused by construction.
features.materialize(as_of=...) refuses any timestamp greater than utc_now(). Agents cannot read future data; the data layer refuses, not the prompt.
A proposal's feature set / cited notes / inputs hash are re-generated at a higher temperature. Drift past the Jaccard threshold flips the outcome from eligible to flagged for operator review.
Agents write Provenance rows. They never call record_order. The ADR-0019 promotion contract still requires a named human approver to flip the live switch.
ADR-0041's bundled tool catalogue, snapshotted at build time from alphakernel.server.mcp.builtin_tools(). A Python drift test (test_catalogue_matches_live) keeps this list in sync with the live JSON-RPC server. Write tools (register,promote,record_order) are not on this surface — the agent reads broadly but writes only to its own leaderboard entry.
Execute a walk-forward backtest against a registered model + feature set (ADR-0005). Returns the EvalReport shape every discovery method emits (ADR-0023). Body lands in #IMPL-0041 follow-up; argument validation is live.
Roll up realised PnL bucketed by cited research note (ADR-0013/ADR-0014). Reads the BookState rooted at `$ALPHAKERNEL_BOOK_STATE_ROOT`; refuses with `no_state_root` when unset. Optional ISO-8601 since/until window bounded by the ADR-0016 bitemporal floor — since maps to the window start, until to as_of. Read-only. Returns rows sorted by absolute realised PnL descending.
List the most recent decision-provenance rows from the book (ADR-0011/ADR-0040). Returns each row's event_id, exchange_ts, strategy_slug, operator_kind, cited_artifacts. Reads the BookState rooted at `$ALPHAKERNEL_BOOK_STATE_ROOT`; refuses with `no_state_root` when the env var is unset. Read-only — agents cannot write provenance through this surface.
Search pre × post × reduction event-window configurations against a synth target panel + seeded synthetic event stream (ADR-0032 / ADR-0023). Reductions: indicator, count, mean_score, time_since_last. Ranks by Spearman rank-IC; candidates with fewer than min_events matches are excluded.
Randomised symbolic-expression factor mining over a synthetic L1 panel (ADR-0023). Builds short alpha expressions from `inputs` and ranks by |rank_ic| or signed rank_ic against `target`. Returns the top-20 leaderboard. Bounded by spec — over-wide requests refused at construction. MCP shares the HTTP service's threat model; CLI callers can use wider bounds via `FactorMiningSpec` directly.
Greedy forward-selection or exhaustive subset search over a pool of features against a default ridge/walk-forward Model (ADR-0023). Returns a leaderboard ranked by `objective`. The estimator + CV windows are operator-set (not agent-chosen); agents drop to the CLI to vary them. Bounded by spec — over-wide requests refused at construction.
Grid or seeded-random sweep over a default Model's hyperparameters (ADR-0023). Returns a leaderboard ranked by `objective`. Estimator + base CV are operator-set; agents drop to the CLI to vary the baseline. Seed + panel dims are fixed — agents only vary the listed dims.
Enumerate every registered feature in the alphakernel DAG (ADR-0006). Returns name, doc, lag, is_label, deps, content-addressed hash, license_tier, and namespace per feature. A non-empty namespace (e.g. 'kr') marks a feature outside the generic universe — generic callers should filter to namespace == '' unless they specifically want it.
Materialise a feature panel as-of a given timestamp (ADR-0016 PIT). Refuses as_of in the future. Body lands in #IMPL-0041 follow-up PR; currently returns a typed not_implemented refusal.
Return the latest immutable AI supply-demand thermometer print: two axes, categorical verdict, 0-100 diffusion heat index, agreement/dispersion, freshness, component provenance, and methodology hash (ADR-0100).
Fetch one writing entry by slug. Refuses unknown slugs with a typed error so agents cannot propose citations against missing or hallucinated notes (ADR-0015).
Enumerate every published research note (ADR-0012). Returns slug, content hash, status, title, published date, visibility, and superseded_by per entry — the same shape the ADR-0015 citation validator resolves against.
Refusal vocabulary followsADR-0050: every tool publishes the typedreason slugs its handler may raise, so a planner can branch on a known vocabulary instead of parsing prose. Absent backend = typed refusal (no_state_root), never an empty result.
Same tool, same arguments — the only difference between the two payloads is whether ${ALPHAKERNEL_BOOK_STATE_ROOT} resolved. The planner branches on _meta.reason, never on the message text or the absence of rows.
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"isError": false,
"structuredContent": [
{
"event_id": "0195a1c2-...",
"exchange_ts": "2026-05-21T09:14:30Z",
"strategy_slug": "xs_momentum_live",
"operator_kind": "agent",
"cited_artifacts": [
"note:walk-forward-without-leakage",
"agent:claude-opus-4-7/0195a1c2-..."
]
}
]
}
}{
"jsonrpc": "2.0",
"id": 1,
"result": {
"isError": true,
"_meta": { "reason": "no_state_root" },
"content": [
{
"type": "text",
"text": "ALPHAKERNEL_BOOK_STATE_ROOT is unset — the agent has no book to read."
}
]
}
}Rendered from theexamples/agent_propose_xs_momentum/sample_run*.json fixtures — actual output ofpython agent.py with pinned UUIDs and clock. A drift test (test_sample_run_matches_live) re-runs the agent against every pinned config and asserts bytewise equality, so this tile cannot fall behind the live code.
The agent self-reference closes the citation graph — every row this agent landed is queryable end-to-end via the typed prefix scheme (ADR-0030).
A no-op agent that walks the full pipeline. Run it from a clone:
$ python examples/agent_propose_xs_momentum/agent.py --out-dir agent_out
agent operator = claude-opus-4-7/01951b40-...
provenance event_id = 01951b40-...
cited artifacts = ('note:walk-forward-without-leakage',
'note:the-promotion-gate',
'agent:claude-opus-4-7/01951b40-...')
leaderboard entries = 1
book directory = agent_out
leaderboard json = agent_out/leaderboard.jsonOperator(kind='agent', model_artifact='claude-opus-4-7', identifier='<model>/<uuid7>') — pydantic validator refuses construction without a model artifact.
builtin_tools()['features.list'].handler({}) — same callable JSON-RPC tools/call dispatches to. No subprocess, no network.
builtin_tools()['writing.list_notes'].handler({}) returns every published slug. Cited notes outside that set raise ValueError before any write — ADR-0015 enforced at the tool boundary.
cited_artifacts = (note:walk-forward-without-leakage, note:the-promotion-gate, agent:claude-opus-4-7/<run_id>). One write, no orders. Promotion stays a human-only gate (ADR-0019).