> ## Documentation Index
> Fetch the complete documentation index at: https://bilanc.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Cloud Agents

> Capture what cloud coding agents like Devin write, with the same line-level attribution posthook gives you for local agents.

Claude Code, Cursor, and Codex run on an engineer's laptop, so posthook installs once per machine and rides along with every agent session. Cloud agents are different: each session runs on a machine the agent vendor provisions, there is no engineer at a terminal, and the machine goes away when the session ends. Getting posthook onto those machines means using the agent's own environment and hook mechanisms.

The recipe is the same for every cloud agent, and it has three parts:

1. **Install the binary where the agent's environment is defined**, so it is on every session machine before the agent starts working. This also puts the `git` shadow in place, which captures commits with no further setup.
2. **Register posthook as a hook in the agent's hook system**, so the agent calls `posthook ingest` on every tool call and prompt. This is what produces line-level attribution and the session viewer — the git shadow alone only sees commits.
3. **Flush to Bilanc from a hook, not a daemon.** Session machines don't have a login session for launchd or `systemd --user`, so the background sync daemon can't run. A hook on the agent's *stop* event runs `posthook sync` instead.

Devin is the first cloud agent posthook supports. More will follow the same pattern.

## Devin

Devin has two pieces of configuration that map onto the parts above:

| Piece | What it's for | Posthook uses it for |
| - | - | - |
| [Blueprint](https://docs.devin.ai/onboard-devin/environment/blueprints) | Declares the session machine: tools, runtimes, packages. `initialize` runs once and is baked into the snapshot every session boots from. | Installing the posthook binary and the `git` shadow, and setting the identity sessions are attributed to. |
| [Plugin hooks](https://docs.devin.ai/product-guides/plugins#hooks) | A `hooks.json` shipped in a plugin runs a command on the session machine at lifecycle events (`PostToolUse`, `UserPromptSubmit`, `Stop`, …). An admin installs the plugin centrally so it applies to every session in the org. | Calling `posthook ingest --agent devin` on every tool call and prompt, and `posthook sync` when Devin stops. The plugin's `env` carries the team key from Devin Secrets. |

You need both. A blueprint can install software but can't make Devin call it; a plugin can register hooks but can't install software before the session starts. Putting the binary in the blueprint and the hooks in a plugin also keeps the team key off the snapshot image: the blueprint installs posthook in keyless, local-only mode, and the key is injected at runtime from a Devin Secret.

<Warning>
  Devin support needs **posthook 0.3.0 or later** — earlier releases record Devin's tool calls but don't recognise its `edit`/`write` tool names or `UserPromptSubmit`, so you get no line ranges or prompts. The blueprint step below installs the latest release; check with `posthook version` in a session.
</Warning>

<Note>
  Devin plugins are in closed beta and plugin hooks are currently best effort: if a hook fails to load, the session continues without it. Devin's cloud hooks also don't fire `SessionStart`/`SessionEnd`, which is why the flush happens on `Stop`. The `git` shadow doesn't depend on hooks, so commits are captured either way.
</Note>

### Before you start

<Steps>
  <Step title="Get a team install key">
    A Bilanc **Owner or Manager** generates one from **Admin → Connections → AI Tools → PostHook → Generate install link** — see [Get an install link](/posthook/overview#get-an-install-link). You only need the key, the part after `apiKey=`. The same key your engineers' machines use is fine; it is write-only and org-scoped.

    If you only want local attribution (open-source use, no Bilanc workspace), skip this — see [Without a Bilanc workspace](#without-a-bilanc-workspace).
  </Step>

  <Step title="Store the key as a Devin Secret">
    In Devin, go to **Settings → Resources → Secrets** and add an **organization** secret named `POSTHOOK_API_KEY` with the team key as its value. The plugin manifest references it by name in the next section, so the key never appears in the blueprint, the plugin repo, or the snapshot.
  </Step>

  <Step title="Pick an identity for Devin">
    Posthook stamps every session with an engineer email. Devin isn't an engineer, so give it its own: an address like `devin@your-company.com` makes Devin show up as a distinct "engineer" in the Posthook dashboard, and keeps its AI share separate from the humans who review its work. You'll set it in the blueprint below.
  </Step>
</Steps>

### 1. Install posthook in the blueprint

Add a step to the `initialize` section of your **organization-wide blueprint** (Devin **Settings → Environment → Blueprints → Organization blueprint**) so every repository's sessions get it. A repository-level blueprint works the same way if you want to start with one repo.

```yaml theme={null}
initialize:
  - name: "Install posthook"
    run: |
      curl -fsSL https://raw.githubusercontent.com/Bilanc/posthook/main/install.sh | sh
      echo 'export PATH="$HOME/.local/bin:$PATH"' >> $ENVRC
      "$HOME/.local/bin/posthook" identity --set-email devin@your-company.com --set-name Devin
```

What this does on the snapshot:

* Downloads the latest release, verifies its checksum, and installs it to `~/.local/bin/posthook`. No key is passed, so posthook is in local-only mode and nothing is written to disk that you'd mind being in a snapshot.
* Runs `posthook init`, which places the `git` shadow symlink at `~/.local/bin/git`. The `$ENVRC` line puts it ahead of the system `git` for every later blueprint step and for the Devin session itself, so every commit Devin makes is recorded.
* Records the identity Devin's sessions will be attributed to.

The installer also tries to install the background sync daemon and warns that it can't — that is expected on a Devin machine (no `systemd --user` bus) and is harmless: sync is handled by the `Stop` hook below.

<Tip>
  Pin a release with `POSTHOOK_VERSION=0.3.0` in the step's `env` if you'd rather control upgrades. Otherwise each snapshot rebuild picks up the latest version.
</Tip>

### 2. Install the posthook plugin

Posthook ships a Devin plugin in its repository at [`plugins/devin`](https://github.com/Bilanc/posthook/tree/main/plugins/devin). It's a `hooks.json` that runs `posthook ingest --agent devin` on every tool call and prompt, and syncs when Devin stops:

```json plugins/devin/hooks.json theme={null}
{
  "PostToolUse": [
    {
      "matcher": "",
      "hooks": [
        { "type": "command", "command": "posthook ingest --agent devin", "timeout": 10 }
      ]
    }
  ],
  "UserPromptSubmit": [
    {
      "matcher": "",
      "hooks": [
        { "type": "command", "command": "posthook ingest --agent devin", "timeout": 10 }
      ]
    }
  ],
  "Stop": [
    {
      "matcher": "",
      "hooks": [
        { "type": "command", "command": "posthook ingest --agent devin; s=$?; posthook sync >/dev/null 2>&1 && exit $s", "timeout": 90 }
      ]
    }
  ]
}
```

The empty `matcher` runs the hook for every tool. Posthook records each event and extracts line ranges from Devin's `edit`, `write`, and `apply_patch` calls; `UserPromptSubmit` carries the prompt text; `Stop` fires at the end of every turn and is where pending rows are flushed to Bilanc — `posthook sync` first waits for the events the hooks just queued to land in the local store, so the final edit and prompt of a turn are included.

Install it for the whole organisation. In Devin, go to **Settings → Resources → Plugins → Configuration** and add it to the manifest with the environment posthook needs for sync:

```json theme={null}
{
  "requiredPlugins": [
    {
      "source": "git-subdir",
      "url": "https://github.com/Bilanc/posthook.git",
      "path": "plugins/devin",
      "env": {
        "POSTHOOK_CLOUD_TOKEN": "secret:org:POSTHOOK_API_KEY",
        "POSTHOOK_CLOUD_ENABLED": "1",
        "POSTHOOK_CLOUD_ENDPOINT": "https://api.bilanc.co"
      }
    }
  ]
}
```

`env` is passed to the plugin's command hooks in cloud sessions. `POSTHOOK_CLOUD_TOKEN` is read straight from the Devin Secret you created; `POSTHOOK_CLOUD_ENABLED` turns sync on for those commands only; `POSTHOOK_CLOUD_ENDPOINT` is required because the keyless install writes no cloud config — self-hosted Bilanc customers point it at their own API host instead. All three must be set or `posthook sync` skips the flush.

New Devin sessions pick the plugin up from the manifest; sessions already running are unaffected. The entry above tracks posthook's `main` branch; add `"ref": "v0.3.0"` (or a `"sha"`) to pin it, matching the version pinned in the blueprint.

<Note>
  If you're in an enterprise with several Devin organisations, put the plugin in the **enterprise** manifest so it applies everywhere. Org-level manifests only reach cloud sessions of that org.
</Note>

<Accordion title="Prefer to own the plugin?">
  A Devin plugin is just a git repository with a `.devin-plugin/plugin.json` manifest and a `hooks.json` at its root. Copy the two files from `plugins/devin` into a private repository of your own (say `your-org/posthook-devin`), tweak the hooks if you need to, and reference it instead with `{ "source": "github", "repo": "your-org/posthook-devin", "env": { ... } }`.
</Accordion>

### 3. Verify

Rebuild the environment snapshot (Devin does this when the blueprint changes), then start a session in any repository and ask Devin to run:

```bash theme={null}
posthook status            # shadow healthy, recent captures
posthook identity          # devin@your-company.com
posthook sync --status     # rows synced and last flush — populated after Devin's first Stop
```

The first two work from the blueprint alone. `posthook sync --status` shows a successful flush once the `Stop` hook has fired, i.e. after Devin finishes its first turn. Sessions then appear on the **Posthook** page in Bilanc under the identity you chose, with the prompts, files touched, and resulting commits — the same view you get for local agents.

### What gets captured

| | Local agents | Devin |
| - | - | - |
| Tool calls and edits, with line ranges | ✓ | ✓ (`edit`, `write`, `apply_patch`) |
| Prompts | ✓ | ✓ (`UserPromptSubmit`) |
| Commits, via the `git` shadow | ✓ | ✓ |
| Model and token counts | ✓ from the agent's transcript | – Devin's hook payload doesn't include them |
| Sync | background daemon, every few seconds | on every `Stop`; anything unsynced when the machine is torn down is lost |

### Without a Bilanc workspace

Posthook is open source and works with no key at all. In a cloud session the local SQLite database disappears with the machine, but attribution still travels with the code: after every successful `git push`, the shadow pushes `refs/notes/posthook`, so anyone who clones the repository and runs `posthook blame <file>` sees Devin's lines marked as AI-written.

For that, keep the blueprint step from [step 1](#1-install-posthook-in-the-blueprint) and install the plugin **without** the `env` block. No data leaves the session machine except through your own git remote.

### Devin CLI

The Devin CLI runs on an engineer's laptop, so it is a local agent: install posthook the normal way ([share a link](/posthook/overview#share-a-link) or [MDM](/posthook/mdm-deployment)). `posthook init` doesn't write Devin CLI hooks yet, so add the same three hooks to the CLI's user config at `~/.config/devin/config.json` under a `"hooks"` key, or to a repository's `.devin/hooks.v1.json`:

```json theme={null}
{
  "hooks": {
    "PostToolUse": [{ "matcher": "", "hooks": [{ "type": "command", "command": "posthook ingest --agent devin" }] }],
    "UserPromptSubmit": [{ "matcher": "", "hooks": [{ "type": "command", "command": "posthook ingest --agent devin" }] }],
    "Stop": [{ "matcher": "", "hooks": [{ "type": "command", "command": "posthook ingest --agent devin" }] }]
  }
}
```

The sync daemon is already running on a laptop, so the `Stop` hook doesn't need to call `posthook sync`. Note that the Devin CLI also reads Claude Code's `~/.claude/settings.json`: if Claude Code is installed on the same machine, Devin CLI sessions will additionally arrive via that hook and be labelled `claude-code` until you disable `read_config_from.claude` in the Devin CLI config.

## Troubleshooting

<AccordionGroup>
  <Accordion title="Commits show up but no edits or prompts">
    The blueprint worked and the plugin didn't. In a session, ask Devin to run `posthook status`: if it shows no recent `devin` events, the hooks aren't firing. Check that the plugin is listed as required in the manifest for the right scope (org vs. enterprise), that the entry uses `"source": "git-subdir"` with `"path": "plugins/devin"` (a plain `Bilanc/posthook` entry points at the repository root, where there is no `hooks.json`), and that the session was started *after* the manifest change. Plugin hooks fail open, so a malformed `hooks.json` produces no error in the session.
  </Accordion>

  <Accordion title="Events are captured but never reach Bilanc">
    `posthook sync --status` in the session shows the reason. `enabled: false` means `POSTHOOK_CLOUD_ENABLED` isn't set in the plugin `env`; a blank endpoint means `POSTHOOK_CLOUD_ENDPOINT` is missing; a blank token or an auth error in `last_error` means `POSTHOOK_API_KEY` isn't available to the session (check the secret's scope and name). Note that the env only applies to the hook commands, so running `posthook sync --status` from Devin's shell shows the on-disk (keyless) config — ask Devin to run it with the same variables set. Remember `sync` only runs on `Stop`, so a session that's still mid-turn has nothing synced yet.
  </Accordion>

  <Accordion title="Edits are recorded but commits are missing">
    The `git` shadow isn't first on `PATH` in the shell Devin commits from. Ask Devin to run `which git` — it should print `~/.local/bin/git`. If not, the `$ENVRC` line in the blueprint step is missing or the snapshot hasn't been rebuilt since you added it.
  </Accordion>

  <Accordion title="Sessions are attributed to a human's email">
    The identity wasn't set in the blueprint, so posthook fell back to the `git config user.email` Devin commits with — which is often the engineer who started the session, or a bot account. Add the `posthook identity --set-email` line to the blueprint and rebuild.
  </Accordion>
</AccordionGroup>
