Skip to content

Commit 7f121ad

Browse files
tzerweckDavid
andauthored
fix(onboarding): improve hosted cli trace and prompt guidance (#124)
Co-authored-by: David <david@student-net-nw-0650.intern.ethz.ch>
1 parent f2c912a commit 7f121ad

10 files changed

Lines changed: 529 additions & 84 deletions

File tree

README.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ print(agent.get_strategies())
7272

7373
No fine-tuning, no training data, no vector database.
7474

75-
[-> Quick Start Guide](https://kayba-ai.github.io/agentic-context-engine/latest/getting-started/quick-start/) | [-> Setup Guide](https://kayba-ai.github.io/agentic-context-engine/latest/getting-started/setup/)
75+
[-> Quick Start Guide](https://kayba-ai.github.io/agentic-context-engine/latest/getting-started/quick-start/) | [-> Setup Guide](https://kayba-ai.github.io/agentic-context-engine/latest/getting-started/setup/) | [-> Hosted API: Where Do Traces Come From?](https://kayba-ai.github.io/agentic-context-engine/latest/integrations/hosted-api/#where-do-traces-come-from)
7676

7777
---
7878

@@ -117,11 +117,11 @@ All roles are backed by [PydanticAI](https://ai.pydantic.dev/) agents with struc
117117
| **Claude Code** | `ClaudeCode` | Claude Code CLI tasks with learning |
118118

119119
```bash
120-
uv add ace-framework[browser-use] # Browser automation
121-
uv add ace-framework[langchain] # LangChain
122-
uv add ace-framework[logfire] # Observability (auto-instruments PydanticAI)
123-
uv add ace-framework[mcp] # MCP server for IDE integration
124-
uv add ace-framework[deduplication] # Embedding-based skill deduplication
120+
uv add 'ace-framework[browser-use]' # Browser automation
121+
uv add 'ace-framework[langchain]' # LangChain
122+
uv add 'ace-framework[logfire]' # Observability (auto-instruments PydanticAI)
123+
uv add 'ace-framework[mcp]' # MCP server for IDE integration
124+
uv add 'ace-framework[deduplication]' # Embedding-based skill deduplication
125125
```
126126

127127
Have existing agent logs? Extract strategies from them directly:
@@ -202,6 +202,7 @@ The pipeline engine ([`pipeline/`](pipeline/)) is framework-agnostic with `requi
202202
- [Full Documentation](https://kayba-ai.github.io/agentic-context-engine/latest/) — Guides, API reference, examples
203203
- [Quick Start](https://kayba-ai.github.io/agentic-context-engine/latest/getting-started/quick-start/) — 5-minute setup
204204
- [Setup Guide](https://kayba-ai.github.io/agentic-context-engine/latest/getting-started/setup/) — Configuration and providers
205+
- [Hosted API Guide](https://kayba-ai.github.io/agentic-context-engine/latest/integrations/hosted-api/) — Hosted CLI, trace upload, prompt install
205206
- [Architecture](docs/design/ACE_ARCHITECTURE.md) — Core concepts and system design
206207
- [Code Reference](docs/design/ACE_REFERENCE.md) — Implementations, API, usage examples
207208
- [Design Decisions](docs/design/ACE_DECISIONS.md) — Rejected alternatives and rationale

ace/cli/client.py

Lines changed: 93 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22

33
from __future__ import annotations
44

5+
import json
56
import os
7+
import re
68
from typing import Any, Dict, List, Optional
79

8-
import requests
9-
1010

1111
class KaybaAPIError(Exception):
1212
"""Structured error from the Kayba API."""
@@ -19,6 +19,39 @@ def __init__(self, code: str, message: str, status_code: int = 0):
1919

2020

2121
DEFAULT_BASE_URL = "https://use.kayba.ai/api"
22+
MAX_TRACE_UPLOAD_BODY_BYTES = 900_000
23+
24+
25+
def _chunk_trace_uploads(
26+
traces: List[Dict[str, Any]],
27+
) -> List[List[Dict[str, Any]]]:
28+
"""Split uploads into request-sized batches under the body size cap."""
29+
batches: List[List[Dict[str, Any]]] = []
30+
current: List[Dict[str, Any]] = []
31+
current_size = len('{"traces":[]}')
32+
33+
for trace in traces:
34+
trace_size = len(
35+
json.dumps(trace, ensure_ascii=False, separators=(",", ":")).encode(
36+
"utf-8"
37+
)
38+
)
39+
separator_size = 1 if current else 0
40+
candidate_size = current_size + separator_size + trace_size
41+
42+
if current and candidate_size > MAX_TRACE_UPLOAD_BODY_BYTES:
43+
batches.append(current)
44+
current = [trace]
45+
current_size = len('{"traces":[]}') + trace_size
46+
continue
47+
48+
current.append(trace)
49+
current_size = candidate_size
50+
51+
if current:
52+
batches.append(current)
53+
54+
return batches
2255

2356

2457
class KaybaClient:
@@ -35,6 +68,16 @@ def __init__(
3568
api_key: Optional[str] = None,
3669
base_url: Optional[str] = None,
3770
):
71+
try:
72+
import requests
73+
except ImportError as exc:
74+
raise KaybaAPIError(
75+
"DEPENDENCY_MISSING",
76+
"The hosted Kayba CLI requires the cloud extra. Install with "
77+
"`uv add \"ace-framework[cloud]\"` or "
78+
"`pip install 'ace-framework[cloud]'`.",
79+
) from exc
80+
3881
self.api_key = api_key or os.environ.get("KAYBA_API_KEY", "")
3982
if not self.api_key:
4083
raise KaybaAPIError(
@@ -44,9 +87,19 @@ def __init__(
4487
self.base_url = (
4588
base_url or os.environ.get("KAYBA_API_URL") or DEFAULT_BASE_URL
4689
).rstrip("/")
47-
self.session = requests.Session()
90+
self.session: Any = requests.Session()
4891
self.session.headers["Authorization"] = f"Bearer {self.api_key}"
4992

93+
@staticmethod
94+
def _summarize_http_body(body: str, limit: int = 240) -> str:
95+
"""Collapse whitespace so raw HTML and proxy errors stay readable."""
96+
snippet = re.sub(r"\s+", " ", body or "").strip()
97+
if not snippet:
98+
return "Unexpected non-JSON error from the Kayba API."
99+
if len(snippet) <= limit:
100+
return snippet
101+
return snippet[: limit - 3] + "..."
102+
50103
def _request(
51104
self,
52105
method: str,
@@ -69,15 +122,36 @@ def _request(
69122
message=err,
70123
status_code=resp.status_code,
71124
)
125+
message = err.get("message", resp.text)
126+
if (
127+
resp.status_code == 413
128+
or "maximum content size" in message.lower()
129+
or "too large" in message.lower()
130+
):
131+
raise KaybaAPIError(
132+
code="PAYLOAD_TOO_LARGE",
133+
message=message,
134+
status_code=resp.status_code,
135+
)
72136
raise KaybaAPIError(
73137
code=err.get("code", "UNKNOWN"),
74-
message=err.get("message", resp.text),
138+
message=message,
75139
status_code=resp.status_code,
76140
)
77141
except (ValueError, KeyError, AttributeError):
142+
message = self._summarize_http_body(resp.text)
143+
if resp.status_code == 413:
144+
message = (
145+
"Upload rejected because the request body is too large. "
146+
"Try smaller traces or upload fewer files at once."
147+
)
148+
elif resp.status_code in (401, 403):
149+
message = "Authentication failed; check KAYBA_API_KEY"
150+
else:
151+
message = f"HTTP {resp.status_code} from Kayba API: {message}"
78152
raise KaybaAPIError(
79153
code="HTTP_ERROR",
80-
message=resp.text,
154+
message=message,
81155
status_code=resp.status_code,
82156
)
83157

@@ -93,7 +167,20 @@ def upload_traces(self, traces: List[Dict[str, Any]]) -> Dict[str, Any]:
93167
Args:
94168
traces: List of dicts with keys: filename, content, fileType.
95169
"""
96-
return self._request("POST", "/traces", json={"traces": traces})
170+
batches = _chunk_trace_uploads(traces)
171+
if len(batches) == 1:
172+
return self._request("POST", "/traces", json={"traces": traces})
173+
174+
combined: Dict[str, Any] = {"count": 0, "traces": []}
175+
for batch in batches:
176+
result = self._request("POST", "/traces", json={"traces": batch})
177+
uploaded = result.get("traces", [])
178+
combined["count"] += result.get("count", len(uploaded) or len(batch))
179+
combined["traces"].extend(uploaded)
180+
for key, value in result.items():
181+
if key not in {"count", "traces"} and key not in combined:
182+
combined[key] = value
183+
return combined
97184

98185
def list_traces(self) -> Dict[str, Any]:
99186
"""List all traces (metadata only, no content)."""

0 commit comments

Comments
 (0)