trytilde 3.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- trytilde-3.0.1/.gitignore +51 -0
- trytilde-3.0.1/PKG-INFO +325 -0
- trytilde-3.0.1/README.md +307 -0
- trytilde-3.0.1/hatch_build.py +70 -0
- trytilde-3.0.1/pyproject.toml +34 -0
- trytilde-3.0.1/src/tilde/__init__.py +114 -0
- trytilde-3.0.1/src/tilde/__main__.py +9 -0
- trytilde-3.0.1/src/tilde/_cancel.py +50 -0
- trytilde-3.0.1/src/tilde/_inference.py +106 -0
- trytilde-3.0.1/src/tilde/_invocation.py +26 -0
- trytilde-3.0.1/src/tilde/_otlp.py +35 -0
- trytilde-3.0.1/src/tilde/_tools.py +182 -0
- trytilde-3.0.1/src/tilde/_transport.py +28 -0
- trytilde-3.0.1/src/tilde/agent_event_ingress/__init__.py +0 -0
- trytilde-3.0.1/src/tilde/agent_event_ingress/v1/__init__.py +0 -0
- trytilde-3.0.1/src/tilde/agent_event_ingress/v1/sidecars_connect.py +748 -0
- trytilde-3.0.1/src/tilde/agent_event_ingress/v1/sidecars_pb2.py +128 -0
- trytilde-3.0.1/src/tilde/agent_event_ingress/v1/sidecars_pb2.pyi +471 -0
- trytilde-3.0.1/src/tilde/agent_host/__init__.py +0 -0
- trytilde-3.0.1/src/tilde/agent_host/v1/__init__.py +0 -0
- trytilde-3.0.1/src/tilde/agent_host/v1/agent_pb2.py +43 -0
- trytilde-3.0.1/src/tilde/agent_host/v1/agent_pb2.pyi +56 -0
- trytilde-3.0.1/src/tilde/channels.py +212 -0
- trytilde-3.0.1/src/tilde/chat_proxy.py +295 -0
- trytilde-3.0.1/src/tilde/clients.py +127 -0
- trytilde-3.0.1/src/tilde/context.py +1006 -0
- trytilde-3.0.1/src/tilde/deploy.py +420 -0
- trytilde-3.0.1/src/tilde/discovery.py +127 -0
- trytilde-3.0.1/src/tilde/fastapi.py +69 -0
- trytilde-3.0.1/src/tilde/host.py +481 -0
- trytilde-3.0.1/src/tilde/logging.py +213 -0
- trytilde-3.0.1/src/tilde/management/__init__.py +0 -0
- trytilde-3.0.1/src/tilde/management/v1/__init__.py +0 -0
- trytilde-3.0.1/src/tilde/management/v1/access_connect.py +465 -0
- trytilde-3.0.1/src/tilde/management/v1/access_pb2.py +57 -0
- trytilde-3.0.1/src/tilde/management/v1/access_pb2.pyi +102 -0
- trytilde-3.0.1/src/tilde/management/v1/agents_connect.py +668 -0
- trytilde-3.0.1/src/tilde/management/v1/agents_pb2.py +73 -0
- trytilde-3.0.1/src/tilde/management/v1/agents_pb2.pyi +132 -0
- trytilde-3.0.1/src/tilde/management/v1/connections_connect.py +806 -0
- trytilde-3.0.1/src/tilde/management/v1/connections_pb2.py +93 -0
- trytilde-3.0.1/src/tilde/management/v1/connections_pb2.pyi +186 -0
- trytilde-3.0.1/src/tilde/management/v1/deployments_connect.py +919 -0
- trytilde-3.0.1/src/tilde/management/v1/deployments_pb2.py +109 -0
- trytilde-3.0.1/src/tilde/management/v1/deployments_pb2.pyi +306 -0
- trytilde-3.0.1/src/tilde/management/v1/identities_connect.py +660 -0
- trytilde-3.0.1/src/tilde/management/v1/identities_pb2.py +74 -0
- trytilde-3.0.1/src/tilde/management/v1/identities_pb2.pyi +167 -0
- trytilde-3.0.1/src/tilde/management/v1/inference_connect.py +424 -0
- trytilde-3.0.1/src/tilde/management/v1/inference_pb2.py +69 -0
- trytilde-3.0.1/src/tilde/management/v1/inference_pb2.pyi +158 -0
- trytilde-3.0.1/src/tilde/management/v1/logs_connect.py +278 -0
- trytilde-3.0.1/src/tilde/management/v1/logs_pb2.py +56 -0
- trytilde-3.0.1/src/tilde/management/v1/logs_pb2.pyi +123 -0
- trytilde-3.0.1/src/tilde/management/v1/prompts_connect.py +290 -0
- trytilde-3.0.1/src/tilde/management/v1/prompts_pb2.py +49 -0
- trytilde-3.0.1/src/tilde/management/v1/prompts_pb2.pyi +36 -0
- trytilde-3.0.1/src/tilde/management/v1/skills_connect.py +1815 -0
- trytilde-3.0.1/src/tilde/management/v1/skills_pb2.py +147 -0
- trytilde-3.0.1/src/tilde/management/v1/skills_pb2.pyi +324 -0
- trytilde-3.0.1/src/tilde/management/v1/tilde_chat_connect.py +290 -0
- trytilde-3.0.1/src/tilde/management/v1/tilde_chat_pb2.py +48 -0
- trytilde-3.0.1/src/tilde/management/v1/tilde_chat_pb2.pyi +29 -0
- trytilde-3.0.1/src/tilde/management/v1/tools_connect.py +1416 -0
- trytilde-3.0.1/src/tilde/management/v1/tools_pb2.py +135 -0
- trytilde-3.0.1/src/tilde/management/v1/tools_pb2.pyi +341 -0
- trytilde-3.0.1/src/tilde/management/v1/tracing_connect.py +513 -0
- trytilde-3.0.1/src/tilde/management/v1/tracing_pb2.py +78 -0
- trytilde-3.0.1/src/tilde/management/v1/tracing_pb2.pyi +231 -0
- trytilde-3.0.1/src/tilde/messages.py +129 -0
- trytilde-3.0.1/src/tilde/prompts.py +130 -0
- trytilde-3.0.1/src/tilde/provider/__init__.py +0 -0
- trytilde-3.0.1/src/tilde/provider/tilde/__init__.py +0 -0
- trytilde-3.0.1/src/tilde/provider/tilde/v1/__init__.py +0 -0
- trytilde-3.0.1/src/tilde/provider/tilde/v1/chat_connect.py +2090 -0
- trytilde-3.0.1/src/tilde/provider/tilde/v1/chat_pb2.py +165 -0
- trytilde-3.0.1/src/tilde/provider/tilde/v1/chat_pb2.pyi +478 -0
- trytilde-3.0.1/src/tilde/provider/v1/__init__.py +0 -0
- trytilde-3.0.1/src/tilde/provider/v1/connections_connect.py +205 -0
- trytilde-3.0.1/src/tilde/provider/v1/connections_pb2.py +49 -0
- trytilde-3.0.1/src/tilde/provider/v1/connections_pb2.pyi +58 -0
- trytilde-3.0.1/src/tilde/run/__init__.py +0 -0
- trytilde-3.0.1/src/tilde/run/v1/__init__.py +0 -0
- trytilde-3.0.1/src/tilde/run/v1/run_connect.py +367 -0
- trytilde-3.0.1/src/tilde/run/v1/run_pb2.py +57 -0
- trytilde-3.0.1/src/tilde/run/v1/run_pb2.pyi +80 -0
- trytilde-3.0.1/src/tilde/runtime/__init__.py +0 -0
- trytilde-3.0.1/src/tilde/runtime/v1/__init__.py +0 -0
- trytilde-3.0.1/src/tilde/runtime/v1/agents_connect.py +473 -0
- trytilde-3.0.1/src/tilde/runtime/v1/agents_pb2.py +61 -0
- trytilde-3.0.1/src/tilde/runtime/v1/agents_pb2.pyi +86 -0
- trytilde-3.0.1/src/tilde/runtime/v1/cache_pb2.py +38 -0
- trytilde-3.0.1/src/tilde/runtime/v1/cache_pb2.pyi +13 -0
- trytilde-3.0.1/src/tilde/runtime/v1/chat_connect.py +1460 -0
- trytilde-3.0.1/src/tilde/runtime/v1/chat_pb2.py +130 -0
- trytilde-3.0.1/src/tilde/runtime/v1/chat_pb2.pyi +275 -0
- trytilde-3.0.1/src/tilde/runtime/v1/controls_connect.py +282 -0
- trytilde-3.0.1/src/tilde/runtime/v1/controls_pb2.py +47 -0
- trytilde-3.0.1/src/tilde/runtime/v1/controls_pb2.pyi +49 -0
- trytilde-3.0.1/src/tilde/runtime/v1/prompts_connect.py +225 -0
- trytilde-3.0.1/src/tilde/runtime/v1/prompts_pb2.py +43 -0
- trytilde-3.0.1/src/tilde/runtime/v1/prompts_pb2.pyi +20 -0
- trytilde-3.0.1/src/tilde/runtime/v1/skills_connect.py +655 -0
- trytilde-3.0.1/src/tilde/runtime/v1/skills_pb2.py +77 -0
- trytilde-3.0.1/src/tilde/runtime/v1/skills_pb2.pyi +154 -0
- trytilde-3.0.1/src/tilde/setup/__init__.py +0 -0
- trytilde-3.0.1/src/tilde/setup/v1/__init__.py +0 -0
- trytilde-3.0.1/src/tilde/setup/v1/connections_connect.py +595 -0
- trytilde-3.0.1/src/tilde/setup/v1/connections_pb2.py +101 -0
- trytilde-3.0.1/src/tilde/setup/v1/connections_pb2.pyi +232 -0
- trytilde-3.0.1/src/tilde/setup/v1/identity_verification_connect.py +282 -0
- trytilde-3.0.1/src/tilde/setup/v1/identity_verification_pb2.py +49 -0
- trytilde-3.0.1/src/tilde/setup/v1/identity_verification_pb2.pyi +35 -0
- trytilde-3.0.1/src/tilde/skills.py +383 -0
- trytilde-3.0.1/src/tilde/tool_host/v1/tool_host_connect.py +314 -0
- trytilde-3.0.1/src/tilde/tool_host/v1/tool_host_pb2.py +66 -0
- trytilde-3.0.1/src/tilde/tool_host/v1/tool_host_pb2.pyi +115 -0
- trytilde-3.0.1/src/tilde/tool_hosts.py +515 -0
- trytilde-3.0.1/src/tilde/tracing.py +147 -0
- trytilde-3.0.1/src/tilde/types/__init__.py +0 -0
- trytilde-3.0.1/src/tilde/types/v1/__init__.py +0 -0
- trytilde-3.0.1/src/tilde/types/v1/access_pb2.py +47 -0
- trytilde-3.0.1/src/tilde/types/v1/access_pb2.pyi +123 -0
- trytilde-3.0.1/src/tilde/types/v1/agent_pb2.py +53 -0
- trytilde-3.0.1/src/tilde/types/v1/agent_pb2.pyi +151 -0
- trytilde-3.0.1/src/tilde/types/v1/chat_pb2.py +81 -0
- trytilde-3.0.1/src/tilde/types/v1/chat_pb2.pyi +316 -0
- trytilde-3.0.1/src/tilde/types/v1/connections_pb2.py +79 -0
- trytilde-3.0.1/src/tilde/types/v1/connections_pb2.pyi +268 -0
- trytilde-3.0.1/src/tilde/types/v1/deployment_pb2.py +51 -0
- trytilde-3.0.1/src/tilde/types/v1/deployment_pb2.pyi +129 -0
- trytilde-3.0.1/src/tilde/types/v1/prompt_pb2.py +45 -0
- trytilde-3.0.1/src/tilde/types/v1/prompt_pb2.pyi +96 -0
- trytilde-3.0.1/src/tilde/types/v1/runtime_event_pb2.py +62 -0
- trytilde-3.0.1/src/tilde/types/v1/runtime_event_pb2.pyi +243 -0
- trytilde-3.0.1/src/tilde/types/v1/skill_pb2.py +49 -0
- trytilde-3.0.1/src/tilde/types/v1/skill_pb2.pyi +150 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/target/
|
|
2
|
+
/dist/
|
|
3
|
+
node_modules/
|
|
4
|
+
# Build output of an older example-agent layout; the examples live under sdk/ts/examples.
|
|
5
|
+
/dev/example-agent-1/
|
|
6
|
+
/web/dist/
|
|
7
|
+
/.tools/
|
|
8
|
+
/.env.*
|
|
9
|
+
!/.env.example
|
|
10
|
+
!/.env.test
|
|
11
|
+
/.run/
|
|
12
|
+
.run/
|
|
13
|
+
|
|
14
|
+
/sdk/ts/**/dist/
|
|
15
|
+
# Generated TypeScript contracts; @trytilde/contracts builds them from proto/.
|
|
16
|
+
/sdk/ts/packages/contracts/gen/
|
|
17
|
+
|
|
18
|
+
/web/provider-dist/
|
|
19
|
+
|
|
20
|
+
__pycache__/
|
|
21
|
+
|
|
22
|
+
# Local runtime credentials must not enter the initial repository commit.
|
|
23
|
+
/.env
|
|
24
|
+
log
|
|
25
|
+
|
|
26
|
+
/.local/
|
|
27
|
+
|
|
28
|
+
# Bifrost benchmark container state
|
|
29
|
+
/dev/inference-bench/bifrost/*
|
|
30
|
+
!/dev/inference-bench/bifrost/config.json
|
|
31
|
+
|
|
32
|
+
# Local Claude Code worktrees
|
|
33
|
+
/.claude/worktrees/
|
|
34
|
+
|
|
35
|
+
# Python SDK: uv environments and generated contracts (sdk/py/scripts/generate.py).
|
|
36
|
+
/sdk/py/.venv/
|
|
37
|
+
/sdk/py/.venv-crewai/
|
|
38
|
+
/sdk/py/**/.venv/
|
|
39
|
+
/sdk/py/packages/tilde/src/tilde/agent_event_ingress/
|
|
40
|
+
/sdk/py/packages/tilde/src/tilde/agent_host/
|
|
41
|
+
/sdk/py/packages/tilde/src/tilde/ingress/
|
|
42
|
+
/sdk/py/packages/tilde/src/tilde/management/
|
|
43
|
+
/sdk/py/packages/tilde/src/tilde/provider/
|
|
44
|
+
/sdk/py/packages/tilde/src/tilde/run/
|
|
45
|
+
/sdk/py/packages/tilde/src/tilde/runtime/
|
|
46
|
+
/sdk/py/packages/tilde/src/tilde/setup/
|
|
47
|
+
/sdk/py/packages/tilde/src/tilde/tool_host/
|
|
48
|
+
/sdk/py/packages/tilde/src/tilde/types/
|
|
49
|
+
/sdk/py/**/*.egg-info/
|
|
50
|
+
.pytest_cache/
|
|
51
|
+
.ruff_cache/
|
trytilde-3.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: trytilde
|
|
3
|
+
Version: 3.0.1
|
|
4
|
+
Summary: Tilde agent SDK for Python: dial-in agent hosts, invocation context, channel tools, FastAPI integration and the chat proxy
|
|
5
|
+
License-Expression: Apache-2.0
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Requires-Dist: connectrpc<1,>=0.12
|
|
8
|
+
Requires-Dist: httpx>=0.27
|
|
9
|
+
Requires-Dist: opentelemetry-api>=1.30
|
|
10
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-common>=1.30
|
|
11
|
+
Requires-Dist: opentelemetry-sdk>=1.30
|
|
12
|
+
Requires-Dist: protobuf>=5.29
|
|
13
|
+
Requires-Dist: pydantic>=2.7
|
|
14
|
+
Requires-Dist: pyqwest>=0.5
|
|
15
|
+
Provides-Extra: fastapi
|
|
16
|
+
Requires-Dist: fastapi>=0.115; extra == 'fastapi'
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# Tilde Python SDK
|
|
20
|
+
|
|
21
|
+
`trytilde` (import `tilde`) is the Python counterpart of `@trytilde/sdk`: the same dial-in
|
|
22
|
+
agent host, invocation-bound context, channel tools, message history, run reports, tracing and
|
|
23
|
+
logging, plus FastAPI integration and the server-side chat proxy. Framework adapters live in
|
|
24
|
+
sibling packages: `trytilde-langchain`, `trytilde-pydantic-ai`, `trytilde-openai-agents` and
|
|
25
|
+
`trytilde-agno`.
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
cd sdk/py
|
|
29
|
+
uv sync --all-packages # workspace environment with every package and example
|
|
30
|
+
uv run python scripts/generate.py # Protobuf messages and Connect stubs from proto/
|
|
31
|
+
uv run pytest # adapter unit tests and the host tests against a fake gateway
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Generated contracts sit inside the package under the proto package names
|
|
35
|
+
(`tilde.runtime.v1.chat_pb2`, `tilde.types.v1.chat_pb2`, `tilde.run.v1.run_connect`, ...);
|
|
36
|
+
they are not committed. Every RPC uses `connectrpc` with Google protobuf messages over
|
|
37
|
+
`pyqwest` HTTP/2.
|
|
38
|
+
|
|
39
|
+
## Agent host
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
from tilde import AgentContext, run_connected_agent
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
async def run(ctx: AgentContext) -> None:
|
|
46
|
+
await ctx.reason("Inspecting the request.") # execution activity, never a message
|
|
47
|
+
goal = await ctx.goals.create(objective=ctx.objective)
|
|
48
|
+
task = await ctx.tasks.create(title="Reply", goal_id=goal.id)
|
|
49
|
+
|
|
50
|
+
async def words():
|
|
51
|
+
yield "Hello "
|
|
52
|
+
yield "there."
|
|
53
|
+
|
|
54
|
+
await ctx.send_native_message(words())
|
|
55
|
+
await ctx.tasks.update(id=task.id, status="completed")
|
|
56
|
+
await ctx.goals.update(id=goal.id, status="completed")
|
|
57
|
+
await ctx.set_run_status("completed")
|
|
58
|
+
ctx.stop()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
run_connected_agent(run=run) # TILDE_GATEWAY_URL and TILDE_DEPLOYMENT_TOKEN from the environment
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Tilde never calls an agent over HTTP. A host dials out: `connect_agent` opens
|
|
65
|
+
`tilde.run.v1.RunService.Watch` with the deployment token (from `RegisterDeployment` or
|
|
66
|
+
`IssueDeploymentToken`) and receives each wake as a stream frame, so the process needs no
|
|
67
|
+
inbound port, public URL or shared secret. Wakes run concurrently; the host heartbeats every
|
|
68
|
+
3 seconds and reconnects after 1 second whenever the stream ends, until `close()`.
|
|
69
|
+
`run_connected_agent(**options)` is the blocking form for standalone scripts: it connects,
|
|
70
|
+
waits for SIGINT/SIGTERM and closes. Inside an existing event loop use `connect_agent`:
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from tilde import connect_agent
|
|
74
|
+
|
|
75
|
+
host = connect_agent(gateway_url=RUNTIME_URL, deployment_token=TOKEN, run=run, ready=database_is_up)
|
|
76
|
+
await host.wait() # until host.close()
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`gateway_url` and `deployment_token` default to `TILDE_GATEWAY_URL` and
|
|
80
|
+
`TILDE_DEPLOYMENT_TOKEN`. `ready` is an optional sync or async check whose result is sent with
|
|
81
|
+
every heartbeat (not ready when it raises); Tilde only routes wakes to ready instances.
|
|
82
|
+
|
|
83
|
+
For each wake the host opens `InvocationControlService.WatchCommands` before user code starts,
|
|
84
|
+
fetches the invocation-scoped tool catalog, and reports `accepted`, every `reason()` delta and
|
|
85
|
+
exactly one `stopped` through `RunService.Report` with the invocation token.
|
|
86
|
+
|
|
87
|
+
`ctx.tools` holds SDK-local `stop`, `goals.*` and `tasks.*` helpers plus provider tools with
|
|
88
|
+
server-authored descriptions, JSON schemas and optional chunk schemas. `ctx.channel.current`
|
|
89
|
+
exposes only the inbound connection's tools; `ctx.channel.slack`, `github`, `agentmail`, `linq`,
|
|
90
|
+
`whatsapp`, `telnyx_whatsapp` and `native` resolve providers, `ctx.channel.for_connection(id)`
|
|
91
|
+
and `ctx.channel.provider(id)` select explicitly, and `call_channel_tool(name, json)` reaches
|
|
92
|
+
custom providers. Tools are awaited with the framework's tool-call ID:
|
|
93
|
+
`await ctx.channel.slack.send_message({"channelId": "C1", "text": "Hi"}, tool_call_id=call_id)`.
|
|
94
|
+
Streaming tools accept an async iterable of chunks in the provider's chunk format.
|
|
95
|
+
|
|
96
|
+
Provider tools carry `summary`, `output_schema`, `annotations` and `background`;
|
|
97
|
+
`ctx.tool_source(slug)` returns one tool source's tools keyed by tool name. `ctx.agent_tools`
|
|
98
|
+
collects everything Tilde gives the agent besides messaging (tool sources, Tilde's built-in
|
|
99
|
+
tools, personal tools), beside `ctx.channel.current`.
|
|
100
|
+
|
|
101
|
+
The agent's bundled tools are its own framework tools, written the framework's way and run in
|
|
102
|
+
its process. Each adapter's `await with_tilde_tools(ctx, native_tools, options=...)` returns the
|
|
103
|
+
framework's tool collection with the current channel's tools, `ctx.agent_tools` and the native
|
|
104
|
+
tools, publishes the native tools to Tilde (so `tools.search` and `tools.schemas` describe them,
|
|
105
|
+
a `tools.execute` naming one runs it here, and the agent's Tools tab lists them read-only), and
|
|
106
|
+
audits every call once with its summary. `BundledOptions(summary=..., display="summary",
|
|
107
|
+
annotations=ToolAnnotations(read_only=True))` sets the Tilde metadata per tool name; `display`
|
|
108
|
+
(`"full"`, `"summary"` or `"hidden"`) is how its calls show in end-user chats, and traces keep
|
|
109
|
+
full detail. Call it once per invocation; the set is replaced each time.
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
from langchain_core.tools import tool
|
|
113
|
+
from tilde import BundledOptions
|
|
114
|
+
from tilde_langchain import with_tilde_tools
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@tool
|
|
118
|
+
def read_file(path: str) -> str:
|
|
119
|
+
"""Read a file from the workspace."""
|
|
120
|
+
return Path(path).read_text()
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
tools = await with_tilde_tools(
|
|
124
|
+
ctx, [read_file], options={"read_file": BundledOptions(summary="Read a file")}
|
|
125
|
+
)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
`ctx.message.history(limit=..., before_message_id=..., include_objective=True, include_work=False)`
|
|
129
|
+
returns typed `ConversationMessage`, `ObjectiveMessage`, `GoalMessage` and `TaskMessage` items;
|
|
130
|
+
the adapters convert them to framework messages and hydrate attachments through the scoped
|
|
131
|
+
`ctx.attachments.download`. `ctx.agents.*` and `ctx.invoke_agent(...)` call the registry with the
|
|
132
|
+
current token; the server checks every grant.
|
|
133
|
+
|
|
134
|
+
Cancellation is cooperative asyncio cancellation: a stop control or a lost callback connection
|
|
135
|
+
cancels the task running `run`, and `ctx.stop()` raises `StopLoop` (a `BaseException`, so it
|
|
136
|
+
passes through framework `except Exception` handlers). Call `ctx.check()` at framework
|
|
137
|
+
checkpoints. Returning from `run` never publishes a message; unfinished runs become waiting
|
|
138
|
+
unless `set_run_status` says otherwise. Connect tokens are renewed at four minutes; a denied
|
|
139
|
+
renewal aborts execution. `checkpoint=` receives the context on suspension and must quiesce
|
|
140
|
+
the framework before returning.
|
|
141
|
+
|
|
142
|
+
## Inference, prompts and skills
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
import tilde
|
|
146
|
+
|
|
147
|
+
MODEL = tilde.inference("default") # module scope: resolves the running invocation per request
|
|
148
|
+
SYSTEM = tilde.define_prompt(
|
|
149
|
+
"system",
|
|
150
|
+
template="You are {{name}}.\n{{> rules}}",
|
|
151
|
+
sections={"rules": "Be brief."},
|
|
152
|
+
config={"model": "gpt-4o-mini"},
|
|
153
|
+
)
|
|
154
|
+
SKILLS = tilde.define_skills("skills") # folders holding a SKILL.md, relative to this file
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
async def run(ctx: tilde.AgentContext) -> None:
|
|
158
|
+
client = AsyncOpenAI(
|
|
159
|
+
base_url=MODEL.base_url, api_key=MODEL.api_key, http_client=MODEL.async_client()
|
|
160
|
+
)
|
|
161
|
+
instructions = SYSTEM.render(name="Support") # marks the prompt active for this invocation
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
`tilde.inference(alias)` returns the same `Inference` as `ctx.inference(alias)`, but every
|
|
165
|
+
request resolves the invocation running it (token, callback URL, active prompt stamps), so model
|
|
166
|
+
clients can be built at import time; a request outside `run(ctx)` raises. Both send
|
|
167
|
+
`x-tilde-prompt: name@hash[, ...]` for prompts made active with `ctx.prompt(definition)` or by
|
|
168
|
+
rendering one inside the invocation. `define_skill(name, description, instructions, files=...)`
|
|
169
|
+
declares a skill in code with a generated `SKILL.md`.
|
|
170
|
+
|
|
171
|
+
At runtime `ctx.skills` reads every skill the invocation may use: `list()` (each
|
|
172
|
+
`SkillSummary` says whether it is `deployed`, shipped beside the code, or assigned through the
|
|
173
|
+
registry), `read(name, path="SKILL.md")` (text inline, other files as a short-lived
|
|
174
|
+
`download_url`) and `summary()` (a system-prompt block). `directory()` keeps the registry skills
|
|
175
|
+
in `$TILDE_SKILLS_DIR` (the OS temp directory by default) `/tilde-skills/<agent id>/<name>/…`
|
|
176
|
+
for frameworks that load skill folders from disk: the engine pushes the invocation's skills and
|
|
177
|
+
versions with each wake, and only new and newer versions are downloaded and removed skills
|
|
178
|
+
deleted, so a skill assigned in Tilde reaches
|
|
179
|
+
the next invocation without a redeploy. Frameworks without native skills convert
|
|
180
|
+
`ctx.skills.tools()` (`list_skills`, `read_skill`) like channel tools and add `summary()` to
|
|
181
|
+
their instructions. Framework adapters stamp dynamic prompts with
|
|
182
|
+
`ctx.activate_prompt(name, hash)`.
|
|
183
|
+
|
|
184
|
+
Prompts and skills are registered with a deployment:
|
|
185
|
+
|
|
186
|
+
```sh
|
|
187
|
+
python -m tilde deploy [ENTRY] [--agent-id ID] [--url URL] [--api-key KEY] \
|
|
188
|
+
[--target gateway|sidecar|lambda] [--function-arn ARN] [--external-id ID] [--label L] \
|
|
189
|
+
[--dry-run] [--json]
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
`ENTRY` is a file or dotted module (default `main.py`), imported with `TILDE_DISCOVERY=1`
|
|
193
|
+
(`connect_agent`/`run_connected_agent` then do nothing) and not as `__main__`. The globals of
|
|
194
|
+
the entry and of every module loaded from the working directory are scanned for `define_*`
|
|
195
|
+
objects and offered to framework discoverers (entry point group `tilde.discover`, for example
|
|
196
|
+
`trytilde-crewai`). The inventory goes to stderr; `--dry-run` prints the
|
|
197
|
+
`DeploymentDeclarations` JSON; otherwise binary or large skill files are uploaded, the
|
|
198
|
+
deployment is registered (`$TILDE_AGENT_ID`, `$TILDE_URL` = management API, and on Tilde Cloud
|
|
199
|
+
`$TILDE_API_KEY`; open-source Tilde needs no key) and stdout is the deployment token (`--json`: `{"deploymentId", "token", "created"}`).
|
|
200
|
+
|
|
201
|
+
## FastAPI
|
|
202
|
+
|
|
203
|
+
```python
|
|
204
|
+
from fastapi import FastAPI
|
|
205
|
+
from tilde.fastapi import agent_lifespan, mount_chat_proxy
|
|
206
|
+
|
|
207
|
+
app = FastAPI(lifespan=agent_lifespan(run=run))
|
|
208
|
+
mount_chat_proxy(
|
|
209
|
+
app,
|
|
210
|
+
"/api/chat",
|
|
211
|
+
agent_id=os.environ["TILDE_AGENT_ID"],
|
|
212
|
+
api_key=os.environ["TILDE_CHAT_API_KEY"],
|
|
213
|
+
resolve_identity=lambda request, agent_id: request.session.get("user_id"),
|
|
214
|
+
)
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
`agent_lifespan(**connect_agent_options)` calls `connect_agent` on startup and `close()` on
|
|
218
|
+
shutdown, so the application hosts an agent without exposing any agent route. Any ASGI server
|
|
219
|
+
works.
|
|
220
|
+
|
|
221
|
+
## Chat proxy
|
|
222
|
+
|
|
223
|
+
`tilde.chat_proxy.create_chat_proxy(...)` is the Python `@trytilde/chat-proxy`: a pure ASGI
|
|
224
|
+
reverse proxy for the `tilde.provider.tilde.v1.ChatService` RPCs used by the embeddable chat UI.
|
|
225
|
+
It only forwards declared methods, replaces browser credentials with the server-held API key,
|
|
226
|
+
sets the base64url `x-tilde-identity` header from `resolve_identity(request, agent_id)`, keeps
|
|
227
|
+
cookies at the host, rejects cross-origin browser requests and upstream redirects, streams
|
|
228
|
+
request and response bodies without buffering, and never exposes upstream error details.
|
|
229
|
+
`GET /api/chat/agents` lists the agents the caller may use. The same-origin check honors
|
|
230
|
+
`X-Forwarded-Proto`/`X-Forwarded-Host` from a TLS-terminating reverse proxy; pass
|
|
231
|
+
`trust_forwarded_headers=False` when the ASGI server is exposed directly. Multi-agent proxies take
|
|
232
|
+
`agents=[ChatAgentConfig(agent_id, api_key, base_url=...), ...]` and route
|
|
233
|
+
`/api/chat/{agentId}/tilde.provider.tilde.v1.ChatService/{Method}`.
|
|
234
|
+
|
|
235
|
+
## Cloud invoke
|
|
236
|
+
|
|
237
|
+
`create_lambda_handler(run=run)` returns an AWS Lambda handler for the JSON-encoded
|
|
238
|
+
`InvokeRequest` delivered by the cloud invoke API. It runs the invocation through the same
|
|
239
|
+
execution path as a Watch wake and reports through `RunService`.
|
|
240
|
+
|
|
241
|
+
## Tool hosts
|
|
242
|
+
|
|
243
|
+
`tilde.tool_hosts` serves your own tools to agents. Input and output schemas come from the
|
|
244
|
+
pydantic annotations; an `Auth` publishes a provider whose connections (instances) carry
|
|
245
|
+
credentials, parsed into the method's model as `ctx.auth`.
|
|
246
|
+
|
|
247
|
+
```python
|
|
248
|
+
from pydantic import BaseModel, SecretStr
|
|
249
|
+
from tilde.tool_hosts import Auth, Method, ToolContext, create_tool_host
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
class ApiKey(BaseModel):
|
|
253
|
+
api_key: SecretStr # credential fields are strings; SecretStr renders as a secret field
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
async def verify(auth: ApiKey, instance) -> str | None:
|
|
257
|
+
return "acme" # account label; raise to refuse (the message is shown on the setup form)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
auth = Auth(
|
|
261
|
+
id="acme-crm",
|
|
262
|
+
name="Acme CRM",
|
|
263
|
+
methods={"api_key": Method(name="API key", schema=ApiKey)},
|
|
264
|
+
verify=verify,
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
class SearchInput(BaseModel):
|
|
269
|
+
q: str
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
class SearchOutput(BaseModel):
|
|
273
|
+
results: list[str]
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
@auth.tool(description="Search the CRM.")
|
|
277
|
+
async def search(input: SearchInput, ctx: ToolContext[ApiKey]) -> SearchOutput:
|
|
278
|
+
return SearchOutput(results=[])
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
await create_tool_host(auth=auth, tools=[search]).run() # TILDE_GATEWAY_URL, TILDE_TOOL_HOST_TOKEN
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
`@tool(...)` declares a tool without credentials. `create_tool_host` dials
|
|
285
|
+
`ToolHostService.Watch`, runs calls concurrently and reconnects with backoff until `close()`;
|
|
286
|
+
`create_tool_lambda_handler(auth=..., tools=[...])` answers the Protobuf-JSON `LambdaRequest`
|
|
287
|
+
events of a Lambda tool host. A raised exception's message becomes the tool's error, as does an
|
|
288
|
+
output that fails the output model.
|
|
289
|
+
|
|
290
|
+
## Tracing and logs
|
|
291
|
+
|
|
292
|
+
Hosts install an OpenTelemetry tracer and logger provider by default. The W3C context of the
|
|
293
|
+
wake becomes an `agent.invoke` server span; spans and log records emitted inside the
|
|
294
|
+
invocation's asyncio context are batched per invocation and uploaded as OTLP/HTTP protobuf to
|
|
295
|
+
the callback base plus `/v1/traces` and `/v1/logs` with the current connect token. Records from
|
|
296
|
+
the standard `logging` module are routed through a root handler; nothing outside an invocation
|
|
297
|
+
or configured deployment scope is exported. Pass `tracing="existing"` / `logging="existing"`
|
|
298
|
+
and add `agent_span_processor` / `agent_log_processor` to your own providers to keep them.
|
|
299
|
+
`connect_agent` exports logs outside an invocation with the deployment credentials (one
|
|
300
|
+
deployment per process); call `configure_deployment_logging(gateway_url, deployment_token)`
|
|
301
|
+
earlier to capture logs emitted before it.
|
|
302
|
+
|
|
303
|
+
## Management, chat and runtime clients
|
|
304
|
+
|
|
305
|
+
```python
|
|
306
|
+
from tilde import create_management_client, create_tilde_chat_client
|
|
307
|
+
from tilde.management.v1.tilde_chat_pb2 import GetCredentialsRequest
|
|
308
|
+
from tilde.provider.tilde.v1 import chat_pb2
|
|
309
|
+
|
|
310
|
+
tilde = create_management_client("http://127.0.0.1:8080")
|
|
311
|
+
# Management provisions credentials; chat operations use the Tilde chat provider.
|
|
312
|
+
credentials = await tilde.tilde_chat.get_credentials(GetCredentialsRequest(agent_id=agent_id))
|
|
313
|
+
chat = create_tilde_chat_client(
|
|
314
|
+
"http://127.0.0.1:8080", agent_id, api_key=credentials.api_key, identity="alice"
|
|
315
|
+
)
|
|
316
|
+
user = (await chat.get_identity(chat_pb2.GetIdentityRequest())).user
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
`ManagementClient` groups `access`, `agents`, `connections`, `deployments`,
|
|
320
|
+
`identities`, `inference`, `logs`, `tilde_chat` and `traces`; `RuntimeClient` groups `agents`
|
|
321
|
+
and `chat`. `create_tilde_chat_client` is the server-side client for the built-in chat provider:
|
|
322
|
+
resolve `identity` from your authenticated application user, never from browser input. All
|
|
323
|
+
clients use the generated request messages directly. Open-source Tilde's management API is
|
|
324
|
+
unauthenticated (secure it with your own proxy); pass `access_token=` for Tilde Cloud, which
|
|
325
|
+
requires a management credential.
|
trytilde-3.0.1/README.md
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
# Tilde Python SDK
|
|
2
|
+
|
|
3
|
+
`trytilde` (import `tilde`) is the Python counterpart of `@trytilde/sdk`: the same dial-in
|
|
4
|
+
agent host, invocation-bound context, channel tools, message history, run reports, tracing and
|
|
5
|
+
logging, plus FastAPI integration and the server-side chat proxy. Framework adapters live in
|
|
6
|
+
sibling packages: `trytilde-langchain`, `trytilde-pydantic-ai`, `trytilde-openai-agents` and
|
|
7
|
+
`trytilde-agno`.
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
cd sdk/py
|
|
11
|
+
uv sync --all-packages # workspace environment with every package and example
|
|
12
|
+
uv run python scripts/generate.py # Protobuf messages and Connect stubs from proto/
|
|
13
|
+
uv run pytest # adapter unit tests and the host tests against a fake gateway
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Generated contracts sit inside the package under the proto package names
|
|
17
|
+
(`tilde.runtime.v1.chat_pb2`, `tilde.types.v1.chat_pb2`, `tilde.run.v1.run_connect`, ...);
|
|
18
|
+
they are not committed. Every RPC uses `connectrpc` with Google protobuf messages over
|
|
19
|
+
`pyqwest` HTTP/2.
|
|
20
|
+
|
|
21
|
+
## Agent host
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
from tilde import AgentContext, run_connected_agent
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
async def run(ctx: AgentContext) -> None:
|
|
28
|
+
await ctx.reason("Inspecting the request.") # execution activity, never a message
|
|
29
|
+
goal = await ctx.goals.create(objective=ctx.objective)
|
|
30
|
+
task = await ctx.tasks.create(title="Reply", goal_id=goal.id)
|
|
31
|
+
|
|
32
|
+
async def words():
|
|
33
|
+
yield "Hello "
|
|
34
|
+
yield "there."
|
|
35
|
+
|
|
36
|
+
await ctx.send_native_message(words())
|
|
37
|
+
await ctx.tasks.update(id=task.id, status="completed")
|
|
38
|
+
await ctx.goals.update(id=goal.id, status="completed")
|
|
39
|
+
await ctx.set_run_status("completed")
|
|
40
|
+
ctx.stop()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
run_connected_agent(run=run) # TILDE_GATEWAY_URL and TILDE_DEPLOYMENT_TOKEN from the environment
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Tilde never calls an agent over HTTP. A host dials out: `connect_agent` opens
|
|
47
|
+
`tilde.run.v1.RunService.Watch` with the deployment token (from `RegisterDeployment` or
|
|
48
|
+
`IssueDeploymentToken`) and receives each wake as a stream frame, so the process needs no
|
|
49
|
+
inbound port, public URL or shared secret. Wakes run concurrently; the host heartbeats every
|
|
50
|
+
3 seconds and reconnects after 1 second whenever the stream ends, until `close()`.
|
|
51
|
+
`run_connected_agent(**options)` is the blocking form for standalone scripts: it connects,
|
|
52
|
+
waits for SIGINT/SIGTERM and closes. Inside an existing event loop use `connect_agent`:
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from tilde import connect_agent
|
|
56
|
+
|
|
57
|
+
host = connect_agent(gateway_url=RUNTIME_URL, deployment_token=TOKEN, run=run, ready=database_is_up)
|
|
58
|
+
await host.wait() # until host.close()
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`gateway_url` and `deployment_token` default to `TILDE_GATEWAY_URL` and
|
|
62
|
+
`TILDE_DEPLOYMENT_TOKEN`. `ready` is an optional sync or async check whose result is sent with
|
|
63
|
+
every heartbeat (not ready when it raises); Tilde only routes wakes to ready instances.
|
|
64
|
+
|
|
65
|
+
For each wake the host opens `InvocationControlService.WatchCommands` before user code starts,
|
|
66
|
+
fetches the invocation-scoped tool catalog, and reports `accepted`, every `reason()` delta and
|
|
67
|
+
exactly one `stopped` through `RunService.Report` with the invocation token.
|
|
68
|
+
|
|
69
|
+
`ctx.tools` holds SDK-local `stop`, `goals.*` and `tasks.*` helpers plus provider tools with
|
|
70
|
+
server-authored descriptions, JSON schemas and optional chunk schemas. `ctx.channel.current`
|
|
71
|
+
exposes only the inbound connection's tools; `ctx.channel.slack`, `github`, `agentmail`, `linq`,
|
|
72
|
+
`whatsapp`, `telnyx_whatsapp` and `native` resolve providers, `ctx.channel.for_connection(id)`
|
|
73
|
+
and `ctx.channel.provider(id)` select explicitly, and `call_channel_tool(name, json)` reaches
|
|
74
|
+
custom providers. Tools are awaited with the framework's tool-call ID:
|
|
75
|
+
`await ctx.channel.slack.send_message({"channelId": "C1", "text": "Hi"}, tool_call_id=call_id)`.
|
|
76
|
+
Streaming tools accept an async iterable of chunks in the provider's chunk format.
|
|
77
|
+
|
|
78
|
+
Provider tools carry `summary`, `output_schema`, `annotations` and `background`;
|
|
79
|
+
`ctx.tool_source(slug)` returns one tool source's tools keyed by tool name. `ctx.agent_tools`
|
|
80
|
+
collects everything Tilde gives the agent besides messaging (tool sources, Tilde's built-in
|
|
81
|
+
tools, personal tools), beside `ctx.channel.current`.
|
|
82
|
+
|
|
83
|
+
The agent's bundled tools are its own framework tools, written the framework's way and run in
|
|
84
|
+
its process. Each adapter's `await with_tilde_tools(ctx, native_tools, options=...)` returns the
|
|
85
|
+
framework's tool collection with the current channel's tools, `ctx.agent_tools` and the native
|
|
86
|
+
tools, publishes the native tools to Tilde (so `tools.search` and `tools.schemas` describe them,
|
|
87
|
+
a `tools.execute` naming one runs it here, and the agent's Tools tab lists them read-only), and
|
|
88
|
+
audits every call once with its summary. `BundledOptions(summary=..., display="summary",
|
|
89
|
+
annotations=ToolAnnotations(read_only=True))` sets the Tilde metadata per tool name; `display`
|
|
90
|
+
(`"full"`, `"summary"` or `"hidden"`) is how its calls show in end-user chats, and traces keep
|
|
91
|
+
full detail. Call it once per invocation; the set is replaced each time.
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
from langchain_core.tools import tool
|
|
95
|
+
from tilde import BundledOptions
|
|
96
|
+
from tilde_langchain import with_tilde_tools
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@tool
|
|
100
|
+
def read_file(path: str) -> str:
|
|
101
|
+
"""Read a file from the workspace."""
|
|
102
|
+
return Path(path).read_text()
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
tools = await with_tilde_tools(
|
|
106
|
+
ctx, [read_file], options={"read_file": BundledOptions(summary="Read a file")}
|
|
107
|
+
)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`ctx.message.history(limit=..., before_message_id=..., include_objective=True, include_work=False)`
|
|
111
|
+
returns typed `ConversationMessage`, `ObjectiveMessage`, `GoalMessage` and `TaskMessage` items;
|
|
112
|
+
the adapters convert them to framework messages and hydrate attachments through the scoped
|
|
113
|
+
`ctx.attachments.download`. `ctx.agents.*` and `ctx.invoke_agent(...)` call the registry with the
|
|
114
|
+
current token; the server checks every grant.
|
|
115
|
+
|
|
116
|
+
Cancellation is cooperative asyncio cancellation: a stop control or a lost callback connection
|
|
117
|
+
cancels the task running `run`, and `ctx.stop()` raises `StopLoop` (a `BaseException`, so it
|
|
118
|
+
passes through framework `except Exception` handlers). Call `ctx.check()` at framework
|
|
119
|
+
checkpoints. Returning from `run` never publishes a message; unfinished runs become waiting
|
|
120
|
+
unless `set_run_status` says otherwise. Connect tokens are renewed at four minutes; a denied
|
|
121
|
+
renewal aborts execution. `checkpoint=` receives the context on suspension and must quiesce
|
|
122
|
+
the framework before returning.
|
|
123
|
+
|
|
124
|
+
## Inference, prompts and skills
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
import tilde
|
|
128
|
+
|
|
129
|
+
MODEL = tilde.inference("default") # module scope: resolves the running invocation per request
|
|
130
|
+
SYSTEM = tilde.define_prompt(
|
|
131
|
+
"system",
|
|
132
|
+
template="You are {{name}}.\n{{> rules}}",
|
|
133
|
+
sections={"rules": "Be brief."},
|
|
134
|
+
config={"model": "gpt-4o-mini"},
|
|
135
|
+
)
|
|
136
|
+
SKILLS = tilde.define_skills("skills") # folders holding a SKILL.md, relative to this file
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
async def run(ctx: tilde.AgentContext) -> None:
|
|
140
|
+
client = AsyncOpenAI(
|
|
141
|
+
base_url=MODEL.base_url, api_key=MODEL.api_key, http_client=MODEL.async_client()
|
|
142
|
+
)
|
|
143
|
+
instructions = SYSTEM.render(name="Support") # marks the prompt active for this invocation
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`tilde.inference(alias)` returns the same `Inference` as `ctx.inference(alias)`, but every
|
|
147
|
+
request resolves the invocation running it (token, callback URL, active prompt stamps), so model
|
|
148
|
+
clients can be built at import time; a request outside `run(ctx)` raises. Both send
|
|
149
|
+
`x-tilde-prompt: name@hash[, ...]` for prompts made active with `ctx.prompt(definition)` or by
|
|
150
|
+
rendering one inside the invocation. `define_skill(name, description, instructions, files=...)`
|
|
151
|
+
declares a skill in code with a generated `SKILL.md`.
|
|
152
|
+
|
|
153
|
+
At runtime `ctx.skills` reads every skill the invocation may use: `list()` (each
|
|
154
|
+
`SkillSummary` says whether it is `deployed`, shipped beside the code, or assigned through the
|
|
155
|
+
registry), `read(name, path="SKILL.md")` (text inline, other files as a short-lived
|
|
156
|
+
`download_url`) and `summary()` (a system-prompt block). `directory()` keeps the registry skills
|
|
157
|
+
in `$TILDE_SKILLS_DIR` (the OS temp directory by default) `/tilde-skills/<agent id>/<name>/…`
|
|
158
|
+
for frameworks that load skill folders from disk: the engine pushes the invocation's skills and
|
|
159
|
+
versions with each wake, and only new and newer versions are downloaded and removed skills
|
|
160
|
+
deleted, so a skill assigned in Tilde reaches
|
|
161
|
+
the next invocation without a redeploy. Frameworks without native skills convert
|
|
162
|
+
`ctx.skills.tools()` (`list_skills`, `read_skill`) like channel tools and add `summary()` to
|
|
163
|
+
their instructions. Framework adapters stamp dynamic prompts with
|
|
164
|
+
`ctx.activate_prompt(name, hash)`.
|
|
165
|
+
|
|
166
|
+
Prompts and skills are registered with a deployment:
|
|
167
|
+
|
|
168
|
+
```sh
|
|
169
|
+
python -m tilde deploy [ENTRY] [--agent-id ID] [--url URL] [--api-key KEY] \
|
|
170
|
+
[--target gateway|sidecar|lambda] [--function-arn ARN] [--external-id ID] [--label L] \
|
|
171
|
+
[--dry-run] [--json]
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
`ENTRY` is a file or dotted module (default `main.py`), imported with `TILDE_DISCOVERY=1`
|
|
175
|
+
(`connect_agent`/`run_connected_agent` then do nothing) and not as `__main__`. The globals of
|
|
176
|
+
the entry and of every module loaded from the working directory are scanned for `define_*`
|
|
177
|
+
objects and offered to framework discoverers (entry point group `tilde.discover`, for example
|
|
178
|
+
`trytilde-crewai`). The inventory goes to stderr; `--dry-run` prints the
|
|
179
|
+
`DeploymentDeclarations` JSON; otherwise binary or large skill files are uploaded, the
|
|
180
|
+
deployment is registered (`$TILDE_AGENT_ID`, `$TILDE_URL` = management API, and on Tilde Cloud
|
|
181
|
+
`$TILDE_API_KEY`; open-source Tilde needs no key) and stdout is the deployment token (`--json`: `{"deploymentId", "token", "created"}`).
|
|
182
|
+
|
|
183
|
+
## FastAPI
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
from fastapi import FastAPI
|
|
187
|
+
from tilde.fastapi import agent_lifespan, mount_chat_proxy
|
|
188
|
+
|
|
189
|
+
app = FastAPI(lifespan=agent_lifespan(run=run))
|
|
190
|
+
mount_chat_proxy(
|
|
191
|
+
app,
|
|
192
|
+
"/api/chat",
|
|
193
|
+
agent_id=os.environ["TILDE_AGENT_ID"],
|
|
194
|
+
api_key=os.environ["TILDE_CHAT_API_KEY"],
|
|
195
|
+
resolve_identity=lambda request, agent_id: request.session.get("user_id"),
|
|
196
|
+
)
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
`agent_lifespan(**connect_agent_options)` calls `connect_agent` on startup and `close()` on
|
|
200
|
+
shutdown, so the application hosts an agent without exposing any agent route. Any ASGI server
|
|
201
|
+
works.
|
|
202
|
+
|
|
203
|
+
## Chat proxy
|
|
204
|
+
|
|
205
|
+
`tilde.chat_proxy.create_chat_proxy(...)` is the Python `@trytilde/chat-proxy`: a pure ASGI
|
|
206
|
+
reverse proxy for the `tilde.provider.tilde.v1.ChatService` RPCs used by the embeddable chat UI.
|
|
207
|
+
It only forwards declared methods, replaces browser credentials with the server-held API key,
|
|
208
|
+
sets the base64url `x-tilde-identity` header from `resolve_identity(request, agent_id)`, keeps
|
|
209
|
+
cookies at the host, rejects cross-origin browser requests and upstream redirects, streams
|
|
210
|
+
request and response bodies without buffering, and never exposes upstream error details.
|
|
211
|
+
`GET /api/chat/agents` lists the agents the caller may use. The same-origin check honors
|
|
212
|
+
`X-Forwarded-Proto`/`X-Forwarded-Host` from a TLS-terminating reverse proxy; pass
|
|
213
|
+
`trust_forwarded_headers=False` when the ASGI server is exposed directly. Multi-agent proxies take
|
|
214
|
+
`agents=[ChatAgentConfig(agent_id, api_key, base_url=...), ...]` and route
|
|
215
|
+
`/api/chat/{agentId}/tilde.provider.tilde.v1.ChatService/{Method}`.
|
|
216
|
+
|
|
217
|
+
## Cloud invoke
|
|
218
|
+
|
|
219
|
+
`create_lambda_handler(run=run)` returns an AWS Lambda handler for the JSON-encoded
|
|
220
|
+
`InvokeRequest` delivered by the cloud invoke API. It runs the invocation through the same
|
|
221
|
+
execution path as a Watch wake and reports through `RunService`.
|
|
222
|
+
|
|
223
|
+
## Tool hosts
|
|
224
|
+
|
|
225
|
+
`tilde.tool_hosts` serves your own tools to agents. Input and output schemas come from the
|
|
226
|
+
pydantic annotations; an `Auth` publishes a provider whose connections (instances) carry
|
|
227
|
+
credentials, parsed into the method's model as `ctx.auth`.
|
|
228
|
+
|
|
229
|
+
```python
|
|
230
|
+
from pydantic import BaseModel, SecretStr
|
|
231
|
+
from tilde.tool_hosts import Auth, Method, ToolContext, create_tool_host
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
class ApiKey(BaseModel):
|
|
235
|
+
api_key: SecretStr # credential fields are strings; SecretStr renders as a secret field
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
async def verify(auth: ApiKey, instance) -> str | None:
|
|
239
|
+
return "acme" # account label; raise to refuse (the message is shown on the setup form)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
auth = Auth(
|
|
243
|
+
id="acme-crm",
|
|
244
|
+
name="Acme CRM",
|
|
245
|
+
methods={"api_key": Method(name="API key", schema=ApiKey)},
|
|
246
|
+
verify=verify,
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
class SearchInput(BaseModel):
|
|
251
|
+
q: str
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
class SearchOutput(BaseModel):
|
|
255
|
+
results: list[str]
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
@auth.tool(description="Search the CRM.")
|
|
259
|
+
async def search(input: SearchInput, ctx: ToolContext[ApiKey]) -> SearchOutput:
|
|
260
|
+
return SearchOutput(results=[])
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
await create_tool_host(auth=auth, tools=[search]).run() # TILDE_GATEWAY_URL, TILDE_TOOL_HOST_TOKEN
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
`@tool(...)` declares a tool without credentials. `create_tool_host` dials
|
|
267
|
+
`ToolHostService.Watch`, runs calls concurrently and reconnects with backoff until `close()`;
|
|
268
|
+
`create_tool_lambda_handler(auth=..., tools=[...])` answers the Protobuf-JSON `LambdaRequest`
|
|
269
|
+
events of a Lambda tool host. A raised exception's message becomes the tool's error, as does an
|
|
270
|
+
output that fails the output model.
|
|
271
|
+
|
|
272
|
+
## Tracing and logs
|
|
273
|
+
|
|
274
|
+
Hosts install an OpenTelemetry tracer and logger provider by default. The W3C context of the
|
|
275
|
+
wake becomes an `agent.invoke` server span; spans and log records emitted inside the
|
|
276
|
+
invocation's asyncio context are batched per invocation and uploaded as OTLP/HTTP protobuf to
|
|
277
|
+
the callback base plus `/v1/traces` and `/v1/logs` with the current connect token. Records from
|
|
278
|
+
the standard `logging` module are routed through a root handler; nothing outside an invocation
|
|
279
|
+
or configured deployment scope is exported. Pass `tracing="existing"` / `logging="existing"`
|
|
280
|
+
and add `agent_span_processor` / `agent_log_processor` to your own providers to keep them.
|
|
281
|
+
`connect_agent` exports logs outside an invocation with the deployment credentials (one
|
|
282
|
+
deployment per process); call `configure_deployment_logging(gateway_url, deployment_token)`
|
|
283
|
+
earlier to capture logs emitted before it.
|
|
284
|
+
|
|
285
|
+
## Management, chat and runtime clients
|
|
286
|
+
|
|
287
|
+
```python
|
|
288
|
+
from tilde import create_management_client, create_tilde_chat_client
|
|
289
|
+
from tilde.management.v1.tilde_chat_pb2 import GetCredentialsRequest
|
|
290
|
+
from tilde.provider.tilde.v1 import chat_pb2
|
|
291
|
+
|
|
292
|
+
tilde = create_management_client("http://127.0.0.1:8080")
|
|
293
|
+
# Management provisions credentials; chat operations use the Tilde chat provider.
|
|
294
|
+
credentials = await tilde.tilde_chat.get_credentials(GetCredentialsRequest(agent_id=agent_id))
|
|
295
|
+
chat = create_tilde_chat_client(
|
|
296
|
+
"http://127.0.0.1:8080", agent_id, api_key=credentials.api_key, identity="alice"
|
|
297
|
+
)
|
|
298
|
+
user = (await chat.get_identity(chat_pb2.GetIdentityRequest())).user
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
`ManagementClient` groups `access`, `agents`, `connections`, `deployments`,
|
|
302
|
+
`identities`, `inference`, `logs`, `tilde_chat` and `traces`; `RuntimeClient` groups `agents`
|
|
303
|
+
and `chat`. `create_tilde_chat_client` is the server-side client for the built-in chat provider:
|
|
304
|
+
resolve `identity` from your authenticated application user, never from browser input. All
|
|
305
|
+
clients use the generated request messages directly. Open-source Tilde's management API is
|
|
306
|
+
unauthenticated (secure it with your own proxy); pass `access_token=` for Tilde Cloud, which
|
|
307
|
+
requires a management credential.
|