Put Piggy on Prime Agent, and let it write to the book
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:
@@ -113,11 +113,70 @@ services:
|
||||
# identity every worker shares — the one thing a lease exists to prevent.
|
||||
# A blank line in .env still passes the empty string, which is why the
|
||||
# optional keys are commented out in .env.example rather than left blank.
|
||||
#
|
||||
# The credential USED to share that hazard, and it was the nastiest
|
||||
# instance of it: PRIME_API_KEY and PIGGY_INFERENCE_API_KEY are alternative
|
||||
# spellings of one key, and a blank line for the spelling you did NOT use is
|
||||
# not absent — it is an empty string. Piggy crash-looped complaining about a
|
||||
# key that was set perfectly well under its other name. Both are now read
|
||||
# through a preprocess that treats '' as absent (apps/piggy/src/config.ts),
|
||||
# so a leftover blank line for either spelling is harmless. Every OTHER key
|
||||
# in this block still coerces from the empty string, so the rule above
|
||||
# stands: comment out what you are not setting rather than leaving it blank.
|
||||
environment:
|
||||
- DATABASE_URL=postgres://${POSTGRES_USER:-pig}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB:-pig}
|
||||
# The model credential. One key serves Prime Inference and the Prime
|
||||
# platform API, and apps/piggy/src/config.ts accepts either spelling:
|
||||
# PIGGY_INFERENCE_API_KEY is kept as the legacy alias so a .env written
|
||||
# before the harness swap still boots. Both are listed because either may
|
||||
# be the one the operator set, and the config resolves whichever it finds
|
||||
# into a single value; setting both to different keys is not a supported
|
||||
# arrangement — PRIME_API_KEY wins.
|
||||
- PRIME_API_KEY
|
||||
- PIGGY_INFERENCE_API_KEY
|
||||
- PIGGY_INFERENCE_BASE
|
||||
- PIGGY_MODEL
|
||||
# Prime Agent settings. Every one of these has a default in
|
||||
# apps/piggy/src/config.ts, so they are listed in the bare form: absent
|
||||
# from .env means absent from the container, which is what lets the code
|
||||
# own the default. The note above the block says why the mapped form
|
||||
# would be wrong here.
|
||||
- PIGGY_AGENT_MODEL
|
||||
- PIGGY_AGENT_MODE
|
||||
# How hard the model thinks before answering, and the single setting most
|
||||
# likely to make a working deployment look broken. The harness defaults
|
||||
# this to `medium`, which is tuned for a coding agent: on nemotron that
|
||||
# produced 6,195 output tokens of reasoning and an EMPTY answer, because
|
||||
# the turn hit its token ceiling while still thinking (finish_reason
|
||||
# `length`). `low` was worse. `off` is Piggy's default and maps to the
|
||||
# endpoint's `reasoning_effort: none` — 149 output tokens and a correct
|
||||
# answer for the same question. This is PER MODEL: a deployment that
|
||||
# changes PIGGY_AGENT_MODEL may need a thinkingLevelMap entry for the new
|
||||
# model in apps/piggy/src/agent/models.json before raising this.
|
||||
- PIGGY_AGENT_THINKING
|
||||
- PIGGY_AGENT_MAX_TOKENS
|
||||
# Fixed for this container, like the chat host below, and for a stronger
|
||||
# reason than convenience.
|
||||
#
|
||||
# The harness discovers extensions, skills and context files from its cwd,
|
||||
# and Piggy hands it this directory as both cwd and agent directory. Point
|
||||
# it at a checkout — or bind-mount one over it — and repo contents become
|
||||
# reachable from a CRM agent's prompt. The image creates this path owned
|
||||
# by `node`, mode 0700.
|
||||
#
|
||||
# Set explicitly rather than left to the `~/.pig/piggy-agent` default,
|
||||
# which resolves through HOME: under `docker run` that is /home/node and
|
||||
# works, but a runtime that starts this image with a numeric user and no
|
||||
# passwd entry leaves HOME unset, os.homedir() answers `/`, and the agent
|
||||
# dies creating its directory on the first turn — long after the deploy
|
||||
# reported success.
|
||||
#
|
||||
# NOT persisted, deliberately. Nothing here is worth keeping across a
|
||||
# restart: models.json is rewritten from the image at every boot, the
|
||||
# credential store is in-memory by design, sessions are in-memory, and the
|
||||
# conversations live in Postgres. A volume here would only be a way for a
|
||||
# file to outlive the image that wrote it.
|
||||
- PIGGY_AGENT_DIR=/var/lib/piggy-agent
|
||||
- PIGGY_LEASE_SECONDS
|
||||
- PIGGY_POLL_INTERVAL_MS
|
||||
- PIGGY_MAX_TOKENS
|
||||
|
||||
Reference in New Issue
Block a user