Project

pinecall

0.0
The project is in a healthy, maintained state
The application's side of Pinecall. A class whose declared fields are the state, whose `tool` methods are the model's verbs, whose comments are the prompt, and whose ERB view is the part of that prompt which changes. It speaks commands and reads entries over one socket; it never imports the runtime and never sees audio.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

 Project Readme

Pinecall: voice and chat agents, written as a class

check release npm pypi

Quickstart · Docs · CLI · Self-hosting · Gateway API · Console


Pinecall runs voice and chat agents that you write as a class. The class's fields are the call's state, its @tool methods are what the model may do, and its docstrings and render() are the prompt. Everything else is Pinecall's job: the phone line, the browser call, WhatsApp, speech in and out, turn-taking, memory, retrieval, judges at hang-up, the console and the bill.

This repository holds all of it: the runtime that runs the calls, the console it serves, the SDKs an agent is written with, and the CLI that drives them. Every package is released together, at one version, by one tag.

▸ The agent is a class

import { Agent, tool, type Stages } from "@pinecall/agents";

/**
 * You answer the phone for the business. Short sentences: everything you say is read aloud.
 * For anything the documents do not answer you take a message: who is calling and what about.
 */
export default class FrontDesk extends Agent {
  stage: Stages<"ask" | "done"> = "ask";          // state: plain fields, kept per call
  message?: { name: string; about: string } | undefined;

  /** Writes down the caller's message, once they have said their name and what it is about. */
  @tool({ stage: "ask", pii: ["name"] })           // a verb the model may call, in this stage
  async takeMessage(name: string, about: string) {
    this.message = { name, about };
    this.stage = "done";
    return this.message;
  }

  override render() {                               // the prompt's last block, a function of state
    return this.message
      ? <p>The message is written down: {this.message.name}. Say goodbye and hang up.</p>
      : <p>Greet the caller, and ask for their name and what the call is about.</p>;
  }
}
The same agent in Python
from typing import Literal

from pinecall import Agent, tool


class FrontDesk(Agent):
    """You answer the phone for the business. Short sentences: everything you say is read aloud."""

    stage: Literal["ask", "done"] = "ask"
    message: dict[str, str] | None = None

    @tool(stage="ask", pii=("name",))
    def take_message(self, name: str, about: str) -> dict[str, str]:
        """Writes down the caller's message, once they have said their name and what it is about."""
        self.message = {"name": name, "about": about}
        self.stage = "done"
        return self.message
The same agent in Ruby
require "pinecall"

# You answer the phone for the business. Short sentences: everything you say is read aloud.
class FrontDesk < Pinecall::Agent
  stage :ask, :done
  state :message

  # Writes down the caller's message, once they have said their name and what it is about.
  tool stage: :ask, pii: %i[name]
  def take_message(name:, about:)
    self.message = { name:, about: }
    self.stage = :done
    message
  end
end

The voice, the model, the greeting, the words it must pronounce and the documents it searches are not in the class. They are settings of the agent, per environment and versioned, set with pinecall agent set or in the console.

▸ From an empty folder to a phone call

The runtime is open source. Run it on your own machine, with no account, or use Pinecall Cloud. The agent is the same either way.

on your machine on Pinecall Cloud
$ uv tool install "pinecall-runtime[voice]"
$ pinecall-runtime setup

Reads your machine and runs the stack that fits it — Enter to use it, or change a stage — and asks your email and a password for the console. This machine is then signed in to it: no login. On your machine

$ pinecall login

login signs this machine in through the browser, once; every project then runs in the org its pinecall.json names. Your org gets a sandbox, where nothing reaches a real caller, and production.

Then, in either case:

$ npm i -g pinecall
$ pinecall new acme --agent front-desk        # --python or --ruby for the other SDKs
$ cd acme && npm install
$ pinecall chat                               # a written call against your class, here
$ pinecall test                               # the goldens, judged by a model
$ pinecall start                              # hold the agent: the console's calls reach your process
$ pinecall deploy up                          # run it on the runtime, without your laptop

▸ How a call moves

Phone, browser, chat and WhatsApp reach the Pinecall runtime. Its worker runs LiveKit's AgentSession, speech to text, the model and text to speech; its gateway asks your agent.tsx to run a tool; every step is an entry in the call's log, kept in Postgres, with Redis as the signal; the console, the CLI and the MCP server read the log live

  1. A caller comes in by a carrier, the browser widget, a written chat or WhatsApp.
  2. A worker takes the call. It runs LiveKit's AgentSession, with speech-to-text, the model and text-to-speech built from the org's providers.
  3. The gateway asks your class to act. When the model calls a tool, the gateway sends tool.call down your process's app socket, and your method's answer comes back as tool.result.
  4. Everything is an entry in the call's log. Turns, tool calls, latencies, costs and scores are appended, never rewritten. The console, the CLI, the bill and the judges all read that log.

The runtime is one Python package shaped like a Rails app. Each domain has its models, schemas, repository, service and endpoints, in a fixed bottom-up order that a test enforces. The architecture page walks every folder.

▸ Many orgs, one runtime

One runtime serves many businesses, and each one is an org: its own people and roles, keys, numbers and carriers, vendor keys, quotas, usage and data. Each org writes its own agents as classes, in its own repository, and each agent is a folder with its tests beside it.

  • Your code never runs inside the runtime. The worker runs the speech pipeline; the class runs in the org's own process, or in a gVisor-sandboxed pod of its own when the runtime hosts it, and the gateway asks it to act over its app socket. One shared fleet of workers serves every org, and no two orgs' code share a process.
  • The database holds the line. Every request is scoped to its key's org, and Postgres row-level security shows each connection that org's rows alone, a query that forgot its WHERE included.
  • Two environments per org. The sandbox and production share the gateway and the database, never the workers: each has its own fleet. In the sandbox every person gets a copy of the agents of their own.
  • Settings live per org, per environment, versioned. The voice, the model, the greeting, the lexicon, what the agent knows and remembers, its numbers and its routes. Quotas say what an org may use, and an org may bring its own vendor keys.

Pinecall stands on LiveKit: its media server, its SIP bridge and its Agents framework, whose AgentSession runs every call. In that framework the agent's code runs inside the worker. Pinecall adds the platform around it: the orgs, the people and their keys, the agent's code kept in the tenant's process, the settings, the log, the judges, the console and the bill. Orgs, keys and the two environments has the whole model.

▸ Test it like software

ring what runs command
0 the class as plain code: tools, state, the prompt, no network npm test
1 goldens: a conversation the model must hold, judged turn by turn pinecall test
2 a persona calls the agent over a real line, scored at hang-up pinecall simulate --voice
3 one real call re-checked: latency, errors, consent, talk share pinecall eval <call>
4 every finished call, judged at hang-up: the verdict is an entry in its log nothing to run

A broken call becomes a case: kept, fixed, then approved or dismissed (pinecall cases). Judges, monitors and alerts watch production (pinecall judges, pinecall monitors).

▸ What is inside

folder what it is published as
runtime/ the gateway that answers every door of the API, and the worker that runs the calls on LiveKit pinecall-runtime · PyPI
cli/ the pinecall CLI and its MCP server, the same for every language pinecall · npm
console/ the page the gateway serves, both environments in it inside pinecall-runtime
widget/ one agent as one button on any page inside pinecall-runtime
sdk/typescript/ the TypeScript framework an agent is written in @pinecall/agents · npm
sdk/python/ the Python SDK pinecall · PyPI
sdk/ruby/ the Ruby SDK pinecall · RubyGems
packages/room/ the caller's side of a call, for a page's own UI @pinecall/room · npm
packages/core/ what the console and Pinecall's supervisor app stand on taken as source
examples/ Clínica Norte, the one example agent, in each language read and copied, never published
infra/ the charts, the images and the Terraform a runtime is deployed with make deploy from runtime/

Each folder's README.md says what it is and how to use it.

Every CLI verb
stage verbs
start login · new · generate · whoami
hold and talk start · chat · prompt · console · supervise
the agent's settings agent · lexicon · voices · pipeline · providers
testing test · simulate · eval · runs · cases · personas · judges · judging
knowledge and memory docs · memory · remember
the phone numbers · carriers · line · callbacks
production deploy · secrets · sessions · monitors · webhook · telemetry · limits · data
assistants mcp: the CLI as an MCP server, and mcp install for every assistant on the machine

pinecall <verb> --help explains each one, and the CLI reference has a page per verb.

▸ Working on it

$ git clone https://github.com/pinecall/pinecall && cd pinecall
$ pnpm install          # every TypeScript package, one workspace and one lockfile (Node 24, pnpm 11)
$ cd runtime && uv sync # each Python package is a project of its own, with its own uv.lock

The TypeScript packages share the root's workspace (pnpm-workspace.yaml). runtime/ and sdk/python/ both ship the module pinecall, so they are never one environment: each keeps its own uv.lock. sdk/ruby/ has its Gemfile. Each package's own check is what CI runs for it (.github/workflows/check.yml), only when its folder changed.

▸ Releasing

Every published package carries the same version. scripts/version prints it, or says which package disagrees; scripts/version X.Y.Z writes it into all six. A v* tag publishes them all (.github/workflows/release.yml), each registry by Trusted Publishing, and a GitHub Release whose notes are each package's CHANGELOG.md section for that version.

▸ Security

Report a vulnerability as SECURITY.md says.


$ pinecall --help · docs.pinecall.io · pinecall.io