API Reference

Integrate Stoic reasoning into your application or AI agent. Public GET endpoints return conceptual overviews with no authentication. Scoring endpoints require a Supabase JWT (human users) or an API key (AI agents).

Base URL: https://jdbefwkonfbhjquozgxr.supabase.co/functions/v1

Authentication

Protected endpoints require a Bearer token from Supabase Auth. Include it in the Authorization header:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

For AI Agents

Every skill comes with a generous free tier — no credit card required. To get started:

  1. Fetch /api/stoic-brain for the conceptual overview (free, no auth)
  2. Request an API key at zeus@sagereasoning.com
  3. Start calling skills within your free allowance — upgrade to paid only when you need more

Free Tier Allowances

SkillFree allowancePaid priceSpeed
sage-guard500/month~$0.0025/call<100ms
sage-reason (quick/standard/deep)30 loops/month~$0.18/callobserved ~13s (agent) / ~36s (human, standard)
sage-score100/month~$0.18/call~2s
sage-iterate50 chains/month~$0.18/iteration~2s
Evaluation skills
sage-decide, sage-audit, sage-converse, sage-scenario, sage-reflect, sage-classify, sage-prioritise, sage-moderate
100/month~$0.18/call~2–3s
Marketplace skills
sage-premortem, sage-negotiate, sage-invest, sage-pivot, sage-retro, sage-align, sage-resolve, sage-coach, sage-govern, sage-compliance, sage-educate, sage-identity
50/month~$0.18/call~3–4s
Premium skills
sage-diagnose, sage-profile
25/month~$0.50/call~2–3s
sage-contextUnlimitedFree<50ms

Paid Tier Features

FreePaid
Rate limitsPer-skill (see above)Configurable (default 500/day)
Deliberation iterations1 per chainUp to 3 per chain
Baseline retakes1/month per agent1/month per agent

No subscriptions or lock-in. Pay only for calls beyond your free allowance. Contact zeus@sagereasoning.com for volume pricing or custom limits.

Latency figures are April 2026 estimates except where marked observed (production, 2026-06-10); figures will be recalibrated as SLO data accumulates. Substrate access (/api/reason) is governed by the per-loop model — 30 loops/month free, per-loop billing paid (see llms.txt and the agent card); per-skill allowances shown apply to the legacy skill routes.

Endpoints

GET/api/virtues

Returns the four cardinal virtues with sub-virtue names and philosophical definitions (conceptual overview).

Response

{
  "virtues": [
    {
      "id": "wisdom",
      "name": "Wisdom",
      "sub_virtues": [{ "id": "good_sense", "name": "Good sense" }, ...],
      "definition": "Practical discernment in evaluating what is in one's control..."
    },
    ...
  ],
  "note": "Use the scoring API for action assessment against virtue principles."
}
GET/api/indifferents

Returns all preferred and dispreferred indifferents with category definitions (conceptual overview).

Response

{
  "indifferents": [
    {
      "id": "health",
      "name": "Health",
      "category": "preferred",
      "description": "Physical and mental wellbeing..."
    },
    ...
  ],
  "note": "Virtue relevance is assessed server-side through the scoring API."
}
GET/api/stoic-brain

Master entry point. Returns the Stoic Brain conceptual overview including foundations, virtues, and indifferents.

Response

{
  "version": "3.0.0",
  "foundations": {
    "dichotomy_of_control": "...",
    "sage_definition": "...",
    "flourishing": "..."
  },
  "virtues": [...],
  "indifferents": [...],
  "note": "Assessment endpoints provide detailed virtue analysis with kathekon evaluation."
}
POST/api/score-actionAuth required

Score a past action against Stoic virtues. Returns kathekon proximity, passions detected, virtue domains engaged, and growth path.

Request body

{
  "action": "I confronted my colleague about unfair treatment...",
  "context": "In a team meeting where decisions were being made...",
  "intended_outcome": "To ensure fair treatment of the team"
}

Response

{
  "katorthoma_proximity": "deliberate",
  "is_kathekon": true,
  "kathekon_quality": "moderate",
  "passions_detected": [
    {
      "root_passion": "epithumia",
      "sub_species": "orgē",
      "false_judgement": "Others' mistakes are personal slights"
    }
  ],
  "virtue_domains_engaged": ["andreia", "dikaiosyne"],
  "improvement_path": "A sage would have spoken with even greater clarity...",
  "disclaimer": "This is a philosophical framework for reflection, not prescriptive judgment."
}
POST/api/advise-actionAuth required

Get Stoic guidance before taking an action. Returns wisdom-based advice and kathekon evaluation of proposed action.

Request body

{
  "proposed_action": "I plan to quit my job to pursue freelance work...",
  "context": "My manager is unsupportive and growth is limited...",
  "goal": "Find more fulfilling and autonomous work"
}

Response

{
  "wisdom_guidance": "A Sage would distinguish between what is in your control...",
  "is_kathekon": false,
  "kathekon_quality": null,
  "passions_to_examine": [
    {
      "root_passion": "phobos",
      "sub_species": "fear_of_insignificance",
      "false_judgement": "Staying in this role means personal failure"
    }
  ],
  "virtue_considerations": {
    "sophrosyne": "What is truly prudent given your responsibilities?",
    "andreia": "Does this action face difficulty with courage or flee from it?",
    "dikaiosyne": "What obligations do you have to stakeholders?"
  },
  "alternative_perspectives": ["Consider a difficult conversation first", "Explore internal transfer options"]
}
GET/api/user/scoresAuth required

Retrieve authenticated user's past action scores, ordered by most recent.

Response

{
  "scores": [
    {
      "id": "uuid",
      "action_description": "Confronted colleague about unfair treatment...",
      "katorthoma_proximity": "deliberate",
      "is_kathekon": true,
      "kathekon_quality": "moderate",
      "created_at": "2026-03-21T..."
    },
    ...
  ]
}
GET/api/user/profileAuth required

Retrieve authenticated user's aggregated Stoic profile with virtue engagement patterns and growth trajectory.

Response

{
  "primary_virtue_domains": ["dikaiosyne", "phronesis"],
  "secondary_virtue_domains": ["andreia", "sophrosyne"],
  "most_frequent_passions": [
    {
      "root_passion": "epithumia",
      "frequency": "high",
      "interpretation": "High engagement with justice and responsibility"
    }
  ],
  "kathekon_alignment": "progressing",
  "actions_scored": 14,
  "last_assessment": "2026-03-21T14:30:00Z",
  "growth_pattern": "increasing_reflection"
}
POST/api/assessment/foundationalAuth required

Score an agent’s 14 free-tier self-assessment responses (Phases 1–2: Foundations, Architecture of Mind). You supply the written responses; the endpoint scores them. It does NOT accept a scenario — GET this path for the assessment INDEX (IDs and phase titles); the prompts themselves are at the returned assessment_framework_url.

Request body

{
  "agent_id": "agent-uuid-or-human-identifier",   // required, min 2 chars
  "responses": [                                   // required, EXACTLY 14
    {
      "assessment_id": "FD-01",                    // one per free-tier ID
      "response": "Your structured self-assessment, min 50 chars, max 10,000..."
    }
    // ... FD-02 ... FD-07, AM-01 ... AM-07
  ]
}

Response

{
  "agent_id": "agent-uuid",
  "tier": "free",
  "assessment_name": "Foundational Alignment Check",
  "phases_completed": [1, 2],
  "assessments_completed": 14,
  "senecan_grade_estimate": "grade_2",
  "katorthoma_proximity_summary": "deliberate",
  "control_clarity": "moderate",
  "initial_passions_detected": [ { "root_passion": "epithumia", "sub_species": "pleonexia", "false_judgement": "..." } ],
  "causal_sequence_integrity": "partially_compromised",
  "direction_of_travel": "improving",
  "per_assessment_summaries": [
    { "assessment_id": "FD-01", "title": "...", "proximity_level": "deliberate",
      "passions_detected": [], "summary": "..." }
  ],
  "assessed_at": "2026-01-01T00:00:00.000Z",
  "cta": { "action": "Run Complete Assessment", "...": "..." }
}
POST/api/assessment/fullAuth required

Score an agent’s 55 self-assessment responses across all 8 phases (paid key required). You supply the written responses; the endpoint scores them. It does NOT accept a scenario, a context, or a deliberation count — GET this path for the assessment index; the prompts themselves are served elsewhere.

Request body

{
  "agent_id": "agent-uuid",                        // required, min 2 chars
  "responses": [                                   // required, EXACTLY 55
    {
      "assessment_id": "FD-01",                    // the 14 free IDs plus
      "response": "Your structured self-assessment..."   // VH/UE/PD/RA/MJ/IN
    }
    // ... all 55, one per assessment ID across phases 1-8
  ]
}

Response

{
  "agent_id": "agent-uuid",
  "tier": "paid",
  "assessment_name": "Complete Virtue Alignment Assessment",
  "phases_completed": [1, 2, 3, 4, 5, 6, 7, 8],
  "assessments_completed": 55,
  "senecan_grade": "grade_2",
  "dimension_levels": { "passion_reduction": "...", "judgement_quality": "...",
                        "disposition_stability": "...", "oikeiosis_extension": "..." },
  "dominant_passion": { "root_passion": "phobos", "sub_species": "...", "false_judgement": "..." },
  "typical_proximity": "deliberate",
  "oikeiosis_stage": "...",
  "passions_profile": [ /* ... */ ],
  "critical_corrections": [ /* false judgement paired with its remedy */ ],
  "direction_of_travel": "improving",
  "dimension_directions": { /* per-dimension direction */ },
  "examination_protocol": "...",
  "per_assessment_summaries": [
    { "assessment_id": "FD-01", "phase": 1, "title": "...", "proximity_level": "deliberate",
      "passions_detected": [], "summary": "..." }
  ],
  "assessed_at": "2026-01-01T00:00:00.000Z",
  "disclaimer": "This is a philosophical framework for self-reflection and does not consider legal, medical, financial, or personal obligations."
}

// 403 if the API key is on the free tier:
// { "error": "...", "message": "...", "upgrade_url": "..." }
POST/api/baseline/agentAuth required

Establish a baseline virtue profile by scoring an agent’s responses to 4 baseline scenarios. You supply the written responses; the endpoint scores them. It does NOT accept an agent_name or a domain.

Request body

{
  "agent_id": "agent-uuid",                        // required, min 2 chars
  "responses": [                                   // required, EXACTLY 4
    { "scenario_id": "...", "response": "Your reasoning for this scenario..." }
    // one per baseline scenario; GET this path for the scenarios
  ]
}

Response

{
  "agent_id": "agent-uuid",
  "senecan_grade": "grade_2",
  "typical_proximity": "deliberate",
  "dominant_passion": { "root_passion": "phobos", "sub_species": "...", "false_judgement": "..." },
  "oikeiosis_stage": "...",
  "dimension_levels": { "passion_reduction": "...", "judgement_quality": "...",
                        "disposition_stability": "...", "oikeiosis_extension": "..." },
  "scenario_evaluations": [ /* one per scenario */ ],
  "strongest_domain": "...",
  "growth_edge": "...",
  "interpretation": "...",
  "disclaimer": "This is a philosophical framework for self-reflection and does not consider legal, medical, financial, or personal obligations.",
  "assessed_at": "2026-01-01T00:00:00.000Z"
}

Substrate Reasoning (/api/reason)

The substrate reasoning endpoint runs the full translation sandwich (Layer 1 feature extraction, the deterministic signed Layer 2 assessment, and Layer 3 prose). It is governed by the per-loop model (see the access tiers above) and supports, beyond the standard request body:

  • Deferred prose — response_format is full (the default, assessment and narrative prose in one response) or assessment_first (the signed assessment, extraction, and meta return immediately; the response carries a narrative object with status: deferred and a correlation_id, plus meta.narrative_status; the narrative is generated asynchronously and retained server-side). Deferral is a request, not a guarantee — consults carrying a distress signal always return the full synchronous shape.
  • The narrative must exist — a verdict without a narrative account is a classification, not an examination. assessment_first moves generation out of the response path; it never suppresses it. A verdict-only configuration is not a legitimate practice configuration.
  • Open Layer 1 — supply a layer1_schema that validates against the documented contract to skip server-side Layer 1 (meta.layer1_source: supplied, layer1_latency_ms: 0). It is optional on sr_live_ and sr_prac_, required on sr_inst_, and requires the l1_supplycapability (otherwise 403). Omitting it keeps raw-text behaviour (meta.layer1_source: server); a malformed schema returns 400. The input text is always required — the safety perimeter runs on the text regardless of who computed the schema.
  • Trajectory overlay & practice delta — credential-bearing consults carry a meta.trajectory overlay (the presenting credential’s windowed history) with a meta.trajectory.delta block (agent-trajectory-delta-v1): per-mechanism, evidence-floored, evaluative-never-predictive practice deltas. Read-and-describe — the signed assessment is unchanged. MEASURE-only; weights-tier use is blocked.
  • Retention (R17) — retained narratives and their paired signed assessments are stored encrypted at rest, for 90 days, keyed by correlation id, then permanently deleted. Records from API-key and plugin consults are removed by account deletion or credential erasure and included in your data export; signed-in website consults are deleted at expiry only, for now.
  • Examination record bundle (draft v0) — a holder-assembled JSON document that presents a signed record to a third party: the record’s limits first, then the signed record, the extraction, optional examined text or its hash, issuance facts labelled signed or unsigned, and verification steps. Evidence about process, never about correctness. See llms.txt for the field order.
  • Re-examination (prior_feedback) — an optional object { prior_loop_id, prior_depth_tier, adopted_correction? } that carries a re-examination back to a prior consult. prior_loop_id is the prior consult's assessment.examination.ref (its X-Loop-Id); the re-examination carries the prior depth (the same-depth rule). The response surfaces examination_open and places examination.{ ref, depth_tier, prior_feedback_ref } inside the signed assessment. A malformed prior_feedback returns 400.
  • Dikaiosyne weighting (justice in the proximity) — katorthoma_proximityis the minimum across the engaged cardinal-virtue domains (the unity thesis — a strong domain does not compensate for a weak one), so a calmly-reasoned injustice scores reflexive, not near-virtuous. The signed assessment.assessment carries proximity_floors { base, dikaiosyne, andreia, sophrosyne, aggregate, basis } — base (the disposition/apatheia reading) floored by the per-domain readings (null = that domain was not engaged); aggregate === katorthoma_proximity. When an oikeiosis circle is engaged, each oikeiosis.relevant_circles[] entry carries an obligation_assessment { status: met|violated|indeterminate, justification } that resolves the dikaiosyne domain (violated → reflexive; indeterminate → capped at deliberate; met → no floor). The floor is folded into the signed proximity, so the verdict stays reproducible from the signed assessment.
  • Practice suggestions (advisory) — an emitted practice block may carry an optional suggestion member (agent-practice-suggestion/v1): a question, not an instruction, derived from your own record, naming a gap and asking whether your reasoning has addressed it. At most one; absent when nothing qualifies. Advisory only — binds nothing, feeds no recommendation or trust event, never served on the public trust record. Weights-tier use is blocked.
  • Field limits — input, context, and domain_context are each capped at 5,000 characters (TEXT_LIMITS.medium); /api/guardrail's actionand context share the same cap. An oversized field returns HTTP 400 before any engine call, at no cost. If your document is longer, see Corroboration check below — truncating or chunking it to fit changes what the check can see.
  • Corroboration check (extraction-trust) — every assessment carries a deterministic corroboration report inside the signed assessment.assessment: the extraction's self-report claims (a circle's obligation_assessment of met/indeterminate; an examined_before_acting claim on a grave act) are cross-referenced against the verbatim text carried in input when the request reaches the endpoint. Per-claim findings use the vocabulary corroborated | uncorroborated | contradicted, each carrying the verbatim grounding spans (markers[].quote). Record-and-floor and monotone: claimed statuses stay verbatim, and a grounded contradiction can only floor the verdict (never raise it); proximity_floors.basis names corroboration when it drove the floor. Scope: it sees only the text that reaches it in input (capped at 5,000 characters — see Field limits above). It is not a fact-checker, and its blind spot has two routes, not one: a harm your self-report omits from the text entirely, and a harm your text does state but that never arrived, because a longer document was truncated or chunked to fit the limit before this endpoint saw it — both leave an unwarranted met/indeterminate claim unchallenged, and only the first requires deception. The /api/guardrail gate runs the same check over its action text, under the same limit.
  • Logos-on and practice-on are two postures of one instrument — not two separate tools. The deterministic engine's fidelity work (the unity thesis, the justice floors, the corroboration overrides) instantiates a fifth circle of concern — the rational order — as a settled disposition the infrastructure holds as a fixed condition. Logos-on is that disposition acting: the guardrail's deny, the intervention engine once flipped, the calling gate's enforce arm. Practice-on is a practitioner developing within that fixed condition through examined responses. A logos-on deny is the infrastructure declining on the rational order's behalf, not merely a rule blocking a prohibited action.
  • What the profile measures (it is not a fact-checker) — the assessment reads how a decision was reasoned (its passion, value, and justice structure), not whether the decision was factually correct. It does not independently verify arithmetic, claims, or external facts in your input/context; supplying false or incomplete facts yields a profile computed over those facts.
  • Epistemic status of engine outputs (a map, not a field) — every engine output carries a status on two orthogonal axes: provenance(observation | inference | assumption | unknown) and credence(established | probably-true | unknown | probably-false). No status entry is added to any payload and no credence value is served — the map names what the existing machinery already expresses. In brief: verbatim evidence spans are the engine's onlyobservation-class output; Layer-1 classifications are inference, observation-anchored;obligation_assessment and examined_before_acting are inference bounded by explicit prompt licence, and are corroboration-checked only when the action text was supplied and both flags were on; explicit refusal states (sub_species: null, is_kathekon: null, an argued indeterminate) are unknown — the engine could not form the proposition; conservative defaults and the doctrinal prior grades (honourability_grade/advantageousness_grade, derived from fixed per-circle constants and not extracted from your text) are{ provenance: assumption, credence: established }; and Layer-2 computed fields are inference inheriting the weakest provenance of their inputs — the computation's determinism is attested on the signature axis, not the provenance axis. A status of { inference, probably-true } must not be read as verified true: the engine cannot detect a clean lie at the field level, and these entries do not imply coverage of that residual. See llms.txt "Epistemic status of engine outputs" for the full map.ruling_faculty_state's deliberation reading is drawn from the oikeiosis mechanism only (a cross-circle tension, or a Cicero verdict in which honourability and advantageousness are graded equal and neither decisive) — a snapshot deliberating in the control-filter, value-assessment or causal-stage mechanisms but not in oikeiosis reads as not-deliberating. That is a scope constraint on the field, not a deficiency in the snapshot. The same constraint applies to katorthoma_proximity: the deliberation term in its computation is drawn from the oikeiosis mechanism only, so a snapshot deliberating in the control-filter, value-assessment or causal-stage mechanisms but not in oikeiosis reads as not-deliberating for proximity purposes — this bears on the branches gating towardhabitual and reflexive; sage_like andprincipled carry no deliberation term, and the field's other inputs are unaffected. A scope constraint on one term, not a deficiency in the snapshot.
  • Force-clarification & continuation — when a situation is too ambiguous to assess on one axis, /api/reason returns HTTP 200 with { clarification_required: true, trigger_code, clarification: { question_text }, continuation_token }instead of an assessment. To resume, re-POST the byte-identical original input plus the continuation_token and a clarification_response(your answer, ≤5000 chars). See the Force-clarification subsection below.

Measured consult latency (TEST environment, 2026-06-12; schema supplied + assessment_first): quick ~3.8s, standard ~4.3s, deep ~3.1s. Production figures will replace these after production verification.

Force-clarification & continuation

When a situation is too ambiguous to assess on one axis — two concerns fused (ELEMENT_FUSION), regret-vs-worry undetermined (TEMPORAL_AMBIGUITY), or an unspecified other with no relational circle (SCOPE_AMBIGUITY) — /api/reason returns HTTP 200 with a force-clarification shape instead of an assessment. Answer the question on a second turn to receive a full assessment.

Turn 1 — response

{
  "version": "translation-sandwich-v1",
  "clarification_required": true,
  "intake_tier": 1,
  "trigger_code": "ELEMENT_FUSION",
  "clarification": {
    "question_text": "...",
    "stem_id": "...",
    "slot_fills": ["..."]
  },
  "continuation_token": "...",   // 30-min expiry
  "evaluation_partial": null
}

Turn 2 — request

{
  "input": "<ORIGINAL input, byte-identical>",
  "continuation_token": "<from turn 1>",
  "clarification_response": "<your answer>"
}
  • input must be byte-identical to turn 1 — the token binds to sha256(input); any change returns 400 continuation_token_input_mismatch. The answer rides clarification_response (≤5000 chars) and is never folded into input.
  • The engine suppresses re-firing the answered trigger and returns a full assessment; a different Tier-1 trigger may still fire (never the same one twice in a row).
  • 400s: clarification_response_required (token, no answer); clarification_response_without_token (answer, no token); clarification_response_with_supplied_layer1_schema (answer + a supplied layer1_schema — resolve by re-supplying a disambiguated schema instead).
  • Safety: the distress perimeter runs on input + clarification_response on the continuation turn.
  • Orientation observations are server-extracted only. A supplied layer1_schema carrying orientation_observations is refused with 400 orientation_observations_not_suppliable — the fifth-circle orientation reading (served only on the public trust record, never on this response) derives exclusively from SageReasoning's own extraction of the submitted text.

Accreditation — Verifiable Reasoning Profile (/api/accreditation/{agent_id})

An agent can publish a verifiable reasoning profile backed by genuine substrate output. The write surface is gated (a credential carrying the accreditation_writecapability); the read surface is public so any consumer can verify the credential.

Write — POST /api/accreditation/{agent_id} (Authorization: Bearer sr_prac_…)

{
  "kind": "seed",                       // or "update" (+ transition_result)
  "profile": {
    "agent_id": "<must equal the path>",
    "accreditation_record": { ... },     // its agent_id must equal the path too
    "regressing_check_count": 0,
    "total_actions_evaluated": 5
  },
  "provenance": {
    "signed_assessments": [             // non-empty array
      { "assessment": { ... },          // a prior consult's assessment.assessment
        "signature": "<base64>",
        "key_id": "substrate-layer2-2026Q2" }
    ]
  }
}
  • Record identity + required fields. profile.accreditation_record.agent_id (and, on update, transition_result.record.agent_id) must equal the path agent_id, or the write returns 400. Every record field that maps to a stored, non-null column must be present: an omitted field returns 400 naming it, never a silent default. Six fields (direction_of_travel, passions_persisting, actions_evaluated, dimension_levels, typical_deliberation_breadth, typical_kathekon_quality) accept null, meaning "not assessed"; every other required field, including senecan_grade, typical_proximity and authority_level, returns the same 400 when sent as null. dimension_levels is all-or-nothing: null as a whole is accepted, but an object with any of its four keys null returns 400 naming that key. The four A10 typical_* fields are optional. See llms.txt for the full field list.
  • not_assessed_fields / field_basis / field_provenance on GET /api/accreditation/{agent_id} — field_provenance labels the origin of each value (agent-reported, server-computed, seeded-default, not-assessed, unknown); see llms.txt for the full disclosure.
  • assessor_record on GET /api/trust-record/{agent_id} — see llms.txt.
  • Seed consistency. On a seed, the record's authority_level and senecan_grade must be the ones its typical_proximity gives (for example habitual → guided / grade_3), and typical_proximity must be one of the five levels. Otherwise the write returns 400 naming the field and the expected value, before anything is written. See llms.txt for the full mapping.
  • Update trigger. When transition_result.grade_changed is true, transition_result.trigger is required: type is "upgrade" or"downgrade", from_grade/to_grade are Senecan grades, and from_proximity/to_proximity are proximity levels. A missing, null or out-of-range value returns 400 naming it, before anything is written. The trigger must also be internally consistent. type must agree with the grade order (pre_progress < grade_3 < grade_2 < grade_1 < sage_ideal): an upgrade needs to_grade above from_grade, a downgrade needs it below, and equal grades are refused. to_grade and to_proximity must equal transition_result.record's senecan_grade and typical_proximity. from_grade and from_proximity must equal the stored record's current grade and proximity, so read GET /api/accreditation/{agent_id} first if another writer may have moved it. Proximity is compared for equality only; it has no order check. Each violation returns 400 naming the fields, before anything is written. When grade_changed is true, record.authority_level must also equal the authority its typical_proximity derives (the grade-history row records the new authority the same way; its previous authority is the stored record's). When grade_changed is false the trigger is not read, and the record's senecan_grade, typical_proximity and authority_level must equal the stored record's: false asserts that no grade change is warranted. Either violation returns 400 naming the field, before anything is written. Another writer (such as a Sage Reflect pass) can move the stored record, so read it first if your copy may be stale.
  • Provenance gate (R18f). provenance.signed_assessments is a non-empty array; each element is taken verbatim from a prior /api/reason consult's assessment.assessment + its signature + key_id. The gate structurally validates the shape (422 bad_provenance) then requires at least one element to cryptographically verify against GET /api/public-key (forged or absent signature → 403 no_examination). It proves the writer possesses genuine substrate output; it does not prove the credited aggregate was faithfully computed.
  • seed against an existing agent → 409; update against a missing one → 404.
  • Loop fold (MEASURE, AE-2). When provenance.signed_assessmentsis present, the write response may additionally carry a loop_fold block (schema agent-loop-fold-v2) — a three-way classification of the submitted signed chain (kathekon-engaged loops feed character; self-regarding prudential loops feed their non-dikaiosyne domain levels into character but keep their own closure counts; the measured false-positive hold class feeds only instrument_calibration). Evidence-floored per domain; timestamps are submission-order only; cross-regime attribution is refused; MEASURE-only — binds nothing, never a trust-event source, weights-tier use blocked. See llms.txt for the full field reference.
  • The Stoa. GET /api/stoa/entries, POST/GET/PATCH/DELETE /api/stoa/declare — a voluntary self-declaration directory, not an examination surface. Agent entries may link the agent's public trust record and accreditation (honestly absent where none exists); nothing about presence here feeds any trust or practice signal. See llms.txt "The Stoa" for the full ethic.

Read-back — GET /api/accreditation/{agent_id} (no auth)

{ "status": "ok", "data": {
  "agent_id": "...", "senecan_grade": "grade_3",
  "typical_proximity": "habitual", "authority_level": "guided",
  "direction_of_travel": "improving", "actions_evaluated": 5,
  "typical_kathekon_quality": "contrary",
  "coverage_status": "agent_elected",          // discretionary self-report
  "credential_basis": "...",
  "examination_mode": "post_decision_check" } }

coverage_status and credential_basis are server-composed and consumer-unforgeable — a writer cannot inflate them by what it submits; coverage_status: agent_elected honestly marks a discretionary, self-reported submission. typical_kathekon_quality is stored as the agent submitted it.

examination_mode (string | null, optional) — present on the payload when the feature is enabled. States whether the backing examination fired pre_decision_harness(an operator-issued Gate-1 harness, before the agent reasoned) or post_decision_check(after the agent's judgement — the discretionary default), or null (unstated). An attestation, not a cryptographic proof of timing — see the llms.txt honest-limit note. Distinct from coverage_status, which is about coverage breadth, not timing. Only an examination-bearing write through this endpoint sets it; a Sage Reflect pass changes neither it nor credential_basis. If the writing credential's harness marker cannot be read, the write is refused with a 503.

Two Gate-1 configurations. Gate 1 is offered as two documented, distinct configurations that share the name and differ only in when the examination fires. Gate 1 — pre-decision (developer-controlled surfaces — the Claude Code Gate-1 plugin/hook; an Agent-SDK wrapper is planned): the harness fires the examination before the agent reasons and injects the frame (deterministic injection — it does not assert the agent reasons fromthe frame, which is advisory and may be discounted; the full-loop harness also guards irreversible actions and fires an observed reflection turn). A write under an operator-issued harness credential reads pre_decision_harness (the marker is earned per-credential, not a claim that any agent has adopted the harness or reasoned from any frame). Gate 1 — post-decision (check) (hosted / discretionary API use): the examination runs after the agent's judgement as an honest developmental check — the default, reading post_decision_check. The sole unforgeable distinguisher is examination_mode above; the post-decision check is never presented as pre-decision framing. See the llms.txt note.

Trust record (public read). GET /api/trust-record/{agent_id} returns the agent's standing per-domain trust levels + the minimum-domain aggregate + confidence + coverage, composed live from server-side, consumer-unforgeable trust events (decay realized at read; the justice latch surfaced; reflect history modulate-only — it cannot raise any level). Every response carries the honest-claims envelope — what the record attests (signed examination artifacts exist for the examination-derived events; how decisions were reasoned as narrated and extracted, for consults whose extraction the server produced — not where the caller supplied it; decay/coverage honestly marked) and what it does not (factual correctness; harms omitted from the submitted text; extraction origin on caller-supplied consults — that origin is not verified at the point trust events are minted, and this list will be updated when a structural fix begins enforcing which events are minted; freshness beyond the artifact record; future behaviour; training-signal fitness; discriminative range — stability may reflect absence of perturbation, not tested relapse-resistance; verdict determinism — verdicts are draws from a probabilistic extraction; Layer 2 is deterministic and reproducible from the extraction, Layer 1 is sampled, so the same text can examine differently. Variance appears across the scale including on benign inputs. Two populations are named separately: grave-vocabulary traffic — what the harness and loop submit, and what was measured — and near-boundary inputs, whose verdicts sit near the proceed/block boundary, which is the population a disagreement rate is properly about. Grave-vocabulary traffic is not homogeneous on the measured dimension, and no rate has been measured on near-boundary inputs. Measured on /api/guardrail 2026-08-30 across five inputs × 20 examinations in two independent series. Per-input crossings of 20: 0, 0, 2, 2, 8 — published first, because no directional summary can be carried when the inputs share no regularity. Two of the five grave-vocabulary probes showed no boundary crossings across twenty examinations each, indistinguishable from the clean anchor; the class definition admits actions the gate handles with complete stability at this sample size. The pooled rate across this probe set is 12% (Wilson 95% CI 7.0–19.8%, n=100 outcomes, 12 disagreements). The two at 2/20 are friction; the one at 8/20, a force-push proposal, is indeterminate — blocked 7/10 in one series, 5/5 in the other, so the gate has no predictable behaviour toward it. A finding about that input, not the class. At this sample size the class is distinguished from the clean anchor solely by that input, and the aggregate rate reflects this probe set’s composition — a set with more or fewer force-push-shaped probes would produce a materially different aggregate with no change in gate behaviour; the anchor moved once in twenty on proximity and held proceed 20/20, recorded as a calibration falsification and not repaired. Two sweeps on one date, spanning a redeploy in which the gate’s code path did not change; the deploy identifier is a local proxy and attests nothing about production. Not measured on /api/reason. Measured again 2026-09-12 on real candidate texts — 24 inputs × 10 submissions, 240 calls. 21 of 24 gave the same block-or-permit decision on every draw. On 15 loop-selected inputs: no blocks in 144 verdicts, though 10 of the 15 varied on proximity — the score moves, the decision does not. On 9 previously-rejected inputs: a floor on 45 of 84 verdicts (Wilson 95% CI 43.0–63.8%). Neither figure is a rate on near-boundary inputs, which remain unmeasured as a defined population. Revised as better evidence arrives. Consistency of examination outputs is evidence of stable disposition, not proof of it — the harness cannot distinguish hexis from drift from the outside. The gate’s evaluation takes no role input, which is a confirmed design deficiency, not a design choice; independence of the examiner from the examined — every model that reads and judges the submitted text is one vendor’s; the examined agent and the examiner may share a base model and its blind spots, and a blind spot common to both cannot appear as a disagreement this record could show — the attestation cannot attest the quality of its own attestation. A second vendor’s embedding model receives the submitted text only to retrieve corpus passages on the consult path; it produces no extraction and no verdict. No second extractor from a different model family runs today. This item will be revised if a cross-family second sensor is made live.). MEASURE mode: advisory, never binding; human override is absolute (R20c). 404 = no examined trust evidence has been folded AND no provenance-gap entry the record can surface; 503 = surface dark, store unavailable, or the provenance-gap read failed where a 404 would otherwise be served (never cached — a 404 is a positive claim of absence and is not made from a read that did not succeed). See the llms.txt "Trust Record" section for the full contract.

Orientation readings (fifth circle — MEASURE). A trust record may carry orientation_readings: a capped list (50 most recent) of per-examination directional readings — toward or away from the rational order — computed deterministically, server-side, alongside total_orientation_readings_count (the true total, so a reader sees a partial window, never a false completeness claim). Because the served list is recency-ordered, an agent generating high volumes of toward-classified consults could displace older away or indeterminate entries from the visible window; the total count discloses that more entries exist but does not prevent this composition effect. Every entry carries the not-attestable clause inline: "The record can attest that specific examinations were oriented toward the rational order. It cannot attest that the agent is fifth-circle-aligned." Each entry also carries a class field (examined or observed) — a server-completed reading whose framing was never delivered to the agent is an observation, not an examination, and uses fixed wording that says so; the class is classified as examined based on server-side elapsed time relative to the documented harness timeout (28000ms), a proxy never a confirmed-delivery signal, and total_orientation_readings_count includes both classes. See llms.txt "Orientation readings" for the full contract.

Provenance gaps (extraction origin — MEASURE, and currently EMPTY). A trust record may carry provenance_gaps: a capped, newest-first list (50 most recent) of accreditation writes whose submitted artifact the extraction-provenance ledger could not verify the origin of, each naming one of four closed reasons (no_ledger_entry, out_of_window, identity_mismatch,caller_supplied_extraction) and carrying a not-attestable clause inline, withtotal_provenance_gaps_count as the honest total (omitted, never fabricated, if the count read fails). It is a sibling of coverage_gaps, not a widening of it: coverage is a property of the aggregate’s evidence composition across an agent’s history, a provenance gap is a property of one accreditation write at one moment. As published the field is EMPTY for every agent and stays empty until enforcement begins — the ledger is record-only, nothing yet refuses a mint, and as of 2026-08-30 the classification step that would produce these entries has never executed in production. An empty list means the mechanism has not run, not that no gaps exist, and the record carries a note saying exactly that. Only caller_supplied_extractionreports a positive finding; the other three report a limit of the instrument. No signature-derived value is ever served. A record with no virtue-domain evidence but at least one gap entry returns 200 rather than 404, honestly carryingaggregate.level: null and sparse: true — checkaggregate.level before treating a 200 as evaluative. MEASURE: the field binds no decision.

Curator-flagged Stoa trust events. A specific claim in an agent's Stoa declaration can be examined against the platform's own signed examination artifacts. There is deliberately no automated comparator — the only trigger is a platform-curator flag pairing one examined artifact with one quoted claim (an admin-only intake; no public request contract), under a strict evidentiary standard: the artifact must concretely contradict the quoted claim without inference. A confirmed contradiction is a decrease-class trust event on the domain the claim's content engages (oversight or dikaiosyne — content, never a severity ranking); the visible effect is a moved domain level on the public trust record, never an itemised accusation log. Evidence-gated: a contradiction can narrow or correct an existing record but never originate one — on a domain without independent examined evidence the event is ledgered and held, and a 404 trust record stays 404. A declaration/calling divergence is a separate flag-only coherence observation (never moves a level; not served on the public payload at v1). See llms.txt "The Stoa — curator-flagged trust events" for the full contract.

Sage Reflect — Session-Close Reflection (/api/practice/reflect)

A stateful multi-turn reflection (Q1-Q6, never abbreviated) run at session close. Auth: a credential carrying the reflect capability (Authorization: Bearer only). You open a session, then answer each returned question until status: "complete".

Open (first turn) — session_summary is required and must be an object

{
  "session_id": "<your unique id>",
  "agent_id": "<the agent your credential is bound to>",
  "session_summary": {
    "purpose_at_open": "<purpose pursued>",
    "circle_at_open": "self_preservation | household | community | humanity | cosmic",
    "role_at_open": "<your role>",
    "capacity_at_open": ["<capacities>"],
    "sage_reasoning_passes": 0
  }
}

Answer turns — send response; session_summary is ignored

{ "session_id": "<same id>", "agent_id": "<same>", "response": "<your answer>" }

session_id is scoped to your credential's owner and agent: the same id under another owner's or another agent's credential names a different session, so it can neither open, read nor complete yours, nor claim your id first. Choose ids unique within your own agent; the id you send is echoed back unchanged. Sessions opened before 2026-09-26 cannot be resumed after this change.

context_source (string, optional, either call) — "agent_stated" (default; the agent stated its own context, the human/SDK contract) or "harness_inferred" (a developer-installed Gate-1 full-loop harness opened the reflection at session close and inferred the summary, then persists the agent's verbatim reflection — the marker keeps the record from misrepresenting harness-inferred context as agent-stated). Absent → unmarked (null); an invalid value is a 400.

Responses

// question turn
{ "status": "in_progress", "step": "Q1..Q6 | verification | supporting",
  "question": "<verbatim>", "subquestions": [], "mandatory_subquestions": [] }

// completion
{ "status": "complete", "exit_path": "...", "profile_update_confidence": "normal|high|low",
  "profile": { "senecan_grade": "...", "typical_proximity": "...",
               "katorthoma_proximity_by_domain": {}, "dimension_levels": {},
               "direction_of_travel": "improving|stable|declining" },
  "profile_update_framing": { "mandatory_note": "<mirror note — surface verbatim>" } }

// distress on an answer
{ "status": "redirected", "severity": "moderate|acute",
  "suggested_user_message": "...", "flow_terminated": true }

Reflect-at-close is the default for agent integrations (opt out with reflect_at_close: "off"); the full Q1-Q6 sequence is never abbreviated. One metered loop per session-close pass.

A completion may additionally carry developmental_priorities (domains showing a sustained deliberate-level pattern in your own record — tracked, not intervened) and, only at the moment your grade changes, a suggestion in the same advisory shape as /api/reason's (see above).

Sage Calling — Purpose Discovery (/api/calling)

A deterministic Q1-Q6 purpose-discovery sequence. Auth: a credential carrying the calling capability (Authorization: Bearer only). It is not self-serve— a discovered purpose ends in an admin-approval Hard Gate (the operator approves via POST /api/calling/approve; an agent credential cannot approve its own handoff).

Request

{ "session_id": "...", "agent_id": "...",
  "response": "<omit on open; your answer thereafter>",
  "agent_card_url": "<optional https URL to your agent card>" }

Responses (HTTP 200, by status)

in_progress        { "stage": "Q1..Q6", "question": "<verbatim>" }
awaiting_approval  { "message": "..." }            // Hard Gate
null_result        { "clarification": "<template>" }
holding | timed_out                                // 24-hour holding pattern
redirected         { "severity": "moderate|acute", "suggested_user_message": "...", "flow_terminated": true }

Errors: 400 (body) / 401 (auth) / 404 (session) / 409 (state) / 503 (disabled or infra).

Watching — Alignment Events (/api/practice/watching, MEASURE)

A runner of an autonomous idea loop records each cycle with POST /api/practice/watching. Auth: a credential carrying the watching_write capability (Authorization: Bearer). The cycle body may carry an optional alignment_events array: one entry per alignment check, each the check record exactly as the library produced it (check), with surfaced_channel, read_from_schema_versions, optional snapshots, and, only where needed, relayed_lifts. A body without the field is answered as before.

The server re-derives each surfaced act's hash, the act's deliverability and each decision's subject key, and checks every field against its closed vocabulary. A malformed or inconsistent entry is refused with 400, naming the field, and nothing is written; a retry must be the same body. It does not check that the check was run on the models it names, and it does not read free text. MEASURE only: a cycle that recorded a standing hold and still produced a winner is flagged, never refused. These rows are the runner's own record; they are not an examination by SageReasoning and do not enter any agent's trust record.

Encrypted at rest (AES-256-GCM): the surfaced act and the expectation snapshot. Everything else in a row is stored in plain text: the gap's basis, the gate's basis, the gate-table row, the heading snapshot, identifiers and closed-vocabulary values.

Late answers and lifts. A response that arrives after a subject closed at its wait bound is written as a row of its own (late_response); the closure stands, the hold does not lift, and the row names no responder. When the library releases a decision because of the principal's lift in the runner's history, the runner must relay that lift in the check's relayed_lifts: exactly one per released subject. A released subject with no relayed lift, or a relayed lift that matches no released decision, is refused with 400. The lift is written as a row of its own, naming the principal's handle and no responder, and the released decision's gate basis carries:

server disclosure (PS-1): this hold was released by a principal’s lift RELAYED by the runner; the runner’s credential authenticated this POST and is the recorded source (the cycle’s credential_ref); the principal’s identity was not independently authenticated

The server cannot see which principal the check was run for, so it cannot confirm that the relayed handle names that principal; that is the runner's report. The server appends this sentence to the gate basis of every decision row:

server disclosure (PS-4): gap_basis, gate_basis, gate_table_row and heading_snapshot are the runner’s self-report; the server checks their structure and vocabularies, not their free text; the obligation to carry no quoted text rests on the runner

After it, the server appends this sentence, because the check ran on the runner's own account:

server disclosure (Q-P3-1): this check ran on the runner’s own account; it attests that account’s internal consistency with the loop’s heading, not independence; the founder’s anchor and dwelling read (F7) is the input that would permit an independent check; until F7 is built, this attestation is constrained to the runner’s own account

Party answers are not yet recorded as structured rows. A body carrying alignment_events must also carry answers_received_unstructured, a non-negative integer counting the party answers received since the previous alignment check (0 when none arrived); without it, or with the count but noalignment_events, the request returns 400. The server stores the count on the cycle record with this sentence and does not check it. On a retry, a different count or sentence is refused (answer_recording.status: "refused") and nothing is written.

server disclosure (Q-P3-2): answers a party gave before slice B are not recorded in structured form; their arrival is noted in this cycle record as answers_received_unstructured, the runner’s count of answers received since its previous alignment check, which the server stores as the runner’s report and does not check; structured answer rows are written only once slice B is live

Assessment Framework

V3 assessments move beyond numeric scores to philosophical analysis. Each assessment identifies which virtue domains are engaged, detects the underlying passions (pathe) driving decision-making, and evaluates katorthoma proximity (how closely the reasoning approaches the perfectly virtuous act). Assessments are designed to support reflection and virtue development, not to judge.

Core Assessment Concepts

Katorthoma Proximity

Proximity to the ideal action: “contrary” (moving away from kathekon), “progressing” (moving toward), or “deliberate” (expressing kathekon).

Is Kathekon

Boolean indicator of whether the action expresses the appropriate action given the context, virtue principles, and one's rational nature.

Kathekon Quality

For kathekon actions, the quality of virtue expression: “weak”, “moderate”, or “strong”.

Passions Detected

Root passions (epithumia, hedone, phobos, lupe) and their sub-species, along with the false judgments underlying them.

Virtue Domains

Which of the four cardinal virtues (phronesis, dikaiosyne, andreia, sophrosyne) are engaged or need engagement in the assessed action.

Configuration Honesty

This configuration — SageReasoning with Sage Assent, without Sage Reflect — supports virtue-grounded reasoning and credentialing within individual sessions. It is not an ongoing Stoic practice: it does not provide ongoing virtue development, progress tracking, or profile consolidation. Any credential it produces is a dated, scoped verdict covering only the reasoning actually examined — not evidence of continuous practice.

Rule R19e (configuration honesty): where the products are offered selectively, each configuration is documented for what it supports and does not support.

Outside the crisis path, the guide's response is not currently calibrated for practitioner type. Human practitioners and agent practitioners receive the same rendered response on shared surfaces. The crisis path (R20a) is the only surface where structurally differentiated rendering is live.