What it is
Reasoning sits between your agents and the models. Rather than call one model and hope, it runs a disciplined pipeline — route → extract → validate — that picks the right handler for the input, extracts structure, and checks the result against a schema before returning it. You get validated JSON, not a paragraph to parse.
On top of that sit two reusable units of work: skills (a versioned, testable prompt with an input and output schema) and chains (skills and prompts composed into a pipeline with conditionals, parallelism, budgets and guardrails).
| Property | Value |
|---|---|
| Agent surface | MCP over Streamable HTTP, or stdio |
| Service surface | REST over HTTPS, base path /v1 |
| Tenancy | Per-organisation; always derived from your credential, never from the request body |
| Inference | Local-first, with cloud fallback and per-tenant policy |
| Streaming | Server-sent events for chain execution |
Quickstart
1. Call a tool over MCP
Point any MCP-capable client at the endpoint and authenticate with an API key.
curl -X POST https://<YOUR_REASONING_HOST>/v1/mcp \
-H "X-API-Key: <YOUR_API_KEY>" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"jsonrpc": "2.0", "id": 1,
"method": "tools/call",
"params": { "name": "list_skills", "arguments": {} }
}'
{ "skills": [ { "name": "pm.generate_feature_spec_from_goal",
"description": "Draft a feature spec from a goal | Category: pm/spec_generation | Model: balanced",
"input_schema": { "type": "object", "properties": { … } } } ],
"count": 1 }
2. Run a skill
curl -X POST https://<YOUR_REASONING_HOST>/v1/skills/run-by-name \
-H "Authorization: Bearer <YOUR_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"skill_name": "pm.generate_feature_spec_from_goal",
"inputs": { "goal": "Let users log in with SSO",
"constraints": ["must support SAML"] },
"temperature": 0.7
}'
{ "skill_id": "b1e7…", "skill_name": "pm.generate_feature_spec_from_goal",
"skill_version": 3,
"output": { "feature_name": "SSO Login", "acceptance_criteria": ["…"] },
"execution_id": "8f2c…", "outcome": "success", "duration_ms": 1240,
"model_used": "…", "tokens_used": 847, "validation_passed": true }
Authentication
| Credential | Use |
|---|---|
X-API-Key | Agent and service keys for the MCP surface. A per-agent key additionally identifies which agent is calling, which is what the audit log records. |
Authorization: Bearer | A signed token for the REST surface. Reasoning and manifest routes require this. |
X-Organization-ID | Optional. A service caller may set the acting organisation with it; a user caller cannot use it to reach another organisation. |
Your organisation is always resolved from the validated credential. A user caller that names a different organisation in a request body is refused rather than served, and a service caller must name one explicitly rather than defaulting.
The MCP endpoint publishes OAuth discovery documents and supports dynamic client registration, so a spec-compliant client can connect without manual configuration. Tool calls are rate limited per organisation; concurrent sessions are capped.
If the rate-limit store is unavailable, write tools fail closed and read tools fail open. Losing a counter should never become an accidental licence to mutate.
MCP tools
Seventeen tools, every one annotated so your client can distinguish reading from writing before it runs anything.
Read — product & specification
context, journeys, features — each response naming the next phase.Read — agent specifications
Write — status
backlog · defined · in_progress · developed · testingdraft · in_progress · donespeccing · prompting · building · testing · deployedStatus writes are annotated non-destructive — full version history is retained, so a wrong status is correctable rather than lossy.
Skills
run_skill is deliberately annotated as destructive and non-idempotent — a skill can do anything, so the conservative hint is the honest one. It returns the output plus outcome, skill_version, execution_id, duration_ms, model_used, tokens_used and a trace id.
Tool errors come back as content with an error field rather than as transport failures, so an agent can read the reason and adapt — including when it has been rate limited or has reached across organisations.
MCP prompts
The server also publishes prompts — parameterised starting points a client can offer directly to a user:
| Prompt | For |
|---|---|
s9n-orient | Getting oriented in a product before doing anything. |
s9n-implement-feature · s9n-implement-story | Implementing a specific feature or story. |
s9n-review-spec | Reviewing a specification. |
s9n-orient-agent · s9n-build-agent · s9n-test-agent · s9n-refine-agent-prompt | Working on an agent specification end to end. |
Skills
A skill is a versioned, executable prompt with schemas on both sides. Defining one is a POST, not a deploy.
| Field | Meaning |
|---|---|
name, description | Required. The description is what semantic discovery searches. |
prompt_template | Presence of this makes a skill executable. Rendered in a sandboxed template environment. |
input_schema / output_schema | JSON Schema, enforced before and after the call. |
system_prompt | Persona for the call. |
model_tier | fast · balanced · capable · reasoning. |
category, language, frameworks, agent_types | Discovery metadata. |
source, confidence | manual · learned · imported, and a score. |
Endpoints
POST …/rollback/{n}/history/export, and skill packs via /packs/upload/success-rate · /cost · /recommendationsYou cannot set a skill's confidence through the metrics endpoint — it is computed from execution history, with a penalty for going stale, and a skill promotes itself once it clears the bar. A number you can write is a number that means nothing.
Model affinity
Different inputs deserve different models. Affinity binds a skill to an ordered model preference per context:
PUT /v1/skills/{skill_id}/affinity
{
"affinities": [
{ "context": "simple", "models": ["<fast-model>"], "priority": 0 },
{ "context": "complex", "models": ["<strong-model>", "<fallback>"], "priority": 1 },
{ "context": "default", "models": ["<balanced-model>"], "priority": 2 }
]
}
Models are tried in order; lower priority numbers win. Resolution runs: an explicit model_override on the call (validated against an allowlist), then affinity plus an automatic complexity assessment of the input, then the skill's declared tier, then the platform default.
The complexity assessor is lazy — it only runs when affinity rows exist, so the common path costs nothing.
Chains
A chain composes steps — a skill, a prompt, a raw model call, or another chain — into a pipeline.
POST /v1/chain
{
"name": "spec_pipeline",
"routing_mode": "cloud",
"steps": [
{ "name": "extract_requirements", "step_type": "skill",
"skill_name": "extract_goals" },
{ "name": "generate_spec", "step_type": "prompt",
"prompt_template": "Given requirements: {{ previous_output }}\nGenerate a feature spec.",
"system_prompt": "You are a senior product manager.",
"uses_previous": true, "is_final_output": true }
]
}
| Capability | How |
|---|---|
| Sequencing | uses_previous feeds the prior output forward; is_final_output marks the result. |
| Parallelism | Steps sharing a parallel_group run concurrently and merge by merge_strategy — concat, merge, list or first_success. |
| Conditionals | A condition on a field with comparison operators, plus and/or. Skip the step or jump to a named one — with loop detection. |
| Nesting | A step can be another chain, bounded in depth with circular-reference detection. |
| Budgets | max_cost_usd and max_latency_ms with enforcement of warn, downgrade (switch to a faster model) or abort. Steps marked skippable are dropped first. |
| Guardrails | Pre, inter and post checks — length, quality threshold, basic PII — acting as pass, warn or block. |
| Failure policy | abort, retry_chain, or fall back to a named chain. |
A chain of skills
The useful pattern is composing skills rather than prompts — each step is separately versioned, separately tested and reusable somewhere else. A step can also be another chain, so a chain of skills becomes a building block in a larger one.
{
"name": "intake_pipeline",
"steps": [
{ "name": "classify", "step_type": "skill", "skill_name": "doc.classify" },
// fan out — both run concurrently, results merged
{ "name": "entities", "step_type": "skill", "skill_name": "doc.extract_entities",
"parallel_group": "enrich", "uses_previous": true },
{ "name": "summary", "step_type": "skill", "skill_name": "doc.summarise",
"parallel_group": "enrich", "uses_previous": true,
"merge_strategy": "merge" },
// only escalate when the classifier wasn't confident
{ "name": "deep_review", "step_type": "chain", "chain_name": "expert_review",
"condition": { "field": "confidence", "op": "<", "value": 0.8 },
"skip_on_condition_fail": true },
{ "name": "render", "step_type": "skill", "skill_name": "doc.render_report",
"uses_previous": true, "is_final_output": true }
],
"max_cost_usd": 0.50,
"budget_enforcement": "downgrade",
"on_failure_strategy": "abort"
}
Four things are worth noticing. Steps fan out by sharing a group and reconverge by merge strategy. A step skips itself when its condition fails, so the expensive review only runs when the cheap classifier was unsure. A step is another chain, which is how a library of small pipelines composes into a big one. And the whole run carries a budget — on downgrade it drops to faster models rather than failing, which is usually what you want at 3am.
Nesting is depth-bounded with circular-reference detection, so a chain that references itself is refused at execution rather than discovered as a stack overflow.
Returns final_output, every intermediate_outputs step, errors, total_tokens, execution_time and the models_used — note the plural: one chain routinely spans several models and providers, and the response tells you which ones actually served it.
The same run as server-sent events: chain_start, chain_step_start, chain_step_complete, chain_step_error, chain_complete.
/rollback/{n} — edits auto-snapshot.Understanding pipeline
Underneath skills and chains is the primitive that turns text into structure. A unit is a semantic description plus typed slots; the description drives routing, the slots drive extraction and the schema.
| Stage | Behaviour |
|---|---|
| Route | Semantic match against unit descriptions above a threshold. Total by design — empty input, no match or any internal error falls back to a default unit rather than raising. |
| Extract | Slots are rendered into a prompt and the model returns JSON at low temperature. |
| Validate | A JSON Schema is derived from the slots — types, required fields, lengths, ranges, enums, patterns, formats — and the result is checked against it. |
Slot types cover strings, numbers, integers, booleans, typed arrays, objects, and formatted strings for date, date-time, email and URL, each with its own constraints.
By default validation is lenient: violations are collected and the value is coerced toward the declared type rather than rejected outright, so a nearly-right model response becomes usable data instead of an exception. Strict mode raises with the full error list when you'd rather fail than coerce — choose per call site.
Graph of Thought
Record why, not only what. Reasoning nodes form a directed graph and link to the entities they produced.
A node has a reasoning_type of observation, inference, decision, question or answer, plus content, a confidence, and parent nodes.
A design decision carries title, context, decision, rationale, alternatives considered and consequences, with a lifecycle of proposed → accepted · rejected · deprecated.
/search/stories · /search/codeSemantic search across specification and code, filterable by component type and minimum similarity.
Reads are filtered to your organisation; a caller cannot walk another tenant's reasoning. These routes are service-to-service in shape — they expect embeddings supplied by the caller.
Events
State changes publish through a transactional outbox, so an event is committed with the change that caused it rather than best-effort afterwards. Consumption is idempotent through an inbox.
{ "type": "concept.updated", "version": "1.0",
"timestamp": "2026-08-03T12:00:00Z",
"event_id": "…", "idempotency_key": "…",
"producer": "reasoning", "org_id": "…", "project_id": "…",
"payload": { "concept_id": "…", "concept_type": "Feature",
"properties": { "name": "My Feature", "status": "backlog" } } }
| Family | Events |
|---|---|
| Concepts | concept.created · concept.updated · concept.linked · concept.relationship.created · concept.batch.ingestion |
| Specification | manifest.updated · manifest.item_deleted |
| Extraction | extraction.started · extraction.completed · extraction.entities · cascade.started · cascade.completed |
| Reasoning | reasoning.decision.created · contradiction.detected |
| Skills | skill.promotion.requested |
Webhook deliveries carry the event type, a stable idempotency key that survives retries, and an HMAC signature over the raw body. Delivery is at-least-once with exponential backoff and a bounded retry count before an event is marked failed — so consumers must be idempotent. Ordering is guaranteed per organisation, not globally.
Two idempotency concepts are separate on purpose: a transport key that is stable across retries of the same delivery, and a domain key that dedupes the same logical event.
Multi-model, multi-provider
No single model is right for every call, and no single provider should be a hard dependency. Reasoning treats the model as a routing decision made at call time, from four inputs: the tier a skill declares, its affinity rules, your tenant policy, and what is actually reachable right now.
Tiers, not model names
Skills declare a tier. Swapping the model behind a tier is a configuration change, not a rewrite of every skill that used it.
| Tier | For |
|---|---|
fast | Always-hot small model — classification, routing, cheap transforms. |
balanced | The default for most work. |
capable | Harder reasoning and longer synthesis. |
reasoning | The strongest available, for the calls that justify it. |
Providers
Self-hosted and cloud providers sit behind one interface, so a skill does not know or care which served it.
| Provider | Role |
|---|---|
| Self-hosted (primary) | Your own inference cluster. The default path — lowest cost, and your content stays put. |
| Self-hosted (secondary route) | A second address for the same cluster, so a routing failure isn't a capability failure. |
| Groq · OpenAI · Anthropic · Gemini | Cloud providers, used on fallback or when you deliberately select one. |
The registry knows which models a provider can actually serve — including whether a model is currently loaded on a self-hosted host. A request for a model that isn't there fails over instead of erroring.
Fallback is a policy, not a hard-code
Each tenant can define its own chain: a primary provider, then an ordered fallback list where every step names the conditions that trigger it.
{
"primary": { "provider": "self-hosted" },
"fallback_chain": [
{ "provider": "groq",
"triggers": ["on_connection_error", "on_timeout", "on_5xx", "on_model_unavailable"] },
{ "provider": "openai", "triggers": ["always"] }
],
"enabled": true
}
Errors are classified before the chain is walked, so a timeout and a bad request are not treated the same way — only the steps whose triggers match are tried. Set no policy and you inherit the platform default. A circuit breaker sits in front of every provider, so one that is failing is skipped fast rather than retried into the ground.
Two things fall out of it. Cost: the expensive provider is a safety net, not the default path. Data control: content only leaves for a third party when a rule you wrote says it may — and on an enterprise deployment in your own cloud, you can write a policy where it never does.
Per-skill affinity
Above the tier, a skill can bind specific models to specific contexts — and the input is classified as simple or complex on the way in, so an easy call doesn't pay for a hard model. See Model affinity. Precedence at call time: an explicit override, then affinity plus complexity, then the declared tier, then the platform default.
Reliability
| Guard | Behaviour |
|---|---|
| Circuit breakers | Per dependency — inference hosts, vector search, the graph, sibling services. After repeated failures the breaker opens and calls fail fast, then a single trial probes recovery. |
| Health surface | Breaker state, failure and success counts are exposed for your own monitoring. |
| Skill retries | Chain steps carry their own retry count; chains carry a failure strategy. |
| Outbox | Events survive a consumer being down — they are retried with backoff, then parked as failed rather than lost. |
| Rate limits | Per organisation on the agent surface, with the fail-closed-on-writes rule above. |
Errors
Skill outcomes
| Outcome | Meaning |
|---|---|
success | Ran, and the output matched its schema. |
partial | Ran and produced output, but it failed output-schema validation. Returned with validation_passed: false — and a 200. Check the field, not just the status code. |
failure | Input validation, a guardrail block, a template error, a model failure or an unparseable response. The error is in the output body. |
Status codes
| Code | Meaning |
|---|---|
400 | Skill has no prompt template and can't execute; model override outside the allowlist; a service caller that didn't name an organisation. |
401 | Missing or malformed credential. |
403 | Reaching across organisations. |
404 | No such skill, chain, decision or unit in your organisation. |
422 | Request body failed schema validation. |
429 | Too many concurrent agent sessions. |
503 | A required dependency is unavailable. |
Every response carries a correlation id header — quote it when reporting an issue and the whole call is retrievable.
Your data
Reasoning processes the prompts, conversation turns and embeddings you send it, isolated per organisation and always scoped to the credential that made the call. It retains execution and usage records so you can see what ran, on which model, and at what cost.
Inference is local-first: your content is reasoned over by self-hosted models by default. When a request falls back to a cloud provider — or you configure one deliberately, including with your own provider keys — that prompt content leaves to that third party. Which providers, and what they retain, is set out in the privacy policy.
Availability & access
Reasoning ships as part of the SeKondBrain platform — licensed, not open source. It runs in production today behind our own products; you buy the platform and integrate the engine through its MCP surface and REST API, with your organisation provisioned and isolated for you.
Where it runs
| Model | What it means |
|---|---|
| Multi-tenant cloud | The default. We operate it; your organisation gets its own isolated tenant and its own endpoint. |
| Your own cloud | For enterprise. Deployed into your cloud account — which also means inference can stay entirely inside your boundary. |
What you get when you're set up
| A unique URL | Your organisation is issued its own endpoint — that is the <YOUR_REASONING_HOST> in every example on this page, and the address you give an MCP client. |
| Credentials | An API key for the agent surface and a token for the REST surface, scoped to your organisation. |
| A live API reference | Your instance serves its own OpenAPI, matching the version you are running. |
| Setup guide | Provisioning covers connecting your first client, seeding skills and running your first chain. |
Email hello@sekondbrain.ai with the subject “Access for Reasoning” and tell us what you want to build.