Skip to content

Write and run your own task

The Quickstart runs a task card that ships with this repo (rm_singleturn_demo). This page is for when you want to run your own task or point it at your own model — the two things a new user actually needs, not the bundled demo.

1. Write a minimal task card

A task card is a plain dict (or JSON file) — no schema class, no registration. The smallest one that runs has an items dict of trials and a parser key (None if you don't want structured output):

task = {
    "tasktype": "sc",
    "taskname": "my_first_task",
    "instructions": {"definition": ["Answer each question briefly."]},
    "contexts": ["general"],
    "contexts_id": ["Q"],
    "context_present": False,
    "items": {
        "Q_1": [{"trcode": "Q_1", "stimulus": "What is 2 + 2?", "corrAns": "4"}],
        "Q_2": [{"trcode": "Q_2", "stimulus": "What color is the sky?", "corrAns": "blue"}],
    },
    "chain_type": "item",
    "parser": None,
}

corrAns is optional context for your own scoring — psychscanner-primal doesn't grade it automatically, but it's there in the output for you (or a FeedbackBase handler, see below) to compare against pred_resp.

2. Dry-run it against mock-llm

No API key, no network — good for checking your task card is well-formed before spending money:

from pathlib import Path
from psychscanner import ExpCard, ExpCardInit, ScannerModel, to_csv

card = ExpCardInit(
    model="mock-llm",
    family="mock-llm",
    task_file=task,          # a dict works directly, no need to write JSON to disk yet
    cogtype="no",
    nsim=1,
    memory="SingleTurn",
    proj_dir=Path("./results"),
    projectname="my_first_task",
)

scanner = ScannerModel(expcard=ExpCard(card))
results = scanner.run()
df = to_csv(scanner, path=card.proj_dir)

results[0] is a list of per-trial dicts (trcode, pred_resp, …); df is the same data as a Polars DataFrame, also saved to a timestamped CSV under proj_dir.

3. Point it at a real model

Swap model/family — everything else about the task card stays the same. Only ollama ships out of the box; every other family needs its own LangChain integration package (uv pip install langchain-openai, etc.) and its API key env var (see the table in Quickstart):

card = ExpCardInit(
    model="llama3.2",
    family="ollama",
    task_file=task,
    cogtype="no",
    nsim=5,          # run 5 independent participants
    memory="SingleTurn",
    proj_dir=Path("./results"),
    projectname="my_first_task",
)

4. Move the task card to its own file

Once you're happy with it, save the dict as JSON and point task_file at the path instead — this is what lets you (or a teammate) fetch it later with task_library("my_first_task") instead of hardcoding a path. See Using psychscanner-primal §3 for how task_library() resolves names to files.

5. Add adaptive branching

If a trial's response should determine what runs next — retry on an invalid answer, raise/lower difficulty, insert a follow-up probe — see Conditional Next Trial. That mechanism, not a bigger task card, is the right tool once "what runs next" depends on what the model just said.

See also

  • Using psychscanner-primal — the four different things people use this repo for, and which one you actually want
  • Conditional Next Trial — branch the trial sequence based on the model's response
  • Task JSON schema and multimodal/tool-calling stimuli are documented in full in the upstream psychscanner docs — the schema is identical here, this package just ships fewer bundled task cards