TypeSafe API key via chezmoi

To add a new API key managed by chezmoi without committing it to the dotfiles repo:

  1. Add [data.<scope>] <key> = "<value>" to ~/.config/chezmoi/chezmoi.toml (private config)
  2. In the dotfiles source dir (~/para/areas/dev/gh/ak/dotfiles/), rename the shell config to .tmpl
  3. Use {{ .<scope>.<key> }} template syntax in the file
  4. Run chezmoi apply

CI Release Pipeline Gotchas (charly-vibes suite)

--locked breaks with newer CI Rust

CI runs Rust 0.98.0 (or whatever latest stable is), which expects a different Cargo.lock format than local 1.93.x. Fix: Remove --locked from build steps in release.yml. Keep it in CI.yml (no cross-compile there).

cargo publish dirty directory

Cargo.lock gets regenerated by CI's different Rust version. cargo publish checks working dir is clean. Fix: Add --allow-dirty to cargo publish step AND add include field to Cargo.toml to control what gets packaged.

include field required

Without include, cargo publish packages everything in the repo (beads, openspec, .wai, etc.). Wai was packaging 998 files / 68MiB. Fix: Add include = ["src/**/*", "/build.rs", "/Cargo.toml", "/Cargo.lock", "/LICENSE", "/README.md"] to Cargo.toml. Note: If the crate has a build.rs, it MUST be in the include list.

Binary names vs crate names

Espectacular's Cargo.toml has [[bin]] name = "ah" (not espectacular). The release.yml was trying to copy a non-existent espectacular binary. Fix: Only copy actual binary names from target/release/.

Homebrew/Scoop tap updates

The release workflow's homebrew/scoop update steps fail if TAP_GITHUB_TOKEN secret isn't set. This is non-critical — the GitHub release and crates.io publish are the important parts.

pi install/update does not re-sync flat-skills directories

pi install and pi update only update the npm package in .pi/agent/npm/node_modules/. They do NOT re-copy skills to flat-install locations (~/.agents/skills/, ~/.config/agents/skills/). Any npm package that provides skills via both pi's pi-package/skills/ AND a flat-install path MUST handle re-sync in its own postinstall script. For incitaciones, scripts/generate-pi-resources.mjs includes reSyncFlatInstallSkills() for this purpose.

mise built-in plugins can override custom mise.toml config

If mise.toml has a custom backend tool entry with URL templates (e.g. Julia with url = """..."""), mise may ignore the custom template and use its own built-in plugin's URL resolution. This can produce wrong download URLs (e.g. 1.11.9/ instead of 1.11/). Fix: Remove the tool entry from mise.toml and install it via an alternative mechanism (e.g. GitHub Actions' setup-julia, setup-dotnet, etc.).

OpenRouter Free Models for pi Coding Agent

Free models available (21 as of 2026-09-03)

Rate Limits (from OpenRouter docs)

Creating a Free-Models-Only API Key

Pi Usage

Session Economics

Beads (bd) batch issue creation — workflow gotchas

bd create -f <file> is the batch path; --stdin ignores some flags

bd create -f file.md parses ## Issue Title sections and is the reliable batch path. The --stdin / --body-file - alias reads the description from stdin but does honor -t/-p/-l/--silent flags — useful when a Python driver sets metadata per-ticket.

bd list defaults to 50 open issues — use --all --limit 0 for full set

bd list --json silently caps at 50 open issues. To enumerate every issue (including closed, or beyond 50) for metadata analysis: bd list --all --limit 0 --json.

Markdown batch format: **Field:** not **Field**:

In bd create -f, frontmatter (--- block) sets type/priority/labels. The **File:** / **Files:** label convention puts the colon inside the bold markers (**File:**), not after (**File**:). Regex for parsing must match \*\*Files?:\*\*.

Severity labels can silently drop on P3 (LOW) issues

Creating 14 dont issues with -l severity:low,...: the 5 LOW-severity (P3) issues lost their severity:low label while the other 9 kept theirs. Label propagation has a quirk at P3. Mitigation: after batch creation, verify labels with bd list --json | jq and re-add any dropped: bd label add <id> severity:low.

metadata.files must be set explicitly — body text doesn't count

bd create does not parse file paths from the description body into structured metadata. For file-based queries (bd list --file) and shared-file analysis to work, set metadata.files after creation: bd update <id> --metadata '{"files":["src/main.rs"]}' (JSON object form, not --set-metadata 'files=["..."]' which stores as a string).

bd close needs no flag; --note isn't a close flag

bd close <id> closes with status closed. To record a reason, use --reason <text> or add a bd comment <id> <text> separately. --note is not a close-flag.

bd dolt push requires a configured remote

bd dolt push fails with "Supported remote URLs..." if no remote is set. Add one per repo: bd dolt remote add origin git+ssh://git@<host>/<org>/<repo>.git. Push can fail with divergent-history on repos that had independent bd init — use bd bootstrap to clone instead in that case.

bd create --description with backticks breaks shell interpolation

Backticks in an inline --description="..." trigger shell command substitution and silently corrupt/abort the ticket. Use --body-file <path> (or --stdin) to pass descriptions containing code, paths, or backticks. Environment-variable expansion ($VAR) in inline descriptions is also unsafe.

Re-triggering a tag-pushed publish workflow after a CI fix

If a tag-triggered GHA publish workflow (e.g. on: push tags) fails because of a CI config bug, fixing the workflow on main does NOT fix the failed run — the workflow checks out the tag ref, not the branch. The tag must be moved to the fixed commit and re-pushed:

git tag -d v0.6.0
git push origin :refs/tags/v0.6.0
git tag v0.6.0
git push origin v0.6.0

This re-triggers the publish workflow against the fixed tree.

WeasyPrint RecursionError on truncated email HTML — use DOM-level truncation

Email HTML (Mailchimp/Drip templates) is deeply nested tables; truncating by regex and hand-appending guessed closing tags (</td></tr></table></div>) leaves unbalanced nesting that crashes WeasyPrint with RecursionError in cffi. Instead parse with BeautifulSoup and truncate structurally: find the signature text node, then for each ancestor up to the document root extract() all next_siblings. Always verify post-cut text is free of boilerplate markers before rendering.

The incitaciones installer (install --global) rewrites skill files including provenance injection, and it followed symlinks into the journal repo's resources/skills/, clobbering content with generated output. Rule: after running any installer/tool that mutates skill directories in place, immediately git status the source repos behind those symlinks and revert unintended writes. Prefer point-installers at explicit targets over blanket --global re-installs when symlinked sources exist.

Documentation Drift Prevention

Markdown code fences in docs are never compiled — they drift silently when APIs change. For libraries with copy-paste onboarding snippets, add a CI-compiled mirror test (test file mirroring each snippet, or rustdoc no_run doctests + mdbook test). Coverage must be verifiable: every mirror test maps to a doc location, and a "forbidden patterns" guard fails when removed/renamed API call-shapes reappear in docs. Keep the forbidden list in a human-maintained doc (markdown table), not in the test file — humans update docs, CI consumes them. When bumping a crate version, grep docs for the old pin in the same commit.

Mermaid version pinning for self-contained HTML reports

Mermaid v10+ ships ESM-only builds; <script src="mermaid.esm.min.mjs"> from a file:// page is blocked by CORS in Chromium, so diagrams silently don't render. Fix: vendor the Mermaid v9.4.3 IIFE build (mermaid.min.js, global mermaid) next to the report and reference it with a relative path. Keep the raw Mermaid source in a <details><pre class="mermaid-source"> fallback so nothing is blank without JS. (Established in incitaciones codebase-cartography HTML export spec, 2026-09-01.)

Journal repo: parallel-machine sync on log files

The journal repo gets commits from multiple machines; git pull there often rebases a long unpushed local history onto remote and hits AA/UU conflicts in log files (both machines appended different sessions from an empty base; git rerere preimages may auto-replay stale resolutions). Fix: for each conflict, concatenate both sides' session entries under a single ## Log heading (keep one copy of each ### Session: block), git add, GIT_EDITOR=true git rebase --continue. Check for duplicated session blocks before committing — rerere can glue content weirdly.

hljs CDN "common" bundle coverage — verify per language

The highlight.js CDN highlight.min.js (common bundle) is NOT a fixed superset to assume from memory — it omits julia, clojure, and many others, while toml resolves via the ini grammar's alias and html/svg via xml. Fix: after the bundle, load <script src=".../build/languages/<lang>.min.js"> for each missing grammar (self-registers into the global hljs); verify with hljs.getLanguage(l) per rendered language in headless chromium. (Established in michael site, 2026-09-04.)

hljs 11 re-highlight skip — clear the marker

hljs.highlightElement() sets data-highlighted="yes" and silently no-ops on subsequent calls to the same element (even after textContent is replaced). Fix: el.removeAttribute('data-highlighted') (+ data-hljs for older versions) before re-highlighting. Symptom: "syntax highlighting lost after switching languages."

flatpak Chromium headless — /tmp inaccessible, screenshots land in $HOME

flatpak run org.chromium.Chromium --headless cannot read file:///tmp/... test pages (sandbox) and a --screenshot=foo.png relative path may write to $HOME. Fix: place test HTML + assets in $HOME, use absolute output paths, and use the real home prefix in file:// URLs (e.g. file:///var/home/<user>/..., not /home/<user>). --dump-dom + --screenshot + --window-size is a fast zero-setup way to verify deployed public pages (deploy timing, cache staleness, CTA fold position).

Multi-part edit calls are atomic — verify every edit landed

If one edits[] entry fails to match, none of the edits apply; a later call re-applying only the failed item silently leaves the others out. Symptom: commit message/notes claim a change that isn't in the file. Fix: treat any partial-match error as "all-or-nothing," re-apply the full set, and grep the actual file for every intended change before claiming completion. (Established incitaciones, 2026-09-05: report-template.md macro-mode edit silently dropped, caught only by a post-commit review.)

skills CLI (Vercel agent-skills) — flag semantics

npx skills add <owner>/<repo> installs interactively; the real flags are -a agent1,agent2 (agents, comma-separated), -s skill1,skill2 (skills), -g user-level, --copy instead of symlinks, -l list-only. Trap: -l prints an "installing non-interactively" banner yet performs no install — it is safe read-only. Skills land as symlinks into each detected agent's directory (e.g. ~/.claude/skills); default scope is the current project, not global. (Verified 2026-09-05 against skills add --help while documenting PhormaSci/skills — the README flags I had written from memory were wrong.)

Acceptance-test skill/reference code blocks verbatim, in a clean shell

Skill files are agent-executed code — before committing one, extract the block verbatim from the file and run it under env -i HOME="$HOME" bash -c '...' (or with the target repo's real state). Two broken implementations of the same resolver passed casual review and were only caught by this: a sed comment-strip that couldn't handle column-aligned inline comments in .bashrc, and JOURNAL_PATH="$JORNAL" evaluating empty because the referenced var doesn't exist in non-interactive shells. Method: sed -n '/start/,/end/p' file | sed 's/^ //' → source it. (Established incitaciones close-skill work, 2026-09-05.)

Never reimplement a shell parser — source the config in a subshell

When a script needs values from user shell config (.bashrc etc.) and the shell is non-interactive, don't grep+cut+sed the file: comments, column alignment, quotes, and $VAR indirection each break naive parsing. Instead bash -c 'source ~/.bashrc >/dev/null 2>&1; printf "%s" "${'"'$1'"'}"' — the shell itself resolves comments, quotes, and nested indirection in one step, and sourced vars arrive fully expanded. (Established 2026-09-05; supersedes any grep-based config-reading attempt.)

Eval harnesses: run baseline AND treatment, score the delta

When evaluating a prompt/skill kit, every case must run twice — with the kit and with an inert baseline (--no-skills) — same model, same sandbox. Absolute pass/fail is misleading: strong models pass without the kit (it's dead context weight for them), weak models fail with it. The meaningful output is the per-model effect size. (Established fck eval work, 2026-09-09; kit effect was +3.1 on sonnet but ≈0 on deepseek/gpt-5.6-luna.)

yt-dlp playlist workflows: -J for metadata, IDs as the only join key

For playlist harvesting: yt-dlp -J <playlist-url> gives full metadata (views/likes/descriptions) in one JSON; --print templates break on null-like values (like_count prints unquoted NA, invalid JSON). Two traversals of the same playlist (metadata dump vs subtitle download) can label indices differently — join artifacts by video ID in brackets, never by position. Auto-caption VTTs are rolling: dedupe by keeping the last line of each cue body, not consecutive-line dedup. (Established microdancing conference-corpus work, 2026-09-15; 371 talks harvested.)

Pre-register hypotheses (checksummed) before blind content extraction

When doing content analysis where the requester has suspicions, have them write hypotheses to a file, checksum it, and extract without reading it; score supported/refuted/no-signal afterward. Prevents contamination both ways — the extraction stays honest AND the requester's pet theory gets falsified, not confirmed. (Established microdancing conference analysis, 2026-09-15.)

Concurrent sessions in one repo: sync via the wire protocol, never fight the worktree

Before pushing to a repo, check for an active concurrent session (shifting branches, in-flight uncommitted edits, failing pre-push hooks on files you didn't touch). If one exists, stop touching the worktree — the issue tracker's own sync (e.g. bd dolt push to refs/dolt/data) is the real wire protocol; passive exports like issues.jsonl re-export commits can wait for the other session's push. Retrying pushes against a moving worktree only creates interleaved broken states. (Established dont/whisper session, 2026-09-15.)

chezmoi non-interactive triage: diff direction + template attr + force

chezmoi diff direction is a=live file, b=desired (what apply would write) — the opposite of intuition; verify with chezmoi cat <target> when deciding which side is truth. Drift resolution per file: hand edits newer → chezmoi add <target>; source newer → chezmoi apply --force <target> (TTY-less sessions block on "changed since last written" prompts otherwise). chezmoi add on a .tmpl entry prompts to drop the template attribute — instead edit the template content directly to preserve {{ ... }} constructs. (Established dotfiles session, 2026-09-16.)

Local machine (suipacha, Bluefin)

Jellyfin media server

2026-09-30 — OpenRouter z-ai/glm-5.3-flash mode collapse

z-ai/glm-5.3-flash is routed through 33+ providers on OpenRouter with different quantization levels (fp4→fp8). During peak US hours (8AM-9AM ET = 9AM-10AM ART) Z.AI's native API congests and OpenRouter falls back to cheap fp4 (4-bit) providers. At fp4, the model's attention mechanism collapses under complex tool-use + high thinking load, producing streams of random words (mode collapse).

Symptoms: Assistant message starts with plausible technical jargon, then free-associates into random words (hospital beds, diseases, etc.), model self-identifies drift ("Wait — I notice I'm drifting again") but can't stop. OpenRouter eventually aborts with "Operation aborted."

Diagnosis:

curl -s https://openrouter.ai/api/v1/models/z-ai/glm-5.3-flash/endpoints | python3 -c "
import json,sys
for ep in json.load(sys.stdin)['data']['endpoints']:
    n=ep['provider_name']; q=ep.get('quantization','?')
    u1=ep['uptime_last_1d']
    if n == 'Z.AI' or 'fp4' in q:
        print(f'{n:20} {q:6}  1d={u1:.1f}%')
"

If Z.AI uptime < 99% or fp4 providers degraded, switch model.

Safe hours (ART): 10AM (0.25% failure), 12PM (0.26%), 7PM (0.38%) Risky hours: 9AM (peaked at 13% on Sep 30), 2PM (1.78% baseline) Alternative: deepseek/deepseek-v4-flash routes through providers with fp8 minimum quality floor.

borradores justfile (note-taking)

**just note-stdin <cat> <title> can fail with categoria: unbound variable when invoked from agent shells (set -u + just interpolation quirk). Fallback that always works: write the .org file with #+TITLE:/#+DATE: frontmatter directly into the category dir (exploraciones/, machetes/, etc.), then just --justfile ~/para/areas/dev/gh/ak/borradores/justfile sync "<commit msg>". Filename = kebab-case title + .org.

TypeSafe systemone API request shape (jev)

Ticket lane × complexity routing (canonical as of incitaciones 0.12.0)

Beads tickets carry afk/design/hitl/review + complexity:s/m/l/xl labels. afk = safe to run autonomously (requires an objective gate — gateless repos get no-gate instead). Grade gate: S/M tickets touching public API/invariant/concurrency code are mis-tagged → retag L; L fully covered by existing pattern+test → retag down. Escalation is a retag: 2 failed attempts → complexity+1; semantic failure → lane=design. Schedulable filter: bd list --label afk --label complexity:s. Full criteria in incitaciones content/distilled/issues/references/create-issues/SKILL.md v1.5.0.

Model routing policy (charly ecosystem, 2026-10)

GLM-5.3-flash (OpenRouter, ~$0.003/msg) is the default for known-shape implementation. Escalate to gpt-6.1-sol (Codex plan) for debugging/CI/design — unknown-shape work should always try GPT first since the cap is fixed-price. Astra-xhigh reserved for semantics work (dont/wild/specodelic). Luna for GPT-side batch chores. Baseline facts mined from pi sessions 2026-10-06.

Autonomous orchestration with wai pipelines (orchestrator + subagent pattern)

Reference implementation: specodelic repo (.wai/resources/pipelines/epic-orchestrator.toml, .wai/resources/templates/subagent-brief.md, scripts/epic-status.sh, specodelic-k9v). Established 2026-10-07 after the 2026-10-06 session proved the pattern works (2 tickets landed) but needed 5 user corrections in the first hour.

The rule: orchestration must be tooling-enforced, never improvised mid-session. An orchestrator pipeline whose steps ARE the delegation loop; executor pipelines (tdd-ro5) run INSIDE the subagent. Five failure modes, each with its enforcement point:

  1. Inline-execution drift — the lead starts doing pipeline steps itself instead of delegating. Enforcement: orchestrator step prompts state "you orchestrate only — the lead never edits code in this run".
  2. Ephemeral subagents (--no-session) — usage/cost records lost forever, session undiscoverable. Enforcement: spawn contract in a step prompt: pi -p "$(cat brief)" + -n "subagent:<ticket>:<steps>", never --no-session.
  3. Ticket-boundary chaining — agent continues to the next ticket unprompted. Enforcement: final step is literally stop — "one ticket per run, do not chain; name the next candidate but do not start it".
  4. State invisibility — human has to ask "is X done?". Enforcement: a just epic-status-style snapshot reading .wai/pipeline-runs/*.yml (current_step vs step count in the TOML), bd in-progress, unpushed commits, plus a session audit flagging subagent-shaped runs (# Task: first message) that carry no -n "subagent:" name.
  5. Pipeline bookkeeping bypassed — subagent narrates steps and hand-edits the run YAML (skipped wai pipeline next, bumped current_step 4→8). Enforcement: brief template says "advance ONLY via wai pipeline next, never hand-edit run YAML"; orchestrator verify step checks the run file reached its final step and that artifacts match the claimed steps.

Per-ticket brief overhead: keep shared boilerplate (repo facts, ratchets, commit hygiene, scope-guard shape, report format) in one template; per ticket fill only task/scope placeholders. The subagent report format ("Commits / Gates run / Deviations / Next") makes orchestrator verification diffable against reality.

pi session-dir naming for status scripts: cwd with leading / stripped, /→-, wrapped --…-- under ~/.pi/agent/sessions/.

Git identity fallback ordering (dotfiles)

A [user] fallback in ~/.gitconfig MUST precede [include] ~/.config/git/private: git processes includes at the point they appear and later assignments win, so a fallback placed after the include silently overrides every profile identity. Verified both branches (ak profile → akielbowicz; unmatched repo → fallback). Dotfiles commit 39f6431.

Per-account ssh profiles need IdentitiesOnly yes

When adding a new GitHub-account ssh profile (~/.ssh/profiles/<alias>), always set IdentitiesOnly yes alongside the IdentityFile. Without it the ssh-agent's loaded keys are offered first and ssh -T git@<alias> can authenticate as the WRONG account (observed 2026-10-13: new at/arltificial profile answered as akielbowicz via an agent key). Existing profiles (ak, ch, …) predate this and work because the agent usually isn't loaded with those keys — don't rely on that.

Onboarding a new GitHub account profile = 5 touchpoints

Per-account GitHub identity on this machine spans FIVE files; missing any one causes silent wrong-account behavior. For a new dir ~/para/areas/dev/gh/<dir>/ with alias <alias> and GitHub login <account> (all paths relative to $HOME):

  1. ~/.ssh/id_<alias> — ed25519 keypair (no passphrase, machine-local)
  2. ~/.ssh/profiles/<alias> — Host <alias> → ssh.github.com:443 + IdentityFile + IdentitiesOnly yes (see rule above)
  3. ~/.config/git/private — [url "git@<alias>:<account>/"] insteadOf rewrites + [includeIf "gitdir:…/gh/<dir>/"] → profiles file
  4. ~/.config/git/profiles/<alias> — user.name/user.email (emails live here only, untracked)
  5. ~/.config/nushell/private.nu — PWD env_change hook branch: gh_base/<dir> → $env.GH_TOKEN = (gh auth token -u <account>)

Gotchas for touchpoint 5 (bit us 2026-10-13, gh/at — agent did 1–4, missed 5):

Verification checklist (run in the new repo):

Git semantics gotcha

[include]-file values are inserted at the include point; assignments later in the including file override them. When testing override chains, test BOTH branches — a lazy test (same layout twice) let the broken ordering slip through once.