Skip to content

Commit a0b6b93

Browse files
committed
Release v1.0.0-rc3
# Conflicts: # CHANGELOG.md
2 parents 981b233 + 7c877f9 commit a0b6b93

23 files changed

Lines changed: 2438 additions & 20 deletions

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,9 @@ celerybeat-schedule
130130
Thumbs.db
131131

132132
uv.lock
133+
134+
# Workspace planning artifacts (not part of the application)
135+
_evo/
136+
_evo-output/
137+
_bmad/
138+
_bmad-output/

CHANGELOG.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,64 @@ All notable changes to this microservice will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [v1.0.0-rc3] - 2026-05-17
9+
10+
Release de integração — adiciona ferramentas nativas para o LLM agent (Knowledge Nexus search, manage_conversation_labels, link_product_to_pipeline_item), injeção de catálogo de products no contexto do agente, merge das integrações da tabela `agent_integrations` na config do agente em runtime, e correção de bugs em handshake de Postgres e binding do chat handler. Também declara `EXTENSION_POINTS.md` como contrato público de extensão para a Enterprise edition.
11+
12+
### Added
13+
14+
- **EXTENSION_POINTS.md (EVO-1376)** (#9) — documento com os pontos de extensão expostos pelo processor para a Enterprise edition. Contrato versionado, sem código novo.
15+
- **`knowledge_nexus_search` — tool nativa** — agentes ganham acesso a busca semântica em spaces do Nexus diretamente do prompt, sem necessidade de configurar tool customizada.
16+
- **`manage_conversation_labels` — tool nativa** — list/add/remove de labels na conversa pelo próprio agente.
17+
- **`link_product_to_pipeline_item` — tool nativa** — agente consegue linkar products do catálogo ao item de pipeline ativo.
18+
- **Catálogo de products no contexto do agente** — products attachados ao agente são injetados no prompt, permitindo que o LLM tenha contexto da oferta.
19+
- **Hints de uso de pipeline e labels no prompt**`pipeline_manipulation` e `manage_labels` ganham hints estruturados no system prompt para orientar o LLM sobre quando e como usar.
20+
21+
### Changed
22+
23+
- **`llm-agent` — merge de integrações** — integrações da tabela `agent_integrations` agora são mescladas na config do agente em runtime. Antes a config era estática; agora reflete o estado mais recente das integrações configuradas via UI.
24+
- **Docs** padronizados para Evolution Foundation 2026 (README, LICENSE, NOTICE, TRADEMARKS).
25+
- **Docs (org)** — URLs do GitHub atualizadas de `EvolutionAPI` para `evolution-foundation`.
26+
27+
### Fixed
28+
29+
- **DB — `sslmode``ssl` para asyncpg** — o driver asyncpg não entende `sslmode` (parâmetro psycopg/libpq). Conexões com `sslmode=require` na connection string falhavam silenciosamente; agora traduzimos para o parâmetro nativo do asyncpg.
30+
- **Chat handler — request param rebind** — corrige binding do parâmetro `request` no chat handler que causava `NameError` em determinados caminhos de erro.
31+
32+
## [v1.0.0-rc2] - 2026-05-05
33+
34+
### Fixed
35+
36+
- **Container startup**: invocar `alembic` e `uvicorn` via `python -m` em vez de console scripts, evitando que `sh -c` interprete os entrypoints incorretamente em algumas imagens. (#7)
37+
- **Migration `26a14ac7025d`**: adicionado `if_not_exists=True` no `op.create_table('evo_agent_processor_execution_metrics')`, tornando a migration segura para re-run em ambientes onde a tabela já foi criada por outro serviço (banco compartilhado). (#7)
38+
39+
## [v1.0.0-rc1] - 2026-04-24
40+
41+
### Added
42+
43+
- Primeiro release candidate público do `evo-ai-processor-community`.
44+
45+
### Changed
46+
47+
- Refatorado para remover parâmetro `account_id` em services internos.
48+
- Adicionado workflow de publish multi-arch no Docker Hub.
49+
- Adicionado workflow de build/publish de imagens `develop` para staging.
50+
51+
### Fixed
52+
53+
- Resolvido `UnboundLocalError` em `run_seeders.py`.
54+
- Adicionado `checkfirst` ao `SQLAlchemy create_all` para evitar `DuplicateTableError`.
55+
- Corrigida ordem de middlewares e condições de CORS / rate limiting.
56+
- `agent retrieval` migrado para chamadas assíncronas com refinamento de tratamento de erros no `EvoAuthService`.
57+
- Tratamento de erros aprimorado em response utility e tool de mensagens privadas.
58+
- Adicionado método `PATCH` ao `EvoCrmClient` e suporte a `stage_name` na ferramenta de manipulação de pipelines.
59+
- Removido campo não utilizado `CORS_ORIGINS` do `settings`.
60+
- **EVO-972**: serializa `set` em respostas JSON e enriquece superfície de erro de auth. (#6)
61+
62+
### Security
63+
64+
- Removida chave de service account GCP que vazou em commits anteriores. (#4)
65+
866
## [0.1.0] - 2025-07-02
967

1068
### Added

EXTENSION_POINTS.md

Lines changed: 322 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
1+
# Extension Points
2+
3+
Each extension point below is versioned independently under SemVer; see
4+
the [Compatibility Promise](#compatibility-promise) and the per-EP
5+
`Version` lines. The document itself is not versioned — there is no
6+
single aggregate "contract version".
7+
8+
This document is the public contract between `evo-ai-processor-community`
9+
and any external consumer that wants to plug into agent execution
10+
without forking or patching community source. The authoritative
11+
architectural decision behind this contract is **ADR13 — Extension
12+
Points Versioning Strategy**; the rules below are self-contained.
13+
14+
The community release is fully usable on its own. Every extension point
15+
ships with a working default; a consumer can **replace** the default
16+
implementation of one or more of them without modifying files in `src/`
17+
or `migrations/`.
18+
19+
If you are about to change any of the three extension points below, read
20+
the [Compatibility Promise](#compatibility-promise) first.
21+
22+
---
23+
24+
## Compatibility Promise
25+
26+
Each extension point is versioned independently and treated as a public
27+
API, with the same backward-compatibility rules as the HTTP endpoints
28+
exposed by this service:
29+
30+
- **Backward compatibility is forever.** Once shipped at a given major,
31+
the name, arguments, return shape and observable behavior of an
32+
extension point do not change silently.
33+
- **Breaking changes require a major bump** of the affected extension
34+
point and of the community release that ships it.
35+
- **Deprecation window is at least one minor release.** The old shape
36+
keeps working alongside the new one, and the deprecated path emits a
37+
`DeprecationWarning` via `warnings.warn`.
38+
- **Additive changes are minor bumps.** New extension point, or new
39+
optional capability on an existing one.
40+
- **Bug fixes that preserve the contract are patch bumps.**
41+
42+
Bumping one extension point does not bump the others.
43+
44+
---
45+
46+
## Registration API
47+
48+
**Version:** `1.0.0`
49+
50+
The registration mechanism is itself part of the public contract. Every
51+
override goes through one function:
52+
53+
```python
54+
def replace(name: str, impl: object) -> None: ...
55+
```
56+
57+
**Accepted `name` values (v1.0.0):** `"capability_gate"`,
58+
`"runtime_context"`, `"usage_reporter"`. Adding a new accepted name is
59+
a minor bump; removing or renaming an accepted name is a major bump.
60+
61+
**`impl` contract:** any object whose attributes satisfy the
62+
`typing.Protocol` declared for that extension point. The implementation
63+
is structurally type-checked at registration time; passing an object
64+
that does not satisfy the Protocol raises `TypeError` synchronously.
65+
66+
**Idempotency / replacement order:** `replace` is last-write-wins. Each
67+
call replaces the previously registered implementation atomically. The
68+
returned value is `None`; callers that need the previous implementation
69+
should capture it before calling `replace`.
70+
71+
**When it may be called:** any time before the first call site that
72+
exercises the extension point. The recommended placement is application
73+
boot (a single `install_extension_points()` function called from the
74+
FastAPI lifespan, before the first request is served). Calling
75+
`replace` after the EP has been exercised is allowed and atomic, but
76+
in-flight calls observe the previous implementation.
77+
78+
**Thread / async safety:** `replace` is safe to call from any thread
79+
and from inside an async coroutine. Reads of the active implementation
80+
are lock-free.
81+
82+
**Failure modes:**
83+
- Unknown `name``KeyError`.
84+
- `impl` does not satisfy the Protocol → `TypeError`.
85+
- `impl` is `None``TypeError` (use a distinct reset helper, not
86+
`replace`).
87+
88+
A complementary helper, `evo_extension_points.reset(name: str) -> None`,
89+
restores the community default for a given extension point. Calling
90+
`reset` with an unknown name raises `KeyError`.
91+
92+
---
93+
94+
## Extension points
95+
96+
All three are exposed under the `evo_extension_points` package,
97+
implemented by `src/evo_extension_points/` (shipped in a complementary
98+
story). Contracts are declared as `typing.Protocol` so that consumers
99+
get static type checking without inheritance. Each extension point
100+
exposes its own version as `<extension_point>.VERSION` (e.g.
101+
`evo_extension_points.capability_gate.VERSION == "1.0.0"`); there is
102+
no aggregate `EXTENSION_POINTS_VERSION` constant — version each EP
103+
independently.
104+
105+
### 1. `capability_gate`
106+
107+
**Version:** `1.0.0`
108+
**Default:** always returns `True`; the community release does not
109+
filter capabilities.
110+
111+
```python
112+
from typing import Protocol
113+
114+
class CapabilityGate(Protocol):
115+
def is_enabled(self, capability: str, *, context: dict | None = None) -> bool: ...
116+
```
117+
118+
Default access:
119+
120+
```python
121+
from evo_extension_points import capability_gate
122+
123+
capability_gate.is_enabled("vision", context={"model": "gpt-4o"}) # => True
124+
```
125+
126+
Override:
127+
128+
```python
129+
import evo_extension_points
130+
131+
class MyCapabilityGate:
132+
def is_enabled(self, capability: str, *, context: dict | None = None) -> bool:
133+
return my_consumer.capabilities.enabled(capability, context=context)
134+
135+
evo_extension_points.replace("capability_gate", MyCapabilityGate())
136+
```
137+
138+
**Breaking-change policy:** renaming `is_enabled`, adding a required
139+
positional argument, or changing the return type from `bool` is a major
140+
bump. Adding a new key to `context` or a new accepted `capability`
141+
string is a minor bump.
142+
143+
### 2. `runtime_context`
144+
145+
**Version:** `1.0.0`
146+
**Default:** `current_context_id` returns `None`; `with_context` yields
147+
the callable's result without binding any state (single-scope mode).
148+
149+
```python
150+
from typing import Protocol, Callable, TypeVar
151+
152+
T = TypeVar("T")
153+
154+
class RuntimeContext(Protocol):
155+
def current_context_id(self, request) -> str | None: ...
156+
def with_context(self, context_id: str, fn: Callable[[], T]) -> T: ...
157+
```
158+
159+
`request` is the framework-native request object (FastAPI / Starlette
160+
`Request`). The default implementation reads no headers and binds no
161+
state; consumers wire their own resolution from a neutral header such as
162+
`X-Operational-Context`.
163+
164+
Override:
165+
166+
```python
167+
import evo_extension_points
168+
from my_consumer import current_context
169+
170+
class MyRuntimeContext:
171+
def current_context_id(self, request) -> str | None:
172+
return request.headers.get("X-Operational-Context")
173+
174+
def with_context(self, context_id, fn):
175+
with current_context.bound(context_id):
176+
return fn()
177+
178+
evo_extension_points.replace("runtime_context", MyRuntimeContext())
179+
```
180+
181+
**Breaking-change policy:** renaming `current_context_id` /
182+
`with_context`, or changing the return type of `current_context_id`
183+
from `str | None`, is a major bump. Adding sibling helpers is a minor
184+
bump.
185+
186+
### 3. `usage_reporter`
187+
188+
**Version:** `1.0.0`
189+
**Default:** no-op. The community release always persists each
190+
execution into `evo_agent_processor_execution_metrics` locally and
191+
then calls `report_execution` once with the same data, regardless of
192+
which implementation is installed; the default implementation discards
193+
the call. An external consumer registers a non-default implementation
194+
to mirror the local table into external observability.
195+
196+
```python
197+
from dataclasses import dataclass
198+
from typing import Protocol
199+
200+
@dataclass(frozen=True)
201+
class ExecutionMetrics:
202+
execution_id: str
203+
prompt_tokens: int
204+
candidate_tokens: int
205+
total_tokens: int
206+
cost: float
207+
208+
class UsageReporter(Protocol):
209+
def report_execution(self, metrics: ExecutionMetrics) -> None: ...
210+
```
211+
212+
`execution_id` is the neutral identifier of the agent execution emitted
213+
by the processor; consumers correlate it back to their own systems.
214+
`cost` is the monetary value (`float`) already computed by the processor
215+
in its base currency.
216+
217+
**Call site and threading model:** the processor invokes
218+
`report_execution` inline at the end of the agent execution
219+
coroutine, on the FastAPI event loop. The override therefore runs on
220+
the event loop; a blocking implementation will block other requests
221+
served by the same worker. Consumers MUST keep the call non-blocking:
222+
either return immediately and enqueue the work elsewhere
223+
(`asyncio.create_task`, a background queue, a sidecar), or offload
224+
synchronous work via `asyncio.to_thread`. The Protocol is declared
225+
synchronous at v1.0.0; converting it to `async def` is a major bump.
226+
227+
Exceptions raised by `report_execution` are caught and logged at
228+
`WARNING` by the processor and do not abort the agent execution or
229+
the parent HTTP response — the local persistence into
230+
`evo_agent_processor_execution_metrics` is already committed by then.
231+
232+
Override:
233+
234+
```python
235+
import evo_extension_points
236+
from evo_extension_points import ExecutionMetrics
237+
238+
class MyUsageReporter:
239+
def report_execution(self, metrics: ExecutionMetrics) -> None:
240+
my_consumer.metrics.publish(
241+
execution_id=metrics.execution_id,
242+
tokens=metrics.total_tokens,
243+
cost=metrics.cost,
244+
)
245+
246+
evo_extension_points.replace("usage_reporter", MyUsageReporter())
247+
```
248+
249+
**Breaking-change policy:** renaming `report_execution`, removing or
250+
retyping a field of `ExecutionMetrics`, or changing the call from
251+
synchronous to asynchronous semantics is a major bump. Adding new
252+
optional fields to `ExecutionMetrics` (with a sane default) is a minor
253+
bump.
254+
255+
---
256+
257+
## How to use as a consumer
258+
259+
A consumer wires its replacements once, from its own bootstrap module,
260+
and never patches files inside `evo-ai-processor-community`:
261+
262+
```python
263+
import evo_extension_points
264+
from evo_extension_points import ExecutionMetrics
265+
266+
class MyCapabilityGate:
267+
def is_enabled(self, capability: str, *, context: dict | None = None) -> bool:
268+
return my_consumer.capabilities.enabled(capability, context=context)
269+
270+
class MyRuntimeContext:
271+
def current_context_id(self, request) -> str | None:
272+
return request.headers.get("X-Operational-Context")
273+
274+
def with_context(self, context_id, fn):
275+
with my_consumer.current_context.bound(context_id):
276+
return fn()
277+
278+
class MyUsageReporter:
279+
def report_execution(self, metrics: ExecutionMetrics) -> None:
280+
my_consumer.metrics.publish(
281+
execution_id=metrics.execution_id,
282+
tokens=metrics.total_tokens,
283+
cost=metrics.cost,
284+
)
285+
286+
def install_extension_points() -> None:
287+
evo_extension_points.replace("capability_gate", MyCapabilityGate())
288+
evo_extension_points.replace("runtime_context", MyRuntimeContext())
289+
evo_extension_points.replace("usage_reporter", MyUsageReporter())
290+
```
291+
292+
A consumer is expected to declare the community version range it
293+
supports in its own package metadata (`pyproject.toml`). A future CI
294+
workflow (`extension-points-contract`) will run a neutral consumer
295+
stub against every community PR and fail the build on a contract
296+
break; until that workflow lands, contract regressions are caught by
297+
manual review of changes to this file and the
298+
`src/evo_extension_points/` implementation.
299+
300+
---
301+
302+
## Cross-references
303+
304+
- Companion contract on the CRM side:
305+
[evo-ai-crm-community/EXTENSION_POINTS.md](https://github.com/evolution-foundation/evo-ai-crm-community/blob/main/EXTENSION_POINTS.md).
306+
- Companion contract on the auth-service side:
307+
[evo-auth-service-community/EXTENSION_POINTS.md](https://github.com/evolution-foundation/evo-auth-service-community/blob/main/EXTENSION_POINTS.md).
308+
- The architectural decision that motivates this contract is **ADR13 —
309+
Extension Points Versioning Strategy**.
310+
311+
---
312+
313+
## Versioning history
314+
315+
Each line below tracks one independently versioned surface. The
316+
document itself is unversioned.
317+
318+
- Registration API `1.0.0` — Initial: `replace(name, impl)` +
319+
`reset(name)`.
320+
- `capability_gate` `1.0.0` — Initial contract.
321+
- `runtime_context` `1.0.0` — Initial contract.
322+
- `usage_reporter` `1.0.0` — Initial contract.

0 commit comments

Comments
 (0)