There are many agents. This one is yours.
Diffuin is a small, self-hosted agent runtime that is growing outward from a production GitHub coding bot. Its current connector can review pull requests, refine or plan issues, answer focused repository questions, and implement an explicitly requested change. The runtime supports a domain-neutral profile as well as the original Schedule One profile.
The goal is a compact personal agent gateway: keep the core understandable, connect it where you work, and add niche expertise as skills and capability packs instead of baking every workflow into the runtime.
With the schedule-one profile, Diffuin combines the target repository's
instructions with a bundled, public-safe Schedule One modding skill. At job
time it refreshes shallow, read-only checkouts of the regular and beta
stripped-source branches from
S1CodeArchiver. A local self-hosted
instance may also mount an AssetRipper export read-only. For repositories,
issues, pull requests, and files explicitly linked in the request or its
conversation, Diffuin exposes a session-scoped, read-only GitHub tool broker.
Explicit GitHub repository links in tracked root AGENTS.md or contributing
guidance are included too, so a repository can declare its own read-only source
dependencies without deployment-level special cases. Pull-request jobs read
this guidance from the trusted base branch, not the contributor-controlled head.
The Schedule One profile also refreshes a deployment-owned catalog in
references.json before each job. It includes regular/beta
Mono assemblies, IL2CPP wrappers, bounded archiver history, S1Interop, and
Il2CppInterop implementation references. Private repositories require both the
GitHub App installation and the requesting actor to have read access. The App
mints repository-scoped, contents-read tokens for reference preparation; tokens
never appear in prompts, Git remotes, or model-visible tool results.
The localhost diffuin_github broker exposes reference_status,
reference_history, reference_files, reference_read, reference_diff, and
reference_decompile_type for the snapshots authorized for that job. Both
Codex and Spark can inspect these references without general network or command
access. Decompilation inspects one named type using pinned ILSpy tooling, with
a 20-second timeout, 128 MiB managed heap cap, bounded output, and one process
at a time. This is static metadata/code inspection; generated IL2CPP wrapper
bodies do not establish native IL2CPP game behavior.
Each snapshot reports its commit, subject, date, and upstream refresh status. A failed refresh may retain a verified cache marked stale. A successful refresh establishes the latest upstream repository commit, not the latest Steam version: an archiver that ends at f10 cannot supply an f11 comparison. The assembly commit's version/provenance must be checked separately. No game installation, Steam credentials, Unity runtime, or .NET build SDK is added.
- Verifies the GitHub webhook signature.
- Accepts new issue comments and pull-request review comments containing the configured mention.
- Requires the repository to be allowlisted and the requesting actor to have write-equivalent permission.
- Queues the delivery idempotently in SQLite.
- Prepares the selected agent profile. The Schedule One profile refreshes
regular (
alternate) and beta (alternate-beta) game-source references. - Starts a short-lived localhost GitHub read session scoped to the current and explicitly mentioned repositories. Public repositories are readable; private repositories additionally require the requesting actor to have access through the App installation.
- Runs the selected Codex or Spark provider in a fresh checkout. Reference directories are readable but outside the writable checkout, and the only model-visible remote research surface is the read-only broker. Spark command execution is disabled by default because its command tool is not an OS sandbox.
- For pull requests, fetches the base branch so Codex can review the complete base-to-head diff.
- Scans the response and patch for common secret formats.
- Passes the authorized message to the selected harness, which interprets the full request and uses relevant task skills when helpful. Explicit read-only commands constrain publication, without imposing intent labels or response templates.
- Edits a single status comment into the agent's Markdown response. PR findings are posted on the relevant diff lines when GitHub accepts the location.
- Publishes only deliberate patches when the agent requests publication, the command permits it, and repository and actor authorization still pass.
Diffuin does not have Schedule One or Unity. It must not claim in-game, Play Mode, Mono runtime, IL2CPP runtime, multiplayer, save/load, or full end-to-end validation. Its output separates source/static evidence from the manual runtime checks a maintainer still needs to perform.
The fallback model is gpt-6-luna. Automatic routing uses Luna for bounded
changes, focused questions, and small low-risk reviews, and gpt-6.1-sol for
ordinary reviews, source-backed technical work, and coupled changes.
gpt-6-astra is reserved exclusively for the hardest automatic routes: reviews
with at least 20 changed files, 2,000 changed lines, or eight risk signals, and
source-backed requests with over 10,000 context characters and multiple deep
complexity signals. These routes use max reasoning. An explicit effort override
alone does not select Astra, and the advisor cannot select Astra for middle cases.
Legacy minimal effort overrides are mapped to low for these current models.
The latest request drives the route: a small PR follow-up does
not inherit the full issue or PR's complexity, while broad reviews still account
for the complete diff. Mentions can override both model and reasoning within
deployment-owned allowlists.
Ambiguous middle cases receive a second, bounded routing decision from
gpt-6-luna at medium reasoning. The advisor sees only task metadata,
bounded issue text, diff counts, file names, the deterministic baseline, and
the model allowlist. It runs read-only with network and web search disabled and
returns a closed JSON schema. Explicit overrides, quick reviews, simple focused
requests, disabled routing, and maximum-risk routes skip the advisor. Low
confidence, invalid output, timeouts, and policy violations fall back to the
deterministic route.
Provider selection follows the routed model. The Luna, Sol, and Astra models run through
the Codex SDK. Models listed in SPARK_MODELS run through the custom Spark
automation --stdio protocol; the default Spark model is
gpt-5.3-codex-spark. Spark defaults to medium reasoning for a better
latency-quality balance. Quick reviews classified as non-trivial or
large/high-risk use at least high; an explicit --effort override still wins.
Pull-request requests such as quick review, fast PR review, review this quickly, or take a quick pass over this select the
configured Spark model. An explicit --model always wins over that shortcut.
Both providers return the same structured artifact and go through the same
secret scan, publication guards, and GitHub delivery path.
Create a GitHub App with:
- Webhook URL:
https://YOUR_HOST/webhooks/github - Webhook secret: a new random value
- Repository permissions:
- Contents: Read and write
- Issues: Read and write
- Pull requests: Read and write
- Metadata: Read-only (GitHub adds this automatically)
- Subscribe to events:
- Issues (when private automatic triage is enabled)
- Issue comment
- Pull request review comment
- Pull request review
- Pull request
Generate a private key and install the App only on repositories you intend to
list in ALLOWED_REPOSITORIES.
Set ISSUE_TRIAGE_POLICIES as a private runtime environment secret containing a
JSON object keyed by allowlisted owner/repository. It is disabled by default.
Keep real rules, examples, repository mappings, and deployment credentials outside
the public repository and image. Other operators can supply their own policy:
{
"example/project": {
"rules": "Your private severity rubric and classification examples.",
"bugLabel": "bug",
"featureLabel": "enhancement",
"severityLabels": {
"low": "Low", "medium": "Medium", "high": "High", "critical": "Critical"
},
"topicLabels": ["documentation"],
"model": "gpt-6-luna",
"assignees": ["your-maintainer-login"],
"templates": {
"bug": ".github/ISSUE_TEMPLATE/bug_report.yml",
"feature": ".github/ISSUE_TEMPLATE/feature_request.yml"
}
}
}Subscribe the GitHub App to Issues events. Signed opened, reopened, and
title/body edited events on open issues queue metadata-only jobs, including
reports by outside contributors. Bot events, pull requests, and unconfigured
repositories are ignored. Existing issues are not bulk-reprocessed by deployment.
Comment-only additions do not automatically retriage an issue.
The classifier uses bounded report text, recent comments, existing label metadata,
and the private rubric. It has no repository checkout or GitHub research session;
shell tools, apps, plugins, network access, and web search are disabled. The
application validates its structured response and adds [BUG] or [FEATURE]
plus configured existing labels only at high confidence. Features receive no new
bug severity. Existing severity labels and conflicting type classifications are
preserved as maintainer overrides. Uncertain classifications leave metadata alone.
Optional assignees are added at that same confidence threshold, including on
already-labeled reports, while preserving existing assignees.
Optional templates enable checks against the configured YAML issue forms on the
repository's current default branch. Diffuin compares required form sections and
mandatory checkboxes with the issue body; GitHub does not provide reliable proof
of which template the reporter selected. Missing sections, unanswered required
fields, and unchecked required checkboxes produce a short fixed reminder with
the matching form link. Optional fields are not required. Missing, malformed, or
unsupported templates suppress the reminder. Template completeness does not alter
severity. Only high-confidence bug/feature classifications can post reminders;
existing conflicting classifications still prevent automatic delivery. Each
report type gets at most one reminder from this App, even across edits and retries.
Assignment and template checks are disabled unless configured privately.
Diffuin preserves the issue body and unrelated labels, rechecks the report before
delivery, and never posts the private rationale as an issue comment. Decisions
are recorded in the private SQLite run artifacts; classifier prompts also remain
in the private Codex state. Keep the /data volume private. Redeliveries are
deduplicated in the durable queue. Metadata API calls are additive and retryable,
but GitHub does not offer an atomic transaction with the pre-delivery recheck.
The normal write-actor checks still govern implementation, review, and memory.
On Northflank, set the policy as a service runtime secret or through a secret
group. Use northflank update service runtime-environment --projectId <project> --serviceId <service> --file <private-file.json> with the existing runtime
environment preserved. Do not commit that file or pass private policy inline in
shell arguments. Restart/deploy the service and verify /health, the Issues
subscription, and a private classifier dry run before relying on automatic triage.
If a repository ruleset applies required checks to ~ALL branches, exclude
refs/heads/diffuin/** from that rule or grant the Diffuin App a narrowly
scoped bypass. Otherwise GitHub can accept the initial bot branch but reject
follow-up commits because the required check cannot run until the new commit is
first pushed. The target branch and pull-request merge checks remain enforced.
Copy .env.example to .env. Required values are:
| Variable | Purpose |
|---|---|
GITHUB_APP_ID |
Numeric GitHub App ID |
GITHUB_PRIVATE_KEY_PATH |
Path to a mounted App private-key PEM |
GITHUB_PRIVATE_KEY_BASE64 |
Hosted alternative to the path; set exactly one private-key option |
GITHUB_WEBHOOK_SECRET |
HMAC secret configured on the App |
ALLOWED_REPOSITORIES |
Comma-separated owner/repository allowlist |
CODEX_MODEL |
Fallback model when a routed model is unavailable or automatic routing is disabled |
CODEX_ALLOWED_MODELS |
Comma-separated model allowlist for mention overrides |
CODEX_REASONING_ROUTING |
Enables automatic reasoning selection; defaults to true |
CODEX_REASONING_EFFORT |
Fixed fallback used when automatic routing is disabled |
ROUTING_ADVISOR_ENABLED |
Enables the bounded Luna routing advisor for ambiguous cases; defaults to true |
ROUTING_ADVISOR_MODEL |
Internal advisor model; defaults to gpt-6-luna |
ROUTING_ADVISOR_TIMEOUT_MS |
Advisor timeout before deterministic fallback; defaults to 30 seconds |
SPARK_COMMAND |
Spark executable name or absolute path; defaults to spark |
SPARK_MODELS |
Comma-separated models dispatched through Spark; defaults to gpt-5.3-codex-spark |
SPARK_REASONING_EFFORT |
Default effort for Spark-routed jobs; defaults to medium and explicit mention overrides still win |
SPARK_TIMEOUT_MS |
Per-run Spark subprocess timeout; defaults to 30 minutes |
SPARK_ALLOW_UNSANDBOXED_COMMANDS |
Enables Spark cmd.exec; defaults to false and requires separate host confinement |
DIFFUIN_PROFILE |
Agent profile: schedule-one for the existing modding bot or general for domain-neutral repository work |
SKILL_ROOT |
Root containing shared review, implementation, and writing skills; defaults to ./skills |
SCHEDULE_ONE_SKILL_PATH |
Bundled Schedule One skill directory; defaults to ./skills/schedule-one-modding |
SCHEDULE_ONE_CODE_ARCHIVER_URL |
Runtime source for regular/beta stripped-code references |
SCHEDULE_ONE_ASSETRIPPER_PATH |
Optional read-only AssetRipper export mount for local/self-hosted use |
DIFFUIN_HANDLE, DATA_DIR, the Codex routing settings, the Schedule One
reference settings, and the port have defaults shown in .env.example.
Profiles define the identity, evidence roots, validation boundaries, and domain
contract for an agent run. schedule-one preserves the existing stripped-source
and optional AssetRipper workflow. general runs the same GitHub, Codex/Spark,
repository-guidance, and delivery pipeline without loading Schedule One context.
Skills remain ordinary SKILL.md directories below SKILL_ROOT. Repository-
specific knowledge should live in tracked AGENTS.md or contributing guidance;
deployment-wide niche behavior belongs in an optional profile or skill pack.
Diffuin combines tracked repository guidance with a bounded, inspectable memory
store. Tracked AGENTS.md remains the durable project contract. Approved memory
supplements that contract with provenance and scope; it cannot override the
current authorized request, tracked guidance, or application safety policy.
Only write-equivalent repository actors can create shared memory. Personal memory is visible only in that actor's non-review runs, so one maintainer's preferences do not silently govern reviews requested by everyone else. Path memory is retrieved only when a changed file matches its scope.
@Diffuin remember warning for this repository: Generated clients must be regenerated because handwritten edits drift
@Diffuin remember for src/api/**: run the API contract suite
@Diffuin remember for me: keep implementation summaries terse
@Diffuin what have you learned?
@Diffuin forget 00000000-0000-0000-0000-000000000000
Every stored memory retains the author, evidence comment, creation time, and the trusted base/default-branch SHA available when it was recorded. Diffuin includes applicable memories in the run with lower precedence than repository guidance. If the checkout contradicts a remembered fact, the checkout wins and the agent must report the stale memory.
Repository onboarding is an ordinary explicit implementation request:
@Diffuin onboard this repository
@Diffuin audit our AGENTS.md
@Diffuin promote approved lessons into AGENTS.md
These requests create a normal reviewable patch. Diffuin is instructed to keep
guidance repository-specific and concise, and to add nested AGENTS.md files
only when subprojects materially differ.
Diffuin persists structured artifacts, delivered inline-comment identifiers,
authorized review-thread feedback, and merged/closed outcomes for its own pull
requests. It refreshes write-authorized thumbs-up/down reactions through the
GitHub REST API during later jobs in the same repository because GitHub does not
provide a review-comment reaction webhook. These records are evaluation signals
rather than automatically promoted rules. Subscribe the GitHub App to Pull request events to collect merge/close outcomes. Existing deployments continue
to work without that event, but will not accumulate the corresponding outcome
evidence.
Operators can export attributable run, finding, feedback, and outcome records into deterministic train/validation/holdout partitions for offline evaluation:
bun run learning:export -- /data/diffuin.sqlite ./artifacts/learning-eval.jsonlThe export can contain repository text and maintainer feedback. Keep it private, review proposed prompt or skill changes against the holdout partition, and deploy those changes only through the normal reviewed Git workflow.
GitHub is the first connector. The connector-neutral direction and the planned Discord security/setup contract are documented in docs/architecture.md and docs/discord-connector.md. Discord is intentionally not enabled until its identity, allowlist, and repository-action boundaries are implemented and the operator supplies a bot application and token.
The GitHub broker binds only to 127.0.0.1. The selected harness receives a random session
credential that expires after 30 minutes and is destroyed when the job ends;
it never receives the GitHub App installation token. The broker has no mutation
tools, limits repositories and response sizes, strips search scope qualifiers,
and rejects unmentioned repositories. Arbitrary public GitHub links can be
researched without adding them to ALLOWED_REPOSITORIES; that allowlist still
controls where Diffuin accepts mentions and publishes results.
Place the downloaded GitHub App key at
secrets/github-app-private-key.pem, then run:
cp .env.example .env
docker compose up -d --build
docker compose exec diffuin codex login --device-authThe device login stores and refreshes ChatGPT-managed Codex credentials in the
persistent /data/codex-home volume. Treat that volume like a password.
The Docker image downloads the pinned Spark 0.9.1 Linux release and verifies its
published SHA-256 before installation. It reserves /data/spark-data as
Spark's persistent XDG data directory. When Spark has no dedicated saved login,
Diffuin points it at /data/codex-home, allowing Spark to reuse the existing
ChatGPT-managed Codex tokens without placing them in prompts or job artifacts.
Expose port 8787 through an HTTPS reverse proxy and update the GitHub App's
webhook URL. Verify GET /health before installing the App.
To enable local AssetRipper evidence, first build a compact search corpus in an
ignored directory. The command retains scenes, prefabs, serialized
configuration, animation/controller data, and every .meta file, while
dropping textures, meshes, audio, terrain data, and other large presentation
assets:
bun run assetripper:prepare -- /absolute/AssetRipper_export/ExportedProject/Assets ./data/assetripper-corpusMount that corpus outside /app and set its container path. Never copy it into
this repository or image:
services:
diffuin:
environment:
SCHEDULE_ONE_ASSETRIPPER_PATH: /references/assetripper
volumes:
- /absolute/local/AssetRipper_export:/references/assetripper:roBuild from the included Dockerfile, expose port 8787, and attach a
persistent volume at /data. Set GITHUB_PRIVATE_KEY_BASE64 instead of
mounting a PEM when the platform offers encrypted environment secrets but not
file secrets.
After the service is running, open its shell and run:
codex login --device-authComplete the displayed device flow in your browser. The /data volume must
persist across deployments so Codex can refresh its login, retain the reference
cache, and preserve the job queue.
The persistent volume size applies to /data, not automatically to the whole
container filesystem. Northflank also assigns separate ephemeral storage to
each runtime instance; it is wiped when the container restarts and has its own
quota. Diffuin keeps its credentials, SQLite queue, reference cache, and active
workspaces under DATA_DIR, so a hosted DATA_DIR=/data intentionally counts
all of those against the persistent-volume limit. Workspaces are removed after
each job.
For a private hosted instance, upload only the compact corpus to the persistent volume; do not upload the raw AssetRipper export. With the Northflank CLI, a one-time upload to the existing Diffuin service is:
northflank upload service file \
--projectId diffuin \
--service diffuin \
--localPath ./data/assetripper-corpus/ \
--remotePath /data/references/assetripperSet SCHEDULE_ONE_ASSETRIPPER_PATH=/data/references/assetripper on the service
and redeploy it. Diffuin exposes the corpus to Codex only through a
session-authenticated, read-only MCP broker; it does not add the volume path as
a sandbox write root. Keep enough free volume capacity for /data/codex-home,
the SQLite queue, cloned source references, and temporary workspaces.
Edit the tracked references.json catalog to change reference repositories,
branches, evidence directories, or history depth. A private deployment may
instead upload a catalog to /data/references.json using the Northflank CLI
and set SCHEDULE_ONE_REFERENCES_PATH once. Repository guidance and issue text
cannot add arbitrary cached repositories. Use a new ID when changing a cached
entry's repository or branch; existing IDs are tied to their original source.
Ensure the Diffuin GitHub App installation includes
ifBars/schedule-one-assemblies and ifBars/il2cpp-scheduleone-assemblies.
Missing access is reported as unavailable, including when a previous job has
already cached the repository. Assembly histories default to one commit;
archiver history is bounded to 12 commits, and tooling references to four.
Refreshes run serially with single-threaded Git packing. Cached data persists
under DATA_DIR/references, so budget volume space for checked-out files and
Git objects; obsolete caches are not automatically deleted.
The free Northflank deployment can perform these focused static inspections. Repository builds/contract suites still require their own SDK and dependencies, and portrait rendering, save reloads, and multiplayer behavior still require gameplay validation elsewhere.
Typical review and planning requests are read-only:
@Diffuin review this against the regular and beta game source
@Diffuin polish this issue into an implementation-ready plan
@Diffuin identify the Mono/IL2CPP and save/load risks in this proposal
Explicit commands support safe per-request overrides:
@Diffuin review --model gpt-6.1-sol --effort high
@Diffuin quick review
@Diffuin review --model gpt-5.3-codex-spark -- focus on correctness regressions
@Diffuin investigate --model gpt-6-luna -- research the likely lifecycle seam
@Diffuin plan --model gpt-6-luna --effort xhigh -- focus on persistence and multiplayer authority
Supported commands are review, investigate, plan, implement, and
answer. Use -- before free-form instructions when options are present.
Supported reasoning levels are minimal, low, medium, high, xhigh,
and max. Invalid or disallowed overrides are rejected before a job is queued.
Free-form mentions pass unchanged to the agent, which interprets the full request
and conversation. Requests can combine research, planning, explanations, writing,
and implementation without matching a verb list or declaring an intent, kind,
or workflow. Explicit command modes remain available with --/options or as
standalone shortcuts; explicit read-only commands still prevent publication.
Model and effort overrides also work without a mode:
@Diffuin --model gpt-6.1-sol --effort high -- Do a new investigative plan on the latest and open a new PR
Implementation remains explicit:
@Diffuin fix the null dereference and add a regression test
@Diffuin Open a PR against stable to fix the persistence issue
@Diffuin move the validation helper beside the other test utilities
@Diffuin How does the compatibility fallback work in this PR?
Diffuin reacts with eyes when the request is queued. It comments with the review response when no files changed, and posts a pull-request URL when an authorized implementation request produced a patch. The agent writes free-form Markdown in a small delivery envelope rather than fixed review/plan templates. Blockers and no-change outcomes are explained without fabricating a commit. Every delivered artifact ends with an AI-generated accuracy notice and includes collapsed provider, model, reasoning, elapsed-time, and provider-run metadata.
If an implementation leaves a draft but declines publication, Diffuin delivers
the agent's blocker report and keeps the secret-scanned patch in the run artifact's
unpublished_patch column. It does not commit the draft or replace the explanation
with a generic publication error. Operators can retrieve the patch from the
persistent SQLite database using the run ID shown in the response.
Issue plans can offer an unchecked implementation action at the end. A repository maintainer can
check it to ask Diffuin to implement that exact plan, open a pull request, and
close the issue when the pull request merges. Each plan comment can trigger at
most one implementation job; a failed run can still be retried with an explicit
@Diffuin implement mention.
Diffuin includes recent issue or PR conversation in each job. Follow-up implementation requests therefore reuse earlier research instead of starting a new, potentially contradictory diagnosis. The agent chooses the response format and relevant workflows. The application owns publication: it requires a deliberate publish-changes signal, enforces explicit read-only commands, rechecks actor permission and repository allowlisting, and scans output and patches for secrets.
On a pull request created by Diffuin, a direct request such as
@Diffuin remove the unused compatibility shim from this PR commits and pushes
the change back to that pull request's diffuin/* branch. Implementation
requests on branches not owned by Diffuin continue to open a separate follow-up
pull request instead of writing directly to a maintainer's branch.
Submitting a Changes requested review on an open Diffuin-authored PR also
queues an implementation job automatically, without an @Diffuin mention.
The reviewer must have write-equivalent access and the repository must be
allowlisted. Enable the Pull request review webhook subscription in the
GitHub App settings to receive these submissions.
Diffuin reads the review body and all its inline findings, verifies each against
the current PR head, fixes applicable findings, and pushes to the existing
diffuin/* branch. It posts the commit, explanation, and validation details in
the PR conversation and replies to the review's comment threads. Findings that
are already fixed or not applicable receive an evidence-based explanation;
they do not require a manufactured commit. Threads remain open for the reviewer.
Approved, comment-only, pending, dismissed, bot-authored, and non-Diffuin PR
reviews do not trigger automatic edits. Redelivery of the same review is
deduplicated. If the PR head changes during the run, publication is refused.
Existing reviews are not replayed by deployment; submit a new review or use an
explicit @Diffuin implement request to revisit them.
For read-only issue workflows, Diffuin may polish a materially basic issue title and description while preserving reporter facts and uncertainty. Already actionable reports are left unchanged.
bun install
bun run typecheck
bun run test
bun run buildThe application runtime is Node.js 24 because that is the supported server-side
runtime for the Codex SDK. Bun is used for package management and scripts. The
Spark integration is a subprocess provider for the versioned
spark.automation.v1 protocol exposed by spark automation --stdio.
OpenAI recommends API keys as the default for automation and warns that ChatGPT-managed authentication should not be used for arbitrary public or open-source repository runners. Diffuin's subscription-auth path is intended for a trusted operator, an explicit repository allowlist, and trusted write-authorized requesters. See SECURITY.md before deploying.
The bundled skill contains guidance only. Stripped source is cloned into the persistent runtime reference cache and AssetRipper is an optional external mount. Do not commit, package, attach, or redistribute decompiled game code, assemblies, generated wrappers, AssetRipper exports, prefabs, scenes, textures, or other proprietary game artifacts.
Diffuin is licensed under the MIT License. Although it was created for my personal workflow rather than as a product intended for general adoption, the license permits anyone to use, copy, modify, distribute, and adapt it subject to the license terms.