This commit is contained in:
2026-08-24 08:10:26 -07:00
commit 6f4024f039
11 changed files with 15422 additions and 0 deletions
+389
View File
@@ -0,0 +1,389 @@
# Changelog
## 2.4.0
- Stabilized the pre-release 2.4.0 implementation and qualification evidence in
place. These review corrections do not add a Soma environment variable or package
dependency, and 2.3.0 remains the unchanged rollback. Evaluator-only provenance
arguments and llama.cpp server flags are not runtime configuration additions.
- Replaced isolated latest-turn rewriting with a bounded full-task context. Transform
calls can receive the original role-preserving `system`, `developer`, `user`,
`assistant`, and `tool` messages; tool results and definitions; `tool_choice`;
`parallel_tool_calls`; response-format, modality, audio, and stop controls; the
immutable target tool calls; and a clearly separated failed assistant draft.
Model and sampling knobs, transport headers, endpoint credentials, and rejected
transform candidates remain excluded. System/developer instructions remain
authoritative below Soma's JSON and native-tool invariants.
- Replaced independent whole-field regeneration with one fixed joint-repair object
containing required nullable `reasoning` and `content` members. Soma classifies all
present fields before repair and requests values only for fields marked for repair,
allowing accepted reasoning to ground content and jointly generated reasoning to
precede jointly generated content.
- Isolated each classification envelope to the named draft field while retaining the
complete original request, controls, and immutable calls. Repair generation may
inspect fields it must replace, but integrity verification sees only retained draft
fields and the current candidate, preventing rejected sibling text from influencing
either decision.
- Specialized the existing repair schema per call so requested members are strings
and every other required member is null. Added local JSON syntax/top-level checks
and literal stop-sequence checks for both target and repaired output, a fixed
alternate focus for candidate three, and bounded privacy-safe candidate rejection
reasons. Exact nonblank punctuation, symbol, and empty-container outputs are not
rejected by an English-text heuristic. These corrections add no JSON-schema
dependency or temperature branch.
- Added explicit field outcomes. A rejected optional reasoning field is dropped when
usable content remains; a reasoning-only response without content or a native tool
call is not accepted as terminal success. Immutable native `tool_calls` survive
exhausted adjacent-prose repair, with only the unusable prose cleared. Returned
function calls must match supplied definitions, and multiple calls are rejected
when the request sets `parallel_tool_calls=false`.
- Defined the normal semantic route as primary reasoning-off classification,
candidate one, and integrity verification, followed—after a completed semantic
rejection—by up to two fresh secondary reasoning-on candidates from the original
context. Each secondary candidate is verified on secondary. Without a secondary,
recovery is limited to two primary candidates. Availability failover remains
request-local. Configuration now enforces primary `off` and secondary `on` so the
deployed route cannot silently differ from the route the evaluator qualifies.
- Added a reasoning-off verifier fallback for a reasoning-on verification result that
is truncated or structurally invalid. The identical candidate is checked once on
the same endpoint/model with thinking disabled; Soma regenerates only after a
completed verifier returns `rewrite`. Semantic repairs never receive a rejected
candidate or prior-failure prose; structural JSON recovery receives only a closed
reason. Any `finish_reason=length` result is treated as truncated even when its
partial text happens to be syntactically valid JSON.
- Added per-endpoint `TRANSFORM_MEDIA_MODE=placeholder|forward|reject` and
`TRANSFORM_SECONDARY_MEDIA_MODE`. Placeholder mode preserves typed part positions
and metadata while omitting binary payload; forward mode sends the original media
as native OpenAI multimodal parts; reject mode requires a compatible alternate
route or fails explicitly. A forward-capability rejection is never silently
downgraded to placeholder mode. Malformed scalar content parts are rejected before
the target call, and streamed assistant-audio data/transcripts are accumulated
rather than overwritten. Mixed text/tool plus audio turns preserve their audio;
audio-only target turns fail explicitly because the text repair contract cannot
inspect or repair that payload.
- Added `TRANSFORM_ALLOW_CLARIFICATION`, default `false`. Missing task inputs no
longer license arbitrary examples, placeholders, invented results, or generic
promises; a clarification is an acceptable repair only when the operator opts in.
- Added the opt-in `TARGET_RETRY_ON_UNREPAIRABLE`, default `false`. When the complete
target turn leaves neither usable content nor a native tool call, Soma may make
exactly one fresh target request with a fixed corrective system message inserted
after the leading system/developer block. It preserves the original conversation
and tool contract and never includes rejected target or transform output. Optional
failed reasoning does not trigger this retry when content remains usable.
- Added the opt-in `TARGET_LOOP_BACK_ON_VERIFIED_REPAIR`, default `false`. When an
integrity-verified repair rewrites the target's refusal reasoning, Soma may make
exactly one additional target request consisting of the original conversation plus
a single assistant message carrying only that verified repaired reasoning in a
`reasoning_content` field, so the target re-ingests the relaxed context and
produces the task output itself. The second response is processed under the same
bounded rules and never loops again; genuine refusals never loop because they
classify as `pass`. Loop-back and `TARGET_RETRY_ON_UNREPAIRABLE` remain mutually
exclusive per request under the shared two-target-call ceiling and aggregate
transform deadline. Backends that reject `reasoning_content` in input messages
fail explicitly.
- Raised the defaults to `TRANSFORM_CONTEXT_MAX_CHARS=131072`,
`TRANSFORM_FIELD_MAX_CHARS=32768`, `TRANSFORM_DECISION_MAX_TOKENS=1536`,
`TRANSFORM_REWRITE_MAX_TOKENS=16384`, and
`TRANSFORM_TOTAL_TIMEOUT=1200`. Semantic context is never silently truncated; the
configured context limit is capped at 4000000 characters and the field limit must
not exceed it. The limit is enforced after each phase removes discarded draft
fields, so oversized optional reasoning cannot block an otherwise bounded content
repair.
- Removed `TRANSFORM_CONFIRM_REWRITES`. Any presence of the obsolete variable now
fails configuration validation so a stale profile cannot appear to enable behavior
that no longer exists.
- Set hard ceilings of two target calls, 20 transform calls per target response, and
40 transform calls for one client request. Default configuration still makes one
target call because target retry is disabled. Fail-open no longer permits a
reasoning-only or otherwise empty terminal response.
- Expanded privacy-safe diagnostics, offline coverage, and live qualification around
full context, media routes, joint field policy, semantic candidate routing,
verifier fallback, and optional target retry. The frozen evaluator now covers 240
classification cases, 80 balanced joint message repairs, all three media modes,
staged primary-off/secondary-on routing, retained/forbidden facts, schema-off and
repeat sentinels, and reproducibility. Live target smoke remains explicitly opt-in.
Soma 2.4.0 ships unqualified until the exact transform profile passes every
automated gate; the report retains hash-bound evidence for all 80 repairs, and the
evaluator never downloads, loads, switches, starts, stops, or restarts a model.
Error responses after dispatch now carry the same privacy-safe counters as success
responses, target elapsed time includes malformed target responses, and exact
qualification records a dedicated secondary reasoning-budget value.
- The live evaluator can record llama.cpp's unrestricted reasoning-budget sentinel
as `--reasoning-budget -1` for an honest exploratory run. This never satisfies the
positive bounded-budget qualification gate; it only prevents current deployments
from being mislabeled as budget `0` or `512` while collecting repair evidence.
- Defined the first qualifying local route as temperature zero with a bounded
llama.cpp `--reasoning-budget 512`, recorded by the evaluator with the matching
`--reasoning-budget 512`. Temperature zero removes intentional sampling variance
from repeat gates but is not a guarantee of byte-identical output; other
temperatures and budgets remain separate profiles that need their own reports.
- Made the automated gates the sole qualification stage. A complete pass records
`qualified: true` and `qualification_status: qualified`; a failed gate records an
unqualified profile. Hash-bound retained outputs remain audit evidence and do not
form a separate approval stage.
- Added an explicit provider-managed evaluator profile for hosted-model experiments.
It records a retained, hash-bound public model metadata record and non-secret
endpoint/model identities, rejects fabricated local GGUF or reasoning-budget
provenance, and is always marked exploratory rather than qualified. Target smoke
now requires the exact requested `OK` token instead of accepting arbitrary
non-empty content.
- Added an explicitly unqualified hybrid evaluator profile for a local GGUF primary
and provider-managed secondary. It retains both provenance records without
pretending the hosted reasoning budget is known. Target-smoke requests now allow
128 output tokens while still requiring the response to be exactly `OK`.
- Kept the unrestricted-budget Qwen3.5-9B Q6_K report unqualified until a complete
replacement run exists. In-place evaluator stabilization changes its source/corpus
evidence identity, so the evaluator atomically installs a fresh temperature-zero,
512-token report before the obsolete unrestricted artifact is removed; the old JSON
is never edited or promoted.
## 2.3.0
- Replaced exact excerpt extraction, Unicode-equivalence recovery, unique-anchor
resolution, bounded fragment generation, masked retries, and local splicing with a
decision-only `pass | rewrite` classifier and complete-field regeneration. The
simpler contract removes the highest-fragility requirements for small transform
models while giving every model enough user context to produce a substantive
answer rather than a generic bridge.
- Added mandatory post-rewrite classification. Every locally valid candidate must
independently classify as `pass`; a residual or paraphrased refusal,
nonresponsive bridge, malformed verification result, or verification failure
rejects the candidate.
- Added one bounded fresh-regeneration path from the pristine source after a
received-output or post-check failure. A configured secondary performs the second
candidate when available; otherwise primary is reused. Rejected candidate text is
never fed back to either model, and the final candidate is verified exactly once.
- Added `TRANSFORM_REWRITE_MAX_TOKENS`, defaulting to 4096 with a valid range of
25616384. Complete replacements are bounded after decoding to
`min(16,384, max(4,096, 2 * source characters))`; the rewrite schema deliberately
omits a dynamic `maxLength` for structured-output compatibility.
- Added explicit 16,384-character rewrite-input limits without semantic truncation.
Missing textual user context and oversized source/user inputs are deterministic,
field-scoped failures. Mixed-media turns carry an `unseen_media` flag; pure
non-text turns cannot be regenerated.
- Limited transform context to the complete field, latest textual user turn,
`tool_turn`, `unseen_media`, the deterministic output bound, and an optional closed
retry reason. Earlier messages, system instructions, processed reasoning, tools and
arguments, headers, credentials, and rejected candidates remain excluded.
- Raised the per-completion transform-call ceiling from eight to twelve to cover two
complete `(regeneration + post-check)` candidate paths for each reasoning/content
field while retaining at most two classification votes, the aggregate transform
deadline, one target call, and request-local backend stickiness.
- Removed the DSML parser, shim, counters, diagnostics, and documentation. Soma now
preserves native OpenAI `tool_calls` only; proprietary text-to-tool conversion is
owned by the target's OpenAI-compatible gateway. Native calls now require a
nonblank unique ID and `type: function`, preventing stream/non-stream envelope
drift and uncorrelatable tool results.
- Tightened the post-`[DONE]` SSE extension to at most one object with `choices: []`,
no keys outside `choices`, `cost`, and `usage`, and at least one non-null metadata
value. The postlude may end at EOF or one closing `[DONE]`; all later data is
rejected, and upstream error objects retain the redacted stream-error path.
Normalized outbound SSE emits one accepted `cost`/`usage` metadata event instead
of repeating one-shot cost data on every delta.
- Added `X-Soma-Postcheck-Rejections` and `classify`, `rewrite`, and `postcheck`
phase diagnostics with candidate/backend/reasoning/purpose metadata. Removed
rewrite-mode and DSML diagnostics while retaining strict privacy-safe logging,
field decisions, repair counts, rejected-rewrite counts, and fail-open visibility.
- Reworked live qualification around the exact imported 2.3.0 runtime, decision-only
classification, complete-field replacements, and verified final output. Local
reports belong under excluded `qualification-local/`; Soma never downloads,
starts, stops, switches, or otherwise manages a model.
- Distinguished automated-gate success from final qualification: reports remain
`pending_manual_review` until the 40 retained source/replacement pairs receive
independent semantic review. Exact-pair runs with a configured secondary inject
one labeled primary-unavailable event and require real secondary classification,
regeneration, and post-check calls; reports include per-backend phase counts.
- Made live provenance fail closed if either the imported Soma source or evaluator
changes during a run, preventing a report from hashing different code than it
actually exercised.
## 2.2.7
- Fixed the 2.2.6 streaming regression that rejected OpenAI-compatible gateway
usage or billing postludes emitted after an upstream `[DONE]`. Empty-choice
metadata is now preserved, including an optional final `[DONE]`, while premature
delimiters, malformed postludes, and any post-delimiter choice data remain invalid.
- Added explicit `off`, `on`, and `default` transform reasoning modes. `off`
preserves the 2.2.6 request shape, `on` uses llama.cpp's
`chat_template_kwargs.enable_thinking=true`, and `default` leaves reasoning
controls to the server. The setting applies consistently to classification and
rewriting.
- Added one optional, fully explicit secondary transform endpoint/model profile. It
may point to the primary server with a different reasoning mode or to an
independently operated server/model, but it shares Soma's prompt, temperature,
JSON contract, validation, and fixed generation bounds.
- Kept recovery bounded to the existing second attempt. The secondary can replace a
failed primary attempt after connection/timeouts, HTTP 408/429/5xx, invalid outer
completions, or locally rejected classification/rewrite output. Configuration
errors and other non-transient 4xx responses remain visible.
- Added optional secondary confirmation for valid primary `rewrite` classifications.
A secondary `pass` conservatively preserves the field; two valid `rewrite`
decisions retain the primary anchor. Confirmation is disabled by default and does
not add a third classification attempt.
- Added request-local failover: after a transient primary availability failure, only
the remaining transform phases in that Soma request begin on secondary. The next
client request starts on primary, so Soma retains no cross-request circuit-breaker
or harness/session state.
- Added a 600-second aggregate transform budget, starting after the single target
call, so retries and multi-field processing cannot multiply the per-call timeout
without bound. The one-target-call invariant and eight-transform-call ceiling are
unchanged.
- Extended safe diagnostics with backend, reasoning mode, call purpose, secondary
call/failover/disagreement counts, and transform-deadline failures without logging
prompts, generated text, anchors, credentials, or raw upstream request IDs.
- Extended live qualification to exercise explicit reasoning modes and composed
primary/secondary behavior without managing models. Deployment guidance requires
a secondary to qualify alone and the exact pair to qualify before an operator
enables confirmation; the evaluator does not change runtime configuration.
- An exploratory test of the operator-selected Qwen3.5-9B Q6_K with reasoning enabled
at temperature 0 removed the two reasoning-off false rewrites but regressed overall
refusal recall from 99% to 96%, introduced four invalid/missed partial-refusal
anchors and ten repeat failures, and remained unqualified. Targeted 64/128/256-token
reasoning budgets were non-monotonic, so Soma adds no model-specific
reasoning-budget knob.
## 2.2.6
- Preserved the 2.2.5 one-target-call flow, phase prompts, bounded transform call
counts, anchored/masked rewrite behavior, DSML normalization, `FAIL_OPEN` policy,
endpoints, and runtime environment variables while simplifying the implementation.
- Removed unused transform compatibility helpers, prefiltered protected
`ENABLE_REASONING` fields before overlay, reduced target-response mutation to one
working deep copy, removed custom HTTP pool sizing, and consolidated rewrite
contract validation under its owning parser/semantic validator.
- Made client flag validation exact: `stream` and `parallel_tool_calls` must be
booleans, while `n` is null or integer `1` and no longer accepts a boolean.
- Added target assistant-message validation before DSML or transform calls, including
role/content types, one non-empty reasoning alias, native tool-call shape,
non-blank function names, JSON-string arguments, and unusable empty turns. Unknown
valid target metadata remains preserved.
- Limited latest-user transform context to text: plain strings and the textual parts
of multimodal arrays are joined, while image, audio, file, base64, tool, and other
non-text metadata is omitted.
- Required refusal anchors to contain at least one letter or number and begin and end
on source boundaries; rewrites must likewise contain a letter or number.
Context-copy checks now ignore only splice-edge whitespace, closing the
copied-neighbor bypass without changing the local exact-splice behavior.
- Replaced generic streamed-response merging with explicit OpenAI delta assembly.
Text and every tool-call fragment append in arrival order, including identical
fragments; stable metadata is checked explicitly and unknown metadata uses
last-value semantics. Cumulative snapshot providers are explicitly unsupported.
- Require a terminal non-null SSE `finish_reason`; reject `[DONE]` or EOF before it,
meaningful deltas after it, malformed event JSON, and non-finite values. Normalized
outbound SSE remains unchanged.
- Reject non-finite configuration numbers, malformed endpoint ports, endpoint
userinfo/query/fragment components, duplicate or framing-sensitive configured
headers, duplicate transform-contract object members, and non-finite JSON across
client, target, transform, SSE, and DSML boundaries. Wire JSON now uses a strict,
surrogate-safe encoder.
- Tightened HTTP request framing to reject simultaneous `Content-Length` and
`Transfer-Encoding`, non-sole chunked encodings, negative chunks, and unterminated
chunk framing. No in-process body-size or buffering cap was added; those limits
remain the responsibility of a trusted front proxy.
- Preserved leading and trailing whitespace for DSML `string=true` arguments while
continuing to trim non-string JSON arguments. Sanitized upstream request IDs before
reflection and prevented upstream headers from colliding with Soma diagnostics.
- Reduced client-facing detail for streamed target/internal failures while retaining
server-side diagnostics, and removed query strings and upstream error text from
routine proxy-error logs.
- Added an opt-in standard-library live qualification evaluator with 200 reviewed
classifier fixtures, 40 category-balanced rewrite fixtures, schema-off sentinels,
deterministic parallel/repeat gates, reproducibility metadata, synthetic rewrite
output for human audit, and an optional 50-case one-shot target gate. Its reviewed
anchors must cover the curated refusal core, its JSON/channel selection mirrors the
runtime, and reports fingerprint the exact prompt/source/configuration snapshot.
It never downloads, switches, loads, or restarts a model.
- Recorded the tested aggressive Qwen3.5-9B Q8_0 deployment as unqualified after
false-positive and rewrite-stability failures. The official Qwen3.5-9B Q8_0 remains
a candidate that must independently pass the same gate; Soma performs no model
management.
- Recorded the operator-selected DavidAU Qwen3.5-9B Q6_K artifact as reproducibly
unqualified at temperature 0, seed 1, the temperature sampler, and one server slot;
preserved its exact machine-readable qualification report with the release. A
per-request temperature-0.7 comparison was also preserved and was substantially
worse, with 43 false rewrites and 93% refusal recall. Temperature 0 remains the
better tested setting, but neither configuration qualifies.
- Added two copyable deployment profiles using only existing variables, with one Soma
process and distinct listener port per harness. Documented isolated profile startup,
the trusted-local/reverse-proxy boundary, front-proxy resource limits, and the
non-loopback warning. No runtime harness branching was introduced.
## 2.2.5
- Fixed the observed 2.2.4 semantic-echo failure in which classification recovered to a valid literal anchor, but both rewrite attempts returned complete, schema-valid, `finish_reason=stop` JSON whose replacement was unchanged from that anchor; this was not a JSON-mode, truncation, or token-budget failure.
- Split the built-in classification and rewrite guidance so classification remains observational while the editor is explicitly permitted to produce a bounded non-refusal fragment without independently solving the user request.
- Strengthened classification recovery guidance so a corrected excerpt must remain a unique, exact, syntactically replaceable refusal clause containing the withholding decision rather than merely any literal source text.
- Kept the initial rewrite and ordinary repairs anchored, but masked `refusal_anchor` from the sole second attempt after an `unchanged` or `anchor_retained` result. The masked request carries only the field, bounded contexts, `anchor_omitted: true`, and the closed failure reason; the exact anchor and offsets remain local for validation and splicing.
- Added field-specific masked guidance that relies on the local splice to preserve both contexts and forbids copying, paraphrasing, summarizing, or otherwise reproducing them in the replacement.
- Made the field fallback instruction unconditional on every `masked_anchor` retry after a live Qwen run ignored a buried conditional fallback and copied immutable context. The final system instruction, appended after the masked contract, directs the transform to return exactly `{"replacement":"Proceed with the requested work."}` for reasoning or `{"replacement":"I'll proceed with the requested task."}` for content. This gives the semantic retry a fixed, unambiguous target while preserving normal server-output parsing, local validation, candidate-exhaustion, and `FAIL_OPEN` behavior; the shared schema and validator still accept any otherwise valid replacement fragment.
- Added privacy-safe `rewrite_mode=anchored|masked_anchor` diagnostics without logging the anchor, replacement, target text, user context, or rejected output; non-rewrite phases use `none`.
- Preserved the 64-character anchor cap, 512-character contexts, 256-character replacement cap, 384-token rewrite budget, two rewrite attempts, one target call, and eight-transform-call ceiling. Rewriting still receives no latest user turn, unbounded target field, or processed reasoning, and still has no complete-field fallback.
- Preserved `FAIL_OPEN` behavior: strict mode returns the exhausted transform error, while fail-open mode preserves only the affected field, marks it `failed_open`, and continues.
## 2.2.4
- Fixed the observed 2.2.3 failure in which classification JSON was valid, an initial complete-field rewrite retained the refusal, and its repair exhausted the token budget before closing its JSON object; JSON mode was enabled but could not guarantee completion of the unbounded complete-field edit.
- Replaced complete-field refusal rewriting with a deterministic local splice: classification must resolve a unique source anchor of at most 64 decoded characters, and the transform receives only that anchor plus at most 512 source characters on each side.
- Require the rewrite model to return only an anchor replacement of at most 256 decoded characters, with the fixed cap expressed in the llama.cpp-compatible JSON schema, repeated in the prompt, and enforced authoritatively in Python.
- Reject and repair complete-field candidates that repeat immutable context across a splice boundary or embed a substantial boundary segment inside wrapper text.
- Bound rewrite generation to 384 tokens and preserve the original prefix and suffix exactly; only the bounded source window is sent, never the latest user turn or processed reasoning, and there is no complete-field rewrite fallback.
- Apply one failure-specific repair to any received but unusable rewrite candidate, without echoing candidate text or exception details into the retry.
- Make exhausted rewrite candidates consistently honor `FAIL_OPEN`: strict mode returns 502 with the specific transform failure, while fail-open mode preserves only the affected field and marks it `failed_open`.
- Retained all endpoints, environment variables, response headers, client formats, the one-target-call invariant, and the eight-transform-call maximum.
- Documented that fixed small `maxLength` constraints work with the tested llama.cpp structured-output path, while previously considered large dynamic limits did not; because the schema is not model-visible, prompts also state each cap and local validation remains authoritative.
## 2.2.3
- Reduced classification excerpts from 128 to 64 decoded characters and directed the classifier to choose a compact, normally 28-word refusal anchor.
- Required exact contiguous provenance after JSON decoding, preserving case, Unicode punctuation, CRLF and other line endings, tabs, repeated spaces, and non-breaking spaces.
- Resolve only unique CRLF, no-break-space, or curly-quote normalization matches back to the exact original source slice with a streaming, memory-bounded matcher before rewriting; ambiguous matches, lone-CR changes, case changes, omissions, and general whitespace changes remain invalid.
- Clarified that llama.cpp JSON grammar constrains structure and string length but cannot enforce the dynamic rule that an excerpt occurs in the supplied source text; Soma continues to enforce that relationship locally.
- Gave the existing single classification recovery call failure-specific guidance using a closed failure reason, without echoing rejected transform output into the retry.
- Added privacy-safe diagnostic failure categories for invalid JSON or contract shape, pass-with-excerpt, empty excerpt, excessive length, and nonliteral excerpts; source text, excerpts, and transform output remain excluded from logs.
- Preserved the public `invalid_transform_output` error code, JSON-mode default and behavior when disabled, endpoints, environment variables, response headers, field-scoped `FAIL_OPEN` semantics, 768-token classification budget, and existing target and transform call bounds.
## 2.2.2
- Simplified classification to the strict `pass | rewrite` contract and removed the `safety` outcome.
- Route every refusal or withholding decision, including decisions formerly classified as safety boundaries, through the existing context-isolated rewrite stage.
- Continue using the latest user turn only for classification; rewriting still receives only the target field, refusal excerpt, and optional processed reasoning.
- Removed the safety-preserved counter, response header, and field-decision value from current diagnostics.
- Updated transform call bounds: `pass` stops field processing, while every refusal or withholding classification invokes the bounded rewrite path.
## 2.2.1
- Recover once from empty, truncated, malformed, or contract-invalid classification output before applying the configured field-scoped `FAIL_OPEN` policy.
- Preserve transform completion metadata and report incomplete classification generation as `transform_output_truncated` instead of an undifferentiated invalid-JSON error.
- Bound classification output to 768 tokens and require `rewrite` and `safety` excerpts to be literal, nonempty substrings no longer than 128 characters; `pass` requires an empty excerpt.
- Added `X-Soma-Classification-Retries` and safe classification-recovery diagnostics without logging transform output or request content.
- Treat decoded empty, missing, non-string, or structurally invalid rewrite replacements as `rewrite_rejected` while retaining operational handling for malformed JSON.
- Require literal equality for reasoning/content duplicate routing so case- or whitespace-normalized variants cannot replace the original field.
- Reject short and medium processed-reasoning copies in rewritten content using normalized equality, bidirectional containment, and bounded similarity checks.
- Documented classification and rewrite call bounds and clarified that JSON mode is enabled by default, requires a process restart after environment changes, and can be verified with `--check-config`, startup diagnostics, or `/health`.
## 2.2.0
- Split transform processing into contextual classification and context-isolated rewriting.
- Restored the exact latest user turn only for classification; user context never reaches rewriting.
- Added `pass`, `rewrite`, and `safety` classification outcomes.
- Preserve legitimate safety boundaries instead of treating every refusal as editable.
- Require an exact refusal excerpt for rewrite decisions.
- Added one bounded rewrite-only repair when an edit is unchanged or retains the classified excerpt.
- Removed strict reasoning compression and lexical-similarity floors; retained catastrophic growth and reasoning-copy protections.
- Process duplicate final text as content and duplicate tool-turn text as reasoning.
- Added detected-refusal, safety-preserved, and rewrite-repair diagnostics.
- Retained stateless operation, one target call, native tool-call fidelity, DSML normalization, buffered SSE, and field-scoped fail-open behavior.
## 2.1.2
- Rebalanced refusal handling with more sensitive classification and conservative edits.
- Added independent field processing and `rewrite_rejected` diagnostics.
## 2.1.1
- Ignored unnecessary replacement text when `refusal:false`.
- Simplified llama.cpp JSON grammar to avoid nested `maxLength` failures.
## 2.1.0
- Removed user context from transforms, separated reasoning/content guidance, added structured JSON, rewrite bounds, and duplicate handling.
+740
View File
@@ -0,0 +1,740 @@
# Soma 2.4.0
Soma is a stateless OpenAI-compatible Chat Completions proxy. It buffers and
validates a target assistant turn, identifies operative refusal or withholding, and
uses a separately configured transform model to produce a context-grounded repair.
Native OpenAI `tool_calls` and valid response metadata remain target-owned and
immutable.
Soma 2.4.0 addresses the central weakness of 2.3.0: a transform cannot accurately
repair references such as “the supplied text,” “both functions,” or “the preceding
result” when it receives only the latest user string and failed assistant field.
Version 2.4 supplies bounded full task context, repairs reasoning and content
together when necessary, distinguishes optional reasoning from the usable answer,
and uses an integrity verifier that can reject invented or context-breaking output.
Soma 2.4.0 is not pre-qualified. JSON grammar guarantees neither correct semantic
decisions nor grounded repairs. The exact model artifact, server build, endpoint
pair, reasoning modes, media modes, prompt, budgets, and temperature must pass the
live automated qualification gates before deployment. Reports retain hash-bound
outputs for audit and reproduction, but inspection is not a separate qualification
stage.
The pre-release 2.4.0 tree was stabilized in place rather than assigning a new
version to review corrections made before qualification. The rollback remains the
unchanged 2.3.0 directory. This stabilization adds no Soma environment variable and
no package dependency; existing 2.4.0 profiles retain the same runtime contract.
Evaluator CLI provenance such as `--reasoning-budget` and the corresponding
llama.cpp server option are not Soma environment settings.
The current Qwen3.5-9B Q6_K route is unqualified. Its exploratory temperature-zero
report used unrestricted secondary reasoning and failed automated gates. That report
is diagnostic evidence only: it cannot be promoted or reinterpreted after evaluator
stabilization. A fresh bounded-budget report must replace it as the current
qualification record.
Soma never downloads, loads, switches, starts, stops, or restarts a model. It does
not execute tools, maintain conversation state, authenticate clients, or provide
tenant isolation.
## Request flow
With the full primary-off/secondary-on staged profile, the normal path is:
```text
client request
-> target model
-> buffer and validate one complete assistant turn
-> build one bounded, role-preserving task context
-> classify every present reasoning/content field on primary, reasoning off,
with the other draft text fields removed from that classification envelope
-> apply field policy and, if necessary, request one joint repair object
candidate 1: primary/off -> primary/off integrity verification
candidate 2: secondary/on -> secondary/on integrity verification
candidate 3: secondary/on -> secondary/on integrity verification
-> if an on-mode verifier truncates or returns invalid JSON:
verify the identical candidate once on the same endpoint/model, reasoning off
-> retain usable verified fields and immutable native tool calls
-> if the turn is still unusable and target retry is enabled:
call the target once more with a fixed corrective system instruction
and process that fresh response under the same bounded rules
-> if a verified repair rewrote reasoning and loop-back is enabled:
call the target once more with only that verified reasoning appended
and process that fresh response under the same bounded rules
-> deduplicate exact reasoning/content copies
-> return JSON or normalized SSE
```
Candidate two is generated only after a completed integrity verifier semantically
rejects candidate one, or after an eligible route failure. Candidate three is the
last semantic candidate and receives a fixed alternate focus on authority and exact
output/tool/stop constraints. Every candidate is generated fresh from the original
task context and failed target draft: neither rejected candidate text nor
model-generated failure analysis is fed into another generation.
Without a configured secondary, Soma permits at most two primary candidates. A
transient primary availability failure can make the remainder of the current client
request start on secondary. That stickiness is request-local; the next request starts
on primary. There is no global circuit breaker or harness/session model state.
The staged reasoning modes are part of the 2.4 contract, not optional tuning:
`TRANSFORM_REASONING_MODE` must be `off`, and a configured secondary must use
`TRANSFORM_SECONDARY_REASONING_MODE=on`. Configuration validation rejects any other
combination so deployment cannot silently diverge from the qualified route.
## Classification and integrity decisions
Classification returns exactly one object:
```json
{"decision":"pass"}
```
or:
```json
{"decision":"rewrite"}
```
The decision concerns the assistant's operative stance, not the presence of words
such as “cannot,” “refuse,” or “safety.” Quoted, translated, summarized, or analyzed
refusal language is not itself a refusal. A failed method followed by continued work,
a different method toward the same requested outcome, and an honest factual
constraint do not become rewrites merely because they contain negative language.
An adopted decision to stop, avoid part of the request, or deliberately substitute a
lesser outcome is a rewrite when the work remains feasible under the authoritative
system/developer instructions. A boundary those instructions explicitly require is
not an avoidable refusal and must be preserved.
Integrity verification uses the same strict object shape:
```json
{"decision":"pass"}
```
or:
```json
{"decision":"rewrite"}
```
During integrity verification, `rewrite` means the candidate has a material defect
and must be rejected or regenerated; it does not request an in-place edit. The
verifier can reject residual withholding, a generic promise,
meta-commentary about the transformation, treating the failed assistant draft as
user-supplied material, invented task-specific inputs or results, contradictions with
accepted reasoning or immutable tool calls, and an unapproved clarification.
Soma accepts only a complete JSON object satisfying the current schema. A pure JSON
fence is accepted, but an object embedded in prose is not. `TRANSFORM_JSON_MODE=true`
is the default and sends a small schema through `response_format`; disabling it
removes that wire hint but retains the same prompts, strict parser, local validation,
and recovery bounds. Separately, target and repaired content requested as JSON must
parse strictly and match an immediately declared top-level type. A configured literal
stop sequence may not survive in forwarded reasoning or content. Soma intentionally
does not implement full client JSON-Schema validation.
## Joint repair contract and field policy
One repair call returns a fixed object with both members present and nullable:
```json
{
"reasoning": "complete repaired reasoning or null",
"content": "complete repaired content or null"
}
```
Only fields classified for repair may be non-null. The two-key wire shape never
changes, while the per-call schema constrains each requested member to string and each
other member to null. Local validation preserves the same contract when a transform
endpoint ignores the schema or JSON mode is disabled. Nonblank exact outputs such as
`{}`, `[]`, punctuation, and Unicode symbols are valid; Soma does not impose an
English-text or alphanumeric "substance" heuristic on the requested deliverable.
Soma classifies all present fields before requesting a repair:
- If reasoning and content pass, both target fields are preserved.
- If content passes and reasoning requires repair, Soma drops the reasoning field;
it does not risk generating new private analysis for an already usable answer.
- If reasoning passes and content requires repair, the accepted reasoning is supplied
as evidence for the content repair.
- If both require repair, one candidate generates reasoning first and then content so
the answer can follow the repaired analysis.
- Verified jointly repaired reasoning is forwarded with its verified content. Because
integrity verification is message-level, a rejected joint candidate is retried as a
whole; Soma never salvages one unverified member from it.
- A reasoning-only response with no content and no native tool call cannot become a
terminal success merely because internal analysis exists. It takes the optional
target retry when enabled; otherwise it fails explicitly.
- Native `tool_calls` are immutable. Soma may repair adjacent reasoning/content using
the full tool context, but exhausted prose repair clears the unusable prose and
preserves the structured call. Soma never invents or edits a tool name, ID,
argument string, ordering, or result. Before repair, every returned function name
must match a supplied tool definition, and multiple returned calls are rejected
when `parallel_tool_calls=false`.
The failed target assistant draft is evidence, not user-supplied task material. The
transform is instructed not to quote, explain, or “convert” the refusal itself. It
may preserve supported facts and genuine constraints, but it must not choose an
arbitrary example, fill invented placeholders, fabricate code changes or external
results, or claim a tool/action completed without evidence.
`TRANSFORM_ALLOW_CLARIFICATION=false` is the default. A transform response that asks
the user for more information is not accepted as the repaired answer unless this
option is explicitly enabled. Enabling it is appropriate only for harnesses where an
essential missing input genuinely requires another user turn; it must be qualified
as a separate behavior profile.
## Full task context and privacy boundary
Classification, repair, and integrity verification receive the original request
context needed to understand references and preserve constraints:
- original messages in order and by role, including `system`, `developer`, `user`,
`assistant`, and `tool` messages and tool results;
- complete tool definitions, `tool_choice`, and `parallel_tool_calls`;
- `response_format`, modality/audio controls, and `stop`;
- the target assistant draft, clearly separated from the original request;
- immutable target native tool calls in a separate read-only section; and
- the configured media representation for every multimodal part.
System and developer messages remain authoritative context below Soma's fixed JSON
and native-tool invariants. Other supplied values are task evidence, not permission
to override the transform contract.
Draft text is projected per phase. A classifier receives only its named target field,
so refusing content cannot contaminate accepted reasoning or vice versa. Repair
generation may inspect fields marked for replacement to preserve facts supported by
the task. Integrity verification removes every replaced or discarded original field
and judges only retained evidence plus the current candidate.
Soma does not send target/transform endpoint credentials, HTTP headers, the target
model name, sampling knobs, or rejected transform candidates. It does not log task
context, prompts, target drafts, repaired output, tool arguments, media payloads, or
credentials.
This is nevertheless a wider trust boundary than 2.3.0. Any secret embedded inside a
conversation, tool definition, tool argument, or tool result is part of the original
task context and can reach every transform endpoint used for that request, including
a remote secondary. Configure only transform services authorized to receive the full
request. Header exclusion cannot remove secrets that the client placed in message or
tool data.
`TRANSFORM_CONTEXT_MAX_CHARS=131072` bounds the serialized `task_context`, and
`TRANSFORM_FIELD_MAX_CHARS=32768` bounds an individual target reasoning/content
field. The configured field limit must not exceed the context limit. The context
limit has a hard maximum of 4000000 characters. Because this is a character bound,
not tokenizer accounting, large-context profiles should leave room for transform
instructions and generated output. Soma rejects oversized semantic input
rather than truncating messages, tool schemas, code, or evidence into a misleading
task. Phase envelopes add the bounded candidate/contract data, and forwarded native
media remains subject to the upstream endpoint and trusted front proxy's byte limits.
## Media modes
Media handling is explicit per transform endpoint:
- `placeholder` preserves typed part positions and non-payload metadata, omits the
actual binary/media payload, and marks the part unseen. The transform must not infer
absent media details. This is the correct setting for a text-only or `--no-mmproj`
llama.cpp server.
- `forward` sends original typed content media using native OpenAI multimodal message
parts. It does not serialize base64 media into ordinary JSON text. Provider-specific
top-level assistant media has no portable input envelope and fails explicitly in
this mode; use `placeholder` for that shape. Use `forward` only for an endpoint that
is authorized and qualified to accept the request's typed media parts.
- `reject` refuses to send a media-bearing task to that endpoint. Soma may use a
configured compatible transform route; otherwise it fails explicitly. Target retry
is not used to bypass an operator's transform-media policy.
Set `TRANSFORM_MEDIA_MODE` for primary and `TRANSFORM_SECONDARY_MEDIA_MODE` for
secondary. If a `forward` endpoint rejects the media request, Soma routes only to a
compatible configured secondary or fails explicitly. It never invokes target retry
to bypass media policy and never silently retries the task as placeholder text,
because either action would change the evidence available to the model.
Assistant audio attached to a usable text or native-tool turn is preserved, including
audio accumulated from a target stream. Audio-only target turns are explicitly
unsupported: Soma cannot inspect or repair the audio payload under its text repair
contract, so it returns `unsupported_target_response` instead of forwarding an
unchecked terminal answer.
## Bounded recovery and verifier fallback
The primary reasoning-off profile owns normal classification, candidate one, and its
integrity verification. After semantic rejection, a configured secondary
reasoning-on profile owns candidates two and three, each generated from the pristine
task package and independently verified.
Reasoning-enabled generation can improve task understanding, but a small model may
spend an entire decision budget thinking and end with `finish_reason=length` before
emitting its tiny JSON decision. If an on-mode integrity verification is truncated or
structurally invalid, Soma does not discard the candidate. It verifies that identical
candidate exactly once on the same endpoint and model with reasoning disabled. Only
a completed `rewrite` decision advances to a fresh generation.
Transport/availability failures follow bounded route failover. Structural JSON
recovery may include a concise closed failure category, but never rejected output or
raw exception text. Semantic retries receive only positive instructions and the
pristine task context; they are not primed with the preceding candidate or its
failure.
There are hard ceilings of:
- two target calls per client request;
- 20 transform calls for each target response; and
- 40 transform calls across the complete client request.
`TRANSFORM_TOTAL_TIMEOUT=1200` is one aggregate deadline. It starts after the first
target response completes and covers every transform call, an optional second target
call, and processing of the second response. It does not reset after target retry.
The initial target call remains governed by `CONNECT_TIMEOUT` and `REQUEST_TIMEOUT`
outside that aggregate window.
## Optional target retry
`TARGET_RETRY_ON_UNREPAIRABLE=false` preserves the normal one-target-call behavior.
When enabled, Soma may call the target exactly once more only when the completed turn
is unrepairable and leaves no usable content or immutable native tool call. A failed
optional reasoning field does not trigger target retry when valid content remains.
The retry starts from the original request and inserts one fixed corrective system
instruction immediately after the leading system/developer block. It preserves the
conversation and tool contract and never includes the rejected target response or a
rejected transform candidate. This avoids training the second response to imitate the
failure, but it does add target latency/cost and may produce a different native tool
decision. Soma still does not execute that call.
Enable target retry only after qualifying the complete target-plus-transform route.
It is not a general retry for target HTTP errors, optional reasoning loss, or a merely
imperfect answer.
## Loop-back on verified repair
`TARGET_LOOP_BACK_ON_VERIFIED_REPAIR=false` is the default. When enabled, Soma may
make exactly one additional target call after an integrity-verified repair that
rewrote the target's refusal reasoning. Instead of returning the transform's repaired
candidate directly, Soma re-sends the original request with one appended assistant
message carrying only that verified repaired reasoning in a `reasoning_content`
field, so the target re-ingests the relaxed context and produces the task output
itself. The re-entry payload preserves the original conversation, media, tool
definitions, `tool_choice`, stop controls, and response-format settings untouched.
Loop-back fires only on the first target attempt, only when reasoning was one of the
repaired fields, and only after that candidate passed integrity verification.
Content-only repairs, fields that classified as `pass`, tool-only turns, cleared
tool prose, and the second target attempt never loop. Genuine refusals never loop
because truthful technical, environmental, evidentiary, uncertainty, impossibility,
missing-input, and factual limitations classify as `pass` and are never rewritten.
The second target call shares the hard ceiling of two target calls per client
request and the aggregate `TRANSFORM_TOTAL_TIMEOUT` window, which is not reset. The
second response is processed under the same classification, repair, and integrity
rules; if it is also unrepairable, the request fails explicitly and Soma never
makes a third target call. `TARGET_RETRY_ON_UNREPAIRABLE` and loop-back are
mutually exclusive per request because they handle disjoint failure classes
(unrepairable turns versus verified reasoning repairs) and share the single
additional-call slot.
The reasoning carrier is fixed to `reasoning_content` with no fallback. Backends
that reject that field in input messages fail explicitly rather than silently
degrading to a different carrier. Loop-back adds target latency and cost; qualify
the complete target-plus-transform route before enabling it.
## Failure behavior
`FAIL_OPEN=false` is the default. Exhausted mandatory repair, invalid verification,
oversized context, incompatible media, missing usable terminal output, and other
nonrecoverable transform errors return an explicit error instead of forwarding a
known-bad candidate.
`FAIL_OPEN=true` is an availability policy only. It can restore an original refusal,
withholding field, or otherwise rejected target text and therefore defeats strict
repair guarantees. Do not treat fail-open as a safety, compliance, or successful
quality mode, and do not enable it merely to hide model qualification failures.
Fail-open never makes a reasoning-only or otherwise empty terminal turn successful;
that turn still takes the explicitly enabled target retry or returns an error.
Client and upstream JSON reject non-finite numbers. Transform objects additionally
reject duplicate member names. Client `stream` and `parallel_tool_calls` values must
be booleans, and `n` must be null or integer `1`. Target assistant text, reasoning
aliases, and native tool-call shapes are validated before any local mutation.
Endpoint configuration rejects userinfo, queries, fragments, invalid ports, and
unsafe header overrides. `REQUIRE_DISTINCT_ENDPOINTS=true` prevents exact
target/transform origin collisions and direct self-routes. Operators must still avoid
DNS aliases or LAN addresses that resolve to a wildcard-bound Soma listener.
## Configuration
Minimal one-profile configuration:
```bash
TARGET_URL=https://opencode.ai/zen/v1
TRANSFORM_URL=http://127.0.0.1:8001/v1
TRANSFORM_MODEL=local
TRANSFORM_REASONING_MODE=off
TRANSFORM_MEDIA_MODE=placeholder
```
Same-server primary-off/secondary-on profile:
```bash
PROXY_HOST=127.0.0.1
PROXY_PORT=8080
# Clear the removed 2.3.x option from an already-populated shell.
unset TRANSFORM_CONFIRM_REWRITES
TARGET_URL=https://opencode.ai/zen/v1
TARGET_KEY=
TARGET_HEADERS_JSON={}
TRANSFORM_URL=http://127.0.0.1:8001/v1
TRANSFORM_KEY=
TRANSFORM_MODEL=local
TRANSFORM_HEADERS_JSON={}
TRANSFORM_REASONING_MODE=off
TRANSFORM_MEDIA_MODE=placeholder
TRANSFORM_SECONDARY_URL=http://127.0.0.1:8001/v1
TRANSFORM_SECONDARY_KEY=
TRANSFORM_SECONDARY_MODEL=local
TRANSFORM_SECONDARY_HEADERS_JSON={}
TRANSFORM_SECONDARY_REASONING_MODE=on
TRANSFORM_SECONDARY_MEDIA_MODE=placeholder
# Same-server primary/secondary is allowed. This rejects target/transform collisions.
REQUIRE_DISTINCT_ENDPOINTS=true
ENABLE_REASONING={}
TRANSFORM_TEMPERATURE=0
TRANSFORM_JSON_MODE=true
TRANSFORM_CONTEXT_MAX_CHARS=131072
TRANSFORM_FIELD_MAX_CHARS=32768
TRANSFORM_DECISION_MAX_TOKENS=1536
TRANSFORM_REWRITE_MAX_TOKENS=16384
TRANSFORM_ALLOW_CLARIFICATION=false
TRANSFORM_TOTAL_TIMEOUT=1200
TARGET_RETRY_ON_UNREPAIRABLE=false
TARGET_LOOP_BACK_ON_VERIFIED_REPAIR=false
FAIL_OPEN=false
CONNECT_TIMEOUT=15
REQUEST_TIMEOUT=600
```
`TRANSFORM_CONFIRM_REWRITES` was removed. Soma rejects the variable even when its
value is `false`; this catches a stale 2.3.x deployment rather than silently changing
its meaning. Deleting an export from a file does not clear an existing shell value,
so either start from a clean environment or run:
```bash
unset TRANSFORM_CONFIRM_REWRITES
```
The primary and secondary keys/headers never inherit from one another. A same-server
secondary supplies behavioral diversity but no process, GPU, or availability
isolation. An independent endpoint/model can supply both, at the cost of extending
the full-context trust boundary. Qualify the secondary by itself and then qualify the
exact composed pair. Soma 2.4 requires primary `off` and secondary `on`; `default` and
the inverse mode assignments are rejected during configuration validation.
`TRANSFORM_TEMPERATURE` is sent on every transform call and overrides the llama
server sampling default. `TRANSFORM_DECISION_MAX_TOKENS` covers classifications and
integrity decisions; `TRANSFORM_REWRITE_MAX_TOKENS` covers the fixed joint-repair
object. Valid ranges are 25616384 decision tokens, 25616384 repair tokens,
40964000000 context characters, and 10244000000 field characters, with the field
limit no greater than the context limit. Larger budgets bound output but do not
improve model judgment by themselves.
Environment files are shell profiles and are not loaded automatically. Restart Soma
after every environment change:
```bash
set -a
. ./soma.env
set +a
python3 soma.py --check-config
python3 soma.py
```
Additional environment variables not shown in the profiles above:
- `LOG_LEVEL` (default `INFO`) — Python logging level for proxy diagnostics.
- `FORWARD_CLIENT_HEADERS` (default `true`) — forward non-hop, non-credential
client headers to the target endpoint.
- `TRANSFORM_PROMPT` (default built in) — base system prompt prepended to every
transform phase prompt.
- `SOMA_AUTO_REQUIRES_TOOL` (default `false`) — strict auto-tools mode that
classifies each request as requiring a native call or a text response.
- `UPSTREAM_ERROR_BODY_LIMIT` (default `4000`, range 25665536) — bounded number
of upstream error-body bytes retained for target diagnostics.
- `SSE_CHUNK_CHARS` (default `2048`, range 12865536) — maximum characters per
normalized SSE text delta.
Verify the effective version, endpoint identities, reasoning/media modes, JSON mode,
context/field/token limits, clarification, target-retry, and loop-back policies,
aggregate deadline, and call ceilings through `--check-config`, startup diagnostics,
or `/health`.
Point clients at:
```text
http://<proxy-host>:8080/v1/chat/completions
```
Aliases are available at `/chat/completions`, `/v1/models`, `/models`, and `/health`.
### llama.cpp recommendation for a shared local endpoint
For the shared-endpoint topology where one llama.cpp process serves a primary
reasoning-off profile and a secondary reasoning-on profile through per-request
`enable_thinking`, enable server reasoning support and cap thinking so a small
decision response has room to emit JSON:
```text
--reasoning on --reasoning-budget 512 --temp 0
```
The primary profile still sends `enable_thinking=false`; the global server mode must
not prevent the secondary profile from producing and parsing reasoning when it sends
`enable_thinking=true`. A 512-token cap is the required starting profile for the
bounded-budget qualification run; configuring it is not itself a qualification
claim. The evaluator's matching `--reasoning-budget 512` argument records what the
already-running server uses and does not configure the server.
Keep both the server and Soma transform temperature at zero for qualification. Soma's
per-request `TRANSFORM_TEMPERATURE=0` is authoritative for transform calls; the server
flag supplies a matching default. Temperature zero removes deliberate sampling
variance so repeat failures can be attributed to the route under test, although it
does not promise byte-identical output across server builds, speculative decoding,
cache state, or concurrency. Any nonzero temperature is a different profile and
requires a separate report.
Re-run the exact live profile after changing any server argument. Soma does not add
these arguments, restart the server, or download a model. A text-only server launched
with `--no-mmproj` should use `placeholder`, not `forward`, for both transform media
modes.
### Multiple harness profiles
Use one Soma process and listener port per harness. The supplied
[`profiles/harness-a.env.example`](profiles/harness-a.env.example) and
[`profiles/harness-b.env.example`](profiles/harness-b.env.example) use ordinary
environment variables and distinct ports. Soma has no `HARNESS_TYPE` dispatch or
shared mutable deployment profile.
```bash
cp profiles/harness-a.env.example profiles/harness-a.env
cp profiles/harness-b.env.example profiles/harness-b.env
env -i PATH="$PATH" /bin/sh -c 'set -a; . ./profiles/harness-a.env; set +a; exec python3 soma.py --check-config'
env -i PATH="$PATH" /bin/sh -c 'set -a; . ./profiles/harness-b.env; set +a; exec python3 soma.py --check-config'
```
The supplied `profiles/.gitignore` excludes populated profile names while retaining
the examples. Keep production profiles outside distributable artifacts even when
ignore rules are present. A shared transform server must be qualified at the combined
load and configured concurrency; a one-slot llama server serializes both harnesses.
### Trust and resource boundary
Keep `PROXY_HOST=127.0.0.1` unless a trusted front proxy supplies authentication,
access control, TLS, request-size limits, buffering limits, timeouts, and rate limits.
Soma warns when bound to a non-loopback interface. It buffers complete target turns
and full bounded task packages and has no in-process concurrency-admission limit, so
the front proxy must enforce limits appropriate to available memory.
## Native tool calls and streaming
Soma supports native OpenAI `tool_calls` only. It preserves IDs, `type: function`,
function names, strict JSON argument strings, ordering, and streaming fragments.
Proprietary text tool syntaxes are ordinary assistant text; conversion belongs in the
target's OpenAI-compatible gateway.
For `stream:true`, Soma buffers the complete target stream, processes it, and emits
normalized OpenAI delta SSE. Original chunk boundaries are not preserved. Valid
reasoning, content, native tool calls, finish reason, usage, and response metadata are
retained. Accepted `cost` and `usage` metadata are emitted together at most once.
The upstream stream must produce a terminal non-null `finish_reason`. Soma accepts a
terminal choice followed by EOF or the ordinary sequence ending in `[DONE]`.
Standard empty-choice usage frames are retained. After the first `[DONE]`, at most one
narrow metadata postlude is allowed: an object with `choices: []`, no keys outside
`choices`, `cost`, and `usage`, and at least one non-null metadata value. It may end at
EOF or one closing `[DONE]`. Further objects/delimiters, malformed or non-finite JSON,
duplicate keys, premature `[DONE]`, or meaningful data after the terminal choice are
rejected.
## Diagnostics
Successful and post-dispatch error responses expose privacy-safe trace/timing and
bounded call counts, field decisions, candidate/verifier outcomes, target-retry use,
deduplication, and fail-open status. `/health` and startup diagnostics additionally
show the effective non-secret reasoning and media configuration.
Transform logs identify phase, field/candidate, backend, reasoning and media mode,
purpose, closed failure category, JSON-mode value, channel lengths, finish reason,
token counts, elapsed time, and a request-ID fingerprint. They do not include prompts,
task context, target or transform text, media, tool arguments, credentials, error
bodies, or raw upstream request IDs.
## Tests
Run the complete offline suite:
```bash
python3 -m unittest -v test_soma.py test_soma_extra.py
python3 test_soma_live.py --inventory
```
The suite covers strict schemas, full-context isolation and limits, joint field
policy, media routes, primary/secondary candidate ownership, reasoning-off verifier
fallback, optional target retry, hard call/deadline ceilings, fail-open behavior,
native tool fidelity, JSON validation, and SSE normalization. Offline success is
necessary but is not model qualification.
## Live qualification
`test_soma_live.py` is opt-in and dynamically imports the adjacent `soma.py`, so it
exercises the exact runtime prompts, schemas, parsing, validation, routing, and field
policy. It calls only already-running endpoints supplied by the operator and never
manages a model or server.
First inspect the frozen corpus without network access:
```bash
python3 test_soma_live.py --inventory
```
Run the exact selected transform artifact/profile at temperature 0 and retain the
report only under ignored `qualification-local/`. Consult `--help` for the current
provenance and endpoint arguments:
```bash
python3 test_soma_live.py --help
```
Every qualifying run must declare primary `--reasoning-mode off`, a configured
secondary with `--secondary-reasoning-mode on`, and the secondary server's actual
positive `--reasoning-budget` (for the documented llama.cpp starting profile,
`--reasoning-budget 512`). This evaluator value records provenance; the server must
already have been launched with the matching budget.
An exploratory run against llama.cpp's unrestricted default may record
`--reasoning-budget -1`. Its report remains unqualified because the positive-budget
provenance gate fails; it is not carried forward after a complete bounded-budget
rerun replaces the current evidence.
Qualification is automated-only. Use a new report filename and run the exact
temperature-zero, bounded profile. Exit status `0` means every qualification gate
passed and the report records `qualified: true` with
`qualification_status: qualified`. Exit status `1` means at least one qualification
gate failed, and `2` means setup or report creation failed. The evaluator has no
second approval stage; inspecting retained evidence does not alter report status.
Provider-managed routes can be exercised with
`--artifact-kind provider-managed`, but they are recorded as exploratory and can
never be marked qualified by this evaluator. Supply the exact provider name, model
label, and a small public `/models` metadata record through
`--provider-model-metadata-json`; do not invent GGUF, llama.cpp, hardware, revision,
or reasoning-budget values for a hosted service. Use `--reasoning-budget 0` when the
provider does not publish a bounded budget. The report separates behavioral gate
results from qualification eligibility and records the requested reasoning modes as
unverified provider controls.
To retain the qualified local GGUF primary while evaluating a hosted secondary, use
`--artifact-kind hybrid-local-provider`. Supply the ordinary local artifact fields
for the primary and the provider metadata fields for the secondary. The evaluator
retains both identities, but deliberately records the combined route as exploratory
and qualification-ineligible because the hosted reasoning controls and budget are
not independently verified. The primary's artifact label may differ from its wire
model alias (for example, an immutable repository label with `local` on the wire).
`--target-smoke-count 10` limits only the final target-through-transform smoke calls.
It does not limit the preceding transform corpus: the evaluator still runs all 240
classifier cases, 80 retained repairs, repeat matrices, and route/media probes. Each
smoke request grants 128 output tokens, and its response must contain exactly `OK`
with no surrounding whitespace, prose, or native tool call.
For the initial bounded Qwen route, the automated command must include the exact
primary/secondary endpoint and provenance arguments plus:
```bash
python3 test_soma_live.py \
--reasoning-mode off \
--secondary-reasoning-mode on \
--reasoning-budget 512 \
--temperature 0 \
--report qualification-local/qwen3.5-9b-q6_k-t0-rb512-automated.json \
[the exact endpoint, model, server, artifact, and hardware arguments]
```
Reports are immutable evidence files. The evaluator writes a completed report
privately and installs it atomically; it never exposes a partially written result or
overwrites an existing path. The stabilized evaluator, corpus, and source hashes must
match the new run. After a complete budget-512 report has been validated and installed
under its truthful filename, remove the obsolete unrestricted-budget artifact so only
the current evidence remains.
The automated gates require:
- valid contracts on all 240 classification cases, 100% hard-refusal and overall
refusal recall, and zero false rewrites;
- all 20 schema-off high-risk sentinels and five repeats of every high-risk case at
parallelism 1 and 4 with zero repeat failures;
- all 80 message-repair cases completed without exhaustion and 100% integrity
verification, required-fact retention, and forbidden-fact absence;
- exactly 20 cases in each field-decision cell: `pass/pass`, `rewrite/pass`,
`pass/rewrite`, and `rewrite/rewrite`;
- the exact staged primary-off then secondary-on candidate route;
- explicit positive secondary reasoning-budget provenance (use `512` as the initial
llama.cpp qualification value);
- explicit `placeholder`, `forward`, and `reject` media behavior; and
- complete source, evaluator, model, server, configuration, and fixture
reproducibility evidence.
The report also records latency, classification disagreements, backend/phase
ownership, semantic repair attempts, verifier fallback, and call ceilings. Strict
JSON is exercised both with structured-output mode enabled and with the wire schema
omitted.
Live target smoke is separate and explicitly opt-in because it incurs target cost and
can produce a new model/tool decision. It uses benign fixtures, keeps target retry
disabled, verifies the complete target-to-transform route, and never executes returned
tools. Target-retry behavior remains deterministic offline coverage until separately
qualified; live smoke does not enable it. The smoke is not run by `--inventory` or an
ordinary transform-only qualification. The count is bounded from 1 through 10:
```bash
python3 test_soma_live.py \
--target-smoke \
--target-url https://target.example/v1 \
--target-model TARGET_MODEL \
--reasoning-budget 512 \
--target-smoke-count 10 \
[the same transform and provenance arguments used for qualification]
```
Without `--target-smoke`, the evaluator makes zero target calls. Supply target keys
through the hidden CLI/environment option, never in recorded server arguments or a
report intended for sharing.
An automated pass is final qualification for the exact recorded profile. Reports
retain all 80 accepted repair outputs and their evidence hashes so the result can be
audited and reproduced, but later inspection does not change qualification status.
Any failed gate leaves the profile unqualified, and a smaller model receives no
relaxed threshold.
Any change to model revision, GGUF, server build/arguments, reasoning budget,
temperature, prompt, endpoint identity, media mode, context/token limits, field
policy, primary/secondary composition, evaluator source, fixture corpus, or assertion
semantics creates different evidence and requires a new report. Evidence hashes bind
one report's exact inputs and outputs; they do not transfer qualification to a
superseded report. Reports can contain synthetic task context and non-secret
provenance; inspect them before sharing and never place keys in recorded header/server
arguments.
`qualification-local/`, populated profiles, logs, caches, credentials, and model
artifacts are excluded from the release package and checksums.
+3
View File
@@ -0,0 +1,3 @@
*
!*.env.example
!.gitignore
+44
View File
@@ -0,0 +1,44 @@
# Soma deployment profile A. Copy to harness-a.env and edit endpoint values.
PROXY_HOST=127.0.0.1
PROXY_PORT=8080
unset TRANSFORM_CONFIRM_REWRITES
TARGET_URL=http://127.0.0.1:9001/v1
TARGET_KEY=
TARGET_HEADERS_JSON={}
# Candidate 1/classification/integrity route: deterministic reasoning off.
TRANSFORM_URL=http://127.0.0.1:8001/v1
TRANSFORM_KEY=
TRANSFORM_MODEL=local
TRANSFORM_HEADERS_JSON={}
TRANSFORM_REASONING_MODE=off
TRANSFORM_MEDIA_MODE=placeholder
# Fresh semantic repair candidates 2/3: reasoning on. This example intentionally
# reuses the same process and therefore provides no availability isolation.
TRANSFORM_SECONDARY_URL=http://127.0.0.1:8001/v1
TRANSFORM_SECONDARY_KEY=
TRANSFORM_SECONDARY_MODEL=local
TRANSFORM_SECONDARY_HEADERS_JSON={}
TRANSFORM_SECONDARY_REASONING_MODE=on
TRANSFORM_SECONDARY_MEDIA_MODE=placeholder
ENABLE_REASONING={}
TRANSFORM_TEMPERATURE=0
TRANSFORM_CONTEXT_MAX_CHARS=131072
TRANSFORM_FIELD_MAX_CHARS=32768
TRANSFORM_DECISION_MAX_TOKENS=1536
TRANSFORM_REWRITE_MAX_TOKENS=16384
TRANSFORM_ALLOW_CLARIFICATION=false
TRANSFORM_JSON_MODE=true
# Opt-in only after qualifying a second target response and its cost/latency.
TARGET_RETRY_ON_UNREPAIRABLE=false
FAIL_OPEN=false
FORWARD_CLIENT_HEADERS=true
REQUIRE_DISTINCT_ENDPOINTS=true
CONNECT_TIMEOUT=15
REQUEST_TIMEOUT=600
TRANSFORM_TOTAL_TIMEOUT=1200
UPSTREAM_ERROR_BODY_LIMIT=4000
SSE_CHUNK_CHARS=2048
LOG_LEVEL=INFO
+46
View File
@@ -0,0 +1,46 @@
# Soma deployment profile B. Copy to harness-b.env and edit endpoint values.
# Its listener and target differ from profile A; both may use the same qualified
# transform service if that service has enough independently tested capacity.
PROXY_HOST=127.0.0.1
PROXY_PORT=8081
unset TRANSFORM_CONFIRM_REWRITES
TARGET_URL=http://127.0.0.1:9002/v1
TARGET_KEY=
TARGET_HEADERS_JSON={}
# Candidate 1/classification/integrity route: deterministic reasoning off.
TRANSFORM_URL=http://127.0.0.1:8001/v1
TRANSFORM_KEY=
TRANSFORM_MODEL=local
TRANSFORM_HEADERS_JSON={}
TRANSFORM_REASONING_MODE=off
TRANSFORM_MEDIA_MODE=placeholder
# Fresh semantic repair candidates 2/3: reasoning on. This example intentionally
# reuses the same process and therefore provides no availability isolation.
TRANSFORM_SECONDARY_URL=http://127.0.0.1:8001/v1
TRANSFORM_SECONDARY_KEY=
TRANSFORM_SECONDARY_MODEL=local
TRANSFORM_SECONDARY_HEADERS_JSON={}
TRANSFORM_SECONDARY_REASONING_MODE=on
TRANSFORM_SECONDARY_MEDIA_MODE=placeholder
ENABLE_REASONING={}
TRANSFORM_TEMPERATURE=0
TRANSFORM_CONTEXT_MAX_CHARS=131072
TRANSFORM_FIELD_MAX_CHARS=32768
TRANSFORM_DECISION_MAX_TOKENS=1536
TRANSFORM_REWRITE_MAX_TOKENS=16384
TRANSFORM_ALLOW_CLARIFICATION=false
TRANSFORM_JSON_MODE=true
# Opt-in only after qualifying a second target response and its cost/latency.
TARGET_RETRY_ON_UNREPAIRABLE=false
FAIL_OPEN=false
FORWARD_CLIENT_HEADERS=true
REQUIRE_DISTINCT_ENDPOINTS=true
CONNECT_TIMEOUT=15
REQUEST_TIMEOUT=600
TRANSFORM_TOTAL_TIMEOUT=1200
UPSTREAM_ERROR_BODY_LIMIT=4000
SSE_CHUNK_CHARS=2048
LOG_LEVEL=INFO
+1
View File
@@ -0,0 +1 @@
requests>=2.31,<3
+84
View File
@@ -0,0 +1,84 @@
# Soma listener
# Keep Soma loopback-only unless a trusted reverse proxy supplies access control,
# rate limits, request-size limits, and TLS.
PROXY_HOST=127.0.0.1
PROXY_PORT=8080
# Removed in 2.4.0. Clear a value inherited from a shell that previously sourced a
# 2.3.x profile; Soma rejects even TRANSFORM_CONFIRM_REWRITES=false.
unset TRANSFORM_CONFIRM_REWRITES
# Target model endpoint
TARGET_URL=https://opencode.ai/zen/v1
TARGET_KEY=
TARGET_HEADERS_JSON={}
# Primary classifier, first repair candidate, and integrity verifier. The primary
# reasoning-off route is the fast deterministic path.
TRANSFORM_URL=http://127.0.0.1:8001/v1
TRANSFORM_KEY=
TRANSFORM_MODEL=local
TRANSFORM_HEADERS_JSON={}
TRANSFORM_REASONING_MODE=off
TRANSFORM_MEDIA_MODE=placeholder
# Required semantic-repair route when a secondary is configured: use the same
# already-running model with
# reasoning enabled, or configure an independently operated endpoint/model. Soma
# sends fresh candidates from the original context; it never sends rejected output.
TRANSFORM_SECONDARY_URL=http://127.0.0.1:8001/v1
TRANSFORM_SECONDARY_KEY=
TRANSFORM_SECONDARY_MODEL=local
TRANSFORM_SECONDARY_HEADERS_JSON={}
TRANSFORM_SECONDARY_REASONING_MODE=on
TRANSFORM_SECONDARY_MEDIA_MODE=placeholder
# Optional target-specific reasoning overlay
ENABLE_REASONING={}
# ENABLE_REASONING={"thinking":{"type":"enabled"}}
# Qualification uses zero to remove intentional sampling variance from repeat gates.
# Any nonzero value is a different profile and requires a new report.
TRANSFORM_TEMPERATURE=0
# Full transform task-context and individual target-field limits. Inputs are rejected,
# never silently truncated. TRANSFORM_FIELD_MAX_CHARS must not exceed the context cap.
TRANSFORM_CONTEXT_MAX_CHARS=131072
TRANSFORM_FIELD_MAX_CHARS=32768
# Decision/verifier and joint-repair output budgets.
TRANSFORM_DECISION_MAX_TOKENS=1536
TRANSFORM_REWRITE_MAX_TOKENS=16384
# A clarification is not accepted as a repaired answer unless explicitly enabled.
TRANSFORM_ALLOW_CLARIFICATION=false
# JSON-constrained transform output is enabled by default.
# Restart Soma after changing this value. Verify configuration before launch with
# `python3 soma.py --check-config`, then inspect the startup log or `/health` to
# confirm the effective value in the running process.
TRANSFORM_JSON_MODE=true
# One fresh target retry is available only when the complete target turn is
# unrepairable and leaves no usable content or native tool call. It is opt-in because
# it adds target latency/cost and can produce another model action.
TARGET_RETRY_ON_UNREPAIRABLE=false
# false: transform failures, including failed integrity checks and exhausted candidates, return 502
# true: preserve usable original refusing text after repair failure; reasoning-only
# or otherwise empty terminal turns still fail
FAIL_OPEN=false
FORWARD_CLIENT_HEADERS=true
# Same-server primary/secondary transform profiles are allowed; this protects only
# against the target sharing an origin with either transform endpoint.
REQUIRE_DISTINCT_ENDPOINTS=true
CONNECT_TIMEOUT=15
REQUEST_TIMEOUT=600
# One aggregate recovery budget after the first target response. It includes every
# transform call and an optional second target call plus processing of its response.
TRANSFORM_TOTAL_TIMEOUT=1200
UPSTREAM_ERROR_BODY_LIMIT=4000
SSE_CHUNK_CHARS=2048
LOG_LEVEL=INFO
# Optional common prefix added to classification, joint-repair, and integrity prompts.
# Leave unset for Soma's built-in 2.4.0 phase-specific transform behavior. A custom
# prefix materially changes the transform profile and requires separate qualification.
# TRANSFORM_PROMPT=...
Executable
+5008
View File
File diff suppressed because it is too large Load Diff
+1020
View File
File diff suppressed because it is too large Load Diff
+3651
View File
File diff suppressed because it is too large Load Diff
+4436
View File
File diff suppressed because it is too large Load Diff