Releases: wtthornton/tapps-brain
Release list
v3.22.4
Closes the last masked-500 path in the experience-event incident: AgentForge posts evidence with no attachment, which the 3.22.3 error metric showed still 500ing (13 real calls in minutes). Strict superset of 3.22.3.
Fixed
- Evidence with no (or both) attachment no longer 500s
POST /v1/experience(TAP-2868).EvidenceSpecdocumented "exactly one ofedge_id/entity_id" but never enforced it, so AgentForge's payload (evidence with neither) passed Pydantic and raisedpsycopg.errors.CheckViolationonchk_evidence_xor_attachmentat the DB insert, sinking the whole event with a masked 500. Thetapps_brain_http_errors_totalcounter from 3.22.2 surfaced it as live traffic immediately after the 3.22.3 deploy.EvidenceSpecnow enforces the XOR via amodel_validator, so malformed evidence is caught at the model layer and skipped-with-warning byrecord_event's resilient coercion (TAP-2866) — the core event records and returns 200 with awarningsentry ofkind: "evidence". The field stays the documented contract; bareEvidenceSpec()now raises, matching the schema's NOT-NULL XOR.
v3.22.0
Stabilisation release on top of the 3.21.0 KG/experience activation. Aligns the KG REST data-plane status codes to the documented contract, hardens the unhandled-error envelope, completes the brain_resolve_entity profile wiring, and closes the CI gap that let integration-test failures land on main. Strict superset of 3.21.0.
Note on the 3.21.0 tag.
v3.21.0was tagged frommainon 2026-06-01 without merging its version-bump PR (#186), so the tagged source still read3.20.1andmainnever received a## [3.21.0]CHANGELOG section. That section is backfilled below for an accurate history; this 3.22.0 release is the first correctly-versioned cut since 3.20.1.
Changed
- KG REST create-style data-plane writes return 200 (TAP-2727 / TAP-2801).
/v1/experienceand/v1/experience:batchreturned a runtime201while the OpenAPI contract and every other data-plane write (/v1/remember,/v1/reinforce,/v1/learn_*) documented and returned200. The handlers now return200, aligning code to the already-published contract (no OpenAPI snapshot change)./v1/kg/resolve_entitystays200(idempotent upsert). brain_resolve_entitywired into thefull+operatorprofiles, integration completed (TAP-2725). Follows the 3.21.0 tool exposure with profile registration, corrected tool counts, the tools snapshot, and a knowledge-graph edge test.
Fixed
- CLI structlog output routed to stderr so
--jsonstdout stays clean (TAP-2803).cli/_common.py'sstructlog.configure()carried a comment claiming output went to stderr but only setwrapper_class, neverlogger_factory— so structlog fell back to its defaultPrintLoggerFactory()(stdout). The ERROR-levelpostgres.privileged_role_audit_overrideaudit record (emitted whenTAPPS_BRAIN_ALLOW_PRIVILEGED_ROLE=1) then corrupted the diagnostics--jsonpayload. Now setslogger_factory=PrintLoggerFactory(file=sys.stderr), reserving stdout for command output. Surfaced by the new integration-test gate below. - Unhandled-error envelope hardened on the HTTP adapter (TAP-2801). Added a catch-all
@app.exception_handler(Exception)returning a structured{"error": "internal_error"}500, consistent with every other error envelope in the adapter, so a dropped psycopg connection (or any unhandled service error) never leaks Postgres internals to the client.
Security
- Triaged 6 bandit medium-and-high security findings (TAP-2760).
Documentation
- Documented the integrity-key environment variables; fixed a stale drift-config note (TAP-2768).
Internal / CI
- Gated the 20 mock-based integration test files in the CI
testjob (TAP-2803). CI previously rantests/unit/only, so anytests/integration/failure landed onmainunblocked — exactly how the TAP-2801 status-code drift shipped. The 20 mock-based files (no live Postgres, ~35 s) are now gated as an explicit list (no directory glob) so a new red test cannot silently ride in. DB-dependent + compat/openapi-snapshot suites stay excluded pending TAP-2803 follow-up. - Suppressed vulture false positives on
_protocols.pyvia the dead-code whitelist (TAP-2765). - Cleared pre-existing lint/format/mypy debt blocking
main(PR #191).
v3.20.1
Hotfix release. The 3.20.0 container (and any 3.19.0 container with a fresh restart) fails ASGI lifespan startup with REST route drift detected — update REST_ROUTE_TO_TOOL or mcp_profiles.yaml so the following routes map to a known tool: '/v1/forget' → 'brain_forget', .... Same code path also caused /v1/tools/list?X-Brain-Profile=full to return only the 8 eager tools instead of the full 63-tool profile surface.
Fixed
- HTTP adapter startup crash + broken per-profile
/v1/tools/listfilter.http_adapter.py:1454built the tools snapshot from_tool_manager.list_tools(), which after TAP-1985 returns only the 8 eager tools. Two downstream consequences: the TAP-1929 REST-route drift check (added in 3.19.0) raisedValueErrorbecause every deferred tool (brain_forget,brain_learn_success,brain_record_event, ...) looked missing → ASGI lifespan failure → container never reached the health check. The per-profile filter at request time also returned at most 8 tools because theby_name_jsonlookup only knew about the eager catalog. Use_unfiltered_list_tools()for theby_nameindex (drift check + per-profile filter); keep the no-headerpayloadbuilt from the filtered view so the daily-driver wire contract stays at 8 tools. Both bugs were latent in 3.19.0 — observable only on fresh container starts.
v3.20.0
The consumer-adoption-surface release — closes TAP-2092 (declared-vs-active visibility) and the four child stories that landed it: TAP-2093 brain_audit_consumers, TAP-2094 recall-quality telemetry, TAP-2095 file-backed-mirror spike, TAP-2099 brain_export. Three new MCP tools, all behind defer_loading: true — the 8-tool eager catalog and the wire contract of every existing endpoint are unchanged. Strict superset of 3.19.0.
Added
brain_audit_consumers(project_id, since)MCP tool (TAP-2093). JoinsAgentRegistrywith the per-(project_id, agent_id, tool, status)call counter (STORY-070.12) so operators can answer "which of my declared agents are actually using the brain?". Returns{declared_silent, active, unregistered_active, as_of, window_effective}—declared_silentflags registered agents with zerobrain_*calls,unregistered_activeflags orphan agent IDs seen in the counter but missing from the registry. Backed bytools/audit_consumers.pyCLI for CI/operator workflows. Registered withdefer_loading: trueinfull+operatorprofiles. Service entrypoint:memory_service.audit_consumers.- Recall-quality telemetry —
top_score,oldest_returned_age_days,recall_quality_metrics(TAP-2094).RecallDiagnosticsgains two new per-call fields (highest composite score in the returned set; age in days of the oldest returned memory; bothNoneon empty recall).RecallOrchestratorwrites one sample per recall into a new bounded in-process ring buffer (tapps_brain.recall_quality_buffer, default 1000 samples per project, FIFO eviction, thread-safe) on the way out — no extra Postgres I/O on the hot path. Newrecall_quality_metrics(window_seconds, project_id)MCP tool aggregates the window:{p50_top_score, p95_top_score, p50_oldest_age_days, p95_oldest_age_days, empty_recall_rate, sample_count, window_seconds, project_id, as_of}with linear-interpolated percentiles (deterministic for n<2). Registered withdefer_loading: trueinfull+operatorprofiles. Cross-restart persistence is intentionally out of scope. brain_export(output_dir, layout, redact, top_n_per_tier)MCP tool — one-shot Managed Agents-layout exporter (TAP-2099, follows the TAP-2095 spike recommendation). Snapshots the top-N memories per tier (ranked bymax(confidence, recency_score)) into<output_dir>/manifest.json+<output_dir>/<tier>/<key>.mdper Anthropic's/mnt/memory/<store>/filesystem shape. Each.mdfile carries aREAD-ONLYbanner + YAML-style frontmatter (key,tier,confidence,source,created_at,last_accessed,tags). Redaction pass scrubs AWS access keys, GitHub tokens (ghp_/gho_/ghs_/ghu_/ghr_), JWTs, and email addresses before write; entries taggedsecretare skipped wholesale regardless of theredactflag. Refuses to overwrite a non-empty directory (safety).tools/brain_export.pyCLI wraps the service for operators without an MCP client. Registered withdefer_loading: trueinfull+operatorprofiles. Operator guide:docs/guides/brain-export.md. Explicitly not a continuous mirror — see the spike for the rationale (docs/research/file-backed-memory-mirror.md).
Documentation
- File-backed memory mirror feasibility report (
docs/research/file-backed-memory-mirror.md, TAP-2095). Verifies the Anthropic Managed Agents memory pattern against the primary source (platform.claude.com/docs/en/managed-agents/memory), corrects thememory/working/,memory/episodic/,memory/semantic/claim cited in the 2026-05-17 NLT roadmap (that structure is not in the official spec — paths are free-form), maps Managed Agents concepts onto tapps-brain tiers, sketches the rejected--mirror-dirwire-up, enumerates 6 risks, and recommends the narrower one-shot exporter (now shipped asbrain_exportabove) instead of continuous mirroring. Records the recommendation so future sessions don't relitigate. - Observability guide (
docs/guides/observability.md) gains a worked aggregator example forrecall_quality_metrics. - Operator guide (
docs/guides/brain-export.md) — new.
Profile counts
full 62 → 63 (8 eager + 55 deferred). operator 75 → 76 (8 eager + 68 deferred; 55 standard + 13 admin). All three new tools land deferred — the eager catalog returned by tools/list without the Tool Search BETA header is unchanged at 8.
Fixed
audit_consumers()sort key crashedmypy --strictbecause the per-agent dict literal mixesint/str/dictvalues; the sort key did unary minus onobject. Cast both keys to their concrete types. (Inherited by 3.19.0 main from PR #154 — fixed in PR #156.)test_diagnostics_pipeline_cli_mcp_historywas missed by the TAP-2050 sweep that migrated registration tests off the deferred-loading curtain.diagnostics_reportis deferred, so_tool_manager.list_tools()returned the eager-only catalog and the test threwKeyError: 'tool not found: diagnostics_report'. Use_unfiltered_list_tools()per the TAP-2050 pattern. (PR #158.)
v3.19.0
The operator-observability + KG throughput release — closes EPIC-300 (validation hardening), EPIC-301 (operator observability), EPIC-302 (KG scalability), and TAP-1832 (MCP cold-start). Sister releases the CI cleanup epic TAP-2048 that returned main to green after the TAP-1985 deferred-loading rollout. Strict-superset wire contract: every change preserves existing 2xx clients while adding fields downstream consumers (tapps doctor, AgentForge BrainBridge, NLTlabsPE) have been asking for.
Added
- X-Brain-Profile gating on
/v1/*REST endpoints (TAP-1929 — security). TheX-Brain-Profileheader now enforces the same tool surface on the REST path as the MCP path. HTTP-only consumers (AgentForgeBrainBridge, future SDKs) running withagent_brainprofile can no longer callmemory_*-backed REST endpoints — denials return403with the documented JSON-RPC-32602shape ({"error": "out_of_profile", "data": {"reason": "out_of_profile", "tool": ..., "profile": ...}}). REST route → tool mapping is centralised insrc/tapps_brain/http/rest_profile_gate.pywith startup drift detection;GET /v1/tools/listfilters the catalog by the caller's profile when the header is set. POST /v1/experience:batchbulk experience ingestion (TAP-1934). Patterned after/v1/reinforce:batch; accepts{"events": [event1, event2, ...]}and writes the entire batch in one Postgres transaction via the newExperienceEventRecorder.record_many. Per-event 64 KB, total batch 1 MiB, max 100 events per request. High-throughput consumers (Ralph-style loops at 20+ tasks/min) collapse N round-trips into one.- Native list shapes on
brain_record_eventMCP tool (TAP-1932).entities,edges,evidence, andpayloadnow accept native Python lists / dicts matching the REST/v1/experienceshape. The legacyentities_json/edges_json/evidence_json/payload_jsonstring aliases still work and emitDeprecationWarning; removed in the next minor release. utility_scorehonored on the edge feedback path (TAP-1930).brain_record_feedback(..., utility_score=...)andPOST /v1/kg/feedbacknow accept the continuous signal on edges as well as memory feedback. Onedge_misleading,abs(utility_score)weights the confidence delta (max 0.1 at|score|=1.0); onedge_helpfulthe SQL path is counter-based so the score is recorded in the FeedbackStore audit row only. Validated server-side; out-of-range values get a400. Explicitutility_score=0.0is treated as "no useful signal" — fixed-step behaviour applies (does not silently suppress the misleading penalty). Both transports (MCP + REST) pass the value through verbatim; service layer owns the weighting rule.- Configurable
brain_explain_connectiontraversal ceilings (TAP-1933).TAPPS_BRAIN_KG_EXPLAIN_MAX_HOPS(default 3) caps the hop ceiling;TAPPS_BRAIN_KG_EXPLAIN_BRANCHING_FACTOR(default 50) caps BFS fan-out per layer to keep deeper searches bounded on dense graphs. Per-callmax_hopsis clamped against the ceiling (not rejected) for backward compatibility. - Raised
/v1/experiencebody limit to 256 KB (TAP-1940). The single-event experience endpoint now accepts up to 256 KB so evidence payloads (stack trace + log slice + tool output) fit without consumer-side glue. Other/v1/kg/*endpoints stay at the 64 KB default. New module-level constants_KG_MAX_BODY_BYTES,_EXPERIENCE_MAX_BODY_BYTES,_EXPERIENCE_BATCH_MAX_BODY_BYTESdocument each ceiling. brain_record_events_batchMCP tool — per-event-tx N-event backfill (TAP-1973, parent epic TAP-1966). Sibling ofbrain_record_eventfor high-throughput consumers (notably the tapps-mcp EPIC-203 migration utility) that need to push 100+ events in one MCP round-trip. Unlike the REST/v1/experience:batch(TAP-1934 — single transaction, all-or-nothing), each event in this MCP batch runs in its own Postgres transaction so a single malformed event does not abort the rest. Response:{succeeded: [{index, result}, ...], failed: [{index, error, detail}, ...], count, succeeded_count, failed_count}. Server-side cap at 200 events per call (too_many_eventsenvelope above that). Malformedevents_jsonreturns the EPIC-300bad_jsonenvelope. Registered onfull,coder,agent_brainprofiles (coder17→18,agent_brain10→11;full59→60,operator72→73 — deferred-loading entry so the 8-tool eager catalog is unchanged). Service-layer canonical function:kg_service.record_events_batch_per_event_tx(...).experience_eventspartition-retention policy + optionalpg_partmanmigration (TAP-1974, parent epic TAP-1966). New doc atdocs/engineering/partition-retention.mdnames the default 12-month retention, walks operators through three options (bundledpg_partmanmigration / hand-rolled monthlyDROP TABLE/ dev-only no-op), and documents the scheduling story (cron sidecar / K8s CronJob / pg_cron). New migration022_partman_experience_events.sqlregisterspublic.experience_eventswithpartman.part_config(RANGE monthly,premake=4,retention=<TAPPS_BRAIN_EVENTS_RETENTION_MONTHS> months,retention_keep_table=false). The migration is idempotent and skips silently when thepg_partmanextension is not installed — safe to apply on every deploy regardless of cluster. Paired.down.sqlcallspartman.undo_partition(p_keep_table => true)so existing partitions are preserved. Retention window is operator-overridable viaALTER DATABASE ... SET app.events_retention_months = '24'; clamped to[1, 60]. Schema bump version → 22.
Changed
- PreToolUse Linear hooks emit a defense-in-depth tag on every invocation (TAP-2012, parent epic TAP-2008).
.claude/hooks/tapps-pre-linear-{write,list}.shnow logentry,allow, anddenydecisions to.tapps-mcp/hook-debug.logas JSONL with the tag[hook: defense-in-depth, primary: server-gate]. Existing block/allow behaviour is unchanged — purely observability. Lets operators debug a denied Linear call by reading one file: the docs-mcp / tapps-mcp server-side gate is primary; the hook is the belt-and-suspenders second layer, and the log line disambiguates "server already refused" from "hook is the only enforcement (bare exit 2)" at triage time. brain_record_feedbackreturns the same structuredbad_jsonenvelope ondetails_jsondecode failure (TAP-1969). Validation already existed at the service layer ({"error": "parse_error", "message": "invalid_details_json: ..."}) for the memory path; the MCP wrapper now validatesdetails_jsononce up-front for both the edge and memory paths and returns{"error": "bad_json", "field": "details_json", "detail": "<msg>"}instead. Empty string and"{}"continue to map to "no extra metadata" (back-compat). The service-layerparse_errorshape on the siblingfeedback_rate/feedback_gap/feedback_issue/feedback_recordtools is unchanged — only thebrain_record_feedbacksurface speaks the unifiedbad_jsonenvelope. No KG / memory write occurs on a decode failure.brain_get_neighborsreturns the same structuredbad_jsonenvelope onentity_ids_jsondecode failure (TAP-1968). Malformed input used to silently produce an empty-neighbourhood response, which read as "no edges found" instead of "you sent garbage." Same shape as TAP-1967:{"error": "bad_json", "field": "entity_ids_json", "detail": "<msg>"}. Empty string continues to map to[](back-compat). No KG query is issued on a decode failure.brain_record_eventreturns a structuredbad_jsonenvelope on legacy-alias decode failure (TAP-1967). Malformedpayload_json,entities_json,edges_json, orevidence_jsonarguments used to be swallowed and substituted with empty{}/[]— operator typos shipped as silent no-ops. The tool now returns{"error": "bad_json", "field": "<name>", "detail": "<json_msg>"}and writes nothing. Empty strings and"{}"/"[]"continue to map to empty payloads (back-compat). Nativepayload/entities/edges/evidencekwargs are unaffected.coderprofile description calls out the KG discovery primitives by name (TAP-2006, parent epic TAP-2002).memory_find_related(key, max_hops=2)andbrain_get_neighbors(entity_ids, hops=2)were already in thecodertool list; the YAML description now explicitly names them as the "what else does this file / rule touch?" primitives so agents picking a profile see the discovery surface up front. NewProfileRegistry.get_description(name) -> strexposes the description so callers (HTTP/v1/profiles, future profile-picker UIs) read one source of truth./v1/tools/listadds HTTP cache validation headers + 304 short-circuit (TAP-1971, parent epic TAP-1965). The response now carriesETag: W/"<sha256:16>",Cache-Control: public, max-age=300(unchanged in spirit, formalised in the header),X-Brain-Version: 3.x.y, andX-Catalog-Generated-At: <iso8601>(set when the snapshot is built in the lifespan hook). Clients can presentIf-None-Match: <etag>and receive304 Not Modified(empty body, same headers) when the snapshot hasn't changed —BrainBridge-style consumers stop reparsing the JSON on every probe. ETag is weak (W/) and per-cache-slot, recomputed lazily and dropped automatically when the snapshot is rebuilt. The existing 200 path, profile-aware filtering, and the TAP-1855 p95 < 200 ms benchmark gate are untouched.out_of_profileJSON-RPC / REST denials addsuggested_profile(TAP-1972, parent epic TAP-1965). Thedatapayload on-32602 out_of_profileerrors (both the MCPtool_filterraise sites and the RESTout_of_profile_response_body) now carriessuggested_profile: "<name>" | null— the smallest profile (ascending tool-set size, name tiebreak) that exposes the denied tool, excluding the caller's current profile.nullwhen no profile exposes it. New `ProfileReg...
v3.18.0
Minor release that closes the deprecation window opened in 3.17.0, hardens the MCP cold path, adds operator visibility on probe latency, and lands a batch of correctness fixes (decay, relations, idempotency, atomic writes, prompt-injection detection).
Removed
memory_recall(message=...)deprecated alias (commitstools_memory.py). The kwarg was retained for one minor cycle as promised in 3.17.0 and now goes away. Callers that still passmessage=will get aTypeError. Service-layer signature (memory_service.memory_recall(..., message=...)) is unchanged.brain_learn_success(task_description=...)deprecated alias (tools_brain.py). Same shape as above — the kwarg is gone from the MCP tool surface; service-layer kwargtask_description=stays. The Python client wrappers (TappsBrainClient.memory_recall,TappsBrainClient.learn_success, and the async variants) were renamed toquery/descriptionto match — positional callers are unaffected, keyword callers must rename.
Added
/v1/tools/liststatic-snapshot route + p95/p99 benchmark gate (TAP-1843, TAP-1855). New REST endpoint returns the cached tool catalog without booting the MCP server; pytest-benchmark gate asserts warm-state p95 stays under 200 ms.tapps_brain_mcp_probe_duration_secondsPrometheus histogram on/metrics(TAP-1849). Per-call latency for SessionStart hook MCP probes, so operators can see when the probe is the long pole.- DB-checked
/healthzprobe + tightened docker-compose healthcheck (TAP-1835)./healthzruns aSELECT 1against Postgres; the compose healthcheck no longer reports green when the DB is unreachable. - Migration rollback (TAP-1818). Each
*.up.sqlships a paired*.down.sql; new CLI command applies the down step in reverse order; CI test asserts every up has a down. /v1/tools/listprofile cache + lazy package init (TAP-1833, TAP-1834).tools/listresult is cached per profile with a 300 s TTL;tapps_brain/__init__.pylazy-loads submodules and defersstructlog.configure()to first use — cuts cold-import cost for catalog-only probes.AsyncMemoryStorethread-pool semaphore guards (TAP-1815). Bounds concurrent thread-pool dispatch so a burst of async calls cannot starve the executor.
Changed
- SessionStart hook pre-warms
tapps-brain-httpbefore the MCP probe (TAP-1837). Removes a cold-start tail latency that was tripping the probe timeout on the first request after container restart. - Refactored hot-path search helpers to reduce cyclomatic complexity (TAP-1844). No behaviour change; structure cleanup.
tapps_session_startsharing via.tapps-mcp/sentinel (TAP-1841). Sub-agents in a Ralph loop skip their owntapps_session_startcall when the primary agent has already bootstrapped (sentinel < 1 h old).
Fixed
extract_relationspreservescreated_atandconfidence_historyacross merges (TAP-1812, TAP-1816). Relation merges no longer reset timestamps tonow()or drop the merged entries' confidence history.- Atomic writes
fsyncthe parent directory afteros.replace(TAP-1809). Closes a durability gap where a power loss between rename and parent-dir flush could leave the rename invisible on remount. - Zero-denominator guards on
power_law_decayandexponential_decay(TAP-1811). PreventsZeroDivisionErrorwhen a memory's age equals the half-life pivot. - OTLP exporter timeout + circuit breaker (TAP-1814). Bounds per-export latency and short-circuits after consecutive failures, so an unreachable collector cannot stall the request path.
- Prompt-injection detector recognises base64 / hex / URL-safe-b64 encoded payloads (TAP-1813). Closes a bypass where attackers wrapped the injection string in an encoding the regex didn't cover.
- Idempotency guard hardening + monotonic time fix (TAP-1814 follow-up). Race window between idempotency check and write narrowed; clock-skew sensitivity removed.
- Bare asserts replaced with explicit
RuntimeErrorraises (TAP-1822). Invariants survivepython -O. consolidation-merge-undoCLI gets--dry-run,--yes,TAPPS_BRAIN_CONFIRM_YES(TAP-1810). Lets ops scripts run the undo without interactive prompts._pin_seedshelper at every evaluation entry-point (TAP-1817). Pins NumPy / PyTorch / random seeds viatry/except ImportErrorso eval runs are deterministic.
Migration notes
- No action required for callers using
description=/query=directly. The new canonical names landed in 3.17.0 and are unchanged here. - tapps-mcp
BrainBridgemust already be sendingquery=anddescription=(per the 3.17.0 migration note). Any caller still on the old kwargs starts gettingTypeErroron this release. - Python-client keyword callers of
client.memory_recall(message=...)orclient.learn_success(task_description=...)must rename toquery=/description=. Positional calls are unchanged.
v3.17.2
Patch release that lands the HTTP-wire half of the out_of_profile contract documented in v3.17.1. The structured JSON-RPC error envelope now actually reaches bridge consumers (tapps-mcp BrainBridge, AgentForge) instead of being swallowed by FastMCP's isError wrapper.
Fixed
out_of_profiledenial reaches the wire as a JSON-RPCerrorenvelope (TAP-1619, commit883d8ca). Themcp1.27.x lowlevel server'scall_tooldecorator catchesMcpErrorwith a bareexcept Exception(mcp/server/lowlevel/server.py:583) and converts it to text-onlyresult.isError=true, droppingErrorData.datasilently.tool_filternow installs a second wrapper at_mcp_server.request_handlers[CallToolRequest]so the profile pre-check raisesMcpErroroutside that try/except — the exception then propagates up to_handle_request'sexcept McpErrorblock (~L764) and emits a proper JSON-RPCerrorenvelope with the full structured payload (code=-32602,data={"reason": "out_of_profile", "tool": ..., "profile": ...}). In-process callers (existing TAP-1579 unit tests inTestCallEnforcement) bypass the request handler entirely and continue receivingMcpErrorfrom the original_filtered_call_toolwrapper; HTTP-denied requests never reach_filtered_call_toolbecause the new wrapper short-circuits first, so thedenied_profilemetric is not double-counted. TheProfile wire contractsection indocs/guides/mcp-client-repo-setup.mdno longer needs the "consumers fall back to the canonical text message until that lands" caveat — the wire shape now matches the documented contract.
v3.17.1
Patch release that bakes the agent_brain MCP profile (TAP-1579) and richer ValidationError diagnostics (TAP-1580) into a clean wheel + image. v3.17.0 plus three follow-ups, no behavior changes for callers staying on the full profile. Cuts the deployed tapps-brain-http container off its 2026-05-12 hot-patches.
Added
agent_brainMCP profile (TAP-1579, commit9feb1b1). Canonical 10-tool agent-brain consumer surface —brain_*facade only; the lower-levelmemory_*,hive_*, andfeedback_*tools are gated out. Intended for embedded agents (AgentBrain class, AgentForge) that should only see the high-level facade. Operator scripts and full-fidelity clients continue to use thefullprofile.required_fieldshint on MCPValidationError(TAP-1580, commit01604cc).tool_filter._enriched_tool_error_messagewalks the pydanticValidationError.__cause__chain, extracts missing required-field locations, and prependsrequired_fields: [...]to the FastMCPToolErrormessage — agents no longer have to parse the pydantic body to figure out what to retry with.
Fixed
- Out-of-profile error shape + wire contract documented (TAP-1579, commit
cd659e7).tool_filterraisesMcpError(code=-32602, data={"reason": "out_of_profile", "tool": ..., "profile": ...})for gated tools, matching the contract documented indocs/guides/mcp-client-repo-setup.md#profile-wire-contract.mcp1.27.x's FastMCP HTTP transport currently drops thedatapayload on the wire — tracked as TAP-1619; consumers fall back to the canonical text message until that lands.
v3.17.0
The MCP-tool parameter-alignment release — addresses NLTlabsPE 2026-05-12 feedback on the public MCP tool surface. Two parameter renames + new quick-start in the server instructions= string. Old names still accepted as deprecated aliases for one minor cycle (removed in 3.18.0).
Changed
memory_recall(message=...)→memory_recall(query=...)(NLTlabsPE alignment, TAP-tbd). The new MCP tool surface usesquery=to matchmemory_search(query=...)andbrain_recall(query=...). Themessage=kwarg is a deprecated alias kept for back-compat — calls emitDeprecationWarningand forward to the new path. Passing both raisesValueError. Removed in 3.18.0.brain_learn_success(task_description=...)→brain_learn_success(description=...)(NLTlabsPE alignment, TAP-tbd). Aligns the parameter name withbrain_learn_failure(description=...). Thetask_description=kwarg is a deprecated alias with the same warn-and-forward semantics. Service-layer signatures are unchanged. Removed in 3.18.0.- FastMCP server
instructions=now ships a 5-line quick-start at the top of the server preamble:memory_save/memory_get/memory_recall/brain_remember/agent_scope. Lets agents pick up the canonical save/get/recall surface from the server preamble alone, without aToolSearchround-trip on first use.
Migration notes
- No action required for callers using
tapps-brain>= 3.17.0 directly. Old names still work and emit aDeprecationWarning. - Update tapps-mcp BrainBridge (
packages/tapps-core/src/tapps_core/brain_bridge.py:1191) to sendquery=anddescription=to silence the warning before 3.18.0 lands. Tracked as a cross-repo follow-up.
Changed
/v1/reinforceand/v1/reinforce:batchare now async-native (EPIC-072 STORY-072.9, TAP-1566).AsyncMemoryStore.reinforceadopts the capture+flush pattern previously used bysave/delete, so the reinforced entry's Postgres write goes throughAsyncPostgresPrivateBackendinstead of occupying a thread-pool thread. Newasync_memory_reinforce/async_memory_reinforce_manyservice shims dispatch throughcfg.async_storewhen wired; both handlers fall back toasyncio.to_thread(memory_reinforce*, ...)against the sync store when no async backend is available. Recall (single + batch) is still routed throughasyncio.to_thread— true async-native recall pipeline is tracked as TAP-1568.
Fixed
- async-native write path now persists relations and audit rows (EPIC-072 STORY-072.8, TAP-1565).
_CapturePersistenceBackend.save_relationsandappend_auditwere no-ops in async-native mode — acceptable while the path was opt-in, but a silent-data-loss bug once TAP-1117 made it the default. The capture backend now queues secondary writes alongside primary saves/deletes;AsyncMemoryStore.save/.deletedrain all four queues via the async backend after the syncMemoryStorecall returns. Closes the EPIC-072 known-limitation bullet about relations/audit being deferred.
v3.16.0
The async-native graduation release — EPIC-072 STORY-072.7 (TAP-1117) flips the async-native Postgres write path from an opt-in feature flag to the default. No new APIs; behaviour change is transparent for operators with a Postgres DSN configured.
Changed
- async-native is now the only write path;
TAPPS_BRAIN_ASYNC_NATIVEflag removed (EPIC-072 STORY-072.7, TAP-1117). Graduated the opt-in async-native Postgres write path to default.AsyncMemoryStore.save/.deleteand the HTTP write endpoints (/v1/remember,/v1/forget,/v1/learn_success,/v1/learn_failure) build theAsyncPostgresPrivateBackendautomatically whenever a Postgres DSN is configured. TheTAPPS_BRAIN_ASYNC_NATIVEenv var is no longer read — setting it has no effect, and unsetting it no longer routes through the legacy thread-pool write path.AsyncMemoryStoreretains ato_threadfallback when no async backend is wired (embedded sync-only test setups, no DSN).
Migration notes
- No action required for operators on Postgres. The default path is now what the flag used to enable. Remove
TAPPS_BRAIN_ASYNC_NATIVEfrom your deployment env if it was set — leaving it has no effect. - No schema changes. No new migrations.
- Known limitations carried forward from EPIC-072:
save_relationsandappend_auditremain no-ops on the async-native path; recall/reinforce/batch endpoints still useasyncio.to_thread. Tracked as EPIC-072 follow-ups.