Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,42 @@ jobs:
cache: true
- name: Build
run: go build -trimpath ./cmd/rin

sdk:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.9"
- name: Test Python SDK
run: python -m unittest discover -s sdk/python/tests -p 'test_*.py'
- uses: actions/setup-node@v4
with:
node-version: "18"
- name: Test JavaScript SDK
working-directory: sdk/javascript
run: node --test
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "17"
- name: Compile and test Java SDK
run: |
mkdir -p .cache/java-sdk
find sdk/java/src/main/java sdk/java/test -name '*.java' > .cache/java-sdk/sources.txt
javac --add-modules jdk.httpserver -d .cache/java-sdk @.cache/java-sdk/sources.txt
java --add-modules jdk.httpserver -cp .cache/java-sdk io.github.sunrioa.rin.RinClientTest
- uses: actions/setup-dotnet@v4
with:
dotnet-version: "6.0.x"
- name: Build and test C# SDK
run: dotnet run --project sdk/csharp/Rin.Client.Tests/Rin.Client.Tests.csproj --nologo
- name: Update package index for Lua
run: sudo apt-get update
- name: Install Lua
run: sudo apt-get install -y lua5.1 lua5.4
- name: Test Lua SDK on Lua 5.1
run: lua5.1 sdk/lua/test_client.lua
- name: Test Lua SDK on Lua 5.4
run: lua5.4 sdk/lua/test_client.lua
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@ __pycache__/
/rin-data/
/.cache/
*.log
sdk/csharp/**/bin/
sdk/csharp/**/obj/
sdk/python/**/*.egg-info/
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 sunrioa

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
27 changes: 26 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
GO ?= go
PYTHON ?= python3
NODE ?= node
DOTNET ?= dotnet
JAVAC ?= javac
JAVA ?= java
LUA ?= lua
VERSION ?= dev

.PHONY: fmt test test-go test-adapters race vet build
.PHONY: fmt test test-go test-adapters test-sdks test-sdk-python test-sdk-javascript test-sdk-csharp test-sdk-java test-sdk-lua race vet build

fmt:
$(GO) fmt ./...
Expand All @@ -15,6 +20,26 @@ test-go:
test-adapters:
$(PYTHON) -m unittest discover -s adapters/renpy -p 'test_*.py'

test-sdks: test-sdk-python test-sdk-javascript test-sdk-csharp test-sdk-java test-sdk-lua

test-sdk-python:
$(PYTHON) -m unittest discover -s sdk/python/tests -p 'test_*.py'

test-sdk-javascript:
cd sdk/javascript && $(NODE) --test

test-sdk-csharp:
$(DOTNET) run --project sdk/csharp/Rin.Client.Tests/Rin.Client.Tests.csproj --nologo

test-sdk-java:
mkdir -p .cache/java-sdk
find sdk/java/src/main/java sdk/java/test -name '*.java' > .cache/java-sdk/sources.txt
$(JAVAC) --add-modules jdk.httpserver -d .cache/java-sdk @.cache/java-sdk/sources.txt
$(JAVA) --add-modules jdk.httpserver -cp .cache/java-sdk io.github.sunrioa.rin.RinClientTest

test-sdk-lua:
$(LUA) sdk/lua/test_client.lua

race:
$(GO) test -race ./...

Expand Down
191 changes: 191 additions & 0 deletions README.en.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
# Rin

[简体中文](README.md) | [English](README.en.md)

Rin is a lightweight agent runtime for game characters. It runs as a sidecar
next to the game process and can also be embedded as a Go package in tooling.
The core uses only the Go standard library and is not tied to visual novels,
RPG engines, or any model provider.

Current development line: `v0.5.0` (Living Worlds)

Documentation index: [English](docs/README.md) |
[简体中文](docs/README.zh-CN.md)

## What it solves

Rin separates character reasoning from game-world facts:

- The game submits what a character actually saw as an `Observation` instead
of handing the model an entire save.
- A character creates an `ActionProposal` from memories, goals, boundaries,
and the actions currently allowed by the game.
- A proposal cannot directly change plot, inventory, quests, or
relationships. It takes effect only after the game validates it and calls
`commit`.
- Every state change is written to a hash-chained JSONL event log that can be
replayed and inspected.
- Snapshots bind `game/content/version/hash`; tampered or mismatched saves are
rejected.
- Tick scheduling lets many NPCs think only when needed instead of calling a
model every frame.
- Asynchronous jobs prefetch online-model results so slow requests,
cancellation, and stale state never freeze the game thread.
- Generic structured Generation Jobs route plot, quest descriptions, and
constrained dialogue through the sidecar without storing provider keys in
the game.
- If a model is unavailable, Rin falls back to a deterministic policy and
identifies the source with `policy_source`.
- Ren'Py, Godot 4, and Unity adapters preserve the same
observe/propose/commit authority boundary.
- Python, JavaScript, C#, Java, and Lua SDKs plus Fabric, BepInEx, and Luanti
example mods provide quick integration paths.
- Optional layered memory, conflicting beliefs, candidate subgoals, regional
dormancy, and deterministic multi-actor arbitration are explicitly enabled
through session features.
- A redacted timeline, revision replay, and `rin inspect` make long-running
character behavior reproducible and auditable.

The same boundary works for Ren'Py characters, RPG NPCs, party companions,
simulation residents, and other AI-driven game entities.

## Quick start

Running the sidecar requires Go 1.24 or later. Ren'Py adapter tests also
require Python 3.9+.

```bash
make test
go run ./cmd/rin serve -data ./rin-data
```

The default listener is `127.0.0.1:7374`. Check the service with:

```bash
curl http://127.0.0.1:7374/health
```

Run the complete client example:

```bash
go run ./examples/basic
```

Production integrations should use a dedicated sidecar token:

```bash
export RIN_TOKEN="$(openssl rand -hex 32)"
go run ./cmd/rin serve
```

The client then sends `Authorization: Bearer $RIN_TOKEN`. Tokens, model API
keys, and provider URLs are never written to events, snapshots, or responses.
Generation results may contain only bounded, non-secret operational metadata
such as model name, finish reason, and token counts; games may apply an
additional persistence allowlist.

## API

| Method | Path | Purpose |
| --- | --- | --- |
| `GET` | `/health` | Unauthenticated health check |
| `POST` | `/v1/session/create` | Create a session bound to a game-content version |
| `POST` | `/v1/session/observe` | Submit events actually observed by one or more actors |
| `POST` | `/v1/agent/propose` | Produce a character proposal from game-allowlisted actions |
| `POST` | `/v1/jobs/propose` | Submit an asynchronous proposal job |
| `GET` | `/v1/jobs/{job_id}` | Read proposal-job status and result |
| `DELETE` | `/v1/jobs/{job_id}` | Cancel a queued or running proposal job |
| `POST` | `/v1/generation/jobs` | Submit an asynchronous structured JSON generation job |
| `GET` | `/v1/generation/jobs/{job_id}` | Read a generation job and safe metadata |
| `DELETE` | `/v1/generation/jobs/{job_id}` | Cancel a generation job |
| `POST` | `/v1/action/commit` | Accept or reject a proposal and record its outcome |
| `POST` | `/v1/action/commit-batch` | Atomically commit multi-actor outcomes at one world revision |
| `POST` | `/v1/session/activity` | Update actor region and awake/dormant state |
| `POST` | `/v1/world/arbitrate` | Deterministically arbitrate conflicting parallel proposals |
| `POST` | `/v1/scheduler/due` | Query actors due to think at the current tick |
| `POST` | `/v1/session/get` | Read session state |
| `POST` | `/v1/session/snapshot` | Create and atomically save a snapshot |
| `POST` | `/v1/session/restore` | Validate and restore a snapshot |
| `POST` | `/v1/session/timeline` | Read the redacted event timeline |
| `POST` | `/v1/session/replay` | Replay to a revision and return a snapshot |

Every write request carries a caller-generated `request_id`. Repeating a
request returns the same result without mutating state again. Reusing the same
ID for another operation returns a conflict.

See the [protocol reference](docs/protocol-v1.md) for complete fields and
error semantics, and the [architecture guide](docs/architecture.md) for
responsibility boundaries.

Inspect a session offline. The command verifies the log and prints only a
redacted timeline:

```bash
go run ./cmd/rin inspect -data ./rin-data -session playthrough-1
go run ./cmd/rin inspect -data ./rin-data -session playthrough-1 -revision 42
```

## Game-engine adapters

- Ren'Py: standard-library Python client, `renpy.invoke_in_thread` bridge, and
authored offline fallback.
- Godot 4: asynchronous `HTTPRequest` signal/timer example.
- Unity: asynchronous `UnityWebRequest` coroutine with bounded response
handling.
- General SDKs: Python 3.9+, Node/Fetch, .NET 6+, Java 17+, and Lua 5.1+.
- Example mods: Fabric server, BepInEx 6, and a loopback-sidecar-only Luanti
server mod.

See [game adapters](docs/game-adapters.md) for installation, configuration,
and offline semantics. RPG region, visibility, quest, and multi-NPC event
conventions are in [RPG event conventions](docs/rpg-events.md).
Cross-language structure, thread boundaries, credential policy, and mod
installation are covered by [SDK and mod integration kits](docs/sdk-and-mods.md).

## Optional model policy

Rin makes no network calls by default. Enable an OpenAI-compatible model with:

```bash
export RIN_POLICY=model
export RIN_MODEL_BASE_URL="https://provider.example/v1"
export RIN_MODEL="your-model-id"
export RIN_MODEL_API_KEY="..."
go run ./cmd/rin serve
```

Remote endpoints must use HTTPS. Models on `127.0.0.1`, `::1`, or `localhost`
may use HTTP without a key. Model calls have independent timeouts, a total
budget, bounded retries, a circuit breaker, and a bounded cache. See
[model policy](docs/model-policy.md) for details.

## Repository layout

```text
cmd/rin/ Sidecar command-line program
httpapi/ Strict JSON, authentication, and request-size limits
policy/ Deterministic offline policy with no network dependency
provider/ OpenAI-compatible client, retries, and circuit breaker
jobs/ Bounded asynchronous proposal worker queue
generation/ Bounded structured-generation worker queue and cache
adapters/ Ren'Py Python client and bridge
sdk/ Python, JavaScript, C#, Java, and Lua clients and route contract
compat/ Executable game-protocol compatibility vectors
protocol/ Cross-language v1 data contract
runtime/ Event state machine, proposal validation, snapshots, scheduling
store/ JSONL file store and in-memory store
examples/ Go, Godot, Unity, and Fabric/BepInEx/Luanti mod examples
```

## Intentionally out of scope

`v0.5.0` does not add provider SDKs, a vector database, an ORM, WebSockets,
dynamic plugin execution, or arbitrary file access. Online models remain
optional. If either the provider or sidecar is unavailable, a game can
continue with the deterministic policy or its own offline story.

Future work is tracked in [ROADMAP.en.md](ROADMAP.en.md).

## License

Rin is released under the [MIT License](LICENSE).
21 changes: 17 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
# Rin

[简体中文](README.md) | [English](README.en.md)

Rin 是一个面向游戏角色的轻量级 Agent Runtime。它作为游戏进程旁边的 Sidecar 运行,也可以直接作为 Go 包嵌入工具链。核心只使用 Go 标准库,不绑定视觉小说、RPG 引擎或任何模型供应商。

当前开发线:`v0.5.0`(Living Worlds)

文档索引:[简体中文](docs/README.zh-CN.md) | [English](docs/README.md)

## 它解决什么

Rin 将“角色思考”和“游戏世界事实”拆开:
Expand All @@ -18,6 +22,7 @@ Rin 将“角色思考”和“游戏世界事实”拆开:
- 通用结构化 Generation Job 让剧情、任务描述和受限对白也经过 Sidecar,而不是让游戏保存供应商 Key。
- 模型不可用时自动回退确定性 Policy,并用 `policy_source` 标明来源。
- Ren'Py、Godot 4 和 Unity 适配器保持同一套 observe / propose / commit 权威边界。
- Python、JavaScript、C#、Java、Lua SDK 与 Fabric、BepInEx、Luanti 示例 Mod 提供快速接入层。
- 可选分层记忆、冲突认知、候选小目标、区域休眠和确定性多角色仲裁均由 Session feature 显式启用。
- 脱敏 Timeline、指定 revision Replay 和 `rin inspect` 让长流程角色行为可以复现和审计。

Expand Down Expand Up @@ -80,7 +85,7 @@ go run ./cmd/rin serve

所有写请求都带调用方生成的 `request_id`,重复请求返回相同结果,不重复修改状态。同一 ID 被用于不同操作时返回冲突。

完整字段和错误语义见 [协议文档](docs/protocol-v1.md),职责边界见 [架构文档](docs/architecture.md)。
完整字段和错误语义见 [协议文档](docs/protocol-v1.zh-CN.md),职责边界见 [架构文档](docs/architecture.zh-CN.md)。

离线检查一个会话(会验证日志并只打印脱敏时间线):

Expand All @@ -94,8 +99,11 @@ go run ./cmd/rin inspect -data ./rin-data -session playthrough-1 -revision 42
- Ren'Py:纯标准库 Python 客户端、`renpy.invoke_in_thread` 桥接与 authored 离线回退。
- Godot 4:基于 `HTTPRequest` signal/timer 的异步客户端。
- Unity:基于 `UnityWebRequest` coroutine 的异步客户端和有界响应处理。
- 通用 SDK:Python 3.9+、Node/Fetch、.NET 6+、Java 17+ 与 Lua 5.1+。
- 示例 Mod:Fabric 服务端、BepInEx 6 与本机 Sidecar 限定的 Luanti 服务端 Mod。

安装、配置和离线语义见 [游戏适配文档](docs/game-adapters.md)。RPG 的区域、可见性、任务和多人 NPC 事件约定见 [RPG 事件约定](docs/rpg-events.md)。
安装、配置和离线语义见 [游戏适配文档](docs/game-adapters.zh-CN.md)。RPG 的区域、可见性、任务和多人 NPC 事件约定见 [RPG 事件约定](docs/rpg-events.zh-CN.md)。
跨语言目录规范、线程边界、凭据策略和 Mod 安装步骤见 [SDK 与 Mod 接入文档](docs/sdk-and-mods.zh-CN.md)。

## 可选模型 Policy

Expand All @@ -109,7 +117,7 @@ export RIN_MODEL_API_KEY="..."
go run ./cmd/rin serve
```

远程端点必须使用 HTTPS;本机 `127.0.0.1`、`::1`、`localhost` 模型可使用 HTTP 且可不配置 Key。模型调用具有独立超时、总预算、有限重试、熔断和有界缓存。详细配置见 [模型接入文档](docs/model-policy.md)。
远程端点必须使用 HTTPS;本机 `127.0.0.1`、`::1`、`localhost` 模型可使用 HTTP 且可不配置 Key。模型调用具有独立超时、总预算、有限重试、熔断和有界缓存。详细配置见 [模型接入文档](docs/model-policy.zh-CN.md)。

## 目录

Expand All @@ -121,15 +129,20 @@ provider/ OpenAI-compatible 客户端、重试与熔断
jobs/ 有界异步 Proposal worker queue
generation/ 有界结构化 Generation worker queue 与缓存
adapters/ Ren'Py Python 客户端与桥接层
sdk/ Python、JavaScript、C#、Java、Lua 通用客户端与路由契约
compat/ 可执行的游戏协议兼容向量
protocol/ 可跨语言实现的 v1 数据契约
runtime/ 事件状态机、提案验证、快照和调度
store/ JSONL 文件存储与内存存储
examples/ Go、Godot 与 Unity 最小接入示例
examples/ Go、Godot、UnityFabric/BepInEx/Luanti Mod 示例
```

## 当前有意不做

`v0.5.0` 不引入供应商 SDK、向量数据库、ORM、WebSocket、动态插件执行或任意文件访问。在线模型仍是可选能力;即使供应商或 Sidecar 不可用,游戏仍可继续使用确定性策略或自己的离线剧情。

后续工作记录在 [ROADMAP.md](ROADMAP.md)。

## 许可证

Rin 以 [MIT License](LICENSE) 发布。
Loading
Loading