Wiki Log¶
Append-only chronological record of ingests, queries, and lint
passes. Never rewrite history; append only. Entry prefix is
## [YYYY-MM-DD] <kind> | <title> so grep "^## \[" log.md
parses.
[2026-04-18] ingest | llm-provider contract v1 (issue #26)¶
Landed the production-grade llm-provider contract in
src/yaya/kernel/llm.py — mirrors kimi-cli's kosong.chat_provider
discipline. Ships the LLMProvider Protocol (streaming generate +
StreamedMessage + StreamPart union), the TokenUsage model with
Anthropic cache accounting (input_other + input_cache_read +
input_cache_creation → derived input; total = input + output),
the closed ChatProviderError hierarchy (connection / timeout /
status with status_code / empty), three lazy-import SDK-error
converters (openai_to_chat_provider_error,
anthropic_to_chat_provider_error, convert_httpx_error), and the
frozen RetryableChatProvider hook shape. Event catalog gains
llm.call.delta; LlmCallResponsePayload carries serialised
usage; LlmCallErrorPayload gains optional kind +
status_code. SDK-only rule enforced by extending
scripts/check_banned_frameworks.py with
check_llm_plugin_imports — rejects direct httpx / requests /
aiohttp imports inside src/yaya/plugins/llm_*/. Bundled
llm_openai + llm_echo stay on the legacy subscribe path
(migration is a follow-up, same discipline as tool_bash on the
tool-contract PR). Sidestepped the mypy/pyright asymmetry on
@computed_field+@property by using a model_serializer(mode="wrap")
instead — delivers the same JSON shape without the type-ignore tax.
See: ../../src/yaya/kernel/llm.py, ../../specs/llm-provider-contract.spec,
../../docs/dev/plugin-protocol.md, lessons-learned.md#21
[2026-04-18] ingest | mypy strict tightening (issue #40)¶
Baseline uv run mypy was already clean against strict = true +
every individual flag pinned (the heavy lifting landed in earlier
PRs). This pass tightens the bar further: enabled
disallow_any_unimported = true and the off-by-default
enable_error_code set
(redundant-expr, truthy-bool, truthy-iterable,
unused-awaitable, possibly-undefined, explicit-override).
All 34 source files still pass with zero new violations — the
codebase was already at this bar; pinning the flags keeps it there.
unused-ignore deliberately omitted: half the existing ignores are
guardrails for asymmetric mypy/pyright narrowing where mypy may
stop firing in a future version, and warn_unused_ignores plus
pyright's reportUnnecessaryTypeIgnoreComment = "error" already
cover real staleness. Audited every # type: ignore in src/ and
tests/ against lesson #21 — every one carries a specific code
suffix and now also a rationale comment.
See: ../../pyproject.toml, lessons-learned.md#21
[2026-04-17] ingest | Karpathy — LLM Wiki (gist 442a6bf)¶
Adopted the three-layer pattern (raw sources / wiki / schema) for
docs/wiki/. Added AGENT.md, index.md, this log, and
sources/karpathy-llm-wiki.md. Kept the existing
lessons-learned.md in place as the first lessons/* page.
See: sources/karpathy-llm-wiki.md, AGENT.md
[2026-04-17] ingest | spec migration — .spec.md → .spec (issue #52)¶
Ported kernel-bus-and-abi, kernel-agent-loop, and kernel-registry
from Markdown-with-Gherkin .spec.md files to the canonical
agent-spec .spec format (YAML frontmatter + structured Test:
blocks with Package: + Filter:). scripts/check_specs.sh now
lints all four kernel specs in CI; previously it silently skipped
three. Added lesson #19 (author in tool format, not protocol-doc
pseudo-format). Prose examples in AGENT.md, AGENTS.md,
CLAUDE.md, GOAL.md, docs/dev/architecture.md,
docs/dev/testing.md, and package AGENT.md files updated from
.spec.md to .spec.
See: lessons-learned.md#19, ../../specs/
[2026-04-17] ingest | BMAD-METHOD (bmad-code-org/BMAD-METHOD)¶
Adopted two disciplines: the 4-phase gate model (analysis → plan →
solutioning → implementation, gated on explicit artifacts) and
explicit HALT conditions (no stopping at "milestones" or "natural
breakpoints"). Both codified in docs/dev/workflow.md. Rejected
the full installer, XML workflow DSL, sprint-status YAML tracker,
party mode, and the BMB/TEA/BMGD/CIS sibling modules — out of
scope for yaya. Retrospective ceremony merged into the existing
Karpathy wiki lint operation; no duplicate cadence.
See: sources/bmad-method.md, ../dev/workflow.md
[2026-04-18] ingest | structured logging + error taxonomy (issue #30)¶
Landed src/yaya/kernel/logging.py and src/yaya/kernel/errors.py:
loguru-based stderr + rotated file sinks (10 MiB x 5 backups under
$XDG_STATE_HOME/yaya/logs/yaya.log), JSON-per-line mode on
YAYA_LOG_JSON=1, and a redaction filter scrubbing both secret-looking
keys (token/key/secret/password/passphrase) and inline secret
values (sk-..., Bearer ...). Stdlib logging records are routed
through a depth-walking InterceptHandler installed on the root logger so
third-party libs appear in the unified stream — the kernel modules keep
import logging + logging.getLogger(__name__) rather than importing
loguru directly. Plugins receive a pre-bound logger via
KernelContext.logger = get_plugin_logger(name) (typed Any to keep
loguru out of the Plugin ABI). Error taxonomy is closed: YayaError
→ KernelError/PluginError/ConfigError/YayaTimeoutError. The bus's
_report_handler_failure now annotates plugin.error payloads with
kind (exception subclass name) plus an 8-char error_hash from
sha1(traceback)[:8] so operators can dedupe noisy plugins in log scrapes.
CLI gains --log-level; the existing -v/-q flags still work but
--log-level wins. Hard stop avoided: did not shadow
builtins.TimeoutError (renamed to YayaTimeoutError) — too many call
sites catch the builtin with asyncio semantics in mind.
See: ../../specs/kernel-logging.spec, ../../src/yaya/kernel/logging.py, ../../src/yaya/kernel/errors.py
[2026-04-18] ingest | ordered config loading (issue #23)¶
Landed src/yaya/kernel/config.py: a pydantic-settings KernelConfig
that resolves settings in fixed order — CLI flags → YAYA_* env vars
(with __ delimiter for nested keys) → $XDG_CONFIG_HOME/yaya/config.toml
→ built-in defaults. Plugin sub-trees are accessed via
KernelConfig.plugin_config(name); the registry now feeds that sub-tree
into KernelContext.config so ctx.config is finally populated
(previously hard-coded to {}). Two pydantic-settings quirks worth
noting: (1) env_nested_delimiter only nests declared fields, so a
custom _NestedEnvExtras source lifts YAYA_<NS>__<KEY> for arbitrary
plugin namespaces into model_extra; (2) toml_file is bound inside
settings_customise_sources so tests can monkeypatch the module-level
CONFIG_PATH between instantiations. New CLI: yaya config show [--json]
prints the merged config with secrets (r".*(token|key|secret|password|passphrase).*")
redacted to "***".
See: ../../specs/kernel-config.spec, ../dev/architecture.md
[2026-04-18] ingest | kernel-bootstrap CLI commands (issue #15)¶
Landed the three remaining kernel-built-in CLI commands —
yaya serve, the rewritten yaya hello, and the yaya plugin
{list, install, remove} group — completing the 1.0 command surface
from docs/dev/cli.md. serve boots EventBus + PluginRegistry +
AgentLoop in-process, binds 127.0.0.1 only (no --host flag per
GOAL.md non-goals), picks a free port on --port 0, and opens the
browser only when a web-prefixed adapter plugin is loaded —
otherwise it warns via stderr and keeps the kernel up so yaya
hello still round-trips the bus. plugin install rejects shell
metacharacters before any subprocess via the registry's existing
_validate_install_source, refuses to prompt under --json (must
pass --yes), and honours --dry-run. plugin remove surfaces
the bundled-plugin ValueError as ok=false with a suggestion
pointing at yaya update. Signal handling uses
asyncio.add_signal_handler so Ctrl+C cleanly stops loop → registry
→ bus in that order.
See: ../../specs/cli-kernel-commands.spec, ../../src/yaya/cli/commands/serve.py, ../../src/yaya/cli/commands/hello.py, ../../src/yaya/cli/commands/plugin.py
[2026-04-18] ingest | seed plugins (issue #14)¶
Landed the four non-adapter seed plugins — one per category — to
prove the plugin protocol end-to-end against the kernel registry
that shipped in PR #49. Each plugin is a bundled subpackage under
src/yaya/plugins/<name>/ loaded through the same
yaya.plugins.v1 entry-point ABI as third-party packages. openai
is the only LLM SDK accepted per AGENT.md §4; tool_bash uses
asyncio.create_subprocess_exec exclusively (never shell=True);
memory_sqlite runs stdlib sqlite3 through asyncio.to_thread;
strategy_react implements the observe-think-act decision. Every
response event echoes request_id per lesson #15. Each plugin
ships a BDD .spec (0 WARN from agent-spec lint) + unit tests
under tests/plugins/<name>/.
See: ../../specs/plugin-strategy_react.spec, ../../specs/plugin-memory_sqlite.spec, ../../specs/plugin-llm_openai.spec, ../../specs/plugin-tool_bash.spec, ../../src/yaya/plugins/
[2026-04-18] ingest | bundled llm_echo dev provider (issue #24)¶
Shipped src/yaya/plugins/llm_echo/ so yaya serve round-trips the
kernel end-to-end without any API key — closes the 0.1 onboarding gap
exposed by the Playwright smoke (PR #74). The plugin filters
llm.call.request on provider == "echo" and replies with
(echo) <last user message> at zero token usage; sibling providers
coexist on the same subscription via the same payload-filter pattern
as llm_openai. Auto-selection lives in strategy_react:
_provider_and_model now sniffs OPENAI_API_KEY and falls back to
("echo", "echo") when the key is unset. Temporary env sniff with
TODO(#23) — provider-selection policy migrates to ctx.config once
the config-loading PR lands. Stdlib-only implementation; no LLM SDK.
See: ../../specs/plugin-llm_echo.spec, ../../src/yaya/plugins/llm_echo/, ../../src/yaya/plugins/strategy_react/plugin.py
[2026-04-18] ingest | banned-framework scanner (issue #33)¶
Operationalized AGENT.md §4 — the "no third-party agent frameworks"
rule was previously honor-system. Landed scripts/check_banned_frameworks.py,
a stdlib-only Python scanner that checks two surfaces: declared
dependencies in pyproject.toml ([project] dependencies,
[dependency-groups], [project.optional-dependencies]) and AST-walked
imports under src/ + tests/. Names are normalized per PyPA before
comparison so case + separator variants collide. Wired into pre-commit
(fires on pyproject.toml or **/*.py changes) and the Lint & type
check CI job. AST scan deliberately avoids regex so docstring / comment
mentions of a banned name don't false-positive (lesson 27 — Dependency
Rule classification is per-export, not per-library; this script is the
mechanical layer of the same idea applied to top-level distributions).
Known limitation: dynamic importlib.import_module(...) calls are not
caught (policy enforcement, not hermetic sandbox); transitive uv.lock
deps are deferred.
See: ../../scripts/check_banned_frameworks.py, ../dev/no-agent-frameworks.md
[2026-04-18] ingest | pi-web-ui landing (issue #66)¶
Replaced the 305-line vanilla-JS placeholder under
src/yaya/plugins/web/static/ with a Vite-built integration of
@mariozechner/pi-web-ui@0.67.6. Applied the Dependency Rule
strictly (lesson 27): whitelisted MessageList,
StreamingMessageContainer, Input, ConsoleBlock; blacklisted
every pi-web-ui export that assumes browser-owned agent / API
keys / session storage, plus all of @mariozechner/pi-agent-core
and @mariozechner/pi-ai. A Vite resolveId plugin redirects
pi-web-ui's side-effecting tools/index.js auto-register module to
a local no-op stub so the bundle no longer pulls provider SDKs,
pdfjs, lmstudio, or ollama. WebSocket protocol unchanged; TS frame
types mirror events.py with an exhaustive assertNever (lesson
19). CI gains a Web UI job that runs npm run check/test/build and
fails if static/ drifts.
See: ../../specs/plugin-web.spec, ../../src/yaya/plugins/web/, ../dev/web-ui.md
[2026-04-18] ingest | approval runtime PR #83 review fixes (issue #28)¶
P2/P3 follow-up on the merged approval-runtime PR. Three code fixes:
(P2-1) ApprovalRuntime.stop() now emits
approval.cancelled(reason="shutdown") for each pending approval
before resolving futures — the literal was previously unreachable.
(P2-2) Tool dispatcher widens its pre_approve try/except from the
two sentinel error types to except Exception, so arbitrary subclass
crashes translate to a terminal tool.error(kind="rejected") instead
of leaking through the bus recursion guard and orphaning the
originating tool.call.request. (P2-3) install_approval_runtime
now distinguishes "no timeout_s passed" from "default value passed"
via a sentinel and raises RuntimeError when re-installed with a
different override rather than silently dropping it. Doc notes added
for parallel-gated-call UI grouping (docs/dev/plugin-protocol.md
adapter responsibilities), non-JSON fingerprinting rationale, and the
single-event-loop invariant (approval.py module docstring). New
lesson #29 captures the rule: ALL pre-run checkpoint exceptions
must translate to a terminal tool.error so the caller's future
resolves. Tests: shutdown-cancelled-per-pending,
pre_approve crash → tool.error, install-with-different-timeout
raises, registry install-before-kernel.ready /
uninstall-before-kernel.shutdown ordering, cross-session
end-to-end round-trip (lesson #2 regression).
See: ../../src/yaya/kernel/approval.py, ../../src/yaya/kernel/tool.py, ../dev/plugin-protocol.md, lessons-learned.md
[2026-04-18] ingest | PR #62 follow-up — kernel-CLI lifecycle hardening (issue #84)¶
Address all 11 review findings from PR #62 (merged as 837e502). P1
fixes plug three lifecycle holes: (a) serve now installs the
SIGINT/SIGTERM handler before registry.start() so a Ctrl+C
during boot still hits teardown — KeyboardInterrupt is a
BaseException, not Exception (lesson #30); (b) hello.py and
plugin.py move bus / registry construction inside the try block
with started flags so a startup failure no longer leaks worker
tasks; (c) _run_package_command wraps communicate() in
except (CancelledError, KeyboardInterrupt): proc.terminate() —
asyncio.create_subprocess_exec does not auto-kill the spawned
child on cancel, so the previous code let pip keep mutating the
user's environment for minutes after Ctrl+C (lesson #31). P2 / P3
fixes: --strategy is now click.Choice(["react"]) so typos exit
2; validate_install_source drops the metachar blacklist (security
theater under create_subprocess_exec) and tightens the path
branch to is_absolute() only; webbrowser.open runs on the
default executor so macOS doesn't stall the loop; hello gains a
--timeout flag; emit_ok text is now optional and silent under
human mode for commands that render their own output. _pick_free_port
TOCTOU is acknowledged and deferred — port resolution moves to the
adapter plugin (#16) where a single owner can hold the bound socket
end-to-end.
See: ../../src/yaya/cli/commands/serve.py, ../../src/yaya/cli/commands/hello.py, ../../src/yaya/cli/commands/plugin.py, ../../src/yaya/cli/output.py, ../../src/yaya/kernel/registry.py, lessons-learned.md