Project Twins
Pi-backed project twins for mediated, persistent agent execution
A project twin is a persistent software counterpart to a codebase.
It is not the primary agent. It is the project-native runtime that sits between a general-purpose caller and the project’s execution protocol.
Why a twin exists
General-purpose agents are interchangeable. Project protocols are not.
If every primary agent has to learn the project’s tool surface, memory policy, protocol semantics, and context conventions from scratch, the integration becomes brittle. A twin fixes that by becoming the stable project-facing runtime:
- The primary agent asks for work
- The twin resumes with the right context and memory
- The protocol stays behind the twin boundary
primary agent -> project twin -> project protocol / harness
The twin is the client of record for the project.
Responsibilities
A project twin owns:
- Project-scoped identity
- Persistent session continuity
- Memory compaction and continuation
- Tool policy and allowed capabilities
- Protocol knowledge
- Project context assembly
- Caller-facing summaries and handoffs
A primary agent should not speak the project protocol directly. It should invoke the twin.
Pi-backed runtime
Pi is a good fit for the twin runtime because it already provides:
- Persistent sessions
- RPC mode for long-running subprocess integration
- Tool calling with an explicit harness
- Compaction and summarization hooks
- Context files, prompt templates, and extension loading
That makes the split:
- Twin: product concept and policy boundary
- Pi: reasoning and session runtime
- Host system: orchestration, durable memory, and protocol adapters
Pi powers the twin. It does not define the twin.
Invocation model
The primary agent makes a single mediated call into the twin:
- Resume the twin session
- Inject caller context, project memory, and protocol state
- Let the twin do project-local work inside the harness
- Return a concise result to the caller
The caller should see a stable capability surface such as:
statusinspectplanexecutesummarizehandoff
It should not see raw protocol-shaped operations unless that protocol is itself the public product surface.
Implementation in this repo
This repo now includes a Pi-backed runtime in
bin/project-twin.ts.
The runtime:
- Spawns
pi --mode rpcas a persistent subprocess - Stores project-local session state under
.openscout/twins/<name>/sessions - Exposes a stable
invoke()API for callers - Optionally injects OpenScout relay context if
.openscout/relay*exists
The default harness is intentionally narrow:
- Built-in Pi tools are explicitly pinned to
read,bash,edit,write - Extension, skill, and prompt-template discovery are disabled by default
- Project instructions still come from
AGENTS.mdand related context files
This keeps the twin deterministic unless the host explicitly widens the surface.
Example
import { ProjectTwin } from "@lattices/cli"
const twin = new ProjectTwin({
cwd: "/Users/you/dev/my-project",
name: "my-project",
model: "anthropic/claude-sonnet-4-5",
})
await twin.start()
const result = await twin.invoke({
caller: "primary-agent",
protocol: "openscout-relay",
memory: "The caller is debugging relay enrollment and wants the next safe action.",
task: "Inspect the available project context and summarize what the caller should do next.",
})
console.log(result.text)
await twin.stop()
Design rule
All project-specific protocol semantics should live behind the twin boundary.
The primary agent should invoke the twin as a skill-like capability. The twin should own context assembly, protocol interaction, and the final handoff back to the caller.