Getting Started

Runs locallyDraft 0.1

These samples run. They call the Techlog server in this repository, started on your machine at http://localhost:8080. The server implements no authentication and no event signatures, so the calls send no token. The proposed signing and access model is described under security and trust.

Start the server

From the root of this repository, load the example story and start the server. You need Go 1.27 or later.

make seed    # load the Spring Spin example story into share/techlog.db
make run     # serve the UI, the API and MCP at http://localhost:8080
make skill   # optional: install the Claude skill into ~/.claude/skills

The UI is now at http://localhost:8080/. Run the server explains the options.

What you will do

  1. Record a proposal that is linked to the problem that motivated it.
  2. Record the verification result, with the test report as evidence.
  3. Read the recent history of the system.

The samples use the Spring Spin story: a service called promo-api and a discount game for new clients. make seed loads that story, so the problem report the proposal links to already exists.

A first event

This is the smallest valid event. Every field here is required. The idempotency_key is yours to choose: reuse it when you retry, and the server rejects the repeat. The event model explains each one. When you submit an event, leave out the id: the server assigns it, and ignores one you send. time and schema_version may also be left out; the server fills them in.

{
  "schema_version": "0.1-draft",
  "id": "evt_01J9K2A0Q001",
  "idempotency_key": "promo-api-report-2026-03-12-m.okafor",
  "type": "problem.reported",
  "time": "2026-03-12T08:14:00Z",
  "actor": { "kind": "human", "id": "user:m.okafor" },
  "subject": { "system": "promo-api" },
  "status": "recorded",
  "provenance": { "recorded_by": "user:m.okafor", "method": "human_entered" }
}

Choose your language

Python 3.9+, standard library only.

"""Record events in a local Techlog server. Start it first: make seed && make run."""
import json
import os
import urllib.request

BASE = os.environ.get("TECHLOG_URL", "http://localhost:8080")


def call(method, path, body=None):
    request = urllib.request.Request(
        BASE + path,
        method=method,
        data=json.dumps(body).encode() if body is not None else None,
        headers={"Content-Type": "application/json"},
    )
    with urllib.request.urlopen(request) as response:
        return json.load(response)


# 1. Record a proposal, linked to the original problem report.
#    The server assigns the id and, because "time" is omitted, the time. The
#    idempotency_key makes a retry safe: reusing it is rejected with 409.
proposal = call("POST", "/api/v1/events", {
    "schema_version": "0.1-draft",
    "idempotency_key": "promo-api-spin-proposal",
    "type": "proposal.recorded",
    "actor": {"kind": "ai_agent", "id": "agent:code-assistant", "on_behalf_of": "user:a.novak"},
    "subject": {"system": "promo-api", "component": "spin-service"},
    "status": "proposed",
    "summary": "Spin-to-win: one spin per new account, discount capped at 20%",
    "links": [{"rel": "motivated_by", "target": "evt_01J9K2A0P002", "basis": "confirmed"}],
    "provenance": {"recorded_by": "agent:code-assistant", "method": "agent_submitted"},
})["event"]
print("recorded", proposal["id"])

# 2. Record the verification result. The test report is evidence, kept as a
#    reference inside the event.
call("POST", "/api/v1/events", {
    "schema_version": "0.1-draft",
    "idempotency_key": "promo-api-spin-verification-ci-90412",
    "type": "verification.completed",
    "actor": {"kind": "system", "id": "ci:pipeline"},
    "subject": {"system": "promo-api", "component": "spin-service"},
    "status": "verified",
    "summary": "Unit, integration, and abuse tests passed",
    "evidence": [{
        "kind": "test.report",
        "ref": "ci://run/90412",
        "digest": "sha256:7b1e4d9a2c6f08e35a9d1b7c4e2f6a8091d3c5b7e9f0a2c4d6e8b0a1c3e5f7d9",
    }],
    "links": [{"rel": "verifies", "target": "evt_01J9K2A0P006", "basis": "confirmed"}],
    "provenance": {"recorded_by": "collector:ci", "method": "collected"},
})

# 3. Read the latest events of the system, oldest first.
history = call("GET", "/api/v1/systems/promo-api/history?limit=5")
for e in history["events"]:
    print(e["time"], e["type"], e.get("summary", ""))

What the calls do

MethodPathPurpose
POST/api/v1/eventsRecord an event. The server validates it, assigns the id, and returns the stored event as {"event": {...}} with status 201.
GET/api/v1/systems/{system}/historyRead the latest events of one system, oldest first.

An invalid event gets status 422 and a list of problems, each with the JSON Pointer path of the field to fix. Evidence is recorded inside the event, as references; there is no separate evidence endpoint. The full list of routes is on Run the server.

What runs today and what is proposed

Runs today (this repository)Proposed
One Go binary with the event API, an MCP server, a storage layer with a SQLite backend, a read-only web UI, and a Claude skill. Events are validated against the draft 0.1 schema.Collectors for git, CI, deploy, and alerting systems; policy evaluation; event signatures and authentication; further storage backends.

Run this site locally

The documentation site is a separate, static build in the website/ directory:

npm run build   # assemble src/ into site/ (offline; Node permission model, no network)
npm test        # run the build, example and tool tests
npm run check   # check links and run the content audit on site/
npm run serve   # preview at http://localhost:8090 (python3 required)

Next steps