Examples 03 — GitHub README Quality Report
Example 03

GitHub README Quality Report

Fetches a repository's README from raw.githubusercontent.com, trying the main and master branches in parallel and selecting whichever returns HTTP 200. Runs five AI quality probes concurrently, averages two scores deterministically, routes through a quality lane, and appends a test-coverage warning when the AI detects no mention of tests.

What this example teaches

  • Parallel HTTP fetches with deterministic branch selection — fetch main and master simultaneously, pick the 200 response with SelectStringOp, no try/catch
  • A probe fan-out: five AI ops all reading the same input wire, each asking a different question, all running in parallel
  • Score averaging with AddFloatOp + DivFloatOp — once the AI returns two floats, all maths is deterministic
  • The optional suffix pattern: SelectStringOp selects between an empty string and a warning message; StringConcatOp appends it — no conditional code in the driver
  • A custom preprocessing op (StringTruncateOp) that caps input to 8 KB to stay within AI context limits
View source on GitHub
The workflow

Pipeline structure

Parallel fetches feed a preprocessing op; five probes fan out; deterministic averaging feeds quality lanes; an orthogonal bool gate injects a warning suffix.

HTTPGetOp (main)
HTTPGetOp (master)
SelectStringOp
pick 200
TruncateOp
8 KB cap
purpose (str)
doc_score
clarity_score
has_tests (bool)
has_install (bool)
Add + Div
avg score
lane + concat
± warning
det op

Deterministic steps

HTTPGetOp (×2) + IfIntEqOp + SelectStringOp

Both branch fetches run in parallel. IfIntEqOp checks whether the main-branch status equals 200. SelectStringOp picks the corresponding body — pure boolean logic, no AI.

StringTruncateOp

A custom op that caps the README to 8,192 bytes, preventing very large READMEs from blowing AI context budgets. Configured via a max_bytes parameter in Setup().

AddFloatOp + ConstFloat64Op + DivFloatOp

Computes avg_score = (doc_score + clarity_score) / 2. The denominator is a constant wire (2.0) — no hardcoded values in the driver code.

SelectStringOp + StringConcatOp

If has_tests is false, SelectStringOp routes a warning string; otherwise an empty string. StringConcatOp appends it. No conditional code in the driver.

AI op

AI steps

AIComputeStringToStringOp — purpose

Summarises the project's purpose in one sentence. Runs in parallel with the four score probes.

AIScoreOp (×2) — doc_score, clarity_score

Scores documentation completeness and clarity for new contributors on 0–1. Two independent calls that run concurrently.

AIBoolOp (×2) — has_tests, has_install

Answers yes/no: "does the README mention tests/CI?" and "does it contain installation instructions?". Both run concurrently alongside the score ops.

AIComputeStringToStringOp — quality lane (1 of 3)

Generates an endorsement (excellent ≥ 0.75), a constructive critique (ok ≥ 0.40), or an improvement plan (poor < 0.40), depending on the averaged score.

Implementation

Walkthrough

1. Parallel branch fetches with deterministic selection

Both fetch_main and fetch_master run without any condition — they always execute in parallel. IfIntEqOp converts the HTTP status to a boolean, and SelectStringOp acts as a pure data multiplexer: no imperative branching required.

main.go — Stage 1 (parallel fetches)
// Both fetches run in parallel — no Condition on either vertex.
Vertex("fetch_main").Op("HTTPGetOp").
    Input("URL", "main_url").
    Output("Body", "main_body").
    Output("StatusCode", "main_status").

Vertex("fetch_master").Op("HTTPGetOp").
    Input("URL", "master_url").
    Output("Body", "master_body").

// Was the main-branch fetch a 200?
Vertex("check_main").Op("IfIntEqOp").
    Input("A", "main_status").
    Input("B", "int_200").
    Output("Match", "main_ok").

// Pick the body whose branch actually exists.
Vertex("pick_readme").Op("SelectStringOp").
    Input("Cond", "main_ok").
    Input("IfTrue", "main_body").
    Input("IfFalse", "master_body").
    Output("Result", "readme_raw").
main.py — Stage 1 (parallel fetches)
# Both fetches run in parallel — no Condition on either vertex.
b.vertex("fetch_main").op("HTTPGetOp") \
    .input("URL", "main_url") \
    .output("Body", "main_body") \
    .output("StatusCode", "main_status")

b.vertex("fetch_master").op("HTTPGetOp") \
    .input("URL", "master_url") \
    .output("Body", "master_body")

# Was the main-branch fetch a 200?
b.vertex("check_main").op("IfIntEqOp") \
    .input("A", "main_status") \
    .input("B", "int_200") \
    .output("Match", "main_ok")

# Pick the body whose branch actually exists.
b.vertex("pick_readme").op("SelectStringOp") \
    .input("Cond", "main_ok") \
    .input("IfTrue", "main_body") \
    .input("IfFalse", "master_body") \
    .output("Result", "readme_raw")
index.ts — Stage 1 (parallel fetches)
// Both fetches run in parallel — no Condition on either vertex.
wf.op.hTTPGetOp("fetch_main")
    .input("URL", "main_url")
    .output("Body", "main_body")
    .output("StatusCode", "main_status");

wf.op.hTTPGetOp("fetch_master")
    .input("URL", "master_url")
    .output("Body", "master_body");

// Was the main-branch fetch a 200?
wf.op.ifIntEqOp("check_main")
    .input("A", "main_status")
    .input("B", "int_200")
    .output("Match", "main_ok");

// Pick the body whose branch actually exists.
wf.op.selectStringOp("pick_readme")
    .input("Cond", "main_ok")
    .input("IfTrue", "main_body")
    .input("IfFalse", "master_body")
    .output("Result", "readme_raw");

2. Five concurrent AI probes

After truncation, all five probes read from the same readme wire. With no mutual dependencies, the dagor engine schedules all five concurrently — five AI questions answered in roughly the time of one.

main.go — Stage 3 (five parallel probes)
Vertex("purpose_op").Op("AIComputeStringToStringOp").
    Params(map[string]string{"operation": "summarize the purpose in one sentence"}).
    Input("Input", "readme").
    Output("Result", "purpose_str").

Vertex("doc_score_op").Op("AIScoreOp").
    Params(map[string]string{"criterion": "documentation completeness"}).
    Input("Input", "readme").
    Output("Result", "doc_score").

Vertex("has_tests_op").Op("AIBoolOp").
    Params(map[string]string{"predicate": "does this README mention tests or CI?"}).
    Input("Input", "readme").
    Output("Result", "has_tests").
main.py — Stage 3 (five parallel probes)
b.vertex("purpose_op").op("AIComputeStringToStringOp") \
    .params({"operation": "summarize the purpose in one sentence"}) \
    .input("Input", "readme") \
    .output("Result", "purpose_str")

b.vertex("doc_score_op").op("AIScoreOp") \
    .params({"criterion": "documentation completeness"}) \
    .input("Input", "readme") \
    .output("Result", "doc_score")

b.vertex("has_tests_op").op("AIBoolOp") \
    .params({"predicate": "does this README mention tests or CI?"}) \
    .input("Input", "readme") \
    .output("Result", "has_tests")
index.ts — Stage 3 (five parallel probes)
wf.op.aIComputeStringToStringOp("purpose_op", {"operation": "summarize the purpose in one sentence"})
    .input("Input", "readme")
    .output("Result", "purpose_str");

wf.op.aIScoreOp("doc_score_op", {"criterion": "documentation completeness"})
    .input("Input", "readme")
    .output("Result", "doc_score");

wf.op.aIBoolOp("has_tests_op", {"predicate": "does this README mention tests or CI?"})
    .input("Input", "readme")
    .output("Result", "has_tests");

3. Deterministic score averaging

The average is computed with two op vertices — AddFloatOp sums the two score wires, and DivFloatOp divides by a constant wire. Both are typed library ops; no math lives in driver code.

main.go — Stage 4 (average score)
Vertex("sum_scores").Op("AddFloatOp").
    Input("A", "doc_score").
    Input("B", "clarity_score").
    Output("Result", "sum_scores_val").

Vertex("const_2").Op("ConstFloat64Op").
    Params(map[string]string{"Value": "2.0"}).
    Output("Result", "two_f").

Vertex("avg_score_op").Op("DivFloatOp").
    Input("A", "sum_scores_val").
    Input("B", "two_f").
    Output("Result", "avg_score").
main.py — Stage 4 (average score)
b.vertex("sum_scores").op("AddFloatOp") \
    .input("A", "doc_score") \
    .input("B", "clarity_score") \
    .output("Result", "sum_scores_val")

b.vertex("const_2").op("ConstFloat64Op") \
    .params({"Value": "2.0"}) \
    .output("Result", "two_f")

b.vertex("avg_score_op").op("DivFloatOp") \
    .input("A", "sum_scores_val") \
    .input("B", "two_f") \
    .output("Result", "avg_score")
index.ts — Stage 4 (average score)
wf.op.addFloatOp("sum_scores")
    .input("A", "doc_score")
    .input("B", "clarity_score")
    .output("Result", "sum_scores_val");

wf.op.constFloat64Op("const_2", {"Value": "2.0"})
    .output("Result", "two_f");

wf.op.divFloatOp("avg_score_op")
    .input("A", "sum_scores_val")
    .input("B", "two_f")
    .output("Result", "avg_score");

4. Optional warning suffix with SelectStringOp

SelectStringOp acts as an in-graph ternary: when has_tests is true it routes an empty string; when false it routes the warning text. StringConcatOp appends whichever was selected. No if statements in the driver.

main.go — Stage 6 (warning injection)
// Registered: operator.RegisterOpFactory("warning_const", builtin.ContextValFactory[string](warningKey{}))
// Injected:   ctx = context.WithValue(ctx, warningKey{}, "\n\nWARNING: tests not mentioned")
Vertex("warning_const").Op("warning_const").
    Output("Result", "warning_text").

Vertex("test_warning").Op("SelectStringOp").
    Input("Cond", "has_tests").
    Input("IfTrue", "empty_text").   // tests mentioned: no warning
    Input("IfFalse", "warning_text"). // no tests: warning appended
    Output("Result", "test_warning_str").

Vertex("final_narrative").Op("StringConcatOp").
    Input("A", "narrative").
    Input("B", "test_warning_str").
    Output("Result", "final_narrative").
main.py — Stage 6 (warning injection)
# Registered: operator.register_op_factory("warning_const", builtin.ContextValFactory(warningKey{}))
# Injected:   ctx = context.with_value(ctx, warningKey{}, "\n\nWARNING: tests not mentioned")
b.vertex("warning_const").op("warning_const") \
    .output("Result", "warning_text")

b.vertex("test_warning").op("SelectStringOp") \
    .input("Cond", "has_tests") \
    .input("IfTrue", "empty_text") \
    .input("IfFalse", "warning_text") \
    .output("Result", "test_warning_str")

b.vertex("final_narrative").op("StringConcatOp") \
    .input("A", "narrative") \
    .input("B", "test_warning_str") \
    .output("Result", "final_narrative")
index.ts — Stage 6 (warning injection)
// Registered: operator.registerOpFactory("warning_const", builtin.ContextValFactory(warningKey{}))
// Injected:   ctx = context.withValue(ctx, warningKey{}, "\n\nWARNING: tests not mentioned")
wf.op.customOp("warning_const", "warning_const")
    .output("Result", "warning_text");

wf.op.selectStringOp("test_warning")
    .input("Cond", "has_tests")
    .input("IfTrue", "empty_text")   // tests mentioned: no warning
    .input("IfFalse", "warning_text") // no tests: warning appended
    .output("Result", "test_warning_str");

wf.op.stringConcatOp("final_narrative")
    .input("A", "narrative")
    .input("B", "test_warning_str")
    .output("Result", "final_narrative");

Run it

shell
export CLAUDE_API_KEY=<your key>

# Live slug — main branch
go run ./examples/readme-quality/ --slug akennis/dagor

# Live slug — master branch fallback
go run ./examples/readme-quality/ --slug tj/n

# Offline fixture
go run ./examples/readme-quality/ --fixture examples/readme-quality/testdata/dagor.md
shell
export CLAUDE_API_KEY=<your key>

# Live slug — main branch
python examples/readme_quality/main.py --slug akennis/dagor

# Live slug — master branch fallback
python examples/readme_quality/main.py --slug tj/n

# Offline fixture
python examples/readme_quality/main.py --fixture examples/readme_quality/testdata/dagor.md
shell
export CLAUDE_API_KEY=<your key>

# Live slug — main branch
npx tsx examples/readme_quality/index.ts --slug akennis/dagor

# Live slug — master branch fallback
npx tsx examples/readme_quality/index.ts --slug tj/n

# Offline fixture
npx tsx examples/readme_quality/index.ts --fixture examples/readme_quality/testdata/dagor.md

Run it as an MCP server

The same binary is dual-mode. Pass --mcp and instead of running once and exiting it speaks the Model Context Protocol over stdin/stdout, exposing the entire workflow as a single MCP tool, assess_readme. The Sparsi SDK derives the tool's input schema from the workflow's UserInput struct and validates every tools/call request against it, so an agent runs the whole fetch → five-probe → score → verdict pipeline with one tool call.

shell
# Speak MCP over stdin/stdout instead of running once
go run ./examples/readme-quality --mcp
shell
# Speak MCP over stdin/stdout instead of running once
python examples/readme_quality/main.py --mcp
shell
# Speak MCP over stdin/stdout instead of running once
npx tsx examples/readme_quality/index.ts --mcp

Register it with any MCP client by pointing the client at that command:

.mcp.json
{
  "mcpServers": {
    "readme-quality": {
      "command": "go",
      "args": ["run", "./examples/readme-quality", "--mcp"]
    }
  }
}
.mcp.json
{
  "mcpServers": {
    "readme-quality": {
      "command": "python",
      "args": ["examples/readme_quality/main.py", "--mcp"]
    }
  }
}
.mcp.json
{
  "mcpServers": {
    "readme-quality": {
      "command": "npx",
      "args": ["tsx", "examples/readme_quality/index.ts", "--mcp"]
    }
  }
}

Token usage

Measured on testdata/dagor.md. Input costs are high because the full README (~2,100 tokens) is re-passed as context to each of the six probes independently.

Op Calls Input tokens Output tokens Total tokens
AIBoolOp 2 4,275 8 4,283
AIScoreOp 2 4,303 14 4,317
AIComputeOp 2 4,300 311 4,611
Total 6 12,878 333 13,211
← Example 02 Next: Weather Advisor →