Put Piggy on Prime Agent, and let it write to the book
CI / verify (push) Successful in 7m6s
CI / publish (push) Has been skipped

Piggy was a hand-rolled OpenAI tool loop. It is now a Prime Agent session —
Prime Intellect's own harness, embedded as a Node library — answering from
PIG's tools and, for the first time, able to put information into the CRM
rather than only read it out.

The harness is a coding agent, so the first job was taking the coding agent
away from it. `noTools: 'all'` plus an explicit allowlist leaves the model
with PIG's ten `pig_*` tools and no bash, no filesystem, no IPython. That
holds under attack: a hostile extension, a skill and a settings file planted
in the agent's own directory, then `setActiveToolsByName` called with every
built-in, still leaves ten tools, all ours. Both lines are load-bearing —
`noTools` alone registers nothing, and the allowlist is what admits our own.

Writing is gated rather than assumed. A change is proposed, not made: the
tool returns a description, the transcript renders a diff card, and nothing
reaches the database until someone presses Apply. Contracts, commitments,
allocations and compliance always stop for a human whatever the mode. Every
write runs through `executeMutation` as the calling user, so their
capabilities and the audit trail apply exactly as they would to a human's.

Four things about the SDK are wrong in its own documentation and cost a
debugging cycle each: models.json does not resolve an env var name for
`apiKey`, it sends the literal string; there is no built-in prime-inference
provider in 0.84.1; a ResourceLoader you pass in is never reloaded for you;
and the stock system prompt is a coding-assistant prompt that must be
replaced — but replacing it also silently removes the tool list, because the
harness only renders that section when it owns the prompt. AGENTS.md records
all four.

The expensive one was thinking level. The harness defaults to `medium`, and
nemotron spent an entire 4,096-token budget reasoning and returned an empty
answer. `low` was worse; `off` omits the parameter so the endpoint's default
wins. An explicit `reasoning_effort: none` via `thinkingLevelMap` took a turn
from 6,195 output tokens to 149.

And a turn is now bounded. The harness loop is `while (true)` with no
iteration cap; a runaway on a frontier model would have eaten the credit it
is supposed to report on. Ceilings on model calls and tokens, enforced both
through the harness hook and independently from the event stream, plus a
per-user daily spend limit — and the ledger now records spend on turns that
fail, which it previously discarded.

Signing in lands on /piggy, which is a workspace: conversations down one
side, the agent in the middle, what it did and what it cost beside it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
claude
2026-08-14 05:26:28 -07:00
parent 99d165b5e5
commit f0173440e4
77 changed files with 28108 additions and 1672 deletions
+115 -17
View File
@@ -111,11 +111,31 @@ PIG_INVITE_CODE=
# under the old key has to be entered again.
PIG_SETTINGS_ENCRYPTION_KEY=
# --- Prime Intellect compute API -------------------------------------------
# Used to sync GPU availability into `inventory_listings`.
# Mint a key at https://app.primeintellect.ai/dashboard/tokens with the
# NARROWEST scope that works: `Availability -> Read`. PIG never provisions
# infrastructure and must not hold a key that could. Set an expiry.
# --- Prime Intellect API key ------------------------------------------------
# ONE key, two consumers, and it is worth knowing both before you scope it:
#
# - the API syncs GPU availability into `inventory_listings` from
# api.primeintellect.ai;
# - Piggy calls models on api.pinference.ai, which bills the same account.
#
# Mint it at https://app.primeintellect.ai/dashboard/tokens with the NARROWEST
# scope that works: `Availability -> Read`, plus inference if Piggy is on. PIG
# never provisions infrastructure and must not hold a key that could. Set an
# expiry.
#
# Piggy accepts PIGGY_INFERENCE_API_KEY as an alias for this value, so a .env
# written before Piggy moved onto Prime Inference keeps working untouched. They
# are the same key now; set one of them, not two different ones.
#
# SET IT OR COMMENT IT OUT — do not leave it blank once Piggy is on. Blank is
# harmless to the API, which treats it as absent, but the piggy container is
# handed the empty string and Piggy's config refuses it:
#
# Invalid Piggy configuration:
# PRIME_API_KEY: String must contain at least 1 character(s)
#
# ...followed by a crash loop. The same applies in reverse to the alias below.
# Measured, not theorised: an empty line is not an absent one.
PRIME_API_KEY=
PRIME_API_BASE=https://api.primeintellect.ai
# Rate limits are undocumented upstream; the sync backs off empirically.
@@ -123,14 +143,23 @@ PRIME_SYNC_ENABLED=false
PRIME_SYNC_INTERVAL_MINUTES=30
# --- Piggy (the in-app agent) ----------------------------------------------
# Piggy drains a leased queue and serves chat on an authenticated internal
# listener. Generate one internal token and give the same value to API + Piggy.
# Never publish the Piggy listener or put this token in a URL.
# Piggy is a Prime Agent session — Prime Intellect's own agent harness, run as
# a library inside PIG — holding PIG's CRM tools and NOTHING else. The harness
# is constructed with every built-in tool disabled and an explicit allowlist on
# top, so the model has no shell, no filesystem and no Python; the running tool
# list is compared with the allowlist at session start, and a mismatch is a
# startup failure rather than a surprise.
#
# It drains a leased queue, serves chat on an authenticated internal listener,
# and — new, and the reason the settings below matter — it can WRITE to the CRM.
# Generate one internal token and give the same value to API + Piggy. Never
# publish the Piggy listener or put this token in a URL.
#
# THREE keys turn the agent on, and all three are required together:
#
# PIGGY_ENABLED=true the API offers the chat surface
# PIGGY_INFERENCE_API_KEY the model credential, held only by Piggy
# PRIME_API_KEY the model credential (see above; Piggy also
# accepts the legacy PIGGY_INFERENCE_API_KEY)
# PIGGY_INTERNAL_TOKEN 32+ characters, the same value for API and Piggy
#
# (PIGGY_INTERNAL_URL is the fourth thing the API needs, and docker-compose.yml
@@ -144,14 +173,24 @@ PRIME_SYNC_INTERVAL_MINUTES=30
# below has a working default and exists to be lowered.
#
# EVERY Piggy setting is read from this environment ONCE, at Piggy's boot. None
# of it is admin-selectable at runtime: changing the model or a budget means
# editing this file and restarting the container.
# of it is admin-selectable at runtime: changing a budget, a mode or the DEFAULT
# model means editing this file and restarting the container. The one thing a
# user picks for themselves is which model answers a given conversation, and
# even that is a choice between the five in apps/piggy/src/agent/models.json —
# a file in the image, not a setting here.
PIGGY_ENABLED=false
# Required to turn the agent on. Missing, Piggy exits at boot with
# "PIGGY_INFERENCE_API_KEY is required." and, under `restart: unless-stopped`,
# crash-loops. Mint it at https://app.primeintellect.ai — it is an INFERENCE
# credential and buys tokens, so it is not the same key as PRIME_API_KEY above.
PIGGY_INFERENCE_API_KEY=
# The legacy spelling of PRIME_API_KEY, kept as an alias so a deployment that
# predates the harness swap keeps starting. Set PRIME_API_KEY above instead and
# leave this COMMENTED OUT; uncomment it only if that is the name your host
# already has, and then comment PRIME_API_KEY out.
#
# Commented rather than blank, and that is the whole point of the line. A blank
# `PIGGY_INFERENCE_API_KEY=` is passed to the container as the empty string,
# which fails Piggy's minimum-length check *even when PRIME_API_KEY is set
# correctly* — so the agent crash-loops with a message about the key you did
# not use. With neither name set, the error is the honest one:
# "PRIME_API_KEY ... is required."
# PIGGY_INFERENCE_API_KEY=
# Required to turn the agent on. 32 characters minimum; anything shorter is
# refused at boot rather than accepted as weak.
# openssl rand -hex 32
@@ -160,10 +199,69 @@ PIGGY_INTERNAL_TOKEN=
# http://piggy:8931; set it here only when running Piggy from source.
PIGGY_INTERNAL_URL=http://127.0.0.1:8931
# Model and host. Read by both the API (to display) and Piggy (to call).
# Model and host for the QUEUE worker, which still calls the endpoint directly.
# Read by both the API (to display) and Piggy (to call).
PIGGY_MODEL=nvidia/nemotron-3-nano-30b-a3b
PIGGY_INFERENCE_BASE=https://api.pinference.ai/api/v1
# --- The agent itself -------------------------------------------------------
# All four have working defaults in apps/piggy/src/config.ts. Uncomment one only
# to change it, and read the note on PIGGY_AGENT_THINKING before you change the
# model — the two are related in a way that is not obvious from the outside.
#
# Which model the agent answers with when a user has expressed no preference in
# the picker. It must be one of the five in apps/piggy/src/agent/models.json:
# anything else is not registered with the harness and is rejected at boot,
# which is deliberate — the alternative is a model that 404s on the first turn.
# PIGGY_AGENT_MODEL=nvidia/nemotron-3-nano-30b-a3b
#
# What Piggy may do to the CRM. `confirm` is the shipped default and the one to
# run in production:
#
# read_only the pre-agent behaviour; Piggy answers and never writes
# confirm a write is PROPOSED as a card and applied when a person clicks
# auto Piggy writes directly, as the signed-in user
#
# Whatever this says, contracts, commitments, allocations and compliance records
# ALWAYS require a click — `auto` does not buy them. Every write runs as the
# calling user's own principal, so Piggy can never reach a record its user
# could not.
# PIGGY_AGENT_MODE=confirm
#
# How hard the model thinks before answering. Leave this alone unless you have
# changed the model, and read this paragraph if you have: it is the single
# setting most likely to make a working deployment look broken.
#
# The harness's own default is `medium`, tuned for a coding agent. On nemotron
# that produced 6,195 output tokens of reasoning and an EMPTY answer — the turn
# hit its token ceiling mid-thought and came back with finish_reason `length`.
# `low` was worse. `off` maps, for that model, to the endpoint's
# `reasoning_effort: none` and answered the same question correctly in 149
# output tokens.
#
# The mapping is PER MODEL and lives in `thinkingLevelMap` in
# apps/piggy/src/agent/models.json. A model with no entry (deepseek, opus,
# gpt-5.6) sends no reasoning parameter at `off` and gets the endpoint's own
# default, which may be verbose. So: empty answers, exhausted budgets and
# surprising bills after a model change are this setting, not a broken agent.
# PIGGY_AGENT_THINKING=off # off | minimal | low | medium | high | xhigh | max
#
# Output tokens one agent turn may spend, reasoning included. Clamped down to
# the chosen model's own ceiling, so raising it cannot ask for more than the
# endpoint will return.
# PIGGY_AGENT_MAX_TOKENS=4096
#
# Where the harness keeps its state — the models.json it reads, and anything
# else it writes. docker-compose.yml pins it to /var/lib/piggy-agent, a
# directory the image creates owned by the unprivileged runtime user, and there
# is no reason to set it here for a Compose deployment.
#
# If you do set it, on a bare-metal install: it MUST NOT be the checkout or any
# directory holding code. The harness discovers extensions, skills and context
# files from its cwd, and Piggy points the harness's cwd here. The default is
# ~/.pig/piggy-agent for exactly that reason.
# PIGGY_AGENT_DIR=/var/lib/piggy-agent
PIGGY_CHAT_HOST=127.0.0.1
PIGGY_CHAT_PORT=8931
# Only containers on a private network need this; never combine it with a