nightshift/examples/tutorial/01-imageboard
2026-05-17 16:50:01 -07:00
..
README.md Add an easier first tutorial, add installers 2026-05-17 16:50:01 -07:00

Tutorial 01: Building A Small Imageboard With Real Local Models

This tutorial starts after the quickstart. The quickstart uses fake command agents so you can verify the pipeline deterministically. Here, you will point NightShift at a small web application and let a local model implement one feature slice at a time.

The target is a compact 4chan-style imageboard: boards, threads, replies, images, tripcodes, sessions, reports, and moderation. That is larger than a toy parser, but it is a better first real-model target because each task maps to ordinary web-app files and tests.

Keep the first run scoped to TASK-001. Let later tasks build on the previous completed task.

What You Will Build

You will create a disposable Flask project with SQLite and use NightShift to implement:

  1. Board and thread data model, routes, SQLite schema, and tests.
  2. Image upload and thumbnail generation.
  3. Bump ordering and reply counters.
  4. Tripcodes and session cookies.
  5. Moderation and report queue.

NightShift still controls the workflow. The model proposes code; NightShift validates, applies, tests, records artifacts, and shows the result in the dashboard.

Prerequisites

Install NightShift from this repository:

python -m pip install -e .

Install runtime dependencies for the target project:

python -m pip install flask pillow pytest

Install and start Ollama, then make sure the model is available:

ollama pull qwen2.5-coder:14b
ollama list

NightShift uses Ollama's local HTTP API, normally at http://localhost:11434.

1. Create A Scratch Target Project

Do not run apply-mode experiments directly inside the NightShift repo. Create a disposable project.

PowerShell:

$TargetProject = "$HOME\Documents\nightshift-imageboard"
New-Item -ItemType Directory -Force $TargetProject
Set-Location $TargetProject
New-Item -ItemType Directory -Force agents, tests, static\uploads, static\thumbs, templates

Bash:

mkdir -p ~/nightshift-imageboard/{agents,tests,static/uploads,static/thumbs,templates}
cd ~/nightshift-imageboard

2. Add The Starter App

Create app.py:

from __future__ import annotations

from pathlib import Path
import sqlite3

from flask import Flask, abort, g, redirect, render_template_string, request, url_for


DATABASE = "imageboard.db"


def create_app(database: str | None = None) -> Flask:
    app = Flask(__name__)
    app.config["DATABASE"] = database or DATABASE
    app.config["UPLOAD_DIR"] = Path("static/uploads")
    app.config["THUMB_DIR"] = Path("static/thumbs")
    app.secret_key = "dev-secret"

    @app.before_request
    def open_db() -> None:
        g.db = sqlite3.connect(app.config["DATABASE"])
        g.db.row_factory = sqlite3.Row

    @app.teardown_request
    def close_db(_exc: BaseException | None) -> None:
        db = g.pop("db", None)
        if db is not None:
            db.close()

    @app.get("/")
    def index():
        return redirect(url_for("board", name="test"))

    @app.get("/board/<name>")
    def board(name: str):
        abort(501)

    @app.get("/thread/<int:thread_id>")
    def thread(thread_id: int):
        abort(501)

    return app


if __name__ == "__main__":
    create_app().run(debug=True)

Create schema.sql:

-- NightShift will fill this in during TASK-001.

Create models.py:

"""Database helpers for the imageboard tutorial."""

Create tests/test_app.py:

from app import create_app


def test_index_redirects_to_test_board(tmp_path):
    app = create_app(str(tmp_path / "test.db"))
    client = app.test_client()

    response = client.get("/")

    assert response.status_code == 302
    assert response.headers["Location"].endswith("/board/test")

3. Add NightShift Config

Create nightshift.yaml:

project:
  name: imageboard
  root: .
  task_file: tasks.md
  artifact_dir: .nightshift

safety:
  require_clean_worktree: false
  scoped_paths:
    - .
  allowed_commands:
    - python -m pytest -q
  forbidden_commands:
    - rm -rf
    - git push
    - curl | bash

experiment:
  label: imageboard-real-model
  prompt_variant: ollama-qwen25-coder-14b-v1

agents:
  planner:
    backend: ollama
    model: qwen2.5-coder:14b
    temperature: 0.2
    system_prompt: agents/planner.md

  implementer:
    backend: ollama
    model: qwen2.5-coder:14b
    temperature: 0.1
    system_prompt: agents/implementer.md

  reviewer:
    backend: ollama
    model: qwen2.5-coder:14b
    temperature: 0.1
    system_prompt: agents/reviewer.md

pipeline:
  max_task_retries: 3
  continue_on_task_failure: false
  stages:
    - id: plan
      type: agent
      agent: planner
      output: plan.md

    - id: context
      type: repo_context
      output: context-pack.md

    - id: implement
      type: file_writer
      agent: implementer
      output: proposed.patch

    - id: normalize
      type: patch_normalizer
      output: normalized.patch

    - id: validate_patch
      type: patch_validator
      output: patch-validation.md
      max_files: 8
      max_lines: 700
      on_fail: implement

    - id: apply_patch
      type: patch_apply
      mode: apply
      output: patch-apply-output.txt
      on_fail: implement

    - id: test
      type: command
      commands:
        - python -m pytest -q
      output: test-output.txt
      shell: true
      timeout_seconds: 20
      on_fail: implement

    - id: review
      type: agent_review
      agent: reviewer
      on_fail: implement
      output: review.md

    - id: summarize
      type: summarize
      output: final-notes.md

4. Add Agent Prompts

Create agents/planner.md:

You are the planning agent for NightShift.

Create a concise implementation plan for the current task.

If you need repository context before planning, output lookup requests exactly like this:

lookup_requests:
- tool: read_file
  path: relative/path.py
- tool: grep
  path: .
  pattern: search_regex

After context is provided, write a short plan with:
- files to edit
- tests to add or update
- risks

Do not write code.

Create agents/implementer.md:

You are the implementation agent for NightShift.

Output only complete file content blocks.
Use one fenced block per file with this exact opening form:
```file:relative/path.py
<complete file content>
```
Do not include explanations before or after the file blocks.
Include tests when needed.
Keep the change as small as possible.
Only edit files needed for the task.

Create agents/reviewer.md:

You are the review agent for NightShift.

Review the task, plan, patch artifacts, test output, and final state.

Output exactly:

status: pass | fail | retry | escalate
reason: <short explanation>
next_stage: <optional stage id>
context_update: <compact useful note>

Use retry when the implementation is close but needs another patch.
Use fail when the patch is unsafe, unrelated, or clearly broken.
Use pass only when the acceptance criteria are satisfied.

5. Add The Task List

Create tasks.md:

# Tasks

- [ ] TASK-001: Board and thread foundation

Description:
Implement the initial imageboard data model and read routes. Add a SQLite schema and model helpers for boards, threads, and replies. Implement `/board/<name>` and `/thread/<id>` routes with simple HTML responses. Include tests that initialize a temporary database, create board/thread/reply records, and verify both routes.

Acceptance Criteria:
- Defines SQLite tables for boards, threads, and replies
- Provides database initialization and model helper functions
- Implements `/board/<name>` route showing threads for that board
- Implements `/thread/<id>` route showing the thread and replies
- Includes route and model tests using a temporary database

- [ ] TASK-002: Image upload and thumbnails

Dependencies:
- TASK-001

Description:
Add image attachment support for new threads and replies. Store uploaded image metadata in SQLite, save uploaded files under `static/uploads`, and generate thumbnails under `static/thumbs`.

Acceptance Criteria:
- Accepts image uploads for threads and replies
- Stores image filename, thumbnail filename, MIME type, and size
- Generates thumbnails with Pillow
- Rejects unsupported or oversized files
- Includes upload and thumbnail tests

- [ ] TASK-003: Bump ordering and reply counts

Dependencies:
- TASK-002

Description:
Sort board threads by most recent bump. Creating a reply updates the thread bump timestamp and increments reply counters.

Acceptance Criteria:
- Board pages sort threads by latest bump time
- Replies increment thread reply count
- Reply creation updates bump timestamp
- Tests cover ordering and counters

- [ ] TASK-004: Tripcodes and session cookies

Dependencies:
- TASK-003

Description:
Add anonymous names, optional tripcodes, and a session cookie for lightweight poster identity.

Acceptance Criteria:
- Supports optional name and tripcode input
- Stores tripcode hashes without storing raw tripcode secrets
- Sets and reuses a poster session cookie
- Displays stable poster identity on posts
- Includes tripcode and session tests

- [ ] TASK-005: Moderation and report queue

Dependencies:
- TASK-004

Description:
Add post reporting and a simple moderation queue. Moderators can view reports, dismiss reports, and hide reported posts.

Acceptance Criteria:
- Users can report threads and replies
- Reports are stored with reason and timestamp
- Moderation queue lists open reports
- Moderation actions can dismiss reports or hide posts
- Includes moderation and report queue tests

6. Validate And Run

Validate the project:

python -m nightshift.cli validate --config nightshift.yaml

Run only the first task:

python -m nightshift.cli run --config nightshift.yaml --task TASK-001

Start the dashboard:

python -m nightshift.cli web --config nightshift.yaml --host 127.0.0.1 --port 8765

Open http://127.0.0.1:8765/.

Notes On Scope

This is still a non-trivial first project. The advantage over a tiny interpreter is that failures are ordinary web-app failures: missing routes, schema mistakes, file handling, or tests. Those are easier to inspect in NightShift artifacts than parser recursion or tokenizer loops.

Keep the tasks sequential. Do not ask the model to implement uploads, tripcodes, or moderation before TASK-001 is passing.