The contract
A sidecar is a gRPC server on a Unix socket. Listen on the path in NEBO_APP_SOCK and
implement apps.v0.UIService. Nebo calls HandleRequest for every request; HealthCheck and Configure are part of the service but are not called
today. The socket must appear within the manifest's startup_timeout, which defaults to
10 seconds and may be at most 120.
syntax = "proto3";
package apps.v0;
service UIService {
rpc HealthCheck(HealthCheckRequest) returns (HealthCheckResponse);
rpc Configure(SettingsMap) returns (Empty);
rpc HandleRequest(HttpRequest) returns (HttpResponse);
}
message HttpRequest {
string method = 1; // GET, POST, PUT, DELETE, ...
string path = 2; // relative to /apps/{id}/api/
string query = 3; // raw query string
map<string, string> headers = 4;
bytes body = 5;
}
message HttpResponse {
int32 status_code = 1;
map<string, string> headers = 2;
bytes body = 3;
} Sidecars run on macOS and Linux.
Where Nebo finds the binary
In the app's folder, Nebo uses the first of these that exists:
- A file named
binary - A file named
app - The first file in
tmp/ - The first file in
bin/ - The first executable in
sidecar/target/release/, for local development
The binary must be a regular, executable file, not a symbolic link, and at most 500 MB. Published
apps carry it at bin/<name>. When the file changes on disk, Nebo restarts the
sidecar.
Environment
The sidecar starts with an empty environment and these variables:
| Variable | Value |
|---|---|
NEBO_APP_ID, NEBO_APP_NAME, NEBO_APP_VERSION | The app’s identity. |
NEBO_APP_DIR | The app’s folder. |
NEBO_APP_SOCK | The socket to listen on, <app folder>/<id>.sock. Nebo sets its mode to 0600. |
NEBO_DATA_DIR | The app’s data folder, <data>/appdata/agents/<id>/. Also the working directory. |
NEBO_API_URL | The bot’s local API, http://127.0.0.1:<port>. |
NEBO_APP_TOKEN | A token for calling the bot’s app API. |
PATH, HOME, TMPDIR, LANG, LC_ALL, TZ | Passed through. |
Standard output and standard error go to sidecar.log in the data folder. Standard input
is a pipe; end of file means Nebo has gone away and the sidecar should exit. The sidecar runs in its
own process group.
Requests from the page
A request to /apps/<id>/api/<path>, which the page makes with neboFetch and a relative path, reaches HandleRequest with path set to <path>. Paths that start with _ are refused.
Request bodies are limited to 10 MB and responses to 32 MB.
Tools for the employee
Declare tools in the app's agent.json. Each becomes a tool named app__<app>__<name>, loaded when the employee needs it. A call becomes a
request to the sidecar at method and path.
"tools": [
{
"name": "list_deals",
"description": "List open deals, optionally filtered by stage.",
"method": "GET",
"path": "/deals",
"input_schema": { "type": "object", "properties": { "stage": { "type": "string" } } }
}
] Calling Nebo
The sidecar can use the same services as the page by calling $NEBO_API_URL/api/v1/apps/$NEBO_APP_ID/... with the header Authorization: Bearer $NEBO_APP_TOKEN.
| Route | Does |
|---|---|
GET /storage, GET|PUT|DELETE /storage/<key> | The app’s key-value store. |
POST /agents/invoke, POST /agents/stream | Asks an employee: {message, agent, data}. Stream responses are server-sent events. |
POST /janus/complete, POST /janus/stream | A direct model call: {messages, model, temperature, max_tokens, system}. |
POST /http/proxy | An outside request, {url, method, headers, body}, allowed by the app’s network: permissions. |
GET /identity | The app’s employee. |
Lifecycle
- Nebo probes the socket every three seconds.
- A failed sidecar restarts after one second, doubling to at most 60 seconds. After five failures in a row it is marked failed. A sidecar that stays up for 60 seconds is healthy again.
- A request to a stopped sidecar starts it and waits up to 15 seconds. GET and HEAD requests are retried.
- To stop a sidecar, Nebo sends SIGTERM to its process group, then kills it after two seconds.