A DBOS demo (in Go) that showcases controlling an agent's permissions with a durable workflow.
The agent performs an online search and saves the result to disk. It has two permissions:
- Web Search — required to search the web (Gemini + Google Search grounding)
- Filesystem Access — required to save the result to disk
Both permissions live in a Postgres table and are toggled from the UI. When you launch a search, the app spawns a two-step DBOS workflow. Before each step the agent checks the corresponding permission — first web search, then filesystem access. If a permission is not granted, that step throws and the workflow durably stops in the ERROR state.
launch search ──► DBOS workflow "ResearchAgent"
│
├─ check "web_search" permission ─────► (deny → ERROR)
├─ step 1: Gemini web search ────────► summary text
│
├─ check "filesystem_access" permission ─► (deny → ERROR)
├─ step 2: save summary to ./saved_results/
│
└─ COMPLETED — display the summary
- Each permission check is a fast-failing DBOS step (no retries): it reads the permission from Postgres and returns an error if it is not
allow, which leaves the workflow inERROR. - The Gemini search is a retriable DBOS step, so transient API errors (e.g. a 503) recover automatically.
- The save step writes a markdown file named
<timestamp>-<slug>.mdunder./saved_results/. - Workflow progress is published as a DBOS event (
agent_status) that the UI polls.
- Agentic permissioning — permission checks gate every side-effecting step; a denial durably fails the workflow with a clear, observable error.
- Durable workflows — restart the app mid-run and it resumes from the last completed step (checkpointed in Postgres).
- Observability — every run and its terminal state (
COMPLETED/ERROR) is visible in the UI and in DBOS Conductor when a Conductor key is set.
.
├── main.go # DBOS workflow + steps, permission gate, Gin HTTP server
├── html/app.html # Single-page UI (served by Go — no Node build step)
├── dbos-config.yaml # DBOS config (used by the dbos CLI)
├── launch_app.sh # Loads .env and runs the app
├── .env # Secrets / config (git-ignored)
└── saved_results/ # Where the agent writes summaries (git-ignored)
- Go 1.25+ — the DBOS Go SDK requires it. With
GOTOOLCHAIN=auto(the default), the Go command downloads the required toolchain automatically, so an oldergobinary is fine. - PostgreSQL — for the DBOS system database and the
permissionstable. Both the database and the table are created automatically on first launch. - Google Gemini API key — get one at aistudio.google.com.
- (Optional) DBOS Conductor key — get one at console.dbos.dev to manage/observe workflows.
Put your configuration in .env:
export GOOGLE_API_KEY="AIza..."
# Optional — connect to DBOS Conductor
export DBOS_CONDUCTOR_KEY="dbos_..."
# Optional — defaults to postgres://postgres:dbos@localhost:5432/research_with_permissions
# export DBOS_SYSTEM_DATABASE_URL="postgres://user:password@localhost:5432/dbname"./launch_app.shThen open http://localhost:8080.
Or run directly:
source .env
go run .- On the right, both permissions default to Allow.
- On the left, type a query (e.g. "How much revenue did Apple make this year?") and click Launch Agent.
- The workflow searches the web, saves the summary to disk, and displays it — status
COMPLETED, with the saved file path shown. - Flip Web Search or Filesystem Access to Deny, launch another search, and watch the workflow stop in the
ERRORstate at that step with a "permission denied" message. (Deny only Filesystem Access and you'll see the search complete but the save fail — the report is produced, then the workflow errors at step 2.)
| Method | Path | Description |
|---|---|---|
GET |
/ |
Serves the single-page UI |
POST |
/agents |
Start a workflow. Body: { "query": "..." } → { "agent_id": "..." } |
GET |
/agents |
List all runs with their status, report, saved path, and error |
GET |
/permissions |
Current permission states → [{ "name", "state" }] |
POST |
/permissions |
Update one. Body: { "name": "web_search" | "filesystem_access", "state": "allow" | "deny" } |
POST |
/crash |
Kill the process immediately (for demonstrating crash recovery) |
Because the workflow is durable, you can kill the process mid-run (e.g. curl -X POST localhost:8080/crash) and, on restart, DBOS resumes any in-flight workflow from its last completed step.