Concepts

One task, a few explicit steps.

A Ploinky Workers task says what to do at each phase and where to go next. Execution follows that file. The model is selected through a configured tier.

The whole picture

Suppose you want to extract a title from a piece of text and save it. The caller supplies the text and a working directory. The first phase asks a model for the title. The second phase writes a file and returns the result.

INPUTTask + dataThe caller supplies a .json file or text request, plus input and an optional directory.
PHASEModel or codeA tier routes a prompt to a model; a null tier runs code without a model.
OUTPUTResult + statusThe task ends with a value. Detached tasks also retain their phase and result by ID.

The local proxy sits between model phases and providers. It resolves the tier, applies request limits, records traffic, and can serve an identical model request from cache.

A task file you can read

A task is a validated map of named phases, starting at begin, loaded from JSON or Markdown. Markdown uses ## phaseName, field subheadings such as ### tier and ### template, and literal fenced prompt/code blocks. Functions, lambdas, executable modules and wrapped task definitions are rejected. Phase code contains statement strings executed in the confined phase sandbox. These statements can use await import("./lib/transform.mjs") for local libraries and explicitly construct the next phase input. Library imports stay inside the working directory and sandbox; codeFile is rejected. A static .mjs declaration may contain only export default followed by strict JSON; it is parsed, never imported.

save-title.json
{
  "begin": {
    "tier": "tiny",
    "template": "Return a short title for: ${input}",
    "code": "this.title = result; this.next(\"save\")"
  },
  "save": {
    "tier": null,
    "code": "await this.writeFile(\"title.txt\", this.title); this.end({ title: this.title })"
  }
}

Run it with pworker run ./save-title.json --input 'A report about rainfall' --cwd ./project. The first phase substitutes ${input} into the prompt. Its code stores the model answer and calls this.next("save"). The second phase uses no model, writes under the caller's directory, and calls this.end(...).

Each task has its own variables. this.next(name) selects the next phase; this.end(value) completes it. Phase code can transform results between model calls. Execution has a 100-phase limit.

Tiers choose models

The task says tiny, not a provider-specific model ID. In configuration, that tier points to an ordered provider/model chain. Available tiers include nano, micro, tiny, small, medium, good, and best, with supertiny as an alias. The first chain entry is primary and later entries are fallbacks, so one tier can mix providers.

Connect a provider in the interactive CLI first. Then choose a tier and select its primary model directly from one searchable list of usable text models across connected providers. The picker shows the provider, published input/output pricing, and plan request cost where available. A model already known to require a separate credit balance is excluded, as is a model that does not return text. A configured local model or an OpenAI-compatible endpoint can be a provider too.

Start from a request or a file

Pass an existing .json task for repeatable, reviewable work. Or pass a natural-language request: Ploinky Workers asks the configured good tier to compile it into a task file and caches that file in the user home. The same request can reuse the compiled task.

two ways to run
pworker run ./save-title.json --input 'A report'
pworker run 'Create a short title from the input' --input 'A report'

Model-free task files can run without an available provider. Compiling a text request requires one and produces the same validated JSON phase map. No task file is imported or evaluated as a module. Review phase statements before granting them access to a working directory.

The caller owns the directory

--cwd DIR gives a task the exact directory chosen by its caller. Ploinky Workers does not create a task-specific subdirectory. Phase code can use confined methods such as this.readFile(), this.writeFile(), and this.listFiles(). Paths must stay inside that directory. If two tasks receive the same directory, they can access the same files, while their in-memory task variables remain separate.