feat(agent): add-on system with ztk as the first built-in#2279
Draft
joshsny wants to merge 1 commit into
Draft
Conversation
Introduces a per-task add-on system that lets users enable opt-in agent capabilities by name, configured from a `task.options.add_ons` JSON blob. The first built-in is `ztk`, a Claude PreToolUse hook that wraps Bash commands through the ztk token-reduction CLI. Architecture - New `packages/agent/src/add-ons/` module with a typed `AddOnDefinition` contract, a registry that resolves names → contributions for the active adapter, and a process-wide default registry populated with the built-in add-ons. - Unified registry, per-adapter capability honoring. Claude consumes every contribution slot (env, systemPromptAppend, preToolUse, postToolUse) via `buildSessionOptions`. Codex honors only `systemPromptAppend`; `preToolUse` / `postToolUse` have no equivalent in the upstream `codex-acp` binary, so add-ons that need command interception declare `supportedAdapters: ["claude"]` and are skipped on Codex with a logged warning. - Add-on configuration travels through `_meta.addOns` on the ACP `newSession` request, parallel to the existing `claudeCode` and `jsonSchema` channels. Resolved by the adapter's `newSession`, `loadSession`, `unstable_resumeSession`, and `unstable_forkSession`. Cloud parity - `AgentServerConfig.addOns` plus a matching `--addOns <json>` CLI flag on the sandbox agent server forwards the config to the cloud `newSession`, so a task carries its add-ons whether it runs locally or on the cloud sandbox. Task model - New `Task.options` JSONField on the Django Task model is the client-side home for add-on config (`task.options.add_ons`). The Django migration is not in this PR — until it lands the field is absent on every API response and add-ons can only be exercised programmatically. ztk specifics - `ztk` add-on contributes a single Claude `PreToolUse` hook that rewrites `tool_input.command` for `Bash` tool calls to `ztk run [--skip-permissions] -- <original>`. Options: `binaryPath`, `skipPermissions`. Binary resolution checks the configured path, then PATH, then known install locations, then `~/.local/bin`. Skipped silently on Codex. Tests - 19 new tests across `registry.test.ts` (merge semantics, adapter gating, unknown-name handling, options-validation failure, prepare ordering) and `ztk.test.ts` (Bash rewriting, double-wrap protection, shell-quote escaping, --skip-permissions, Codex skip). Generated-By: PostHog Code Task-Id: de63edf0-0b52-40b4-9ffc-4758850b10d3
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
PostHog Code tasks need a way to opt in to extra agent behaviors per-task — currently the only knobs are buried inside
claudeCode.optionsormcpServers, and there's no symmetric path for cloud and local. The first concrete use case is ztk (https://github.com/codejunkie99/ztk), a CLI that wraps shell commands to reduce LLM token consumption, but the surface should support future capabilities (PostHog-aware system-prompt addenda, MCP-based integrations, etc.) without bespoke plumbing each time.Changes
Adds a typed add-on system to
@posthog/agentand atask.options.add_onsconfiguration channel so a user can enable extensions by name when kicking off a task. Architecture:packages/agent/src/add-ons/definesAddOnDefinition(name, options schema, optionalprepare,contribute) andAddOnRegistry. The same registry is consumed by both Claude and Codex adapters; each honors only the slots it can (Claude consumes all of env / systemPromptAppend / preToolUse / postToolUse; Codex consumes only systemPromptAppend becausecodex-acpexposes no pre-execution tool interception). Add-ons that need command rewriting declaresupportedAdapters: ["claude"]and are skipped on Codex with a logged warning._meta.addOnsof the ACPnewSessionrequest, alongside the existingclaudeCode/jsonSchemachannels. Resolved inClaudeAcpAgent.createSession(Claude) andCodexAcpAgent.{newSession, loadSession, unstable_resumeSession, unstable_forkSession}(Codex).AgentServerConfig.addOnsand a matching--addOns <json>CLI flag on the sandbox agent server forward the same JSON onto the cloudnewSession, so a task carries its add-ons regardless of where it runs.ztkAddOncontributes a ClaudePreToolUsehook that rewritestool_input.commandfor Bash tool calls intoztk run [--skip-permissions] -- <original>. Options:binaryPath,skipPermissions. Binary resolution checks the configured path, then$PATH, then known install locations (/opt/homebrew/bin,/usr/local/bin,/usr/bin), then~/.local/bin.options?: TaskOptionsfield (withadd_onsinside) to both the agent-packageTasktype and the apps/code sharedTasktype, so the renderer and main process can populate it ahead of the Django migration.Follow-ups not in this PR
Taskmodel needsoptions = models.JSONField(default=dict, blank=True)plus a matching serializer field. Until that lands the field is absent on every API response and add-ons can only be exercised programmatically (e.g. by callers that build_meta.addOnsdirectly).task.options.add_onsis settable from code but there's no "Add-ons" disclosure in the task input form.How did you test this?
registry.test.ts(empty config, env merging with last-wins precedence, systemPromptAppend concatenation, pre/postToolUse aggregation, adapter gating, unknown-name warnings, options-validation failure, prepare/contribute ordering, prepare failures propagate, duplicate-registration rejection) andztk.test.ts(options schema rejects invalid input, missing-binary errors fromprepare, Bash command rewriting, non-Bash pass-through,--skip-permissions, Codex skipped via adapter gating, end-to-end registry resolution, double-wrap protection, single-quote escaping for paths with quotes).pnpm --filter agent typecheckclean.pnpm --filter agent test src/add-ons— 19/19 pass. Pre-existing options.test.ts (3) still pass.Publish to changelog?
no
Created with PostHog Code