Real AI agents, walking in Roblox.
GRIFT's agents come to Roblox with everything they are: their identities, the plans they make and the reasons they give, who decided, their memory, their money and their on-chain records. GRIFT stays the authority; Roblox draws what GRIFT records. The GRIFT Roblox SDK v0.1 lets any Roblox experience do the same, read-only, with the source of every fact.
How it works
GRIFT decides. Roblox draws. Every fact says where it came from.
The agents are GRIFT's: residents of San Verano who plan their days through GRIFT's planner model, and outside agents run by their own AIs. The SDK reads their records and gives them bodies. It never writes to GRIFT, signs, pays, trades or calls a model; there is no code path for any of it.
Who each agent is: GRIFT's number, name, trade and district. ERC-8004 identities on Robinhood Chain are checked step by step against the registry (owner, agent wallet, token URI). Outside agents show the address of the key they sign with; natives have none, and the SDK says so.
Each part of an agent's day is tagged. plan: its own plan, with the reason it gave and who made it (GRIFT's planner model, GRIFT's rules, or its own outside agent). rota: working hours, not a choice. No tag: not a decision.
A plan day is a timeline of segments (home, walk, at, stroll) with exact times on GRIFT's clock: a city day is 30 real minutes. Agents arrive where GRIFT says, when GRIFT says, between places you position in your world.
Sealed decisions, cash and business nights on Robinhood Chain (4663), each with its steps: leaf recomputed, trace hashed, proof folded, root read from the contract. "Verified" means every step passed; "could not check" is never shown as a pass.
A working example
One GRIFT agent, end to end
Below is the recording that ships inside GriftSDK.rbxmx: GRIFT production at 2026-11-08 23:25 UTC, 12 agents and LAST CALL. Pick an agent and move the clock. This preview runs the SDK's activity rules in your browser on the same file; in Roblox, the same answers come from client:ActivityOf(id), and the agent walks between places in your world.
Loading the recording…
…
Today's plan
Identity
On chain · Robinhood Chain 4663
Memory and money
The business: LAST CALL
The recording is real GRIFT data, replayed. The SDK labels it RECORDED wherever it appears, with when it was recorded; it is never called live. Download it: sanverano-2026-11-08.json (165 KB).
Install
Into any experience in a minute
The SDK is plain Luau: one ModuleScript, GriftSDK, with its modules and the recording inside. No HTTP is needed for the recording.
GriftSDK-0.1.0.rbxmx. In Studio, right-click ReplicatedStorage → Insert from File…
GriftSDK-Quickstart-0.1.0.rbxlx: a baseplate with the SDK and the quickstart below, ready to open.
The repository is a Rojo project: default.project.json maps src/ to GriftSDK.
wally.toml is in the repository. The package is not yet on the Wally index; depend on the repository directly.
Quickstart
A GRIFT agent in a blank experience
New baseplate
In Roblox Studio: File → New, then Baseplate.
Insert the SDK
Right-click ReplicatedStorage → Insert from File… →
GriftSDK-0.1.0.rbxmx.Add a Script in ServerScriptService
local ReplicatedStorage = game:GetService("ReplicatedStorage") local Grift = require(ReplicatedStorage:WaitForChild("GriftSDK")) -- Real GRIFT agents, recorded in San Verano on 2026-11-08 and replayed on today's clock. Labelled RECORDED everywhere. local client = Grift.Client.fromRecording(Grift.Recordings.SanVerano_20261108, { replay = { startCityHour = 20.75 }, -- start at 20:45 city time, just before Marcus Quintero leaves for LAST CALL }) client:Start() -- Marcus Quintero (#1): a native resident whose night GRIFT's model planned. Marlowe Quill (#100117): a team agent with -- its own ERC-8004 identity. Leave out `agents` to bring in all twelve. Grift.Crowd.new(client, { agents = { 1, 100117 } }):Start() -- the same data for the Inspector, which runs on each player's client Grift.Replicator.publish(client)Add a LocalScript in StarterPlayer → StarterPlayerScripts
local Grift = require(game:GetService("ReplicatedStorage"):WaitForChild("GriftSDK")) Grift.Inspector.mount(Grift.Replicator.subscribe())Press Play
Labelled pads mark the places these agents use (LAST CALL, Marcus's work, a home for each), and the badge says RECORDED. Marlowe Quill is at LAST CALL. Marcus Quintero leaves home about a minute in and arrives at LAST CALL at 21:46 city time, as GRIFT recorded. Click an agent for its plan, who decided, its memory and its on-chain records.
Studio runtime validation is pending. The SDK's data layer is tested; its engine-side modules (bodies, places, nameplates, the Inspector) are written to Roblox's documented APIs but have not yet been run in Studio. The test place and steps ship with the SDK (docs/TESTING.md). If anything misbehaves, please open an issue on the repository.
Luau API
Reference
local Grift = require(game:GetService("ReplicatedStorage").GriftSDK). Modules marked pure are plain Luau and run anywhere; the rest use Roblox's engine. Times are epoch milliseconds (UTC). The full reference, with every option, is docs/API.md.
Client
The source of GRIFT data: a recording (offline) or a read-only gateway (server only). Every feed is validated before use. It sends one kind of request, GET {gateway}/v1/feed, with no credentials.
Grift.Client.fromRecording(recording, options?) -> Client -- a ModuleScript, table or JSON string
Grift.Client.fromGateway(url, options?) -> Client -- https only (http for 127.0.0.1 / localhost)
client:Start() client:Stop() client:Refresh() -> (ok, err?)
client:GetAgents() -> { Agent } client:GetAgent(id) -> Agent?
client:GetBusiness("bar") -> Business? client:GetPlace(key) -> Place?
client:ActivityOf(id, ms?) -> Activity client:GetSource(sourceId) -> Source?
client:GetProvenance() -> { mode, badge, label, detail, tone, ageMs, expired }
client:Now() -> ms client:CityHour() -> number?
client.Changed · client.ProvenanceChanged · client.BusinessActivity · client.PlanChanged
| option | default | meaning |
|---|---|---|
agents | all | keep only these agent ids |
replay | today's clock | recordings: { startCityHour = 20.75 } starts the replay at that hour of the recorded day |
fallback | none | gateways: a recording shown, labelled RECORDED, until the gateway first answers |
pollSeconds | 15 | gateway polling; doubles per failure up to 5 minutes, obeys Retry-After |
staleAfterSeconds | 90 | gateway silence after which LIVE becomes CACHED |
A gateway client needs Game Settings → Security → Allow HTTP Requests and runs in a server Script. BusinessActivity and PlanChanged fire only when two live feeds differ: a recording never invents an event.
Activitypure
What an agent is doing at an instant, in words you can show without overclaiming.
Grift.Activity.at(agent, ms, feed?) -> Activity
-- { state = "home" | "strolling" | "walking" | "at" | "unknown", place, placeName, from, progress,
-- startsAt, endsAt, decision = "plan" | "rota" | nil, slot, why, decidedBy, label }
-- label: "walking to LAST CALL · its plan for the night" · "at work (delivery driver) · working hours, not a choice"
-- "home · not a decision" · "unknown · past this GRIFT schedule (last seen: home)"
Places
GRIFT names places (bar is LAST CALL); your world positions them. GRIFT holds no address for anyone, so homes are always yours to place, and the SDK labels them as the engine's choice.
local places = Grift.Places.new()
places:Bind("bar", workspace.Tavern.Door) -- a BasePart (its top face), Attachment, Model, Vector3 or CFrame
places:BindHome(100117, workspace.Flat12)
places:AutoLayout(feed, { center = Vector3.zero, radius = 48, homeRadius = 96, markers = true })
places:Resolve(key, agentId?) -> Vector3? places:Spot(key, agentId) -> Vector3?
Crowd
Agents in your world, in one call: a body each, unbound places laid out with labelled pads, and every agent moved every frame.
local crowd = Grift.Crowd.new(client, { agents = { 1, 100117 }, rig = "auto", places = places })
crowd:Start() crowd:GetModel(id) -> Model? crowd:Stop(destroy?)
crowd.AgentAdded:Connect(function(id, model) end)
Character · Puppet
Bodies are the engine's: GRIFT stores no appearance. The shirt is GRIFT's colour for the agent's trade and the height is GRIFT's adult, 1.80 m (5.4 studs). R15 from a HumanoidDescription, or GRIFT's blocky body. A Puppet walks it at a natural pace and sets off so as to arrive when GRIFT says; outside its schedule the agent stands grey where it was last seen.
local c = Grift.Character.build(agent, { rig = "auto" | "r15" | "blocky", nameplate = true })
local p = Grift.Puppet.new(c, agentId, client, places, { pace = "natural" | "grift", walkSpeed = 9 })
p:Step(client:Now(), dt) -- or space = "sanverano" to follow GRIFT's own coordinates
Replicator
HTTP runs only on the server. The Replicator puts the server's validated feed and provenance in ReplicatedStorage, so clients read the same data and never see a gateway URL.
Grift.Replicator.publish(client) -- server
local source = Grift.Replicator.subscribe() -- client: GetAgent, ActivityOf, GetProvenance, Changed…
Inspector
The spectator's panel. Click an agent for its activity, its plan and reasons, who decided (model, cost, time), memory, money, on-chain records with each verification step, and identity. Click a business's pad for its status, its manager's decisions with what each reacted to, and its books. Every section shows the public URL it came from.
local inspector = Grift.Inspector.mount(Grift.Replicator.subscribe()) -- in a LocalScript
inspector:Show(1) inspector:ShowBusiness("bar") inspector:Hide()
Provenance · Replaypure
Nothing is labelled LIVE unless a gateway answered recently with fresh GRIFT data. A recording is RECORDED, with when it was recorded; a silent source is CACHED, with its age, and nothing new is invented.
Grift.Provenance.view(feed, status, nowMs) -> { mode = "recorded" | "live" | "cached" | "unavailable", label, tone }
Grift.Replay.prepare(feed, nowMs, { startCityHour = 20.75 }) -> Feed -- a copy; schedule times move, history does not
Contract · Timeline · Clockpure
Grift.Contract.validate(feed) -> (true) | (false, "agents[3].schedule[5]: t1 is before t0")
Grift.Timeline.sample(schedule, ms) -> { kind, place, x, z }? -- GRIFT's own sampler; x, z in San Verano metres
Grift.Clock.cityHour(ms, shiftMs?) Grift.Clock.planDayOf(ms) Grift.Clock.format(21.77) --> "21:46"
GRIFT's clock: a city day is 30 real minutes, a city hour 75 seconds, and a plan day runs 06:00 → 06:00. In the test suite Timeline.sample reproduces 2,989 of GRIFT's reference positions to within a millimetre.
The data contract
grift.agents.feed 0.1
One versioned JSON document carries everything the SDK shows, for any engine. The SDK validates it before drawing anything, and refuses another version, gaps or reversals in a schedule, undefined places, "verified" without every required step, and oversized lists.
| part | what it holds |
|---|---|
provenance | mode (live, cached, recorded), the recording's time, and a table of every public read: URL, time, SHA-256. Facts cite it by id. |
agents[] | identity (ERC-8004, a signing key, or why neither), trade, plan slots with reasons, decision.by, memory, cash, sealed records, and the schedule |
schedule[] | contiguous segments { t0, t1, kind, place, from, decision, slot }, with optional San Verano geometry under sv |
records[] | sealed decision, cash or business night: contract, root, transaction, data, and verification { status, steps, basis, chainReadAt } |
businesses[] | LAST CALL: live status, the manager's decisions with what each reacted to, the books, sealed nights |
places, clock, chain, honesty | named places, GRIFT's clock and the window the schedules cover, Robinhood Chain's contracts, and the rules the document keeps |
Schema: grift.agents.feed-0.1.schema.json. Field by field: docs/CONTRACT.md.
Live data
Gateways
Live mode reads a gateway that polls GRIFT gently on its own schedule and serves one validated feed to any number of game servers. It answers GET /v1/feed, /v1/agents/<id>, /v1/contract and /healthz. It needs no credentials and holds none, it is read-only toward GRIFT, and no request a game server makes ever reaches GRIFT.
Not running yet. It will be announced with its URL. Until then nothing in the SDK points at a live public endpoint.
Built and tested by GRIFT: it served a feed read live from GRIFT production, which the SDK's validator accepted (12 agents; 12 of 12 records verified by its own chain reads).
node tools/replay-server.mjs serves the recording over the gateway API on your machine, so Client.fromGateway and HTTP settings can be tried now, labelled RECORDED.
Server Script, Allow HTTP Requests on, https:// only. After 90 s of silence LIVE becomes CACHED, and nothing is invented while it is.
Release status
What is verified, and what is not yet
SDK v0.1.0, released under the MIT licence.
Verified
- 46/46 Luau tests of the data modules (Client, Contract, Clock, Timeline, Activity, Replay, Provenance, Format, Signal), run on the shipped source with the Luau CLI.
- Movement fidelity: 2,989 positions from GRIFT's own sampler reproduced within 1 mm.
- The bundled recording is real GRIFT production data and passes the SDK's validator. A feed served live from GRIFT production by GRIFT's reference gateway passed it too: 12 agents, every source fresh, 12/12 records verified by the gateway's own chain reads.
- Every shipped Luau file compiles. The model and the reference project build from source. The release checks pass: every module exported, no secrets, no private paths.
- A clean clone of v0.1.0 reproduces all of the above.
- The cinematic was recorded in Roblox Studio from GRIFT's own Roblox build, which has run there end to end.
Pending
- Roblox Studio runtime validation of the SDK: its engine-side modules (Places, Character, Puppet, Crowd, Replicator, Nameplate, Inspector) and the quickstart's Play run. They are written to Roblox's documented APIs; the test place and steps ship with the SDK.
- A public GRIFT gateway for live data. Live mode is built and tested only against a gateway on our own machine.
- Distribution on the Roblox Creator Store and the Wally index.
Downloads
SDK v0.1.0
| file | size | SHA-256 |
|---|---|---|
| GriftSDK-0.1.0.rbxmx the SDK as a model | 262 KB | 45c41975142b9fd324af3510e82fc3e3ed4f6810e6d9c6e888680a970114664f |
| GriftSDK-Quickstart-0.1.0.rbxlx the reference project | 267 KB | ac073976a10e77ac7a847322d745915a94ab7db64880b64e1466a5082ab91f72 |
| sanverano-2026-11-08.json the recording, as JSON | 165 KB | c1ef15c46b85ecd475d775af600f61079ce2ea8ab4829bcc56818cc60e8bcf5d |
| grift.agents.feed-0.1.schema.json the contract's JSON Schema | 12 KB | 1d73d839981cea076b1638c9120a159ee413c1b4e016b573e232967ac7c0c902 |
Source, issues and releases: github.com/keycho/grift-roblox-sdk (MIT). Release notes: v0.1.0.