Add installable agent lifecycle hooks with Ponytail as the reference integration #76
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Goal
Add a small DS4Server-owned agent lifecycle extension mechanism so integrations such as Ponytail can be installed, enabled, and run inside DS4Server without depending on another host's Pi, OpenCode, Claude, or Codex installation.
Ponytail is the reference integration and must work end to end when installed from its own Git repository. The design should expose the lifecycle semantics Ponytail needs, not attempt to reproduce the complete Pi or OpenCode plugin APIs.
Verified reference behavior
Pi now presents hooks as part of its unified extension system. Ponytail's current Pi adapter uses session_start, input, before_agent_start, agent_start, and agent_end together with command registration, per-session state, status notifications, and bundled skills.
Ponytail's OpenCode adapter uses a per-turn system-prompt transform, a command-before hook, persistent mode state, and bundled skill paths.
Ponytail also ships a portable package descriptor at .codex-plugin/plugin.json and a command-hook manifest at hooks/claude-codex-hooks.json. That manifest currently requires only:
Those hooks already emit additional instruction context and mode status in a structured form. Supporting this narrow portable surface allows DS4Server to install the Ponytail repository directly without loading its Pi extension module or OpenCode plugin.
References:
Recheck the current Ponytail release and manifests when implementation begins. The issue was prepared against Ponytail 4.9.0.
Current DS4Server state
DS4Server already:
It does not currently have:
The implementation must extend these existing paths rather than introduce a second agent loop.
Phase 1: define the minimal package contract
Use a versioned Rust-owned extension model. For the first version, accept the portable subset already present in Ponytail's .codex-plugin/plugin.json:
Reject invalid manifests, duplicate IDs, unsupported schema shapes, absolute paths, path traversal, and symlink escapes. Every referenced file must resolve inside the installed package root.
Parse only the required subset. Unknown optional presentation fields may be ignored, but unsupported executable capabilities must produce a clear diagnostic rather than silently appearing active.
Store each package under DS4Server's Application Support directory with a separate writable data directory. Persist:
Installation and updates must be atomic: stage and validate the new checkout first, then replace the active version. Keep the previous version until activation succeeds so a failed update can roll back.
Phase 2: add explicit install and management controls
Add an Agent Extensions section to the existing Preferences window using the shared panel, row, button, and toggle styles.
The minimum UI must support:
Use the existing Rust Git dependency directly. Do not invoke git, npm, npx, pi, opencode, codex, or another package manager as a subprocess. Do not run package post-install scripts or automatically install JavaScript dependencies.
Before enabling an extension with command hooks, show a clear trust confirmation explaining that its hook commands execute local code with the user's account permissions. Installation alone must not silently enable an unreviewed extension. Updates that change the resolved commit must require the new version to pass validation before activation.
Do not auto-update extensions.
Phase 3: implement a bounded command-hook runner
Run hook commands from the package's own manifest, not from another agent's installed plugin directory.
For compatibility with Ponytail's existing hook command:
The compatibility environment is local to the child process and must not read or modify ~/.pi, OpenCode configuration, or a Codex plugin cache.
Execution rules:
A missing executable such as node must produce one actionable extension error. It must not crash the app or emit an error before every model token.
Hook failures outside a security gate should omit that hook's output for the turn, surface the failure, and allow the core agent to continue. Later successful invocations may clear the transient error.
Phase 4: map the lifecycle into the existing agent loop
Implement deterministic dispatch in installation order for these initial events:
SessionStart
Fire when:
Provide session ID, reason, canonical project directory, model, and timestamp. Respect each manifest matcher.
Treat hookSpecificOutput.additionalContext as hidden system-level agent context. Persist enough source metadata to avoid duplicate injection in one visible context and to re-arm it after compaction. A hook may not replace DS4Server's base tool schema, workspace instructions, direct user instructions, or safety policy.
Treat systemMessage as user-visible extension status or activity metadata, not as untrusted assistant text.
UserPromptSubmit
Fire once for each actual user-authored prompt before the coding-agent generation starts. Provide the original prompt, session ID, and project directory.
Additional context returned by the hook must affect that same turn. The original user prompt remains authoritative and must not be silently discarded or rewritten by the initial protocol.
This event must cover queued user input when it later becomes active. Host-only commands such as /compact must retain their current behavior.
SubagentStart
Fire before each fresh Ralph child round with agent_type set to ralph, its round number, the parent session ID, and the same project root.
Inject returned additional context into that child's system prompt only. It must not leak the parent conversation or weaken Ralph's fresh-context boundary. Future native subagent types should reuse this dispatcher rather than add a Ponytail-specific path.
The first version does not need generic tool-call interception. If before-tool hooks are added later, transformed calls must be revalidated and risk-assessed after every transformation, and extensions must never be able to auto-approve a tool.
Phase 5: expose extension skills through the existing skill system
For every enabled extension:
Do not copy extension skills into ~/.agents/skills. Their lifecycle belongs to the installed extension version.
Phase 6: Ponytail end-to-end validation
Install a pinned current Ponytail commit from https://github.com/DietrichGebert/ponytail.git through the new DS4Server flow. Do not use an already installed copy from Pi, OpenCode, Claude, or Codex.
Verify all of the following:
Use a small deterministic fixture extension for ordinary automated tests so the test suite does not require network access or Ponytail. Keep the real pinned Ponytail installation as an explicit integration test.
Security and correctness tests
Add focused coverage for:
Acceptance criteria
Likely code areas
Non-goals
implement hooks for the agent process in the form as pi has itto Add installable agent lifecycle hooks with Ponytail as the reference integrationImplemented and pushed in commit
3977261(Add installable agent lifecycle extensions).Implementation:
Review: DS4 has no corresponding agent-extension lifecycle, so this is a new DS4Server feature. Existing generation, tool validation/approval, AGENTS.md, Dev Brain, compaction, and Ralph paths remain authoritative and the no-hook path stays direct.
Verification: