ive harnessed the harness
klbr DOGFOOD.md
7.4 kB
Markdown
at main

dogfood runtime #

This checkout now has one normal execution path: the Rust runtime/store owns state and mechanisms, a persistent Python workbench supplies policy and self-hosted code work, and the browser uses only the v2 lane protocol. The legacy agent/memory/tool loop is not started. Configured Discord ingress is normalized into the same durable runtime admission path. The transport owns no inbox database or model loop, and outbound sends remain explicit host effects.

source checkout #

The commands below are argv commands and work from Nushell. They do not depend on Bash.

bun run --cwd klbr-web build
cargo run -p klbr-daemon --bin klbr-daemon -- init
cargo run -p klbr-daemon --bin klbr-daemon -- doctor
cargo run -p klbr-daemon --bin klbr-daemon -- serve

Open http://127.0.0.1:8765/. serve is also the default subcommand. The service publishes the editable behavior/ tree as an immutable content-addressed release before opening a session. It refuses a non-loopback bind. Browser WebSockets also reject non-loopback origins; native local clients do not need an Origin header.

Runtime paths are explicit:

variable default
KLBR_WORKSPACE current directory
KLBR_DATA_DIR the platform data directory plus klbr
KLBR_PYTHON python3
KLBR_WORKER_RUNNER <workspace>/python/klbr-runtime/src/klbr_runtime/__main__.py
KLBR_BEHAVIOR_DIR <workspace>/behavior
KLBR_SKILLS_DIR <workspace>/skills
KLBR_WEB_DIR <workspace>/klbr-web/dist
KLBR_BIND_ADDR 127.0.0.1:8765
KLBR_REMOTE_PLACEMENT unset; local workbench

The state directory contains runtime.sqlite, releases/, drafts/, artifacts/, and skills/. The old agent.db is neither opened nor seeded by this path.

Nix later #

Dogfooding is source-first for now. The repository's existing NCI/crate2nix-oriented flake is left intact; a relocatable package and service are deferred until the runtime path is settled.

working on this checkout from a cell #

print(await search("TODO", "klbr-runtime/src", max_results=20))
note = await open_file("docs/note.md")
await note.replace("old", "new")                    # one occurrence, or an error
check = await run(("cargo", "check", "-p", "klbr-runtime"), timeout=120)
print(check.exit_code, check.stderr.decode(errors="replace"))

open_file, ls, search, delete and run are bound in every cell and act on the session's workspace, resolved once per kernel generation (klbr.workspace.PRELUDE_NAMES lists them; ws = await workspace.current() is the object form). ls lists one level and ls(path, recursive=True, match="*.rs") walks with a name glob, and search reports truncated rather than letting a capped result read as a complete one.

Paths are root-bound and symlink escapes are rejected. workspace.run is a typed host RPC, not a shell call inside Python. The Rust host owns the process group, output cap, timeout, and cancellation cleanup, so kernel replacement cannot strand a build process.

skills #

Instruction bodies live in skills/<id>/SKILL.md. The first non-empty line is # <id> and the directory name must equal it; that title is the only schema, so the id is a handle and the body is the trail to the state the id names. Nothing is stored beside the body, because a description would become a second, staler definition of it.

import klbr.skills as skills

dir(skills)                 # the ids this kernel generation can import
help(skills.workspace)      # one body
skills.refused              # ids found and not offered, with the reason
skills.revision             # content identity of the whole offered set
skills.roots                # where those ids were read from

Builtin ids are read from KLBR_SKILLS_DIR (default <workspace>/skills), so they ship with the checkout. Managed ids are read from <data>/skills, which init and serve create; a skill the agent writes there survives a checkout move. Both are one namespace, and an id defined in both is refused instead of silently winning - klbr doctor prints the ids and the refusals, and the daemon logs the index at startup.

The host validates the roots and hands the bodies to the worker as a spawn payload, so a worker never reads a skills directory itself: one reader, one validator, and a set that cannot change under a running turn. The kernel generation's kernel.reset record carries the revision it loaded, so the durable log says which instruction set a turn could have read rather than what the directories happen to hold now.

The model sees the ids in its contract message and imports a body by id. A maintenance turn additionally loads maintenance by default. Because the set is fixed at spawn, a skill written during a turn is offered on the next generation rather than mid-turn.

operator path #

These commands use the same v2 socket as the UI and do not need a successful model or policy callback:

klbr status
klbr status default
klbr reset default
klbr compact default
klbr drain default
klbr stop default
klbr activate default 3 /absolute/path/to/releases/<sha256>

reset states explicitly that Python heap state was not restored. drain waits for accepted host work before stopping. Behavior activation is an epoch compare-and-swap against a verified content-addressed release.

model seam #

klbr-core still reads the existing KDL model configuration. Defaults target OpenAI-compatible local endpoints at http://localhost:8001/v1, :8002/v1, and :8003/v1. doctor validates the configuration and worker import path; a normal send is the live model/provider check. Provider keys belong in the process environment or the configured KDL file, not source control.

Prompt caching is a route property, declared per route and never guessed:

llm {
    url "https://api.example/v1"
    model "m"
    cache_profile "openai_prompt_cache_key"   // or openai_prompt_cache_retention | openai_prompt_cache_options
    cache_affinity "klbr:work"                // stable key: the same one for related requests, never per request
    cache_retention "24h"                     // only on a profile that accepts it: in_memory | 24h, or 30m on an options route
}

cache_profile is opt-in because /chat/completions is shared by many incompatible OpenAI-style routes, and an unknown route must not receive a field it may reject. A profile declares which cache fields that route accepts; cache_retention values are checked against it at parse time, so a mismatch is a configuration error here rather than a parameter error from the route after the request was paid for. Route extra_options cannot smuggle a cache field: cache_prompt, cache_control, prompt_cache_key, prompt_cache_retention and prompt_cache_options are removed from route options and only re-added by the declared profile.

Cache reads and writes are reported separately and are never inferred: a provider that reports nothing leaves the measurement absent, which is a different statement from a provider that reported zero. OpenAI's prompt_tokens_details.cached_tokens and cache_write_tokens, and DeepSeek's prompt_cache_hit_tokens / prompt_cache_miss_tokens, are all read; a route with an automatic cache (DeepSeek) needs no profile at all.

Rebuild the source UI after frontend changes with bun run --cwd klbr-web build.