pinecallDocs
Getting started / The gateway, the key, the project

The gateway, the key, the project

Where the CLI finds the gateway and the key, what ~/.pinecall keeps, and a project's layout.

Where the gateway and the key come from#

The project's, the way any app reads its own secrets (src/env.ts). Every verb reads PINECALL_KEY and PINECALL_URL from the process's environment — a server's secrets, a CI job's — else from the nearest .env walking up from the directory it runs in, which pinecall link wrote. PINECALL_URL is https://cloud.pinecall.io unless one of the two names another. Nothing else is read: v1's PINECALL_API_KEY never is, because a shell that still had v1's key exported once pointed every verb at another org with every line reading the same.

A project folder is one org. pinecall link in the folder writes your key for the org you pick; a project of another org is another folder with its own .env, and nothing is ever switched. There are no profiles, no active mark and no flag that points one command elsewhere.

Terminal
~/clinica $ pinecall link
  1. clinica
  2. dental-sur
which org is this project? 1
▸ clinica · PINECALL_KEY written to .env

One gateway, two environments, one identity. Production and the sandbox are two environments of the same gateway — each with its own data, neither reaching the other — and PINECALL_URL is that one gateway, for both. The environment a request acts in is decided there, from the key and the header, never from the name: every verb knocks at the same URL with the same key, and says which environment it means.

the verb knocks at with saying
without --prod — the sandbox PINECALL_URL the project's key pinecall-env: sandbox
with --prod PINECALL_URL the project's key pinecall-env: production

A person's key opens both environments. Nothing is derived, minted or discovered for the sandbox: the key pinecall link wrote is the one every verb sends, and the pinecall-env header on every request and every socket (@pinecall/agents/client's signed.ts) says which environment it acts in — absent, the gateway takes the sandbox. --prod is for one command and keeps nothing, so the next command is in the sandbox again. Production lets a person through only while their production switch is on — an admin's always is — and otherwise answers 403 <name> has no production access: an admin gives it in Team.

Terminal
~/clinica $ pinecall sessions            # your sandbox calls
~/clinica $ pinecall sessions --prod     # production's, at the same gateway
~/clinica $ pinecall agent set --voice carolina --note "warmer" --prod

A server's token is the other kind of key: the org's, made in the console's Tokens screen of the environment it is for, and put in the server's secrets as PINECALL_KEY. It acts in its own environment alone, and the header may only agree. A sandbox token (pc_test_) is caught before it knocks: this PINECALL_KEY is a sandbox server's token, made at <url>: run the verb without --prod. A production token starts pc_live_ like a person's key, so the CLI cannot tell the two apart: a production token sent without --prod is refused by the gateway, in its words (this key is a production server's token, and this request is for sandbox: a server's token opens the environment it was made in). Production is that path.

whoami and callbacks open by printing where they went: gateway <url> · key from <where> · <environment>. The rest get on with the answer. With no key anywhere every one of them is refused: no PINECALL_KEY here: `pinecall link` in the project's folder writes it to .env (a server keeps it in its secrets), and the exit code is 2. pinecall whoami is the first thing to run when an endpoint refuses you and will not say why.

~/.pinecall/#

file what it holds
session.json { "gateways": { "<url>": { key?, calling? } }, "last": … } — this machine, one row per gateway it knows (src/signed-in.ts): key, this machine signed in as a person (login), which link mints a project's key from; calling, the phone pinecall line from said, re-sent by every start. No project reads it for its own key: that is the .env

The directory is 0700 and the file 0600; PINECALL_HOME moves it. A key is never printed, never logged, and never put in a URL. A .env that link creates is 0600 too.


Writing the agent#

The project#

One layout, whether the repository holds one agent or five. Every folder a verb reads is under the project's root, by the agent's name (src/home.ts):

agents/<name>/agent.tsx          the class — and beside it only what this agent uses (callbacks.ts);
                                 agent.ts, agent.rb or agent.py for a class in another language
lib/                             what two or more agents share
test/<name>/agent.test.ts        unit tests: the class as software
test/<name>/goldens/             goldens: the conversations `test` runs; beside them `docs.json`,
                                 the questions `docs eval` asks the base, and `memory.json`,
                                 the questions `memory eval` asks recall
test/<name>/memory/              the extraction cases `remember` runs

The agent's slug is its folder's name. agents/sales/agent.tsx is the agent sales on the gateway — its settings, its numbers, its callers and judges — whatever its class is called. A class whose static slug says another is refused when it is served, naming both: rename the folder.

The business is not in the repository. What the agent knows by heart — hours, prices, what needs an authorisation — is one field of its settings, per environment and corner, written in the console's Configure ▸ General (its Knowledge section) or with pinecall agent knowledge edit, and read whole into the prompt on every call. Nor are the documents a turn searches: they are a base, written in the console's Knowledge ▸ Docs, and a local docs/<name>/ is only what docs push sends when no directory is named. A class that still carries a voice, a model, an opening, a memory policy, a knowledge file or a base is refused at load, and the refusal names the verb that sets it now (Writing an agent).

At the root, every verb that reads a class acts on every agent, each against its own folders, or on the one --agent <name> names — by its folder's name, which is its slug (sales):

at the root does
pinecall start every agent at once, in one process of their language; each line prefixed by the slug, one console URL each. --show-prompt prints every agent's prompt under its slug
pinecall test each agent's goldens through its own class, one after another; the exit code is the worst. Paths and --watch are for one agent, so they need --agent
pinecall docs push · eval every agent that has docs/<name>/ · test/<name>/goldens/docs.json; the rest are named and skipped
pinecall personas every verb, since a caller is one agent's: --agent <name> when the project holds several
pinecall chat · simulate · prompt · remember · memory eval · line · start --ui one agent: --agent <name> is required when there are several
pinecall judges one agent's judges: --agent <name> when the project holds several

A project of several with nobody named is refused with the names: chat talks to one agent and this project has 2: add --agent dispatch or --agent sales. A directory with no agents/ at all is one somebody typed the verb in by mistake, and the refusal says where it looked. A folder the verb reads and nobody has written yet is empty, not an error: an agent with no goldens/ has no goldens, the console's roster says so, and pinecall test names the folder to write one in.

--agent means two things, one per kind of verb. In a verb that reads a class it names an agent of this project, by its folder's name. In a verb that only asks the gateway — sessions, pipeline, runs drift, numbers import, callbacks, docs attach — it is a slug, because there is no class to find. --file is always a file.