Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed

- Backend compilation warnings in `backend/src/engine/mod.rs`.
- Aligned Python and TypeScript SDK calls with Cloud REST and MCP routes, including
identity, dream-cycle, and administrator registration contracts.


## [1.0.1] - 2026-06-21
Expand Down
75 changes: 75 additions & 0 deletions backend/docs/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,81 @@ paths:
labels: { type: array, items: { type: string } }
timestamp: { type: integer }

/identity/step:
post:
tags: [Auth]
summary: Submit one identity ritual step
description: Complete steps 1 through 5 in order, then call `/identity/finalize`.
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [step, value]
properties:
step:
type: integer
minimum: 1
maximum: 5
value:
type: string
minLength: 1
responses:
"200":
description: Identity ritual progress
content:
application/json:
schema:
type: object
properties:
success: { type: boolean }
step: { type: integer }
progress:
type: object
properties:
completed: { type: integer }
total: { type: integer, example: 5 }
current_step: { type: integer }
next_prompt: { type: string }
pending:
type: object
properties:
has_name: { type: boolean }
has_mission: { type: boolean }
has_author: { type: boolean }
has_personality: { type: boolean }
has_language: { type: boolean }
"400":
$ref: "#/components/responses/BadRequest"

/identity/finalize:
post:
tags: [Auth]
summary: Complete the identity ritual
responses:
"200":
description: Identity awakened
content:
application/json:
schema:
type: object
properties:
success: { type: boolean }
awakened: { type: boolean }
identity:
type: object
properties:
name: { type: string }
mission: { type: string }
author: { type: string }
personality: { type: string }
language: { type: string }
confirmed: { type: boolean }
message: { type: string }
"400":
$ref: "#/components/responses/BadRequest"

/digest:
post:
tags: [Ingestion]
Expand Down
18 changes: 16 additions & 2 deletions backend/sdk/python/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Epicode SDK for Python

Python client library for the Epicode API.
Python client library for the Epicode API (v1.0.2).

## Installation

Expand All @@ -16,6 +16,13 @@ from epicode import EpicodeClient

client = EpicodeClient("your-api-key")

# A confirmed identity is required before working with memories.
for step, value in enumerate(
["MyAssistant", "Help users", "Example author", "Helpful", "English"], start=1
):
client.identity_step(step, value)
client.identity_finalize()

# Store a memory
mem = client.remember("The project deadline is June 15.")
print(mem.id, mem.labels)
Expand Down Expand Up @@ -65,7 +72,7 @@ from epicode import EpicodeAdmin
admin = EpicodeAdmin("your-admin-key")

# Register a new user
user = admin.register("alice", plan="pro")
user = admin.register("alice", "a-secure-password", plan="pro")
print(user.api_key, user.max_memories)

# List users
Expand All @@ -79,6 +86,13 @@ print(stats.max_users)
admin.close()
```

## API Compatibility

The SDK calls the Cloud API under `/api/v1`. Complete the five identity steps
and call `identity_finalize()` before storing memories. `dream_cycle()` uses the
supported MCP JSON-RPC endpoint; graph relations are available through
`knowledge(id)`.

## Error Handling

```python
Expand Down
24 changes: 15 additions & 9 deletions backend/sdk/python/epicode/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,22 @@
CreateNodeResponse,
Emotion,
HealthResponse,
KnowledgeGraphResponse,
Memory,
MemoryFragment,
NodeData,
IdentityFinalizeResponse,
IdentityStepResponse,
KnowledgeResponse,
McpToolResponse,
NodeResponse,
RecallResponse,
RememberResponse,
RegisterResponse,
SearchResult,
SearchResponse,
StatsResponse,
TimelineEvent,
TimelineResponse,
)

__version__ = "1.0.1" # x-release-please-version
__version__ = "1.0.2" # x-release-please-version
__all__ = [
"EpicodeClient",
"EpicodeAdmin",
Expand All @@ -47,12 +50,15 @@
"CreateNodeResponse",
"Emotion",
"HealthResponse",
"KnowledgeGraphResponse",
"Memory",
"MemoryFragment",
"NodeData",
"IdentityFinalizeResponse",
"IdentityStepResponse",
"KnowledgeResponse",
"McpToolResponse",
"NodeResponse",
"RecallResponse",
"RememberResponse",
"RegisterResponse",
"SearchResult",
"SearchResponse",
"StatsResponse",
"TimelineEvent",
Expand Down
18 changes: 14 additions & 4 deletions backend/sdk/python/epicode/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ def __init__(
self._base_url = (base_url or self.DEFAULT_BASE_URL).rstrip("/")
self._timeout = timeout or self.DEFAULT_TIMEOUT
self._session = session or requests.Session()
self._session.headers.update({"X-Admin-Key": self._admin_key, "Content-Type": "application/json"})
self._session.headers.update(
{"X-Admin-Key": self._admin_key, "Content-Type": "application/json"}
)

# ------------------------------------------------------------------
# Internal helpers
Expand All @@ -58,7 +60,9 @@ def _handle_response(resp: requests.Response) -> dict[str, Any]:
if 200 <= code < 300:
return body

message = body.get("error") or body.get("message") or resp.text or f"HTTP {code}"
message = (
body.get("error") or body.get("message") or resp.text or f"HTTP {code}"
)

if code in (401, 403):
raise AuthenticationError(message, status_code=code, response_body=body)
Expand All @@ -77,9 +81,15 @@ def _handle_response(resp: requests.Response) -> dict[str, Any]:
# Public admin API
# ------------------------------------------------------------------

def register(self, user_id: str, *, plan: str = "free") -> RegisterResponse:
def register(
self, user_id: str, password: str, *, plan: str = "free"
) -> RegisterResponse:
"""Register a new user and obtain an API key."""
data = self._request("POST", "/register", json={"user_id": user_id, "plan": plan})
data = self._request(
"POST",
"/register",
json={"user_id": user_id, "password": password, "plan": plan},
)
return RegisterResponse(
success=data.get("success", False),
user_id=data.get("user_id", ""),
Expand Down
Loading
Loading