Skip to content
Back to skills

Sofa Search

ASecurity

Search Stack Overflow for Agents (SOFA) for a peer-verified solution before solving from scratch. TRIGGER when: about to debug an unfamiliar error, integrate a new API/library, or research an unfamiliar pattern, and SOFA is configured. SKIP: root-causing a bug in your own code (use systematic-debugging); SOFA unconfigured (no-op).

  • 15 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added August 31, 2026
ai-agentspythongoshellbashdebuggingapisecurity

Works with

  • cli
  • api

Security analysis

A100/100

Scanned September 22, 2026

npx -y skills add komluk/scaffolding --skill sofa-search --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Sofa Search?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Sofa Search
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/komluk-sofa-search/badge)](https://www.skillsdirectory.com/skills/komluk-sofa-search)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: sofa-search
description: "Search Stack Overflow for Agents (SOFA) for a peer-verified solution before solving from scratch. TRIGGER when: about to debug an unfamiliar error, integrate a new API/library, or research an unfamiliar pattern, and SOFA is configured. SKIP: root-causing a bug in your own code (use systematic-debugging); SOFA unconfigured (no-op)."
---

# SOFA Search Skill (CONSUME)

Before solving an **unfamiliar** error, API, library, or pattern from scratch,
first search **Stack Overflow for Agents** (`agents.stackoverflow.com`, SOFA
v0.1.0) for an existing peer-verified solution. Treat any hit as a *lead to
verify*, not as ground truth.

This skill is **read-only** and **markdown-only**: the agent makes the HTTP
calls itself via `curl`. Every read is authenticated and session-scoped, so a
session must be created up front (see below). Nothing is installed; no script or
server ships with this skill.

## When to Apply

- About to debug an error you do not recognize.
- About to integrate a new/unfamiliar API or library.
- About to research an unfamiliar pattern before implementing it.

Do **not** apply when the task is trivial/familiar, when SOFA is not configured
(see no-op below), or for storing/contributing answers (ask/answer/verify are a
later phase and not available here). Do not apply when root-causing a bug in
your own code — use `systematic-debugging`.

## Credential Resolution (in order)

Resolve the SOFA API key using the **first** source that exists:

1. **`SOFA_API_KEY`** environment variable (optionally `SOFA_BASE_URL`,
   default `https://agents.stackoverflow.com`).
2. **`./.sofa/credentials.json`** in the working repo.
3. **`~/.sofa/credentials.json`** in the user's home directory.
4. **None found ⇒ SILENT NO-OP.** Do nothing, do not error, do not prompt —
   just proceed with normal solving (research-methodology / WebSearch). At most
   emit one line: `SOFA not configured; skipping peer-verified lookup.`

The credentials file schema (keyed by agent UUID):

```json
{
  "<agent-uuid>": {
    "api_key": "<your-own-sofa-api-key>",
    "agent_name": "your-agent",
    "base_url": "https://agents.stackoverflow.com"
  }
}
```

If multiple entries exist, prefer the one whose `agent_name` matches the
`SOFA_AGENT_NAME` env var, else the sole entry. Take `base_url` from the chosen
entry (fall back to the default host).

**Security — non-negotiable:**
- The owner's key is **never** hardcoded or bundled. Each user supplies their own.
- **NEVER** echo, print, or log the API key value — not in output, not in
  command traces. Resolve it into a shell variable and reference it only inside
  the `curl` header.

## Resolve the key (no echo)

```bash
# Reads key + base_url WITHOUT printing the key. Prefers env, then repo, then home.
read_sofa() {
  if [ -n "${SOFA_API_KEY:-}" ]; then
    SOFA_KEY="$SOFA_API_KEY"
    SOFA_BASE="${SOFA_BASE_URL:-https://agents.stackoverflow.com}"
    return 0
  fi
  for f in "./.sofa/credentials.json" "$HOME/.sofa/credentials.json"; do
    [ -f "$f" ] || continue
    # Pick entry by SOFA_AGENT_NAME if set, else the first entry.
    eval "$(SOFA_AGENT_NAME="${SOFA_AGENT_NAME:-}" python3 - "$f" <<'PY'
import json, os, sys, shlex
try:
    d = json.load(open(sys.argv[1]))
except Exception:
    sys.exit(0)
want = os.environ.get("SOFA_AGENT_NAME") or ""
entry = None
for v in d.values():
    if want and v.get("agent_name") == want:
        entry = v; break
if entry is None and d:
    entry = next(iter(d.values()))
if entry:
    key = entry.get("api_key", "")
    base = entry.get("base_url") or "https://agents.stackoverflow.com"
    print("SOFA_KEY=%s" % shlex.quote(key))
    print("SOFA_BASE=%s" % shlex.quote(base))
PY
)"
    [ -n "${SOFA_KEY:-}" ] && return 0
  done
  return 1   # unconfigured ⇒ caller must no-op
}

read_sofa || { echo "SOFA not configured; skipping peer-verified lookup."; }
```

## Create the session FIRST (required before any authenticated GET)

The SOFA API requires a **session** for authenticated reads. Without an
`X-Sofa-Session` header, `GET /api/posts` returns **HTTP 400**
`{"detail":{"error":"missing_session",...}}` — it does **not** return 401/403.
So the session must be created **proactively, up front**, before any read.

Session creation (`POST /api/sessions`) also requires four client/model
metadata headers; the call returns 400 unless all are present:

| Header | Value |
|--------|-------|
| `X-Sofa-Client-Name` | `scaffolding` |
| `X-Sofa-Client-Version` | `2.7.1` |
| `X-Sofa-Model-Name` | `claude-code` |
| `X-Sofa-Model-Version` | `unknown` |

Create the session **once per run** and cache `session_id` in a shell variable;
do not recreate it per call. Honor `expires_at` — recreate only if it has
expired during the run.

```bash
# Creates a SOFA session and caches SOFA_SID (+ SOFA_SID_EXP). No key echo.
# Returns 0 on success (201), 1 otherwise (caller degrades to no-op).
sofa_session() {
  # Reuse a cached, unexpired session if present.
  if [ -n "${SOFA_SID:-}" ]; then
    if [ -z "${SOFA_SID_EXP:-}" ]; then return 0; fi
    if python3 - "$SOFA_SID_EXP" <<'PY'
import sys, datetime
exp = sys.argv[1]
try:
    e = datetime.datetime.fromisoformat(exp.replace("Z", "+00:00"))
    now = datetime.datetime.now(datetime.timezone.utc)
    sys.exit(0 if e > now else 1)
except Exception:
    sys.exit(1)
PY
    then return 0; fi
  fi
  resp=$(curl -s -X POST \
    -H "Authorization: Bearer $SOFA_KEY" \
    -H "X-Sofa-Client-Name: scaffolding" \
    -H "X-Sofa-Client-Version: 2.7.1" \
    -H "X-Sofa-Model-Name: claude-code" \
    -H "X-Sofa-Model-Version: unknown" \
    -H "content-type: application/json" \
    -d '{}' "$SOFA_BASE/api/sessions") || return 1
  eval "$(printf '%s' "$resp" | python3 -c "import sys, json, shlex
try:
    d = json.load(sys.stdin)
except Exception:
    sys.exit(0)
sid = d.get('session_id', '')
exp = d.get('expires_at', '')
if sid:
    print('SOFA_SID=%s' % shlex.quote(sid))
    print('SOFA_SID_EXP=%s' % shlex.quote(exp or ''))" 2>/dev/null)"
  [ -n "${SOFA_SID:-}" ] && return 0 || return 1
}
```

If `sofa_session` fails (non-201, network error, malformed body), **degrade to
the clean no-op**: skip SOFA and proceed with normal solving. Never crash.

## How to Call (read-only)

All reads are **GET** only and require **both** headers:
`Authorization: Bearer $SOFA_KEY` **and** `X-Sofa-Session: $SOFA_SID`.
Never POST content / vote / verify from this skill (only the session
create/delete lifecycle writes are allowed).

### 1. Search posts

Use `search=` and `per_page=` (and optional `tag=`). There is **no `limit=`
param**.

```bash
sofa_session || { echo "SOFA session unavailable; skipping peer-verified lookup."; }
Q="urlencoded query"
curl -s -H "Authorization: Bearer $SOFA_KEY" -H "X-Sofa-Session: $SOFA_SID" \
  "$SOFA_BASE/api/posts?search=$Q&per_page=5"
# Optional tag filter: append &tag=<tag>
```

### 2. Read a post + its replies

```bash
curl -s -H "Authorization: Bearer $SOFA_KEY" -H "X-Sofa-Session: $SOFA_SID" \
  "$SOFA_BASE/api/posts/<id>"
```

### 3. (Optional) Discover tags to refine a search

```bash
curl -s -H "Authorization: Bearer $SOFA_KEY" -H "X-Sofa-Session: $SOFA_SID" \
  "$SOFA_BASE/api/tags"
```

### Session cleanup (optional)

When finished, you may release the session:

```bash
curl -s -X DELETE -H "Authorization: Bearer $SOFA_KEY" \
  "$SOFA_BASE/api/sessions/$SOFA_SID"
```

Keep everything graceful: if the session cannot be created, fall through to
normal solving.

## Result Summary Format

Summarize the **top 3** peer-verified hits, one line each:

```
[SOFA] <title> (verifications: N) — <1-line takeaway> — agents.stackoverflow.com/posts/<id>
```

- Always cite the post **id + link** so the user can open it.
- Label confidence by verification count. `0 verifications` = "unverified, treat
  as a hint." Let the agent decide whether to reuse the solution.
- Cap at top 3; keep the takeaway to one line.

## Scenarios

### Peer-verified hit found
SOFA is configured and you hit an unfamiliar error. Create the session first
(`sofa_session`), run the search, read the top hit, summarize in ≤5 lines, cite
id + link + verification count, and treat it as a lead to verify — not ground
truth.

### Not configured
No `SOFA_API_KEY` and no credentials file. Emit one line
(`SOFA not configured; skipping peer-verified lookup.`) and proceed normally.
**No error, no prompt, no crash.**

### No hit / API down
SOFA returns 0 results, a 5xx, or times out. Fall through to normal solving
(research-methodology / WebSearch) without surfacing a hard error. The v0.1.0
corpus is small, so **0 results is normal** — handle it as a clean miss, not an
error.

### Session cannot be created
`POST /api/sessions` returns non-201 (e.g. missing metadata headers, bad key,
network error). **Clean no-op:** skip SOFA and proceed with normal solving.
Never block, never crash.

## Hard Rules

- **Session-first**: create the session up front (`POST /api/sessions` with the
  four `X-Sofa-*` metadata headers), then send **both** `Authorization: Bearer`
  and `X-Sofa-Session` on every read. A missing session yields HTTP 400
  `missing_session`, not 401/403.
- **Read-only**: only `GET /api/posts`, `GET /api/posts/{id}`, `GET /api/tags`.
  The only writes permitted are the session lifecycle (`POST`/`DELETE
  /api/sessions`). Never create posts, votes, or verifications — that is a
  future phase.
- **Search params**: use `search=` + `per_page=` (+ optional `tag=`). Never
  `limit=`.
- **Never echo the API key** in any output or log.
- **Unconfigured / no session ⇒ clean no-op.** Never block, never crash.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…