leather

TGPSKI/leather

Local agent runtime in one stdlib-only Go binary.

v0.5.3 GPL-3 go1.26 examples pass

Scheduled jobs, one-shot runs, webhook-driven pipelines, tool calling, and auditable outputs. No Python stack. No hosted control plane. No broker, telemetry, or dependency pile. go.mod has zero require entries.

Runs on your workstation, server, or Raspberry Pi as a single local process. Loopback API by default.

binary
stdlib only — zero external deps
queues
backpressure, retries, dead-letter in-process
context
oversized inputs paged into bounded cuts
lineage
JSONL run history, deterministic replay
triggers
cron + HMAC-validated webhooks, one process
tools
YAML skillsets, stdio MCP, shell-mcp

Experiment 11 — high-volume CI under burst load

examples/11-high-volume-ci fires 100 simulated GitHub webhooks in bursts at leather serve. Each webhook gets its own single-use queue (queue_pattern), fans out to 3 parallel analysis agents, and fans back into a decision agent — 500 LLM jobs against a local vLLM endpoint (Qwen3.6-35B-A3B on one RTX PRO 4500, 20-core host).

completed
100/100 webhooks through every stage, 190s end to end
llm work
500 jobs · 965,437 tokens · 4,601 prompt + 453 gen tok/s sustained
host cpu
6.5% avg / 14% peak — orchestration for 500 LLM calls, in the noise
pressure
0% PSI stalls (cpu/io/mem); disk 2% util; leather writes peak 0.4 MiB/s
gpu
79.7% avg / 100% peak util — the pipeline is GPU-bound end to end
failures
0 — no retries exhausted, no dead-letter events

Wall time scales with model throughput, not with leather. Tuning the agents — thinking: false plus completion_reserve: 768 — took a 40-webhook burst from 323s with intermittent failures to 62s with none: a 5.2× speedup. The load campaign also surfaced the five concurrency bug classes fixed in v0.4.0 (shared-state mutation, lost retries, dropped fan-in groups, reasoning-model flakiness, fan-in hangs).

WEBHOOK_COUNT=100 BURST_SIZE=25 BURST_DELAY_MAX=0.5 \
  ../scripts/profile/profile-run.sh make 11  # reproduce, from examples/

Experiment 14 — how much accuracy lives in the runtime

examples/14-sig-triage assigns a Kubernetes SIG to issues that have none, on a model that fits a 6 GB laptop GPU. The same frozen 4B (Qwen3-4B-Instruct, AWQ) scores anywhere from 59.6% to 81.6% on a 250-issue gold corpus depending only on what the runtime puts around it — the weights never change, the hardware never changes. Twenty-two points of accuracy live in the runtime design.

corpus
250 real issues · tiered smoke/acceptance/holdout · adjudicated gold labels
campaign
102 archived cells across two model scales · six contrasts pre-registered, frozen by commit, replicated
spread
59.6% → 81.6% on one frozen 4B — runtime design is the only variable
largest lever
explicit domain rules in the prompt, +12.8
what helped
task before reference +6.5 · raw notes over a distilled shortlist across context clears +6.9
what hurt
one extra decomposition turn −5.2 · per-turn clears −11.6 · a fresh-session queue hop −14.9

Every figure is a paired comparison on identical issues, scored by the harness that ships in the example — run manifests, evidence archives, replayable analysis, a measured noise floor, and two quarantined wrecks with post-mortems. Six contrasts were pre-registered and frozen by commit before any confirmatory cell ran; five of six survive an issue-clustered permutation test under Holm at α=0.05, and the sixth (richer retrieval payload, +3.0) closed unresolved by signed amendment. Replication moved two effects against the author's interest — depth −9.2 → −5.2, payload +6.4 → +3.0 — and the corrections are left visible. Two findings shipped back into the runtime in v0.5.0: per-turn clear: true (bounded context without losing the agent across the boundary) and recoverable refusal of out-of-scope tool calls (one small-model failure mode dead-lettered 214/250 issues before the fix). A third followed in v0.5.1: the eval's greedy-decode workaround (temperature: 0 silently required in two places) became a runtime fix — explicit zero is respected from a single setting, and leather doctor now reports true per-key config provenance.

make 14       # run the example, from examples/
make 14-eval  # the harness: corpus → ablation arms → paired verdicts
install
go install github.com/TGPSKI/leather/cmd/leather@latest
go install github.com/TGPSKI/leather/cmd/shell-mcp@latest
leather --version  # no LLM endpoint required
write an agent

An *.agent.md — front matter declares identity, body is the system prompt.

---
name: summarizer
---
You are a concise planning assistant. Output bullet points only.
schedule it

*.lifecycle.yaml carries the when and how.

agent: summarizer
schedule: "0 9 * * *"
model: qwen3
prompt: Summarize the three most important things to do today.
run
leather validate
leather run ~/.leather/agents/summarizer.agent.md
leather serve --pretty --stats

Curings

A curing binds one agent to one input queue. Compose pipelines by writing one curing's output into the next curing's input queue — all under leather serve, dispatched by HMAC-validated webhook. Raw inputs arrive as hides, get sliced into bounded cuts, and finished outputs are promoted to artifacts with full lineage.

Add a tannery.yaml and leather serve stands up HMAC webhooks, content-addressed hides, and artifacts queryable over the HTTP API.

leather defends the host serving this page. A scheduled ban-sync agent runs every 2 hours: it plans nftables bans from live traffic, deploys the set, and verifies it — one agent, one linear tool sequence, no offset crons.

Plans are compared by set_hash — sha256 of the sorted IP list, not a ban count — so two plans with the same count but different members never read as “no change.” Verification is a tool call, not narration: the agent SSHes to the server, reads the live nftables sets back, and can only report what the tool printed. State advances only after a verified deploy; a failed cycle retries in full next run. The deploy-capable skill is held by ban-sync alone — debugging agents get a separate read-only skill.

action: deployed
verified: true
reason: set_hash changed from previous state; deployed and verified against the remote host