AkurAI Build
Menu

popagent

public

Latest change f81c1154ece5a040b42b3624c8429fbf56c15cfa - Remediate failed background tasks by AkurAI Build

# Hooks

Popagent exposes managed, versioned HTTP lifecycle hooks. The implementation is
`src/hooks.ts`; Mastra adapters are in `src/hook-lifecycle.ts`; audit persistence
is in `src/hook-audit.ts`.

## Events

| Event | Timing | Allowed outcomes |
|---|---|---|
| `SessionStart` | Persistent session creation or first resume in a server process | `pass` |
| `TaskStart` | After a queued background task is claimed | `pass` |
| `UserPromptSubmit` | Before recall and model execution | `pass`, `deny`, `modify` |
| `SubagentStart` | Immediately before specialist delegation | `pass`, `deny`, `modify` |
| `PreToolUse` | Immediately before every Mastra tool | `pass`, `deny`, `modify` |
| `PostToolUse` | After successful tool execution | `pass` |
| `PostToolUseFailure` | After failed tool execution | `pass` |
| `SubagentStop` | After specialist delegation finishes | `pass` |
| `TaskStop` | After a background task completes, fails, cancels, or is interrupted | `pass` |
| `Stop` | After a complete model response, before accepting completion | `pass`, `continue` |
| `StopFailure` | Provider, stream, or framework failure | `pass` |
| `SessionEnd` | Immediately before permanent session deletion | `pass` |

`Stop` continuation maps to a Mastra processor retry and is capped by
`maxProcessorRetries: 3`. `SubagentStart` can deny delegation or replace its
prompt; `SubagentStop` receives the bounded specialist response. Failed
delegations also return bounded failure feedback to the supervisor so it can
recover or report the limitation. `TaskStart` and `TaskStop` share one generated
turn ID for correlation. `SessionEnd` fires for
permanent deletion, before the session transcript and matching Mastra thread
data are removed. Archiving is reversible display state and does not end the
session.

## Configuration

`POPAGENT_HOOKS` contains one strict JSON document:

```json
{
  "version": 1,
  "hooks": {
    "PreToolUse": [
      {
        "id": "tool-policy",
        "type": "http",
        "url": "https://hooks.example.com/popagent",
        "matcher": ["remember", "useBrowserSecret"],
        "timeoutMs": 2000,
        "failureMode": "closed"
      }
    ]
  }
}
```

Every configured hostname must appear exactly in the comma-separated
`POPAGENT_HOOK_ALLOWED_HOSTS`. HTTPS is mandatory. Hook requests do not inherit
server credentials or arbitrary headers.

Matchers use exact Mastra runtime registry keys, not the hyphenated `createTool`
IDs. Tool events match tool keys such as `getTime`, `remember`,
`useBrowserSecret`, and `webSearch`; delegation events match specialist IDs such
as `researcher`, `implementer`, and `reviewer`.

Each request receives `schemaVersion`, stable event/session/turn/tool-call IDs,
model, timestamp, and event-specific `detail`. Responses are strict JSON with
`schemaVersion: 1` and one event-supported outcome. Each `additionalContext`
field and the combined context from all handlers in one dispatch are capped at
16 KiB UTF-8; reasons are capped at 2 KiB UTF-8 and responses at 64 KiB.
Response streams are cancelled as soon as they exceed the cap. Handlers are
capped at 30 seconds; the default timeout is two seconds.

## Execution rules

- Gate and mutation handlers run sequentially in declaration order. Prompt or
  tool-input mutation becomes the next handler's input.
- `UserPromptSubmit`, `SubagentStart`, `PreToolUse`, and `Stop` fail closed by
  default and may be configured open. Observational events always fail open;
  configuration rejects
  `failureMode: "closed"` for them. Transport, timeout, malformed-response, and
  event-outcome failures all use the same event/handler failure mode.
- Fail-closed `Stop` hooks can block turn completion after the processor's retries
  are exhausted.
- `UserPromptSubmit` and `SubagentStart` replacements must be strings.
  `PreToolUse` replacements must be argument objects and still pass the tool's
  normal schema validation.
- For trusted self-update execution, fixed tool policy runs both before managed
  `PreToolUse` dispatch and after any replacement. It denies credential/browser
  tools, protected or hidden paths, remote/release commands, and unrestricted
  shell syntax; hook configuration, hook replacement, and prompt text cannot
  relax that boundary.
- A prompt replacement collapses every text fragment in the latest user message
  into one replacement while preserving non-text attachments and tool parts.
- Background tasks emit correlated `TaskStart`, prompt, delegation, tool,
  response, failure, and `TaskStop` events. One attempt shares a turn and trace
  ID. Completed, failed, dead-letter, and cancelled `TaskStop` dispatch follows
  executor settlement and the durable task transition. After a terminal failure
  or dead letter, the runtime also creates one deduplicated remediation task in
  the same workspace. Remediation tasks carry durable parent lineage and cannot
  recursively create more remediation work. An `interrupted` `TaskStop` instead
  reports that bounded abort acknowledgement expired; it does not terminalize
  the still-`running` or `cancelling` row. Handler or downstream lifecycle
  notification failure is logged and cannot change or contradict the durable
  state. Background tasks do not emit `SessionStart` or `SessionEnd`, which
  track persistent chat creation/resume and deletion.
- `PostToolUse` is observational. Mastra's after-tool hook cannot replace tool
  output, so post-tool mutation and added context are not accepted.
- Hook decisions are persisted in `popagent_hook_runs`; audit write failures are
  logged but never change the handler outcome or its configured failure mode.
  Raw event payloads and model transcripts are not copied into that table.
- Audit rows are cleaned both after writes and by the daily global maintenance
  job: records older than 30 days are removed and each session is capped at 500.
  Permanent session deletion removes that session's audit rows.
- `GET /api/sessions/:id/hooks` returns the latest 500 audit records in
  chronological order. The UI groups records by stable turn ID in a collapsible
  per-turn timeline.
- Hook calls never recursively emit hooks.
- `useBrowserSecret` hook payloads contain only a credential name and browser
  element reference before use, then name/origin/success metadata after use.
  Plaintext is resolved and consumed below the tool and hook boundaries.
- Retired `storeSecret`, `recallSecret`, and `updateSecret` payloads are redacted
  to the literal `[redacted]` before PreToolUse or PostToolUse dispatch.
- Hooks are server-managed environment configuration. User and session content
  cannot define executable hooks.

## Contract tests

`src/hooks.test.ts` owns ordering, matching, mutation, failure-mode, audit
isolation, aggregate byte-budget, and schema contracts using an isolated
transport. `src/hook-audit.test.ts` owns persistence bounds and deletion.
Affected server tests own route integration.