Event-driven employees¶
An employee is an enrolled managed agent targeted by a persistent Flow. Each accepted event has a durable Flow execution and an independent bounded runtime conversation. Codex uses the existing SDK sidecar; Nanobot provides the governed DeepSeek-compatible harness. Configure an account-owned model with the Preloop model gateway enabled. Employee tasks refuse to use a broad runtime credential or an ungoverned provider connection.
Configure an employee Flow¶
Enroll and start the selected runtime using its Agent Control integration.
Create a Flow with agent_type: "codex" (the persistent path selects the enrolled
agent's actual runtime), the gateway-enabled ai_model_id, and this configuration:
{
"agent_config": {
"execution_path": "persistent",
"target_agent_id": "ACCOUNT_OWNED_MANAGED_AGENT_UUID",
"limits": {"max_turns": 10, "max_total_tokens": 32000, "max_usd": 2}
},
"timeout_seconds": 300,
"trigger_event_source": "discord",
"trigger_event_types": ["member_joined", "channel_message"],
"trigger_config": {
"employee_events": {
"source": "discord",
"connection_id": "ACCOUNT_OWNED_CONNECTION_UUID",
"kinds": ["member_joined", "channel_message"],
"subjects": ["guild:example:members", "guild:example:channel:help"]
}
},
"webhook_config": {"employee_secret": "GENERATE_AT_LEAST_32_RANDOM_CHARACTERS"}
}
Generate the ingress secret locally with python -c 'import secrets;
print(secrets.token_hex(32))'. Keep it in the account's Flow configuration and the
bridge environment. The Flow write API accepts employee_secret; list/detail responses redact it, and updates that omit the field preserve the stored value. Explicit null clears it. It authenticates this integration; it is not a user login or
a permission grant. The service checks the owning account, managed-agent lifecycle,
Flow enablement, source, connection, event kind and subject on every delivery.
Payloads are limited to 64 KiB and bot/webhook messages are excluded.
For GitHub, GitLab and Jira use the existing tracker webhook setup. Set
trigger_event_source and employee_events.connection_id to the tracker UUID,
employee_events.source to the provider, and subscribe to issue_created or
pull_request_merged (merge_request_merged for GitLab). Subject scope can use a
trailing :*, such as github:example/repository:issue:*, to permit new issue IDs
inside one repository. Other wildcards are not supported. Ordinary tracker Flows
retain their existing behavior. Existing project and organization trigger filters
continue to apply before employee intake.
Additional trusted adapters can call
preloop.services.employee_events.ingest_employee_event after provider
verification, supplying account/source/connection identity independently of
message text. Chat assistant connections handle linked human commands separately;
the Discord bridge below supplies employee channel and membership events. The returned execution ID and PENDING status acknowledge durable
intake; they do not claim that the task completed. Read execution status, failure,
model usage, approvals and history through the existing Flow APIs/console.
Discord Gateway bridge¶
Enable Server Members for joins and Message Content for explicitly selected channels in the Discord application's privileged intents. Grant the bot access only to the desired server/channels. Configure these environment variables in the bridge's private service environment:
export DISCORD_BOT_TOKEN='SET_IN_PRIVATE_ENVIRONMENT'
export DISCORD_GUILD_ID='example'
export DISCORD_CHANNEL_IDS='help'
export PRELOOP_EMPLOYEE_EVENT_URL='https://preloop.example.com/api/v1/employee-events/FLOW_UUID'
export PRELOOP_EMPLOYEE_EVENT_SECRET='SAME_RANDOM_SECRET_AS_FLOW'
python -m preloop.integrations.discord_employee_bridge
The bridge reads joins and selected channel messages and sends no Discord replies.
It signs raw HTTP bodies with HMAC-SHA256. Stable Discord event identities dedupe
replays. A private checkpoint under ~/.preloop/discord-employee-*.json records
Gateway session/sequence only after ingress acknowledgement; reconnects and process
restarts resume from that point. Discord can expire a Gateway session; reconnecting
with a fresh session cannot recover events outside Discord's available replay
window. Observe provider availability and execution status in operations.
GlitchTip¶
Configure a GlitchTip/Sentry-compatible signed webhook to
https://preloop.example.com/api/v1/employee-events/FLOW_UUID. Set its signing
secret to the Flow's employee_secret; requests carry the hex HMAC-SHA256 of the
raw body in X-Sentry-Hook-Signature. Subscribe with source: "glitchtip",
kinds: ["error"], a connection identity chosen by the account owner, and subject
scope project:PROJECT_ID:issue:*. The accepted notification body includes
data.project.id, data.event.event_id (or id), and data.event.issue_id (or
group_id). If a GlitchTip version exposes a different notification envelope or
an unsigned webhook, translate it in an authenticated local relay to this shape;
do not expose an unauthenticated generic endpoint.
Lifecycle, limits and recovery¶
PostgreSQL's existing delivery unique index arbitrates simultaneous submissions. The event row is committed before dispatch. Existing NATS task dispatch, exclusive claims, lease heartbeats, stale-lease recovery and bounded retry decisions run the Flow. A failed publish leaves an observable pending execution for recovery. Suspending an employee, disabling the Flow or using the account kill switch takes effect on subsequent intake. Per-execution gateway credentials enforce model and MCP scope, token/turn/USD limits and revocation; runtimes also enforce duration and prompt/history bounds. The last gateway response may cross an already-spent ceiling once, as documented by the existing Flow limit policy.
A role prompt and relevant workspace files provide the small current-state index. Each independent event starts a new conversation; old task histories remain in the execution/session timeline and are not concatenated into new prompts. Native session results are mapped to the originating command. A stop addresses that owned command/session, never whichever unrelated session happens to be current.
The Codex sidecar stores command receipts beside its control config by default
(employee_state_path can override). On restart, terminal deliveries replay their
recorded result without repeating effects. If it crashed during a command, it
reports an explicit failure requiring review before deliberate retry. Keep this
ledger with the sidecar identity and back it up with private runtime state. Do not
run multiple sidecars sharing one ledger concurrently. Runtime/gateway failures
remain visible in the Flow; unknown prior effects are not automatically retried.
Gateway API keys in command storage are encrypted using the existing server Fernet key. Only authorized runtime delivery decrypts them. Activity/history and result projections redact both plaintext and encrypted credential fields.
To roll back, disable the Flow, stop the bridge/runtime, then rotate the ingress secret. Preserve execution records and the runtime replay ledger for audit and idempotency. No database migration is required for employee execution itself.
Local acceptance fixtures¶
All examples use synthetic identities and mocks; no external messages/model calls
are made. Set PRELOOP_DISABLE_TELEMETRY=true for every scripted run.
PRELOOP_DISABLE_TELEMETRY=true PYTHONPATH=backend pytest \
backend/tests/services/test_employee_events.py \
backend/tests/services/test_employee_event_reservation.py \
backend/tests/services/test_control_credentials.py \
backend/tests/endpoints/test_employee_event_ingress.py \
backend/tests/agents/test_agent_control_executor.py
PRELOOP_DISABLE_TELEMETRY=true npm --prefix runtime-plugins/codex-preloop test
Against an isolated PostgreSQL database migrated to the current Alembic head,
test_employee_events_db.py runs intake/reservation/replay/tenant-denial fixtures
for both Codex and Nanobot employee identities. Existing
test_flow_execution_worker_orchestration.py and
test_agent_control_persistence.py exercise claims, stale recovery, durable command
acknowledgement and reconnect. Nanobot's runtime plugin tests exercise the actual
supported Nanobot SDK seam with a synthetic provider. These fixtures verify local
contracts; they do not certify a live provider's credentials or bot connectivity.
Updating employee runtimes¶
Install the matching Preloop CLI and Codex sidecar together. For a development
CLI use the sanctioned cd cli && make install-local; deploy the sidecar built
from the same checkout using its normal plugin installation workflow. Do not copy
a binary over an existing CLI executable. Employee mode probes for the new
permission-hook --require-flow-credential capability and fails closed before
launching Codex when an older CLI is installed. This keeps an old hook from
falling back to a broad managed-agent credential. Testing/building the checkout
does not update an installed CLI or running sidecar.
Each Codex employee task gets a private CODEX_HOME with scoped provider/MCP
configuration and mandatory scoped native permission hooks. Credentials stay in
the subprocess environment, never in config files or command arguments. Curated
global AGENTS guidance, repository instructions and independent native hooks are
preserved, along with the explicit native approval policy and configured sandbox.
Broad auth/provider/MCP configuration and unrelated credential environment values
are not inherited. Task homes retain native session history in the private host
runtime temporary directory; do not delete one while its task runs. Subsequent
independent events start fresh sessions. The backend does not resume a prior task
from this temporary home after a host restart; it reports uncertain interrupted
work through the durable receipt, allowing an operator to review before retry.
The execution model URL and the API/MCP URL may use separate hosts. The
controller supplies gateway.api_url from PRELOOP_URL; set this to the API
origin used for runtime enrollment. The runtime verifies that API URL against
its enrolled control origin. Model calls use the supplied gateway URL, while
native permission and MCP calls use the verified API origin with the same scoped
execution token. Employee tokens also bind the owned managed agent, so suspending
that identity or revoking the token prevents further native, model and MCP calls.