Recipe, Google ADK + Shelvia
Shelvia is the memory layer the ADK calls. The ADK runs the agent; Shelvia returns reviewed context and accepts new memory only through the review queue.
Principle
ADK's function-tool abstraction maps directly onto Shelvia's REST surface. Register one tool for reads (memory.context) and one tool for writes (memory.candidates). The write tool returns a pending candidate id, not a trusted memory id.
Setup
- Create a Workspace API token in Settings > API with scopes: memory.read + memory.context (read-only agent) OR + memory.candidates (agent that proposes memory).
- Store the token in SHELVIA_API_KEY. Never log it.
- Install google-adk per the ADK quickstart in your Python environment.
Pattern, function tool for reads
Register a Shelvia retrieval as an ADK function tool. The agent calls it before generating; the response is a structured context pack.
import os, httpx
from google.adk.agents import Agent
from google.adk.tools import FunctionTool
SHELVIA_BASE = "https://shelvia.net"
SHELVIA_KEY = os.environ["SHELVIA_API_KEY"]
def fetch_shelvia_context(project_id: str, purpose: str) -> dict:
"""Retrieve reviewed Shelvia project memory before acting.
Returns decisions, sources, prompts, next steps, and constraints
with a health verdict. Pending candidates are excluded.
"""
resp = httpx.post(
f"{SHELVIA_BASE}/api/v1/project-memory/projects/{project_id}/context-pack",
headers={"Authorization": f"Bearer {SHELVIA_KEY}"},
json={"purpose": purpose, "max_chars": 12000, "semantic_ranking": True},
timeout=30.0,
)
resp.raise_for_status()
return resp.json()
shelvia_context_tool = FunctionTool(fetch_shelvia_context)
agent = Agent(
name="release_coordinator",
instruction=(
"Before answering, call fetch_shelvia_context to retrieve reviewed "
"Shelvia memory. Cite decisions and sources by id."
),
tools=[shelvia_context_tool],
)Pattern, function tool for candidate write
def propose_shelvia_candidate(
project_id: str,
type: str, # decision | next_step | open_question | prompt | source | note
title: str,
content: str,
source_url: str | None = None,
run_id: str = "unknown",
) -> dict:
"""Propose a Shelvia memory candidate. Lands in the review queue
with status pending. A workspace member approves before it becomes
trusted memory."""
resp = httpx.post(
f"{SHELVIA_BASE}/api/v1/project-memory/projects/{project_id}/candidates",
headers={
"Authorization": f"Bearer {SHELVIA_KEY}",
"Idempotency-Key": f"agent-google-adk-{run_id}-{type}-{hash(content)}",
},
json={"type": type, "title": title, "content": content, "source_url": source_url},
timeout=30.0,
)
resp.raise_for_status()
return resp.json()
shelvia_write_tool = FunctionTool(propose_shelvia_candidate)Propose memory candidates safely
ADK function tools that propose Shelvia memory candidates must follow the candidate-write safety pattern. Agents propose. Humans approve. The candidate enters the review queue; it does not become trusted memory until a workspace member approves.
- Identity: create a narrow-scope Workspace API token per ADK deployment (label it, e.g., "Google ADK production support agent"). Reuse shv_live_<hex> with memory.candidates + memory.context.
- Idempotency: every candidate write should carry an Idempotency-Key shaped agent-google-adk-<run-id>-<candidate-hash>. ADK agents may retry on transient failures; idempotency keeps the review queue clean.
- Runtime tag: call log_shelvia_continuation(suggested_tool="google-adk", ...) at the end of each run. The candidate body itself does not carry a runtime field.
- Never write to trusted memory directly. There is no propose-and-approve tool. Approval lives only in the Shelvia UI.
- Never log raw chain-of-thought. ADK exposes the agent's reasoning trace separately; do not put it in candidate content. Keep candidate content to the final claim.
- Never include secrets or tokens. ADK function-tool docstrings are part of the prompt, keep them clean of credentials. The validator rejects forbidden keys with a structured error code.
- Agents must not approve their own memory. No ADK function tool can promote a Shelvia candidate. Promotion is human-only.
Observability (optional)
A function tool can log a continuation after the agent run. The event records which reviewed context was used and what should happen next. This is an optional audit / observability emit, it does NOT become trusted memory and does NOT bypass review-before-save. Scope required: continuations.log.
def log_shelvia_continuation(
project_id: str,
next_best_action: str,
why: str | None = None,
suggested_tool: str = "google-adk",
priority: str = "normal",
) -> dict:
"""Audit-log a continuation event after an ADK run. Records
which reviewed context was used and what should happen next.
Does NOT write to trusted memory."""
resp = httpx.post(
f"{SHELVIA_BASE}/api/v1/project-memory/projects/{project_id}/continuations",
headers={"Authorization": f"Bearer {SHELVIA_KEY}"},
json={
"next_best_action": next_best_action,
"why": why,
"suggested_tool": suggested_tool,
"priority": priority,
},
timeout=30.0,
)
resp.raise_for_status()
return resp.json()
shelvia_observability_tool = FunctionTool(log_shelvia_continuation)Safety notes
- ADK runs the agent. Shelvia is the reviewed memory it retrieves and proposes back into.
- Every proposed candidate is pending. A workspace member must approve before it becomes trusted memory.
- The continuation log is audit-only. It does not write to trusted memory tables.
- Workspace anchor is on the token; the agent cannot retarget Shelvia to a different workspace.
- The function-tool docstrings are part of the prompt, keep them accurate. Don't claim Shelvia writes trusted memory directly.
Where to go next
- REST API reference, /docs/developers/api
- Authentication scopes, /docs/developers/auth
- Review-before-save concept, /docs/concepts/review-before-save
For runnable code samples and the developer reference, see /developers. For the trust model in depth, see /security.