API Clients + structured JSON results: app-level tokens for Synap/WSIT integration

- New api_clients + api_client_scopes tables; tokens scoped per-instance
- Admin UI tab at /admin for token create/rotate/revoke/delete with one-time reveal
- Dual-auth dependency (user session OR Bearer app token) on trigger + runs endpoints
- /api/instances/{id}/trigger pre-creates a run and returns run_id + cached last_result instantly
- New GET /api/runs/{id} for polling
- Generic trigger path for sub-agent instances (weather, calendar, etc.)
- runs.result column for structured JSON alongside markdown output
- agent_catalog.result_schema describes each agent's result shape
- Weather, daily-briefing, project-monitor retrofitted to emit structured results
- log_run: env INSTANCE_ID/RUN_ID only used when target matches, so nested sub-agents don't clobber parent runs
- Wiki docs: API Clients & Token Scoping + Calling Agents From Your Apps
This commit is contained in:
Eric Jungbauer
2026-04-20 17:54:32 +00:00
parent f01553c511
commit 043aa18f3f
8 changed files with 983 additions and 111 deletions
+45 -10
View File
@@ -61,22 +61,57 @@ def api_request(url, data=None, headers=None, method="GET", retries=DEFAULT_RETR
raise last_error
def log_run(agent_id, status, output="", err="", metadata=None, instance_id=None):
"""Log a run to the dashboard API."""
def log_run(agent_id, status, output="", err="", metadata=None, instance_id=None, result=None, run_id=None):
"""Log a run to the dashboard API.
Resolution rules:
* `instance_id`: explicit wins; else env INSTANCE_ID; else skip (no run logged).
* `run_id`: explicit wins; else env RUN_ID — but ONLY when the target instance matches
env INSTANCE_ID (the subprocess was triggered for THIS instance). A sub-agent called
inside a briefing (e.g. project_monitor with its own instance_id) does NOT inherit
the briefing's RUN_ID and will create a fresh run row instead.
Pass `result` (dict) to populate the structured-output column that API consumers read.
"""
try:
if instance_id:
api_request(
f"{DASHBOARD_API}/api/instances/{instance_id}/runs",
data={"status": status, "output": output, "error": err, "metadata": metadata or {}},
method="POST",
retries=1, # Don't retry logging too aggressively
)
else:
env_inst = int(os.environ["INSTANCE_ID"]) if os.environ.get("INSTANCE_ID") else None
env_run = int(os.environ["RUN_ID"]) if os.environ.get("RUN_ID") else None
target_instance_id = instance_id if instance_id is not None else env_inst
if target_instance_id is None:
print(f"Warning: no instance_id, run not logged for {agent_id}", file=sys.stderr)
return
target_run_id = run_id
if target_run_id is None and env_run is not None and env_inst == target_instance_id:
target_run_id = env_run
payload = {
"status": status,
"output": output,
"error": err,
"metadata": metadata or {},
}
if result is not None:
payload["result"] = result
if target_run_id:
payload["run_id"] = target_run_id
api_request(
f"{DASHBOARD_API}/api/instances/{target_instance_id}/runs",
data=payload,
method="POST",
retries=1, # Don't retry logging too aggressively
)
except Exception as e:
print(f"Warning: failed to log run to dashboard: {e}", file=sys.stderr)
def get_instance_config(instance_id):
"""Fetch an instance's config from the dashboard API. Used by agents run from /trigger."""
return api_request(f"{DASHBOARD_API}/api/instances/{instance_id}/config")
def wiki_headers():
return {"Authorization": f"Bearer {WIKI_TOKEN}"}