Skip to content

Repository files navigation

 Perfectman

AI personas use attention, emotion, and memory to select when they send messages.

Perfectman is a social simulation with AI personas in shared chat channels. Agents do not take fixed turns. Each agent uses an internal model to select an action or remain silent.

The model uses event visibility, attention, interpretation, motivation, emotion, pressure, and inhibition. An agent sends a message only when its pressure is sufficient to overcome its inhibition.

This model can produce delayed messages, private channels, and inferred exclusion. These behaviors result from the model. The system does not use a script for each social interaction.

Highlights

  • Action selection: A ten-stage pipeline selects an action. The agent can remain silent.
  • Inferred exclusion: An agent can infer exclusion from the absence of public messages, without an explicit notification.
  • Private channels: Agents can create private channels through curiosity, gossip, jealousy, secrecy, or relationship repair.
  • Biased memory: Emotion can change the episodic memory and relationship memory. These memories are not exact event records.
  • Behavior tests: The reported results cover 123 deterministic scenarios without a model connection. CI requires all behavioral signals to pass.

Note

Status: This is an early experiment before version 1.0. The event-oriented runtime and social presence engine operate together. Work continues on exclusion, masking, grudges, and biased memory. Errors and limitations remain.

Table of contents

What this is

Each agent action goes through this pipeline:

event → visibility → attention → interpretation → motivation
      → emotion → pressure → inhibition → intent → resolver → committed event

Any stage can stop an action. Silence is a valid result.

The pipeline supports these behaviors:

Behavior What it looks like
Selective attention An agent can select a message for attention and then send a message or remain silent.
Private alliance An agent can create or enter a private channel through curiosity, gossip, jealousy, secrecy, or relationship repair.
Inferred exclusion An agent can infer exclusion from the absence of public messages.
Meaningful lateness A delay can change the meaning of a message.
Biased memory Emotion can change the memory of an event.

A rule-based spectator layer uses the committed event log to give a narrative summary of social changes. This summary does not contain backend metrics.

Quickstart

The application requires Node.js and pnpm.

Install the dependencies. Then build the application:

pnpm install
pnpm build

Create the local configuration directories. Then copy the example configuration:

Git ignores these local configuration paths to protect persona data.

mkdir -p config/personas config/persona-notes
cp examples/simulations/mock.inline-personas.example.json config/index.json

Start the simulation:

pnpm --filter @perfectman/server simulation

The default configuration uses providerType: "mock". This simulation does not require an API key or a model.

Watch a run in the browser

The CLI writes a static HTML file after the run stops. The web runner takes markdown instead of JSON and streams the run while it happens:

pnpm build
pnpm web

Open http://localhost:4317, drop in a scenario and its personas, and press Run — or pick a ready-made cast and scene from examples/presets/.

The form compiles the markdown on every change and shows the resulting config before anything runs — cast, channels, detected language, and which canonical persona each agent inherited its engine calibration from. Agent thinking and per-pulse emotion are operator events with no table behind them, so each run also writes out/runs/<id>/replay.json, which is the only place they survive.

For UI work, pnpm web:dev serves the interface with hot reload on port 5317 and proxies the API to the run server.

Turn a saved run into video

Use an existing transcript, replay, or script to create a local MP4 and an interactive story viewer with channels, private conversations, and music:

pnpm video --input docs/eval/evidence/deepseek/scenarios/v1_exclusion_inferred.json --out out/exclusion.mp4

The command keeps source order and recorded emotion data. It requires FFmpeg and a completed build. See video inputs and usage.

Running with a real model, for free

These options do not require a paid API key.

Native Ollama — recommended on macOS / Apple Silicon

Docker Desktop does not support GPU passthrough on macOS. The reported speed is approximately 1.2–1.5 tokens per second with Docker on the CPU. The reported speed with native Metal is approximately 40 tokens per second.

Install Ollama directly on macOS. Then download the model. Start the server:

brew install ollama        # or download from https://ollama.com
ollama pull qwen3:1.7b
ollama serve               # API on :11434

Copy examples/simulations/qwen3-local.example.json to config/index.json.

Local Qwen3 via Docker — Linux with an NVIDIA GPU
pnpm qwen:dev              # pulls qwen3:1.7b, portable CPU-safe default
# or: pnpm qwen:dev:8b for the 8B model

If your machine has an NVIDIA driver, enable GPU passthrough with this command:

docker compose -f docker/qwen3/qwen3.compose.yml \
               -f docker/qwen3/qwen3.gpu.compose.yml up -d
FreeLLMAPI — one proxy for free-tier provider keys
cp .env.example .env   # set FREELLMAPI_ENCRYPTION_KEY
pnpm freellm:dev       # API on :3001, dashboard on :5173

Add the provider keys in the dashboard. Create a unified key in the dashboard. Set FREELLMAPI_KEY in .env.

Simulation in Docker — local Node.js and pnpm are not necessary
mkdir -p config
cp examples/simulations/mock.inline-personas.example.json config/index.json
docker compose -f docker/app/app.compose.yml up --build

The first Compose file determines the base directory for relative paths. Keep docker/app/app.compose.yml first so that these paths use docker/app/.

If Ollama runs in the same Compose project, set the provider baseURL to http://qwen3:11434/v1. This address uses container DNS. A localhost address refers to the simulation container itself.

Start the services. Then display the simulation log:

docker compose -f docker/app/app.compose.yml \
               -f docker/qwen3/qwen3.compose.yml up -d
docker compose -f docker/app/app.compose.yml \
               -f docker/qwen3/qwen3.compose.yml logs -f simulation

For a real provider, give the container read-only access to the .env file that the CLI uses:

docker compose -f docker/app/app.compose.yml run --rm \
  -v "$PWD/.env:/app/.env:ro" simulation

Bring your own personas

See docs/personas/README.md for the persona interview and compilation procedure. The local persona paths under config/ and docs/personas/ contain person-specific files. Git ignores these paths. Generic instructions and templates remain in the repository.

Benchmarks

The benchmark suite tests the behaviors above. It uses a mock provider and a rule judge. It does not require an API key or a model server.

Run the benchmark suite. Then check the CI acceptance criteria:

pnpm --filter @perfectman/eval bench --mode mock --judge rule --out out/bench.json
node scripts/ci/check-bench-gate.mjs out/bench.json

Reported scenario results: 123 deterministic scenario runs cover five behavioral categories. These figures describe the existing benchmark report. They are not results from a new test run.

Category Runs Signal pass rate
motive_archetype 51 100%
v1_behavior 27 100%
stagnation_attractor 18 100%
calibration 15 100%
edge_chaos 12 100%
Total 123 100%

Reported behavioral signals: 276 assertions check the actions of the social engine. These assertions do not assess the quality of generated text.

Signal Passed Rate
no_llm_failures 123 / 123 100%
event_committed 75 / 75 100%
private_channel_created 48 / 48 100%
emotion_rises 15 / 15 100%
emotion_stays 15 / 15 100%

No scenario runs failed in the report. The mean result for runtime probes is 91.0%. The lowest results are content-repetition (21.1%) and memory-write (70.7%). CI records these results for comparison but does not use them as acceptance criteria.

CI acceptance criteria

Layer Result Gated in CI?
Behavioral signals 100% Yes. All signals must pass.
Runtime probes 91.0% No. CI records these results for comparison.
Judge axis scores 6 of 8 targets met No. These scores are advisory.

Important

CI does not use judge axis scores as acceptance criteria. The rule judge has not met its calibration target. Cohen's κ against the golden-labeled set is 0.219 against a target of 0.7 (n=39). These scores permit comparisons, but they are not evidence of qualitative accuracy.

The reported scores for in_character (2.99 / 4.0) and voice_match (1.16 / 3.8) are below target. The largest disagreement with the golden labels occurs on voice_match.

All reported deterministic behavioral signals passed. The qualitative scores are not sufficiently reliable. Judge calibration remains open work.

The reported unit and hygiene suite contains 1,426 tests across 126 files. The recorded run passed on Node 22.

Check the types. Then run the unit tests and hygiene checks:

pnpm lint       # typecheck, all four packages
pnpm test:all   # unit tests + hygiene gates

Architecture

The full design is in docs/README.md. It contains the authoritative architecture, the emotion model, the social presence engine, and open design questions.

The main layers have these responsibilities:

Layer Responsibility
Event-oriented runtime (packages/server) It contains the append-only event log, command handlers, and intent resolver. It creates delivery, spectator, operator, and engine snapshot projections. It supports Socket.IO, Discord, stdout, and mock transports.
Social presence engine (packages/engine) It calculates attention, motivation, emotion, pressure, and inhibition without I/O.
Agent mind (packages/server/src/agent) It contains persona identity, writing style, and relationship beliefs. Its LLM runtime converts perception into intent.
Evaluation harness (packages/eval) It contains the scenario registry, rule and LLM judges, probes, calibration, and narration.
Continuity system It contains episodic memory, relationship memory, emotional drift, and rumination. Emotion can change the memory.

Status / open questions

This is an active experiment. It is not a completed product.

The open design questions include:

  • What can spectators see from private channels?
  • How much scoring logic does the system need?
  • Which symbolic actions must the system keep?
  • How can judge calibration meet its target?

Contributors can start with these questions.

Releases

For each tagged release (v*), docker-release.yml publishes a runtime container image to GHCR at ghcr.io/caiotheodoro/perfectman. The image tag omits the initial v. For example, release v0.1.0 produces image tag 0.1.0. Releases that are not prereleases also update latest.

Download the latest release image:

docker pull ghcr.io/caiotheodoro/perfectman:latest

For reproducible runs, use a release-specific image tag instead of latest.

Contributing

See CONTRIBUTING.md. Issues with the good first issue label have a limited scope. You do not need to read the full architecture document to start these issues.

Star history

Star history chart for caiotheodoro/perfectman

License

MIT

About

Social simulation experiment with the goal is to make AI personas feel like people: noticing unevenly, replying late, lurking, masking, forming private alliances, misreading silence, and creating emergent social drama.

Topics

Resources

Contributing

Stars

10 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages