Skip to main content
Connect a codebase by keeping the OAO credential and repository access in a trusted server process. Create the first session and run together, execute any caller-owned file tools under a narrow repository policy, and use the durable project event stream to resume after disconnects. The current MVP supports three code-context patterns:
  1. Attach up to eight supported files directly to an initial or later turn. OAO copies their original bytes into the agent sandbox. See Send files to an agent for media types and limits.
  2. Put a small, trusted text excerpt directly in initialMessage or a later turn. The complete message must be no more than 100,000 JavaScript characters.
  3. Publish narrow caller tools such as read_repository_file and search_repository, then run those tools in your own server process against one allowlisted repository root.
Use attached files for a small, deliberate set whose filenames should remain visible in the transcript. Use inline text for a tiny excerpt. Use caller tools when the agent needs to choose which files to inspect over several turns or when the repository exceeds the per-turn attachment limits. Caller-tool arguments and results are durable public records, so return only content that is safe to persist and display.

Create a server-side API key

Open API keys in the console, select Create API key, enter a descriptive name, and grant only the scopes the integration needs. Save the secret from the one-time acknowledgement dialog before closing it. For automated provisioning, create the integration key through the API using an already-authenticated human or service principal with project:admin:
The successful creation response shows the oao_... secret once. Copy it into the integration’s secret store immediately. OAO stores only a keyed hash and cannot reveal the plaintext again. See Authentication for browser-cookie authentication, tenant authorization, and key handling. Grant the smallest set of scopes for the process you are running: The complete SDK example uses every scope except run:cancel. An operator can create the agent separately and remove agent:write from the long-lived worker key afterward. Store the key only in the server environment:
OAO_API_URL is the API origin, without /v1, when using @oao/sdk-js. External HTTP examples below add /v1 themselves.
Never put an OAO API key in browser JavaScript, a mobile bundle, a repository, a query string, an SSE URL, or logs. Send it only as Authorization: Bearer oao_... from the server.
Every mutation also needs an Idempotency-Key. Persist a key before the first attempt, and reuse that same key and identical JSON body when the outcome is uncertain. A replay returns the original public response. Reusing the key for a different body returns 409 idempotency_conflict.

Pattern 1: attach a small trusted text file

Read the selected file on your server and send canonical base64. The API stores the original bytes durably with the run, verifies them by SHA-256 at dispatch, copies them into the sandbox, and exposes only safe metadata in transcript reads. It does not inject the file content into the model prompt:
Do not use this pattern for secrets, entire repositories, or unsupported binaries. File names cannot contain paths, so use a plain display filename and put any reviewed repository-relative path in the message.

Pattern 2: inline a small trusted text excerpt

Read and review the text on your server before including it. Reject binary data and secrets, add a clear path label, and check the final assembled message rather than only the file length:
This is text in the conversation, not an attachment. Do not use this pattern for untrusted files, generated archives, images, PDFs, executables, entire repositories, or any content that may contain credentials.

Pattern 3: run allowlisted repository tools

The following private-workspace example creates one agent, creates its first session atomically with initialMessage, handles resumable SSE, services two caller-owned tools, and optionally submits one later turn after the previous run settles. Add the private SDK to an OAO workspace package that owns the integration:
The SDK is currently private: true; this install form is for packages in this monorepo. Save the following as src/codebase-agent.ts in that package and run it with the package’s TypeScript runner.
Run it with a secret-free checkout dedicated to this worker:
The state file contains identifiers, idempotency keys, and the last applied event cursor, but not the API secret. Put it outside the reviewed repository in production, restrict its file permissions, and back it with durable storage if the worker can move between hosts.
This runnable example persists session and run mutations, but keeps each tool claim, renewal, and result idempotency key only for the life of the process. Before using the worker for effectful production tools, add the per-tool operation journal described below. Repository reads are safe to repeat, but arbitrary external side effects may not be.

Why the file boundary is strict

The example applies all of these checks before returning content:
  • Resolves one configured repository root to a canonical absolute path.
  • Rejects absolute paths, NUL bytes, traversal outside the root, denied directories, secret-like filenames, key/certificate extensions, and unknown text extensions.
  • Rejects every symbolic-link component and verifies the canonical result is still under the root.
  • Opens only regular files, caps each read at 256 KiB, rejects NUL-containing or invalid UTF-8 data, and applies a conservative secret-content check.
  • Treats search as literal text, caps files inspected, matches returned, and excerpt length, and skips unreadable or disallowed files.
  • Returns generic failure text instead of exception messages, absolute paths, file metadata, or stack traces.
These are defense-in-depth controls, not a universal secret detector. Use a read-only, secret-free checkout; run the worker under a dedicated OS identity; keep it separate from credential directories; and do not allow an untrusted process to mutate the checkout while it is being read. Tighten the extension, directory, size, and content policies for your repository.

Claim leases, fences, and results

A caller tool starts at caller_pending. The worker claims it for a lease from 1,000 through 300,000 milliseconds and receives a positive-integer string fence. Renew and release operations must use the current fence. The example renews a 60-second lease every 20 seconds and stops the heartbeat before result submission. Submit the fence with exactly one safe, versioned result envelope:
The value must match the published outputSchema. A safe failure uses status: "failure" with one of the current public failure codes and a generic message. Never put credentials, raw tool payloads, absolute host paths, stack traces, or raw model reasoning in safeResult. Use one idempotency key per logical claim, renewal, release, or result. Retry an uncertain request with the same key and body. A tool result is immutable: the same result key and body returns outcome: "replayed"; a different key or body conflicts. If a lease expires or its fence becomes stale, do not submit anyway. Let the work be reclaimed and re-executed under the new fence.

Make production tool processing crash-safe

Persist one operation record per toolCallId in your own durable store. At a minimum it needs phase, claimIdempotencyKey, the claimed fence, the exact result envelope (or its canonical bytes and hash), and resultIdempotencyKey. Update this record before each network request:
  1. Generate and persist the claim key and claim body, then call claim.
  2. Persist the returned fence before executing the handler.
  3. For an effectful handler, pass a stable downstream idempotency key derived from the tool-call identity; a lease fence prevents stale OAO commits but cannot undo an external effect that already started.
  4. Persist the final safe-result body and result key before submitting it.
  5. Mark the operation complete only after OAO accepts or replays that exact result.
After a crash, retry an uncertain request with its stored key and identical body. If the stored fence has expired, re-list the tool call and reclaim it; never submit a result under the stale fence. This journal is what extends OAO’s durable tool ledger across crashes in your integration process.

Resume the event stream durably

GET /v1/projects/{projectId}/events is project-wide. Each SSE frame’s id is an opaque cursor backed by committed PostgreSQL position. Persist it only after your application has applied the event, then reconnect with:
The SDK does this automatically when reconnect: true. It waits reconnectDelayMs (1 second by default) between connections, or an SSE retry value if the server sends one. The server closes a normal stream about every 25 seconds; that is not data loss. It also writes : comment lines when the stream opens and after 10 seconds of silence, so proxies keep the connection alive. Parsers must skip those lines. PostgreSQL is authoritative, while LISTEN/NOTIFY only wakes readers. Expect duplicate events after a crash between applying an event and saving its cursor. Make your projection idempotent by event id or by (aggregateType, aggregateId, aggregateSequence). Re-fetch the run or session when an event kind is unknown. The example always re-reads the target run and lists durable tool calls, so an SSE frame is a notification rather than the sole copy of state. On SIGINT or SIGTERM, abort the stream and any outstanding HTTP request, stop lease renewal, finish the state-file rename, and exit. On restart, load the saved cursor and any persisted mutation key before making another request. If a process dies while holding a tool claim, its lease expires and another worker can claim the durable work.

External repository: use HTTP directly

Because @oao/sdk-js is not currently published, a server outside the OAO monorepo should use the HTTP and SSE contracts. This complete Node.js 22 example uses the inline-text pattern, creates a session, watches its first run, and sends one optional follow-up only after settlement. Use it with an agent that has no caller-owned tools.
Choose and persist idempotency keys outside the source tree, then run:
For an external caller-tool worker, use the same HTTP paths as the SDK methods: Keep the same repository executor, result envelope, lease heartbeat, fence, and idempotency rules. Do not weaken the boundary because the worker lives in a different repository.

Failures and recovery

Run failures are durable terminal outcomes, not transport exceptions. Read the run, session transcript, safe timeline, and public request ID before deciding whether to submit a new turn. POST /runs/{runId}/resume exists for a settled run and creates another run in its session, but it does not continue an unsettled execution or bypass the one-active-run invariant.
Authorized session views include provider thinking and tool arguments/results. Public events and application logs still omit that content, credentials, authorization headers, and provider reasoning signatures.