Guide16 min read8 sections

Resumable creator tasks

docs/CREATOR_TASKS.mdSource updated 2026-10-26
Markdown
On this page 8 sections

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:

Terminal
npm run author -- tasks http://localhost:5181
npm run author -- tasks http://localhost:5181 request.json

The 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:

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.json

For 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:

  1. {"tool":"world.observe","input":{"format":1,"kind":"summary"}}
  2. task.create with scope, baseRevision and baseHash from that observation, or task.read with {} to resume the configured task.
  3. world.observe with kind schema or entities to inspect commands/content. Pagination uses the returned document hash.
  4. world.validate with a normal world transaction, then task.propose with {"version":1,"transaction":{...}}. Use the actual task version returned by the preceding operation. Neither command changes the saved world.
  5. playtest.run with a fresh UUID-v4 and the current ready task version:
json
{
  "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:

json
"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:

json
{
  "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:

json
"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:

json
"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:

json
{
  "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:

json
{
  "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.

End of Creator tasks