376ef3d5975f009ce95a45f26cd61be0ac46b15a
7 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
f0173440e4 |
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> |
||
|
|
99d165b5e5 |
Rebuild Piggy's interface, and give the demo book a business to describe
Piggy answered in raw markdown, threw away every tool result it streamed, and fought the reader's scroll on every token. The three surfaces that made it worth having — what it read, how it reasoned, what it cost — were all on the wire and none of them reached the screen. The transcript is now composed of five parts under components/piggy: answers render through streamdown, the container sticks to the bottom without pinning the reader there, tool steps say what they read and link to the record, and each turn carries its model and token count. Three lifecycle bugs went with them: Stop left a permanent spinner, a truncated stream was indistinguishable from thinking, and a failed send destroyed the message it failed to send. Underneath, the inference path grew timeouts, jittered retries on 429 and 5xx, tolerance of the malformed frames a 30B model emits, and an agent_runs row per turn so chat spend is observable. The system prompt now states that a field ending in Cents is cents — without it nemotron renders costPerGpuHourCents: 189 as "$189 per GPU-hour", which is a 100x error on the most scrutinised number in the room. The demo book was arithmetically incoherent: every deal's value contradicted its own allocation revenue by up to 3.6x, nothing had ever closed, no customer had any paper, and the marketplace was empty. Deal value is now derived from the allocation, the book clears 5.3% across five blocks with one deliberately underwater, and the renewal, compliance and agent-provenance machinery finally has rows to act on. A --clear that deleted every obligation, SLA term and capacity request in the database regardless of origin is scoped to the demo's own ids. Around that: accounts have a detail page, ⌘K searches the book, Settings can mint the API keys it always claimed to, and deploy.sh actually ships the agent instead of silently skipping its compose profile. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
a21ecf9e53 |
Stop deploy.sh from rewriting itself while bash is reading it
`git reset --hard origin/main` replaces this script mid-execution. bash does
not slurp a script — it reads incrementally and remembers a byte OFFSET, so
after the reset it resumes at that offset into different content.
This is not theoretical. The
|
||
|
|
13dec6b4b8 |
Rebuild the shell, add Calendar and Learn, and govern reads
Seven parallel agents and an adversarial verification pass. The three things worth knowing before reading the diff: RBAC WAS ALREADY BUILT. docs/build-plan.md marks F2 and F3 outstanding and is stale — packages/core/src/permissions.ts and lib/mutation.ts shipped long ago. So this does not rebuild them; it closes the gaps an audit found. The big one is that reads were entirely ungoverned: every GET was "any authenticated member", so a junior demand rep and a research contractor could both pull per-block supplier cost and break-even prices from /api/capacity/margin, and every contract's negotiated terms. For a company whose margin is the business, that was the hole that mattered. Adds book:read / economics:read / team:read, a readGuard middleware, and a `viewer` role below member. THE BUTTON AND THE 403 DISAGREED — the exact thing F3 said must never happen. Contracts.tsx never called can() at all, so its save button was always enabled against a server requiring contract:sign; Capacity.tsx gated commitment creation on deal:write/demand while the server wanted commitment:write/supply. POST /api/activities was the one write bypassing executeMutation: no capability check, and any member could mutate accounts.lastActivityAt as a side effect. It is now a proper mutation() behind activity:write. The shell becomes three panes — a collapsible shadcn sidebar with an account switcher on the Piggy accent, a header with real search, and Piggy docked to the right, page-aware and persistent across navigation. The phone keeps its bottom tab bar, which is the thing this product already beat trycompai/crm on, and gains the sidebar as a sheet. Calendar is a projection over thirteen dated sources rather than a new table, because a table would duplicate dates that already live on contracts, deals and commitments and would drift — and one ledger answering the question is the whole argument. It surfaces export_authorizations and compliance_artifacts, which had indexed expires_at columns, schema comments saying they must be alerted on, and no read endpoint or UI anywhere. Learn carries two tracks. Concepts are members-only; the platform track can be opened with a share code by someone with no account. The code mints a scoped learn-only token and never a Principal — every route here resolves a principal and then checks capabilities, so a principal-minting code would be one missing check away from leaking the book. "Only platform-track rows may be code-visible" is a database CHECK constraint as well as a write-path rule, and a test asserts a valid learn token still gets 401 on /api/dashboard, /api/accounts and /api/contracts — the same invariant scripts/deploy.sh refuses to ship without. CD becomes tag-to-ship. CI publishes an image to the Gitea registry on a release-* tag and cloud-2 pulls it, so no credential on the shared runner can execute anything on production — by construction rather than by policy. Both halves of deploy.sh's original rule survive: nothing on the runner reaches the host, and a human still decides when it ships. deploy.sh gains a rollback and a public-origin check, and PIG_IMAGE now reaches compose through `sudo env`, without which sudo's env_reset silently resolved every release to pig:local. Tests 141 -> 261. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
a6167629cc |
Move from npm to pnpm across the workspace, CI and the image
CI / verify (push) Successful in 3m23s
The monorepo was on npm workspaces. pnpm gives it a content-addressed store shared between the eight packages, a lockfile that records the whole graph rather than a flattened view of it, and — the reason this mattered in practice — `workspace:*`, which makes an internal dependency unambiguous instead of a version range that npm may satisfy from the registry. Mechanics: - `packageManager: pnpm@11.21.0` pins the version; corepack installs it in CI and in the image, so all three environments resolve identically. - The npm `workspaces` array is replaced by `pnpm-workspace.yaml`. pnpm ignores the former, and keeping both would leave two sources of truth. - All six internal dependencies moved to `workspace:*`. - Root scripts use `pnpm -r --if-present` and `pnpm -F <pkg>`. Two findings worth recording, both from running it rather than reading it: `tsx` was a devDependency, but the server runs TypeScript directly in production — the container's command is `pnpm exec tsx apps/api/src/server.ts`. Under npm this was concealed by the runtime stage re-installing tsx by hand after pruning dev dependencies. Under `pnpm install --prod` that sleight of hand stops working and the image simply fails to start. tsx is now declared in `dependencies`, which is what it has always actually been. The first image build failed with ERR_PNPM_ABORTED_REMOVE_MODULES_DIR_NO_TTY. That is not a pnpm bug: it had decided the modules directory was stale and wanted confirmation before deleting it, which a non-interactive build cannot give. The trigger was the host's `node_modules` reaching the build context — there was no `.dockerignore` at all. pnpm's tree is symlinks into a content-addressed store, so copying it into an image produces dangling links and a directory pnpm rightly considers corrupt. Fixed by adding `.dockerignore` and setting `CI=true`, which is required in any non-interactive pnpm build. `esbuild` is denied install scripts via `allowBuilds`. Its platform binary arrives through the optional dependency `@esbuild/linux-x64` and the postinstall only verifies it; confirmed by running the binary directly, which reports 0.25.12. Verified under pnpm: typecheck clean, 150 tests / 0 failures, e2e passes, web builds. The image was built and booted against a real Postgres — health ok, `/api/dashboard` 401 with an issuer configured, `/` and `/capacity` serve the SPA, `/og.png` serves as image/png, and the migrator runs from the pruned runtime stage. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
d4d7095605 |
Migrate before starting the application
CI / verify (push) Successful in 2m32s
|
||
|
|
73231a8944 |
Add CI, a test suite, and a deploy script
CI / verify (push) Failing after 34s
`npm test` did nothing until now. CI that runs no tests is theatre, so the tests came first — 39 of them, over the two places where an error would be silent and expensive. packages/core: the margin arithmetic. Every dashboard figure, idle-capacity alert and agent answer resolves through it, and wrong numbers still look like numbers. The cases pin decisions rather than implementation: cost is charged against the full commitment (a naive version reports the opposite sign on a loss-making block), aggregation sums cents rather than averaging percentages (averaging reports +22% on a book that is losing money), break-even prices the remaining hours and returns null rather than Infinity when there are none, and internal research burn counts as cost with no revenue. packages/prime: the upstream mapping. Rounding rather than truncating cents, because 2.43 is 2.4299999 in binary and a lost cent compounds across millions of GPU-hours. And interconnect normalisation, where an unrecognised fabric maps to Unknown rather than Ethernet — guessing low loses a deal, guessing high sells a training customer a cluster that cannot train. CI runs on push and pull request: typecheck all six packages, unit tests, migrations applied twice to a real Postgres, a seed-idempotency assertion that fails the build if row counts move on a second run, a server boot, the front-end build, and a Docker build. It also asserts the inline theme script's hash still matches the CSP the proxy allows. That script prevents a white flash for dark-mode users; if it changes without the CSP being updated, the browser silently blocks it and nothing anywhere reports an error. Deployment stays a script rather than push-to-deploy. Automating it would put an SSH key with production write access on the CI runner — a real escalation for a project this size. The script takes a database dump before migrating and refuses to finish if an unauthenticated request returns anything but 401. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |