Merge gitea/main into the Motion branch
Motion was written against a base five commits behind main, so the integration is the interesting part of this commit: - The migration is renumbered 0014 -> 0015. Main shipped 0014_piggy_conversations, and two migrations sharing an index is a journal that applies one of them. - The seed-idempotency gate keeps main's all-tables diff rather than the motion_templates counter this branch added; the general check subsumes the specific one. - Nav gains a Motion group alongside main's new Workspace group, and Piggy keeps the mark main gave it. - Stat keeps main's container-scaled figure, which already carries the min-w-0 this branch added for the same reason. - Piggy's page labels keep main's refusal wording for the four pages with no tool of their own, and gain the three Motion routes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -293,7 +293,7 @@ In production you must additionally set **either** `SUPABASE_URL` **or**
|
||||
|
||||
| Variable | Default | Notes |
|
||||
|---|---|---|
|
||||
| `PRIME_API_KEY` | unset | Scope it to `Availability → Read` only |
|
||||
| `PRIME_API_KEY` | unset | Scope it to `Availability → Read`, plus inference if Piggy is on — the same key buys the agent's tokens. Nothing that can provision |
|
||||
| `PRIME_API_BASE` | `https://api.primeintellect.ai` | The compute/pods host. Inference is a *different* host — see below |
|
||||
| `PRIME_SYNC_ENABLED` | `false` | Warns if on without a key |
|
||||
| `PRIME_SYNC_INTERVAL_MINUTES` | `30` | |
|
||||
@@ -310,9 +310,15 @@ means editing `.env` and restarting the container.
|
||||
| Variable | Default | Read by | Notes |
|
||||
|---|---|---|---|
|
||||
| `PIGGY_ENABLED` | `false` | API, `deploy.sh` | Gates the chat surface, and tells `scripts/deploy.sh` to ship the `piggy` Compose profile with the app |
|
||||
| **`PIGGY_INFERENCE_API_KEY`** | — | Piggy | Required by the Piggy process. Missing, it exits at boot and crash-loops. The model credential never reaches the API container |
|
||||
| `PIGGY_INFERENCE_BASE` | `https://api.pinference.ai/api/v1` | both | OpenAI-compatible |
|
||||
| `PIGGY_MODEL` | `nvidia/nemotron-3-nano-30b-a3b` | both | The API reads it to display; Piggy reads it to call |
|
||||
| **`PRIME_API_KEY`** | — | API, Piggy | One key, two hosts: the availability sync calls `api.primeintellect.ai`, the agent calls `api.pinference.ai`. Required by the Piggy process; missing, it exits at boot and crash-loops |
|
||||
| `PIGGY_INFERENCE_API_KEY` | — | Piggy | The legacy spelling of `PRIME_API_KEY`, still accepted so a `.env` written before the harness swap keeps starting. Set one, not two |
|
||||
| `PIGGY_AGENT_MODEL` | `nvidia/nemotron-3-nano-30b-a3b` | Piggy | The default answer model. Must be one of the five ids in `apps/piggy/src/agent/models.json`, or Piggy refuses to start — an unlisted model is not registered with the harness and would fail on a user's first question instead |
|
||||
| `PIGGY_AGENT_MODE` | `confirm` | Piggy | `read_only`, `confirm` or `auto`. Contracts, commitments, allocations and compliance require a click in every mode |
|
||||
| `PIGGY_AGENT_THINKING` | `off` | Piggy | `off`…`max`. **Read [the trap](#the-thinking-level-trap) before raising it or changing the model** |
|
||||
| `PIGGY_AGENT_MAX_TOKENS` | `4096` | Piggy | Output tokens per agent turn, reasoning included. Clamped down to the model's own ceiling |
|
||||
| `PIGGY_AGENT_DIR` | `~/.pig/piggy-agent` | Piggy | The harness's own directory. Compose pins it to `/var/lib/piggy-agent`; it must never be a checkout, because the harness reads context files from its cwd |
|
||||
| `PIGGY_INFERENCE_BASE` | `https://api.pinference.ai/api/v1` | both | OpenAI-compatible. The agent reads its base URL from `models.json`; this one still drives the queue worker |
|
||||
| `PIGGY_MODEL` | `nvidia/nemotron-3-nano-30b-a3b` | both | The queue worker's model. The agent uses `PIGGY_AGENT_MODEL` and the picker |
|
||||
| `PIGGY_LEASE_SECONDS` | `300` | both | Queue lease duration |
|
||||
| `PIGGY_POLL_INTERVAL_MS` | `2000` | Piggy | How often an idle worker looks for a task |
|
||||
| `PIGGY_MAX_TOKENS` | `1024` | Piggy | Per queued task |
|
||||
@@ -328,6 +334,24 @@ means editing `.env` and restarting the container.
|
||||
| `PIGGY_CHAT_PORT` | `8931` | Piggy | Never published to the host |
|
||||
| `PIGGY_CHAT_ALLOW_NON_LOOPBACK` | `false` | Piggy | Compose sets `true`, because the API reaches it across the Compose network |
|
||||
|
||||
##### The thinking-level trap
|
||||
|
||||
Worth its own heading, because it costs an afternoon otherwise.
|
||||
|
||||
The harness defaults `thinkingLevel` to `medium`, which is tuned for a coding
|
||||
agent. On the default nemotron model that produced **6,195 output tokens of
|
||||
reasoning and an empty answer** — the turn hit its ceiling mid-thought and
|
||||
returned `finish_reason: length`. `low` was worse. `off` maps, for that model,
|
||||
to the endpoint's `reasoning_effort: none`, and the same question came back
|
||||
correct in **149 output tokens**.
|
||||
|
||||
The mapping is per model, in `thinkingLevelMap` in
|
||||
`apps/piggy/src/agent/models.json`. The nemotron entries have one; deepseek,
|
||||
opus and gpt-5.6 do not, so at `off` they send no reasoning parameter at all and
|
||||
inherit the endpoint's default. **If you change `PIGGY_AGENT_MODEL` and start
|
||||
getting empty or truncated answers, this is why** — give the new model a
|
||||
`thinkingLevelMap` before touching `PIGGY_AGENT_THINKING`.
|
||||
|
||||
#### Integrations — all optional, all validated as a group
|
||||
|
||||
Setting one member of a group without the others fails at boot rather than
|
||||
@@ -351,7 +375,8 @@ across five packages, green CI.
|
||||
apps/
|
||||
web/ React 19 + Vite + Tailwind + shadcn-idiom components
|
||||
api/ Hono HTTP API — auth, validation, capacity and contract services
|
||||
piggy/ The agent: a lease-based queue worker plus a private chat server
|
||||
piggy/ The agent: a Prime Agent session over the CRM tools, served by a
|
||||
private chat server, plus a lease-based queue worker
|
||||
mcp/ MCP server (stdio) — 10 tools
|
||||
cli/ `pig`, the HTTP surface for scripts and agent kernels
|
||||
packages/
|
||||
@@ -439,7 +464,23 @@ degraded view of the other. There are two distinct surfaces.
|
||||
|
||||
### Piggy — the in-app agent
|
||||
|
||||
`apps/piggy` is one image running two processes' worth of behaviour:
|
||||
`apps/piggy` runs **Prime Agent** — Prime Intellect's own agent harness
|
||||
(`@earendil-works/pi-coding-agent`, MIT), embedded as a Node library rather than
|
||||
shelled out to — with PIG's CRM tools and nothing else. Models come from Prime
|
||||
Intellect inference (`api.pinference.ai`) on `PRIME_API_KEY`; the picker offers
|
||||
five, defined in `apps/piggy/src/agent/models.json`, priced and sized in the one
|
||||
file the runtime and the UI both read.
|
||||
|
||||
**The harness has no shell, no filesystem and no Python.** It is constructed
|
||||
with `noTools: 'all'` and an explicit allowlist, and there are three independent
|
||||
gates behind that: PIG's own boundary check on the tool list before a session
|
||||
opens, a comparison of the harness's live `state.tools` against exactly what was
|
||||
handed in — a startup error if they differ, so a future harness release cannot
|
||||
widen the set quietly — and a test that pins the same comparison. Prompt
|
||||
templates, skills, extensions and context-file discovery are all disabled, and
|
||||
the harness's cwd is a dedicated directory that holds no code.
|
||||
|
||||
It is one image running two processes' worth of behaviour:
|
||||
|
||||
- **The queue worker** claims a task with `SELECT … FOR UPDATE SKIP LOCKED`
|
||||
inside a transaction, holds a renewable lease (default 300s, renewed at half
|
||||
@@ -450,20 +491,36 @@ degraded view of the other. There are two distinct surfaces.
|
||||
counts and either a summary or the error. Its tool set is exactly two:
|
||||
`pig_get_subject` and `pig_record_fact`, and a fact is refused without both a
|
||||
source URL and an evidence excerpt.
|
||||
- **The chat server** listens on `127.0.0.1:8931` and is never published to the
|
||||
host. The API authenticates the user, forwards bounded context, and calls it
|
||||
with a shared internal bearer token. Chat is **read-only**: seven tools
|
||||
(`pig_get_record`, `pig_get_account_lifecycle` and five page-scoped
|
||||
summaries), each of which aggregates first and returns at most a handful of
|
||||
exemplar rows, because interactive chat runs at 2048 max tokens across at
|
||||
most four turns. Ambient coding tools are rejected before inference by an
|
||||
explicit boundary check.
|
||||
- **The chat server** listens on `8931` and is never published to the host. The
|
||||
API authenticates the user, forwards bounded context and the caller's
|
||||
principal, and calls it with a shared internal bearer token. Its tools are
|
||||
scoped to what the user is looking at: a focused record reader or one of the
|
||||
page-scoped summaries, plus lookups (`pig_search_records`,
|
||||
`pig_get_record_by_id`, `pig_list_renewals`, `pig_list_inventory`,
|
||||
`pig_get_account_lifecycle`). Each aggregates first and returns at most a
|
||||
handful of exemplar rows.
|
||||
|
||||
**Chat can now write**, which it could not before: `pig_log_activity`,
|
||||
`pig_create_contact`, `pig_create_task`, `pig_update_deal_stage` and
|
||||
`pig_update_record_fields`. Every one of them runs through the same
|
||||
`executeMutation` path the HTTP API uses, as the calling user's own
|
||||
`Principal` — so Piggy holds no privilege of its own and cannot touch a record
|
||||
its user could not. `PIGGY_AGENT_MODE` decides how far it may go on its own
|
||||
(`read_only`, `confirm`, `auto`), and in `confirm` a change is proposed as a
|
||||
card the user applies. Contracts, commitments, allocations and compliance
|
||||
records require a click in **every** mode; that rule is one function,
|
||||
`requiresApproval` in `packages/core/src/piggy-protocol.ts`, so it cannot be
|
||||
true in one place and false in another.
|
||||
|
||||
Conversations persist in `piggy_conversations` and `piggy_messages`
|
||||
(migration 0014), and `/piggy` is a full workspace rather than a docked panel
|
||||
alone.
|
||||
|
||||
Piggy is off by default. `PIGGY_ENABLED` defaults to `false` and the Compose
|
||||
service sits behind `profiles: ['piggy']`, so a default `docker compose up`
|
||||
starts the CRM without it. Turning it on is three values in `.env` —
|
||||
`PIGGY_ENABLED=true`, `PIGGY_INFERENCE_API_KEY` and a 32-character
|
||||
`PIGGY_INTERNAL_TOKEN` — and then a deploy:
|
||||
`PIGGY_ENABLED=true`, `PRIME_API_KEY` (or the legacy `PIGGY_INFERENCE_API_KEY`)
|
||||
and a 32-character `PIGGY_INTERNAL_TOKEN` — and then a deploy:
|
||||
|
||||
```bash
|
||||
bash scripts/deploy.sh
|
||||
@@ -560,8 +617,12 @@ ever written to `agent_tasks` (both from record creation). `write_brief`,
|
||||
nothing produces them. Piggy therefore does far less than the queue implies —
|
||||
not because the machinery is missing, but because nothing asks.
|
||||
|
||||
**Piggy chat cannot write.** By design for now, but worth stating: the
|
||||
interactive agent reads and cites; it cannot create or update a CRM record.
|
||||
**Piggy's write surface is five tools, not the whole CRM.** It can log an
|
||||
activity, create a contact or a task, move a deal stage and update fields on a
|
||||
record it can already read. Everything else — creating an account, a
|
||||
commitment, a contract, an allocation — is still a human's job in the UI, and
|
||||
the mutations it does have route through the same `executeMutation` path and
|
||||
the same capability checks as the HTTP API.
|
||||
|
||||
**The MCP server is stdio only.** There is no Streamable HTTP transport and no
|
||||
`/mcp` endpoint on the API, so remote MCP clients cannot connect over the
|
||||
|
||||
Reference in New Issue
Block a user