Register an app, pick or design a frame, then chat through Sourced or call native APIs. Your model still writes the reply. Sourced cheaply observes what was said—non-generative by default—so people can review candidates before anything is shared.
Sourced cheaply notices structure in the user's words against a frame you chose— non-generative by default. People review candidates before anything is shared.
| Concern | LLM? |
|---|---|
| Chat / completions reply | Yes — your upstream model (BYOK) |
| Observation (default) | No generative LLM (non_generative) |
| Assisted observation | Generative LLM only when a gate fires |
| Person confirmation | Human — never a model |
Full endpoint returns table, observation modes, and OpenAPI links: Full builder guide (docs/product/API_BUILDER_GUIDE.md).
Point the OpenAI SDK (or curl) at Sourced's POST /v1/chat/completions. The reply is from your upstream model, unchanged. Sourced runs cheap non-generative observation against the app's default_frame_id in the background— never blocking or rewriting the chat response.
Auth split (both required)
X-Sourced-App: <app_id>Authorization: Bearer <sourced_app_secret> — Sourced app auth (never forwarded upstream)X-Upstream-Authorization: Bearer <openai_key> — forwarded as upstream AuthorizationResponse header x-sourced-trace-id, then GET /v1/observations/{trace_id} with Sourced app auth only. Register a starter frame first so default_frame_id is set.
OpenAI SDK note: api_key alone is not enough. Use the Sourced secret as api_key (so the SDK sets Authorization) and put the OpenAI key in default_headers["X-Upstream-Authorization"] with X-Sourced-App.
curl -s -D - -X POST "https://sourced.wayway.ai/v1/chat/completions" \
-H "Content-Type: application/json" \
-H "X-Sourced-App: <your-app-id>" \
-H "Authorization: Bearer <your-app-secret>" \
-H "X-Upstream-Authorization: Bearer $OPENAI_API_KEY" \
-d '{
"model": "gpt-4o-mini",
"messages": [{"role": "user", "content": "I care about building things that help people learn."}]
}'
# Response body = upstream chat reply (unchanged).
# Response header x-sourced-trace-id = observation job id.import os
from openai import OpenAI
# Auth split (required):
# X-Sourced-App + Authorization:Bearer <sourced_app_secret> → Sourced
# X-Upstream-Authorization:Bearer <openai_key> → forwarded upstream
# api_key alone is NOT enough — OpenAI SDK needs default_headers for Sourced + upstream.
client = OpenAI(
base_url="https://sourced.wayway.ai/v1",
# SDK sets Authorization from api_key → use the Sourced app secret here.
api_key="<your-app-secret>",
default_headers={
"X-Sourced-App": "<your-app-id>",
"X-Upstream-Authorization": f"Bearer {os.environ['OPENAI_API_KEY']}",
},
)
# Prefer with_raw_response so you can read x-sourced-trace-id
raw = client.chat.completions.with_raw_response.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": "I care about building things that help people learn."}],
)
print(raw.parse().choices[0].message.content) # reply from your upstream model
trace_id = raw.headers.get("x-sourced-trace-id")
# Then: GET /v1/observations/{trace_id} with Sourced app auth only (see curl below).Use with_raw_response (or curl -D -) to read x-sourced-trace-id. Then poll observations:
curl -s "https://sourced.wayway.ai/v1/observations/$TRACE_ID" \ -H "X-Sourced-App: <your-app-id>" \ -H "Authorization: Bearer <your-app-secret>"
Status is pending → observed (or skipped / error). Candidates are non-generative against default_frame_id. Consent injection into the upstream prompt is Phase C — documented only, not built yet.
After you register an app and pick a frame, observe a turn with the native API. You get typed, evidence-linked candidates—non-generative by default—then a report of what landed against the schema.
curl -X POST https://sourced.wayway.ai/v1/frames/<your-frame-id>/observe \
-H "Content-Type: application/json" \
-H "X-Sourced-App: <your-app-id>" \
-H "Authorization: Bearer <your-app-secret>" \
-d '{
"person_id": "demo-user",
"text": "I care about building things that help people learn.",
"source_id": "turn_1"
}'{
"status": "ok",
"trace_ids": ["tr_8c1f3a"],
"trace_count": 1,
"superseded_count": 0,
"candidates": [
{
"trace_id": "tr_8c1f3a",
"dimension": "tom.person.values.helping",
"statement": "Cares about building things that help people learn.",
"confidence": 0.87,
"relation": "cares_about",
"evidence": "I care about building things that help people learn."
}
],
"triggered_abilities": []
}Each candidate trace has a dimension (the schema slot that matched), a relation (how the speaker relates, e.g. cares_about or stuck_on), a confidence score, and the exact evidence that triggered it. Candidates stay provisional until the person confirms or corrects them in review.
curl "https://sourced.wayway.ai/v1/frames/<your-frame-id>/report/demo-user" \ -H "X-Sourced-App: <your-app-id>" \ -H "Authorization: Bearer <your-app-secret>"
{
"status": "ok",
"person_id": "demo-user",
"dimensions": [
{
"dimension": "tom.person.values.helping",
"status": "matched",
"confidence": 0.87,
"trace_count": 2,
"relation": "cares_about",
"evidence_quote": "I care about building things that help people learn."
},
{
"dimension": "tom.person.direction.practices",
"status": "gap",
"confidence": 0.0,
"trace_count": 0,
"description": "Active commitments and daily practices"
}
],
"surprises": [
{
"dimension": "tom.person.energy.flow",
"relation": "alive_in",
"confidence": 0.72,
"evidence_quote": "Time disappears when I'm making things."
}
],
"posture_distribution": {
"receiving": 2,
"moving": 0,
"participating": 1
},
"coverage": 0.33,
"total_traces": 3
}Every dimension comes back scored: matched dimensions are schema slots with traces, gaps are dimensions the person hasn't addressed yet. Surprises are traces that landed outside your schema — things you didn't ask for but the person expressed. The posture_distribution shows the balance of receiving, moving, and participating across the kept traces.
curl "https://sourced.wayway.ai/v1/frames/<your-frame-id>/insights?person_ids=demo-user" \ -H "X-Sourced-App: <your-app-id>" \ -H "Authorization: Bearer <your-app-secret>"
{
"spans": [...],
"traces": [
{
"dimension": "tom.person.values.helping",
"about": "helping people learn",
"relation": "cares_about",
"confidence": 0.87,
"statement": "Cares about building things that help people learn.",
"status": "confirmed"
}
],
"threads": [...],
"connections": []
}Observe matched your text against the frame's schema and extracted typed claims — each trace has a dimension, a verb-style relation, confidence, and evidence. Report shows every dimension scored — what landed (matched), what's missing (gaps), and what surprised you (traces outside your schema). Insights aggregates all traces into spans, threads, and connections.
A complete script that observes text and fetches insights. Copy it, fill in your credentials, and run.
#!/usr/bin/env python3
"""Sourced quickstart — observe, report, fetch insights."""
import requests
BASE = "https://sourced.wayway.ai"
APP_ID = "<your-app-id>"
APP_SECRET = "<your-app-secret>"
FRAME_ID = "<your-frame-id>"
headers = {
"X-Sourced-App": APP_ID,
"Authorization": f"Bearer {APP_SECRET}",
"Content-Type": "application/json",
}
# 1. Observe some text (frame-scoped: returns review-ready candidates)
resp = requests.post(f"{BASE}/v1/frames/{FRAME_ID}/observe", headers=headers, json={
"person_id": "demo-user",
"text": "I feel energized when I build products that help people.",
"source_id": "turn_1",
})
print("Observe:", resp.status_code, resp.json())
# 2. Report — every dimension scored against the author's frame
resp = requests.get(
f"{BASE}/v1/frames/{FRAME_ID}/report/demo-user",
headers=headers,
)
result = resp.json()
matched = [d for d in result["dimensions"] if d["status"] == "matched"]
gaps = [d for d in result["dimensions"] if d["status"] == "gap"]
print(f"Matched: {len(matched)}, Gaps: {len(gaps)}, Surprises: {len(result['surprises'])}")
print(f"Coverage: {result['coverage']:.0%}")
# 3. Fetch insights (spans, threads, connections)
resp = requests.get(
f"{BASE}/v1/frames/{FRAME_ID}/insights",
headers=headers,
params={"person_ids": "demo-user"},
)
print("Insights:", resp.status_code, resp.json())
Every endpoint is documented with request/response schemas and a live “Try it out” button. No setup required — test directly from your browser.
Interactive explorer — try every endpoint with live requests and see response schemas.
Clean reference documentation — all endpoints, models, and parameters in one scrollable page.
| Operation | Endpoint | What it does |
|---|---|---|
| Observe | POST /v1/observe | Extract claims from text — returns kept trace ids and triggered abilities |
| Frame Observe | POST /v1/frames/:id/observe | Observe within a frame context — uses the frame's schema and dimensions; returns review-ready candidates |
| Completions drop-in | POST /v1/chat/completions | OpenAI-shaped passthrough — upstream reply + x-sourced-trace-id; non-generative observation against default_frame_id |
| Observations | GET /v1/observations/:trace_id | Drop-in observation job status and candidates (Sourced app auth) |
| Report | GET /v1/frames/:id/report/:person | All dimensions scored (matched + gaps), surprises, posture distribution, coverage |
| Profile | GET /v1/frames/:id/profile/:person | Full person model — traces, patterns, coverage across all sessions |
| Session Summary | GET /v1/frames/:id/session-summary/:person | Per-session breakdown — what landed in this conversation |
| Chat | POST /v1/chat/:frame/:person | SSE streaming conversation — sends turns, streams facilitated responses |
| Synthesize | POST /v1/frames/:id/synthesize/:person | Generate narrative — portrait, patterns, or summary from traces |
| Insights | GET /v1/frames/:id/insights | Aggregated view — spans, threads, connections across subjects |
| Review Queue | GET /v1/people/:person/review | What the system currently believes, each claim with its evidence quotes |
| Review | POST /v1/traces/:id/review | Confirm, correct, reject, hide, contest, or nuance a claim — corrections supersede, evidence is never erased |
| Grants | POST /v1/people/:person/grants | Record a person's consent for this app to read their context — revocable, scoped by frame/dimension patterns |
| Receipts | GET /v1/traces/:id/receipts | Who accessed this claim, when, for what purpose — written on every context read |
| VIA preview | POST /v1/stance/via/assess | Evidence-linked VIA strength hypotheses from text — preview only; nothing is persisted |
Match and cohort-style compose remain available for Studio and research workflows. They are not the primary builder wedge.
| Operation | Endpoint | What it does |
|---|---|---|
| Match | GET /v1/frames/:id/matches | Cross-subject connections — shared dreams, complements, tensions |
The local stance library (sourced/stance/) detects surface stance and proposes typed claim relations without silently editing the person model. Person confirmations are governance actions. See Method → stance for the four-layer model.
POST /v1/stance/via/assess returns evidence-linked VIA hypotheses. Journal text is not written to Sourced memory. Studio UI: /studio/via.
python examples/via_profile/assess_corpus.py ./journal --db via.db python examples/via_profile/chat.py via.db python scripts/eval_stance_micro.py SOURCED_STANCE_SHADOW=on # metrics → runs/stance_shadow/ python scripts/stance_shadow_report.py
Sign in to create API credentials and start integrating.
Sign InReplaces grant forms with conversation. Looks for the problem, the impact, and your specific role — listens for evidence of accountability.
A demo frame for evidence-linked character-strength hypotheses. It exercises the same reviewable detection contract as other frames; it is not a psychometric assessment or a replacement for the VIA survey.
Replaces intake forms with guided dialogue. Looks for grounding, vision, obstacles, and commitment — asks where you are and what you're ready to change.
A playful philosophical demo. Looks for virtues, their excess, and their deficiency — find the mean between too much and too little.
Span → Trace → Thread. Data stays stable; behavior is configured in YAML.
Schema, Attunement, and Abilities define ontology, retrieval, and outcomes — without hardcoding app semantics.
When confirmed traces meet an ability's requirements, Sourced resolves parameters and hands them to your app. You define what happens next.
Targets with type: "compose" trigger synthesis instead of extraction. When enough evidence accumulates, the Fractal Weave engine can compose an artifact — a portrait, match summary, or cohort theme — from the participant's own words. Self (N=1) portrait compose remains useful in Studio; dyad match and cohort theme are deferred from the primary builder journey.
Every trace gets a relation — how the speaker relates to the thing the trace is about. These ten verbs are the posture vocabulary, the personal-ops default (frames can declare their own). Verb predicates mean database queries read like sentences.
Rules and intent from worldview config, loaded before conversation starts and applied as prompt policy plus evidence strictness.
System’s evolving belief about participant, multiple signals per turn (MICE).
How honest the system is about its own uncertainty.
| Confident | Uncertain | |
|---|---|---|
| Held | Reflect | Wait |
| Seeking | Offer | Be honest |
| Constraint | Unblock | Be honest |
Tension is relational (two held cares colliding), not a row.
MICE, not MECE: Human experience overlaps. One sentence fires multiple relations — reaching_for + stuck_on + torn_between simultaneously. The vocabulary is Mutually Inclusive. MECE discipline applies to the processing pipeline (Constitution → Interpret → Update → Gate → Speak), not the perception layer.
Product observation modes are off, non_generative, assisted, and generative. Chat wire fields still accept legacy extraction_mode names; the API translates them once at ingress. The reference flow defaults to non_generative observation. Billing entitlement (core / assisted) constrains which modes an app may request — it is not the runtime switch itself. “No generative LLM in observation” does not mean “no LLM-written chat reply.”
No observation or candidate extraction for this turn. Chat reply generation remains a separate policy.
Rules + embeddings/markers only — no generative LLM for observation. Chat reply generation is separate and may still use an LLM.
Same as non_generative, plus a generative LLM for observation only when a declared gate fires. Every escalation should record a reason.
Explicit generative observation. Useful for diagnostics; must not be a silent frame default. Product default target is non_generative.
A candidate that the user confirms is just as reliable as a rich AI-synthesized candidate. The user's confirmation is what makes claims true.
/chat): participant-facing guided response.validate and inspect issues for source collisions./insights endpoint for a cross-subject view.tom_base and add specialized targets as needed.