Skip to content

Releases: wtthornton/tapps-brain

v3.22.4

Choose a tag to compare

@github-actions github-actions released this 05 Jun 16:50
9f93376

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). EvidenceSpec documented "exactly one of edge_id / entity_id" but never enforced it, so AgentForge's payload (evidence with neither) passed Pydantic and raised psycopg.errors.CheckViolation on chk_evidence_xor_attachment at the DB insert, sinking the whole event with a masked 500. The tapps_brain_http_errors_total counter from 3.22.2 surfaced it as live traffic immediately after the 3.22.3 deploy. EvidenceSpec now enforces the XOR via a model_validator, so malformed evidence is caught at the model layer and skipped-with-warning by record_event's resilient coercion (TAP-2866) — the core event records and returns 200 with a warnings entry of kind: "evidence". The field stays the documented contract; bare EvidenceSpec() now raises, matching the schema's NOT-NULL XOR.

v3.22.0

Choose a tag to compare

@github-actions github-actions released this 02 Jun 02:01
d6bc47a

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.0 was tagged from main on 2026-06-01 without merging its version-bump PR (#186), so the tagged source still read 3.20.1 and main never 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/experience and /v1/experience:batch returned a runtime 201 while the OpenAPI contract and every other data-plane write (/v1/remember, /v1/reinforce, /v1/learn_*) documented and returned 200. The handlers now return 200, aligning code to the already-published contract (no OpenAPI snapshot change). /v1/kg/resolve_entity stays 200 (idempotent upsert).
  • brain_resolve_entity wired into the full + operator profiles, 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 --json stdout stays clean (TAP-2803). cli/_common.py's structlog.configure() carried a comment claiming output went to stderr but only set wrapper_class, never logger_factory — so structlog fell back to its default PrintLoggerFactory() (stdout). The ERROR-level postgres.privileged_role_audit_override audit record (emitted when TAPPS_BRAIN_ALLOW_PRIVILEGED_ROLE=1) then corrupted the diagnostics --json payload. Now sets logger_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 test job (TAP-2803). CI previously ran tests/unit/ only, so any tests/integration/ failure landed on main unblocked — 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.py via the dead-code whitelist (TAP-2765).
  • Cleared pre-existing lint/format/mypy debt blocking main (PR #191).

v3.20.1

Choose a tag to compare

@github-actions github-actions released this 18 May 19:10
1aa5b9f

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/list filter. http_adapter.py:1454 built 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) raised ValueError because 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 the by_name_json lookup only knew about the eager catalog. Use _unfiltered_list_tools() for the by_name index (drift check + per-profile filter); keep the no-header payload built 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

Choose a tag to compare

@github-actions github-actions released this 18 May 18:37
2f55a50

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). Joins AgentRegistry with 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_silent flags registered agents with zero brain_* calls, unregistered_active flags orphan agent IDs seen in the counter but missing from the registry. Backed by tools/audit_consumers.py CLI for CI/operator workflows. Registered with defer_loading: true in full + operator profiles. Service entrypoint: memory_service.audit_consumers.
  • Recall-quality telemetry — top_score, oldest_returned_age_days, recall_quality_metrics (TAP-2094). RecallDiagnostics gains two new per-call fields (highest composite score in the returned set; age in days of the oldest returned memory; both None on empty recall). RecallOrchestrator writes 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. New recall_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 with defer_loading: true in full + operator profiles. 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 by max(confidence, recency_score)) into <output_dir>/manifest.json + <output_dir>/<tier>/<key>.md per Anthropic's /mnt/memory/<store>/ filesystem shape. Each .md file carries a READ-ONLY banner + 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 tagged secret are skipped wholesale regardless of the redact flag. Refuses to overwrite a non-empty directory (safety). tools/brain_export.py CLI wraps the service for operators without an MCP client. Registered with defer_loading: true in full + operator profiles. 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 the memory/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-dir wire-up, enumerates 6 risks, and recommends the narrower one-shot exporter (now shipped as brain_export above) instead of continuous mirroring. Records the recommendation so future sessions don't relitigate.
  • Observability guide (docs/guides/observability.md) gains a worked aggregator example for recall_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 crashed mypy --strict because the per-agent dict literal mixes int / str / dict values; the sort key did unary minus on object. 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_history was missed by the TAP-2050 sweep that migrated registration tests off the deferred-loading curtain. diagnostics_report is deferred, so _tool_manager.list_tools() returned the eager-only catalog and the test threw KeyError: 'tool not found: diagnostics_report'. Use _unfiltered_list_tools() per the TAP-2050 pattern. (PR #158.)

v3.19.0

Choose a tag to compare

@github-actions github-actions released this 17 May 22:18
af88fbd

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). The X-Brain-Profile header now enforces the same tool surface on the REST path as the MCP path. HTTP-only consumers (AgentForge BrainBridge, future SDKs) running with agent_brain profile can no longer call memory_*-backed REST endpoints — denials return 403 with the documented JSON-RPC -32602 shape ({"error": "out_of_profile", "data": {"reason": "out_of_profile", "tool": ..., "profile": ...}}). REST route → tool mapping is centralised in src/tapps_brain/http/rest_profile_gate.py with startup drift detection; GET /v1/tools/list filters the catalog by the caller's profile when the header is set.
  • POST /v1/experience:batch bulk experience ingestion (TAP-1934). Patterned after /v1/reinforce:batch; accepts {"events": [event1, event2, ...]} and writes the entire batch in one Postgres transaction via the new ExperienceEventRecorder.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_event MCP tool (TAP-1932). entities, edges, evidence, and payload now accept native Python lists / dicts matching the REST /v1/experience shape. The legacy entities_json / edges_json / evidence_json / payload_json string aliases still work and emit DeprecationWarning; removed in the next minor release.
  • utility_score honored on the edge feedback path (TAP-1930). brain_record_feedback(..., utility_score=...) and POST /v1/kg/feedback now accept the continuous signal on edges as well as memory feedback. On edge_misleading, abs(utility_score) weights the confidence delta (max 0.1 at |score|=1.0); on edge_helpful the SQL path is counter-based so the score is recorded in the FeedbackStore audit row only. Validated server-side; out-of-range values get a 400. Explicit utility_score=0.0 is 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_connection traversal 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-call max_hops is clamped against the ceiling (not rejected) for backward compatibility.
  • Raised /v1/experience body 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_BYTES document each ceiling.
  • brain_record_events_batch MCP tool — per-event-tx N-event backfill (TAP-1973, parent epic TAP-1966). Sibling of brain_record_event for 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_events envelope above that). Malformed events_json returns the EPIC-300 bad_json envelope. Registered on full, coder, agent_brain profiles (coder 17→18, agent_brain 10→11; full 59→60, operator 72→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_events partition-retention policy + optional pg_partman migration (TAP-1974, parent epic TAP-1966). New doc at docs/engineering/partition-retention.md names the default 12-month retention, walks operators through three options (bundled pg_partman migration / hand-rolled monthly DROP TABLE / dev-only no-op), and documents the scheduling story (cron sidecar / K8s CronJob / pg_cron). New migration 022_partman_experience_events.sql registers public.experience_events with partman.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 the pg_partman extension is not installed — safe to apply on every deploy regardless of cluster. Paired .down.sql calls partman.undo_partition(p_keep_table => true) so existing partitions are preserved. Retention window is operator-overridable via ALTER 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}.sh now log entry, allow, and deny decisions to .tapps-mcp/hook-debug.log as 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_feedback returns the same structured bad_json envelope on details_json decode 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 validates details_json once 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-layer parse_error shape on the sibling feedback_rate / feedback_gap / feedback_issue / feedback_record tools is unchanged — only the brain_record_feedback surface speaks the unified bad_json envelope. No KG / memory write occurs on a decode failure.
  • brain_get_neighbors returns the same structured bad_json envelope on entity_ids_json decode 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_event returns a structured bad_json envelope on legacy-alias decode failure (TAP-1967). Malformed payload_json, entities_json, edges_json, or evidence_json arguments 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). Native payload / entities / edges / evidence kwargs are unaffected.
  • coder profile description calls out the KG discovery primitives by name (TAP-2006, parent epic TAP-2002). memory_find_related(key, max_hops=2) and brain_get_neighbors(entity_ids, hops=2) were already in the coder tool 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. New ProfileRegistry.get_description(name) -> str exposes the description so callers (HTTP /v1/profiles, future profile-picker UIs) read one source of truth.
  • /v1/tools/list adds HTTP cache validation headers + 304 short-circuit (TAP-1971, parent epic TAP-1965). The response now carries ETag: W/"<sha256:16>", Cache-Control: public, max-age=300 (unchanged in spirit, formalised in the header), X-Brain-Version: 3.x.y, and X-Catalog-Generated-At: <iso8601> (set when the snapshot is built in the lifespan hook). Clients can present If-None-Match: <etag> and receive 304 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_profile JSON-RPC / REST denials add suggested_profile (TAP-1972, parent epic TAP-1965). The data payload on -32602 out_of_profile errors (both the MCP tool_filter raise sites and the REST out_of_profile_response_body) now carries suggested_profile: "<name>" | null — the smallest profile (ascending tool-set size, name tiebreak) that exposes the denied tool, excluding the caller's current profile. null when no profile exposes it. New `ProfileReg...
Read more

v3.18.0

Choose a tag to compare

@github-actions github-actions released this 16 May 20:35

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 (commits tools_memory.py). The kwarg was retained for one minor cycle as promised in 3.17.0 and now goes away. Callers that still pass message= will get a TypeError. 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 kwarg task_description= stays. The Python client wrappers (TappsBrainClient.memory_recall, TappsBrainClient.learn_success, and the async variants) were renamed to query / description to match — positional callers are unaffected, keyword callers must rename.

Added

  • /v1/tools/list static-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_seconds Prometheus 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 /healthz probe + tightened docker-compose healthcheck (TAP-1835). /healthz runs a SELECT 1 against Postgres; the compose healthcheck no longer reports green when the DB is unreachable.
  • Migration rollback (TAP-1818). Each *.up.sql ships a paired *.down.sql; new CLI command applies the down step in reverse order; CI test asserts every up has a down.
  • /v1/tools/list profile cache + lazy package init (TAP-1833, TAP-1834). tools/list result is cached per profile with a 300 s TTL; tapps_brain/__init__.py lazy-loads submodules and defers structlog.configure() to first use — cuts cold-import cost for catalog-only probes.
  • AsyncMemoryStore thread-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-http before 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_start sharing via .tapps-mcp/ sentinel (TAP-1841). Sub-agents in a Ralph loop skip their own tapps_session_start call when the primary agent has already bootstrapped (sentinel < 1 h old).

Fixed

  • extract_relations preserves created_at and confidence_history across merges (TAP-1812, TAP-1816). Relation merges no longer reset timestamps to now() or drop the merged entries' confidence history.
  • Atomic writes fsync the parent directory after os.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_decay and exponential_decay (TAP-1811). Prevents ZeroDivisionError when 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 RuntimeError raises (TAP-1822). Invariants survive python -O.
  • consolidation-merge-undo CLI gets --dry-run, --yes, TAPPS_BRAIN_CONFIRM_YES (TAP-1810). Lets ops scripts run the undo without interactive prompts.
  • _pin_seeds helper at every evaluation entry-point (TAP-1817). Pins NumPy / PyTorch / random seeds via try/except ImportError so 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 BrainBridge must already be sending query= and description= (per the 3.17.0 migration note). Any caller still on the old kwargs starts getting TypeError on this release.
  • Python-client keyword callers of client.memory_recall(message=...) or client.learn_success(task_description=...) must rename to query= / description=. Positional calls are unchanged.

v3.17.2

Choose a tag to compare

@github-actions github-actions released this 14 May 02:06

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_profile denial reaches the wire as a JSON-RPC error envelope (TAP-1619, commit 883d8ca). The mcp 1.27.x lowlevel server's call_tool decorator catches McpError with a bare except Exception (mcp/server/lowlevel/server.py:583) and converts it to text-only result.isError=true, dropping ErrorData.data silently. tool_filter now installs a second wrapper at _mcp_server.request_handlers[CallToolRequest] so the profile pre-check raises McpError outside that try/except — the exception then propagates up to _handle_request's except McpError block (~L764) and emits a proper JSON-RPC error envelope with the full structured payload (code=-32602, data={"reason": "out_of_profile", "tool": ..., "profile": ...}). In-process callers (existing TAP-1579 unit tests in TestCallEnforcement) bypass the request handler entirely and continue receiving McpError from the original _filtered_call_tool wrapper; HTTP-denied requests never reach _filtered_call_tool because the new wrapper short-circuits first, so the denied_profile metric is not double-counted. The Profile wire contract section in docs/guides/mcp-client-repo-setup.md no 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

Choose a tag to compare

@github-actions github-actions released this 13 May 19:22

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_brain MCP profile (TAP-1579, commit 9feb1b1). Canonical 10-tool agent-brain consumer surface — brain_* facade only; the lower-level memory_*, hive_*, and feedback_* 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 the full profile.
  • required_fields hint on MCP ValidationError (TAP-1580, commit 01604cc). tool_filter._enriched_tool_error_message walks the pydantic ValidationError.__cause__ chain, extracts missing required-field locations, and prepends required_fields: [...] to the FastMCP ToolError message — 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_filter raises McpError(code=-32602, data={"reason": "out_of_profile", "tool": ..., "profile": ...}) for gated tools, matching the contract documented in docs/guides/mcp-client-repo-setup.md#profile-wire-contract. mcp 1.27.x's FastMCP HTTP transport currently drops the data payload on the wire — tracked as TAP-1619; consumers fall back to the canonical text message until that lands.

v3.17.0

Choose a tag to compare

@github-actions github-actions released this 12 May 19:49

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 uses query= to match memory_search(query=...) and brain_recall(query=...). The message= kwarg is a deprecated alias kept for back-compat — calls emit DeprecationWarning and forward to the new path. Passing both raises ValueError. Removed in 3.18.0.
  • brain_learn_success(task_description=...)brain_learn_success(description=...) (NLTlabsPE alignment, TAP-tbd). Aligns the parameter name with brain_learn_failure(description=...). The task_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 a ToolSearch round-trip on first use.

Migration notes

  • No action required for callers using tapps-brain >= 3.17.0 directly. Old names still work and emit a DeprecationWarning.
  • Update tapps-mcp BrainBridge (packages/tapps-core/src/tapps_core/brain_bridge.py:1191) to send query= and description= to silence the warning before 3.18.0 lands. Tracked as a cross-repo follow-up.

Changed

  • /v1/reinforce and /v1/reinforce:batch are now async-native (EPIC-072 STORY-072.9, TAP-1566). AsyncMemoryStore.reinforce adopts the capture+flush pattern previously used by save/delete, so the reinforced entry's Postgres write goes through AsyncPostgresPrivateBackend instead of occupying a thread-pool thread. New async_memory_reinforce / async_memory_reinforce_many service shims dispatch through cfg.async_store when wired; both handlers fall back to asyncio.to_thread(memory_reinforce*, ...) against the sync store when no async backend is available. Recall (single + batch) is still routed through asyncio.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_relations and append_audit were 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/.delete drain all four queues via the async backend after the sync MemoryStore call returns. Closes the EPIC-072 known-limitation bullet about relations/audit being deferred.

v3.16.0

Choose a tag to compare

@github-actions github-actions released this 11 May 09:21

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_NATIVE flag removed (EPIC-072 STORY-072.7, TAP-1117). Graduated the opt-in async-native Postgres write path to default. AsyncMemoryStore.save/.delete and the HTTP write endpoints (/v1/remember, /v1/forget, /v1/learn_success, /v1/learn_failure) build the AsyncPostgresPrivateBackend automatically whenever a Postgres DSN is configured. The TAPPS_BRAIN_ASYNC_NATIVE env var is no longer read — setting it has no effect, and unsetting it no longer routes through the legacy thread-pool write path. AsyncMemoryStore retains a to_thread fallback 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_NATIVE from 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_relations and append_audit remain no-ops on the async-native path; recall/reinforce/batch endpoints still use asyncio.to_thread. Tracked as EPIC-072 follow-ups.