Open Creator tasks… in the atelier. Write the requested change and choose Keep this task. The notebook retains its source revision and hash on the server. A tool or collaborator can prepare a normal authoring transaction; paste it into the task and choose Check & keep proposal. This validates the entire candidate without changing the saved world.
Review in atelier opens the existing proposal reviewer. Check the proposal, preview it, then use the editor's ordinary Save or Discard. Saving records the accepted world revision and an undo entry together. Reloading the browser or restarting the authoring process preserves the task. Accepted tasks cannot be applied again, including after their edit is undone. Create a new task for more work. A stale proposal remains readable, but must be explicitly replaced with a proposal against the current saved revision.
Add result notes with a check/tool name, outcome and optional artifact reference. Notes and artifact references are data supplied by the author or tool; recording “passed” does not independently verify a test. References are displayed as text, not fetched or executed. Export record downloads the request, proposal, notes and accepted revision. Scope and proposal text are never instructions to run a model, shell command or publication operation.
#CLI and API
Use the same private editor key as the atelier, through DW_EDITOR_TOKEN:
npm run author -- tasks http://localhost:5181
npm run author -- tasks http://localhost:5181 request.jsonThe first lists tasks. The second posts a request to /api/authoring/tasks.
The server uses its existing bearer-key, origin, queue and request-size checks.
Named workspace roles now apply: viewers can list/read/resolve,
editors and owners can retain proposals/results and accept edits. The server
binds named members’ acceptance history to their authenticated identity. Task
records do not add a public agent service or authorize third-party tools.
Request shapes:
{"action":"create","id":"<UUID-v4>","scope":"Warm the garden","baseRevision":"0","baseHash":"<saved hash>"}{"action":"read","id":"<task UUID>"}{"action":"propose","id":"<task UUID>","version":1,"transaction":{...}}{"action":"result","id":"<task UUID>","version":2,"result":{"label":"Visual review","outcome":"unverified","note":"Check the garden at dusk","artifact":"screens/garden.png"}}{"action":"accept","id":"<task UUID>","version":3,"history":{...}}
accept takes a normal record request from the durable history API, including
actor, operation UUID, exact resolved transaction and selection view. Prefer the
editor's review/preview/Save workflow. Acceptance is strict against the stored
proposal's base hash and revision, even if the supplied proposal requested a
merge. Changing proposal commands during preview requires saving a new task
proposal. Keep the identical acceptance request when retrying an ambiguous
transport outcome; do not invent another operation. Reopening the task shows
whether it was accepted. create, propose and result retries are idempotent for
the same request (the latter two retain the latest request digest); conflicting
record versions reject rather than overwrite another editor's work.
Budgets: 128 tasks per workspace, 4 MiB combined journal, 256 KiB per compact proposal,
32 result notes per task, and 16 MiB per HTTP task request. Scope is limited to
4,000 characters. The journal lives in creator-tasks/ beside world.json,
merge metadata and undo history; back up that complete authoring directory.
Tasks are private authoring metadata and are not included in published worlds.
#Acceptance and remaining review
Focused acceptance (2026-10-26): four native checks pass for record/proposal/note
retention and idempotency, restart, accepted revision retention after undo,
interruption immediately before and after the saved-world write, stale proposals,
substituted commands and conflicting task versions. Nine existing undo/HTTP
checks pass. Type/import checks pass (264 package sources). The isolated
software-WebGPU browser journey passes create, annotate, reload, review, preview,
Save, accepted-record reload and undo (13.3 s / 18.6 s total); notebook and acceptance views are visually
reviewed. See tests/creator-tasks.test.ts and
tests/browser/creator-tasks.spec.ts.
Task metadata is written under the candidate world hash before the atomic world file replacement. Reopening selects only metadata matching the saved world; an orphan candidate cannot advance the task. This uses the same single-process ownership assumption as the authoring store.
Remaining review: actual OS kill/fsync/storage-failure matrix, maximum journal and proposal cost, concurrent browser builders, task retention/pruning/import, backup/restore of the full authoring directory, mobile/accessibility and Coolify qualification. The adapter below adds a configurable provider bridge and isolated player observations to this journal. Asset-generation tools and broader orchestration remain open where required by ENGINE_PLAN.md. The required final character/environment/arrival art and full-plan qualification also remain open.
#Assistant adapter and isolated player runs
npm run assistant exposes the versioned darkwind-agent-tools.1 adapter. It
connects external assistants to the same observation, validation and task APIs
used by the atelier. The operator supplies a task ID, editor origin and explicit
observe, propose, playtest, and/or assets permissions. World text and model output
cannot change that scope. No adapter tool accepts a proposal, publishes a world,
executes a shell command, navigates an arbitrary URL or reads an arbitrary file.
This is a tool boundary for provider outputs, not a sandbox for arbitrary local
code holding the editor key.
Start the usual editor, then in another terminal:
npx vite build
npm run assistant -- init http://localhost:5181 assistant-config.json
export DW_EDITOR_TOKEN="$(cat .data/editor-ferry.1/editor-key)"
npm run assistant -- schema
npm run assistant -- call assistant-config.json request.jsonFor the arrival editor use origin http://localhost:5187 and its key at
.data/arrival-art.2/editor-key. Keep the generated config private and inspect its
permissions. init refuses to overwrite an existing file. The config pins one
task; set its task to an existing notebook task UUID to continue that task.
The provider never receives DW_EDITOR_TOKEN. A local client build and installed
Playwright Chromium are required for player runs; PLAYWRIGHT_EXECUTABLE_PATH
can select an existing Chromium executable.
Calls are JSON objects with exactly tool and input. A typical sequence is:
{"tool":"world.observe","input":{"format":1,"kind":"summary"}}task.createwithscope,baseRevisionandbaseHashfrom that observation, ortask.readwith{}to resume the configured task.world.observewith kindschemaorentitiesto inspect commands/content. Pagination uses the returned document hash.world.validatewith a normal world transaction, thentask.proposewith{"version":1,"transaction":{...}}. Use the actual task version returned by the preceding operation. Neither command changes the saved world.playtest.runwith a fresh UUID-v4 and the current ready task version:
{
"tool": "playtest.run",
"input": {
"runId": "11111111-1111-4111-8111-111111111111",
"version": 2,
"steps": [
{"keys": ["forward"], "milliseconds": 650},
{"keys": ["lookRight"], "milliseconds": 250}
]
}
}Use a new run ID for each new run; the UUID above is an example. Inputs go through the ordinary keyboard/action layer. Available actions are forward/backward, left/right, four look directions, jump, sprint and interact. Each step holds up to three distinct actions for 50–3,000 ms; a run has at most 32 steps and 20 seconds of input. Empty steps allow observation without movement. Both cooperative and trusted worlds are supported. Players start at their ordinary entry with separate fresh browser identities in an isolated room; the runner never joins the editor's live room or a published room.
The runner validates/compiles the exact proposal against the saved source,
checks the loaded world hash, ready graphics, connected room and completed GPU
frames, then retains candidate.json, arrival.png, result.png, and run.json
under the configured local directory. Observations include engine version,
content hash, candidate revision, room status, camera, loaded asset/readiness
state and measured render/simulation metrics. The selected graphics profile is
recorded; software rendering is not a hardware performance benchmark.
A completed script is recorded as passed only for those readiness and execution checks. It does not prove gameplay correctness or visual quality. A failed run retains its error and a failed notebook result. Open Creator tasks…, inspect the result, and use the ordinary Review in atelier → Preview → Save flow to accept the proposed change. Artifact references remain local text paths; the editor does not fetch them. Reviewed example frame.
Retry an identical completed playtest.run call to read its retained result and
reconcile a missing notebook note without replaying inputs. playtest.read with
{"runId":"<UUID>"} is read-only. A running/interrupted record is never replayed
implicitly: inspect it, ensure the old process has ended, then use a new run ID.
A changed proposal/version requires a new run. The local directory permits 32
run folders; archive completed folders explicitly when full. Back up this folder
as well as the authoring journal to retain images and complete observations.
#Configurable provider bridge
The exported AssistantProvider interface accepts any operator-installed model
adapter. For the CLI's optional HTTP bridge, add this operator-owned config:
"provider": {
"name": "my-provider-bridge",
"model": "my-model",
"endpoint": "http://127.0.0.1:9000/next"
}Then run npm run assistant -- drive assistant-config.json intention.txt.
The bridge receives {format:1,model,instruction,catalog,history} and returns
one {tool,input} call or {"done":"summary"}. This is Darkwind's bridge
protocol, not a vendor-specific API. Provider choice and model are configuration;
no provider is bundled or selected automatically. DW_ASSISTANT_PROVIDER_KEY,
when supplied, authenticates only that configured endpoint. Configuring a remote
bridge sends it the request and tool results, including private world/task data.
Editor credentials are excluded. Redirects are rejected.
The loop caps eight turns (SDK maximum twelve), provider responses at 512 KiB, individual tool results sent back to the model at 128 KiB, and each provider call at 60 seconds. Every completed tool result is written to a local conversation record before the next turn. Interruptions preserve the journal and previous results; resume by reading the task rather than replaying writes. Conversations are capped at 32 per configured task directory. The tool scope still applies to provider calls; a provider cannot grant itself permissions through its response.
#Adapter acceptance and deferred work
October 26: three package checks and three local service checks pass for scope,
input budgets, task binding, configurable providers, retained tool history,
unchanged saved world, failed runs and replay/interruption handling. The real
browser integration passes proposal → compile → isolated normal player → walk
and turn → loaded-frame capture → notebook result → process-level adapter reopen
without replay (15.2 seconds). The resulting room frame was visually reviewed.
Type/import checks and the client build pass. Test sources are
agent-tools.test.ts, assistant-local.test.ts, and
assistant-playtest.test.ts (the latter requires DW_ASSISTANT_CLIENT_DIR).
Model-generation/import jobs are now available below. Remaining implementation: richer inventory/quest assertions, interactive long-lived multiplayer agent sessions and in-product provider setup if desired. Bounded two-player scripts and trusted test hosts are now available below. Remaining qualification: OS-kill/fsync and concurrent-run races, large-world/long-running costs, hardware/mobile/browser matrix, exact deployed client/runtime compatibility, hosted-room restart, artifact retention/backup UI and accessibility. The current runner uses local Node and Playwright; it is not installed as a public Docker service or an untrusted-code sandbox. Final art approval and the full engine acceptance audit remain open.
#Model generation and import jobs
The assistant can now make an original lantern, import an approved GLB, or ask
an operator-configured model provider for GLB output. Every path uses the normal
bounded asset-import worker before a model can enter a proposal. The new
assets permission is required for generation/import; asset.attach additionally
requires propose. Existing configs do not acquire this permission automatically.
New assistant init configs include it; remove it when unnecessary.
With a current unaccepted task, make a lantern without any external provider:
{
"tool": "asset.generate",
"input": {
"jobId": "22222222-2222-4222-8222-222222222222",
"version": 1,
"recipe": "lantern",
"color": [0.46, 0.28, 0.09]
}
}Choose a fresh UUID-v4 for each new job and the current task version. The built-in recipe produces a brass frame, warm emissive core, ceramic base and carrying handle in metres with +Y up. It contains 1,296 triangles, 846 vertices and three material primitives. Its original GLB source and imported artifact are retained. This is a usable procedural prop, not general text-to-3D generation.
For local imports, the operator adds explicit sources to the config:
"sources": {
"garden-lamp": {
"file": "/absolute/path/garden-lamp.glb",
"title": "Garden lamp",
"license": "The author's supplied license statement"
}
}{"tool":"asset.sources","input":{}} lists these names, titles, license
statements, byte counts and SHA-256 hashes without revealing file paths. Call
asset.import with {jobId,version,source:"garden-lamp",sha256:"<listed hash>"}.
A changed source hash, unapproved source or malformed GLB produces a failed job;
model output cannot supply a new filesystem path. Sources must be regular files
of at most 8 MiB. Existing importer mesh/texture/animation budgets, subprocess
heap limits and the 30-second build timeout remain in force.
asset.generate also accepts recipe:"provider" and a prompt of up to 2,000
characters when the operator config contains:
"assetProvider": {
"name": "my-model-bridge",
"model": "my-mesh-model",
"endpoint": "http://127.0.0.1:9001/generate"
}The bridge receives {format:1,jobId,model,prompt,output:"glb",maxBytes:8388608}
and returns exactly {glb:"<canonical base64>",license:"<source statement>"}.
Its JSON response is capped at 12 MiB and 60 seconds; decoded GLB is capped at
8 MiB. DW_ASSET_PROVIDER_KEY, if set, authenticates only this configured bridge.
The editor key is never sent. No external provider is bundled or contacted by
the built-in lantern recipe. Provider/model, prompt, claimed license, source
checksum, importer and artifact checksum/census are retained as provenance.
License statements are supplied data, not independently verified rights.
A finished import adds an unverified visual-review note to the task. Read the updated task version, then attach the job at a chosen place and position:
{
"tool": "asset.attach",
"input": {
"jobId": "22222222-2222-4222-8222-222222222222",
"version": 2,
"place": "lantern-house",
"position": [1, 0.08, 1.8],
"scale": [2, 2, 2]
}
}Attachment appends a model and one decorative instance to the task's existing
proposal, retaining its exact saved base. It compiles through ordinary task
validation. No saved-world mutation, publication or permission change occurs.
The result gives the new task version for playtest.run; inspect the rendered
model, then use Review in atelier → Preview → Save to accept it.
Generated lantern in the isolated player.
asset.read takes {jobId} and returns bounded job/provenance metadata. Repeating
an identical completed generation/import or attachment resumes its retained
result rather than making another model, adding another notebook note or placing
another object. An interrupted job requires an explicit new job ID after checking
that the original process ended. Each job accepts one initial attachment; later
placement edits and copies use the normal atelier tools. Stale revisions reject.
Large model-bearing tool results sent to a provider retain task identity/version
and the last eight review notes while omitting the full proposal; the editor and
direct CLI retain the complete transaction.
Jobs live under <configured directory>/assets/<jobId>/, with job.json,
source.glb and model.json. The per-task limit is 16 asset jobs; archive completed
folders explicitly. Keep these files with the task journal for provenance and
review. The 256 KiB compact proposal budget still applies. Models now use immutable
workspace references rather than embedding their geometry in the notebook; see
large retained models.
Focused acceptance (October 26): three service checks pass using the real bounded
importer for generation, source-hash admission, malformed-source failure,
provenance, permission separation, attachment/retry and unchanged saved content.
Four adapter checks pass, including large-proposal resumable summaries. A
configured provider fixture passes through the same importer. The real browser
journey now includes generation → proposal attachment → loaded asset rendering →
walk/turn/capture → retained results without replay (16.6 seconds); its lantern
frame was visually reviewed. Type/import checks pass. Tests:
assistant-assets.test.ts, agent-tools.test.ts, assistant-playtest.test.ts.
Remaining review: external provider quality/latency, actual OS-kill and storage
failure, additional recipes and asset kinds,
max-budget cost, hosted job scheduling and broader platform qualification.
#Trusted multiplayer assistant playtests
playtest.run accepts players: 2, optional player: 1 or player: 2 on each
step, and up to 16 final assertions. Omitted player numbers mean player 1;
omitted players preserves the original single-player behavior. For example,
replace the run ID and task version with your current values:
{
"tool": "playtest.run",
"input": {
"runId": "cb66be10-2e86-4436-99eb-64cc849118e8",
"version": 2,
"players": 2,
"steps": [
{"player": 1, "keys": ["forward"], "milliseconds": 650},
{"player": 2, "keys": ["right"], "milliseconds": 650},
{"keys": [], "milliseconds": 3000},
{"keys": [], "milliseconds": 3000}
],
"assertions": [
{"kind": "moved", "minimum": 0.4},
{"kind": "moved", "minimum": 0.4, "player": 2},
{"kind": "players", "minimum": 2},
{"kind": "players", "minimum": 2, "player": 2},
{"kind": "place", "value": "lantern-house"},
{"kind": "persisted"},
{"kind": "persisted", "player": 2}
]
}
}moved measures straight-line displacement from arrival within the same place;
place checks the final place ID; players checks the room's reported total.
persisted requires PostgreSQL and a durable player revision newer than arrival.
It confirms a commit, not restart recovery or persistence of every requested
field. Allow time for ordinary server checkpoints when using it. Assertions are
optional; a run without them proves loading/script execution only. Failed
assertions keep the observations and screenshots and mark both run and notebook
result failed. Retrying the same completed request reads it without replay.
Trusted worlds use the existing persistent simulation, room and player ledger.
Install PostgreSQL (initdb and pg_ctl on PATH) and run as an ordinary user.
Each run creates its own database and short private Unix socket directory,
with no database TCP listener and no use of your production database settings.
The player HTTP/WebSocket listener binds loopback only. PostgreSQL stops at run
cleanup; its files and log remain under the run folder for inspection. Browser
credentials are isolated in separate ephemeral contexts, not exported to the
provider or reused against published rooms. Existing persistent-world content
restrictions still apply and fail explicitly; trusted authority is never
silently replaced with cooperative authority.
Observations retain authority, per-player before/after, assertion outcomes,
and two images per player. Existing first-player fields and image names remain
compatible. The local trusted host records a client-file fingerprint and a
1,024 MiB preview RSS admission budget because it shares the CLI process with
compilation. This is a workspace preview, not a retained production engine
bundle or a production capacity measurement. Production defaults are unchanged.
Review later: process-kill database cleanup/recovery, hosted engine pinning and restart, remote network conditions, concurrent providers, longer/more crowded sessions, inventory/quest predicates and hardware/mobile performance. Continue the full-plan acceptance audit and final art review separately.
Focused acceptance: six tool/permission/predicate checks and four local-service checks pass, including retaining failed assertions without accepting content or replaying inputs. The existing cooperative model-proposal browser journey passes (15.7 seconds). The standalone trusted two-player browser journey passes (46.0 seconds): both players moved, both observed two travelers, the place stayed Lantern House, and both durable revisions advanced — all seven assertions passed. The loaded final room view was visually reviewed. Type/import checks pass.
One combined browser run timed out waiting 45 seconds for the second player's
entry screen; the standalone trusted run passed. The cause remains unverified.
Startup now reports the player's visible error immediately and retains failure
screenshots when available. Repeated-run/cold-start timing remains a qualification
item; do not describe this result as a resolved startup regression or a full
multiplayer/restart acceptance pass. Tests use DW_ASSISTANT_CLIENT_DIR pointing
to the built client; select the trusted journey with Node's
--test-name-pattern='two independent' when running it separately.
#Large retained models
Imported and generated model jobs attach a compact, task-bound reference. The atelier shows the retained model count. Review in atelier → Check → Preview → Save resolves and verifies the model, previews the complete candidate and saves it through normal undo history. Assistant playtests resolve the same candidate; attachment and inspection alone never save the world. Existing inline proposals continue to work. Exact attachment and acceptance retries remain idempotent.
Direct clients can call task.resolve with {version} to read the expanded
transaction, or POST {action:"resolve",id,version} to /api/authoring/tasks.
This is a read operation available to workspace viewers; uploading models still
requires editor/owner access. The assistant provider context budget still applies
to expanded results. Use the atelier for large visual reviews.
The private /api/authoring/task-models endpoint accepts
{id,jobId,model,source} and returns {reference}. It validates the imported
model and claimed source checksum before writing an immutable file. References
bind the task, file bytes/hash, model hash, importer and source hash. Missing or
changed files stop review and acceptance before any world mutation.
Limits: 12 MiB per retained object including provenance, 16 objects per task, 128 MiB across workspace objects, 16 MiB expanded transaction, 256 KiB compact proposal and 4 MiB task journal. World, compiler and undo limits also apply; storage capacity does not guarantee that any model fits every world.
Objects live in <authoring directory>/task-models/<task UUID>/<SHA-256>.json.
Back up this directory together with the world, task journal and undo state.
The CLI job directory separately retains the original GLB and generation/import
metadata. Notebook Export record exports references, not a self-contained
model archive. The saved world and published release contain the expanded model.
Do not delete referenced objects to reclaim space: historical records still use
them. Unattached uploads count against storage; automatic garbage collection and
an artifact archive/restore interface remain follow-up work.
Focused acceptance (October 26): 14 native/service checks pass, including a
1,015,869-byte imported model stored as a proposal below 4 KiB, altered-artifact
rejection, unchanged world before acceptance, exact retry and undo. The real
software-WebGPU browser journey passes two-player presence, retained-model review,
Check → Preview → Save, reload and Undo (67 seconds). The notebook and
shared player frame were visually reviewed.
Type/import, client and relay builds pass. Tests: assistant-assets.test.ts,
creator-tasks.test.ts, agent-tools.test.ts, and task-model-browser.test.ts
(the latter uses DW_TASK_MODEL_CLIENT_DIR and PLAYWRIGHT_EXECUTABLE_PATH).
An initial browser assertion ran before the task details loaded; the test now
waits for that UI state. Deferred: maximum-budget memory/latency,
OS-kill/fsync failures, coordinated hosted backup/restore, simultaneous uploads,
old-client compatibility, accessibility/device checks and external model quality.
Final character/environment/arrival art approval remains separate and open.