147 lines
8.6 KiB
Markdown
147 lines
8.6 KiB
Markdown
# Porting Allium to Pi.dev: Keeping Upstream by Reference
|
|
|
|
The below (from 'As the agent said')was generated by a coding agent, after implementation. The agent has not picked up on that we could replicate the behaviour of claude code hooks inside a Pi extension. I didn't go for that, yet, as I wanted to make it work, then make it right.
|
|
|
|
There are a couple of third party skills I want to take with me from Claude Code to [Pi](https://pi.dev). Anything markdown and scripts is a matter of moving things around, as Pi supports the agents skill standard.
|
|
|
|
Plugins with agents are a bit different. The mapping is not one-on-one, and that is by design. Pi follows the unix philosophy of small, composable tools, Pi itself being one - invoke with `Pi -p` or `--json` for non-interactive use. The interactive prompt with its' fast `/reload` feels more like a Lisp REPL or Smalltalk workspace.
|
|
|
|
I come from Claude Code. I use it in a variety of ways. Sometimes quickly building out a prototype in as close to production quality as possible, taking bigger steps after iterative brainstorming. Sometimes in-the-loop with short cycles when I want something small and focused, or am exploring how a particular flow with agents or llms' works.
|
|
|
|
Surprisingly [Allium](https://github.com/juxt/allium) adds value for both cases. Allium to me is conversational formal verification. The conversation can be more detailed, while staying playful and iterative. I can generate tests, or take an implementation, and have it find gaps.
|
|
|
|
Read on for a brief explanation of a gap found in [pi-turn-limit](https://gitea.apps.sustainabledelivery.com/QWAN/monotonic-pi-extensions). I made pi-turn-limit exploring how far `/reload` and a local model would go in making an extensoin. Allium + the same local model found a gap, and helped make a new feature with tests. Directed by claude code. That non-interactive mode of `Pi` makes iterating in a loop easy.
|
|
|
|
Enjoy,
|
|
|
|
Willem van en Ende
|
|
|
|
P.S. I see I need to rename the skills. Will probably be skill:allium-* instead of skill:*
|
|
|
|
# As the agent said...
|
|
|
|
(agent was Pi with Qwen3.6-27b)
|
|
|
|
|
|
> How to adapt an AI agent skill framework for a new platform without forking the source.
|
|
|
|
[Allium](https://github.com/juxt/allium) is a formal language for specifying software behavior at the domain level. It sits between informal feature descriptions and implementation code, giving AI agents something more structured to work with than raw Markdown prompts.
|
|
|
|
Allium ships as a Claude Code plugin with six skills: **elicit**, **distill**, **propagate**, **tend**, and **weed** (plus a root skill for routing). Each skill is a SKILL.md file with frontmatter and a body of instructions, supported by reference documents.
|
|
|
|
When I wanted to use Allium with [pi.dev](https://pi.dev), I faced a choice: fork the entire repository and maintain a parallel copy, or find a way to reference the upstream content directly. I chose the latter.
|
|
|
|
## The symlink strategy
|
|
|
|
The key insight: most of Allium's content is universal — the language reference, the elicitation methodology, the distillation workflow. What's platform-specific is the frontmatter (metadata format) and the invocation paths (`/allium:elicit` vs `/skill:elicit`).
|
|
|
|
So the port structure is:
|
|
|
|
```
|
|
.pi/skills/elicit/
|
|
├── SKILL.md ← thin adapter: pi frontmatter + invocation paths
|
|
└── references/ ← symlinks to upstream
|
|
├── language-reference.md → ../../../../allium-main/references/language-reference.md
|
|
└── library-spec-signals.md → ../../../../allium-main/skills/elicit/references/library-spec-signals.md
|
|
```
|
|
|
|
The SKILL.md files are thin adapters. They change the YAML frontmatter from Claude Code format to pi.dev format, update skill invocation references, and include a few pi-specific instructions. Everything else flows through symlinks.
|
|
|
|
**Result**: when upstream Allium updates, I run `git pull` in `allium-main/` and the port is current. No merge conflicts, no duplication, no drift.
|
|
|
|
## What changed
|
|
|
|
### Frontmatter
|
|
|
|
Claude Code plugins use `name`, `description`, `version`, and `auto_trigger`. Pi.dev skills use `name`, `description`, `disable-model-invocation`, `license`, and `metadata`:
|
|
|
|
```yaml
|
|
---
|
|
name: elicit
|
|
description: Elicit requirements and design decisions
|
|
disable-model-invocation: true
|
|
license: MIT
|
|
metadata:
|
|
upstream: https://github.com/juxt/allium
|
|
version: 3
|
|
---
|
|
```
|
|
|
|
### Invocation paths
|
|
|
|
Every `/allium:X` becomes `/skill:X`. The routing table in the root skill maps tasks to the correct skill:
|
|
|
|
| Task | Claude Code | Pi.dev |
|
|
|-----------|---------------------|--------------------|
|
|
| Root | `/allium` | `/skill:allium` |
|
|
| Elicit | `/allium:elicit` | `/skill:elicit` |
|
|
| Distill | `/allium:distill` | `/skill:distill` |
|
|
| Propagate | `/allium:propagate` | `/skill:propagate` |
|
|
|
|
### Agents become skills
|
|
|
|
Allium's **tend** and **weed** are Claude Code agents — they specify a model (Opus) and tool permissions (Read, Glob, Grep, Edit, Write, Bash). Pi.dev doesn't have native agent support with model selection or tool scoping.
|
|
|
|
The pragmatic solution: port them as regular skills with `disable-model-invocation: true`. They run on whatever model you're using, with whatever tools are available. The methodology is the same; only the enforcement differs.
|
|
|
|
### Rules become references
|
|
|
|
Claude Code's `.claude/rules/allium.md` contains syntax rules and anti-patterns that activate on glob patterns. Pi.dev has no glob-based rule activation.
|
|
|
|
Solution: symlink the rules file into `references/allium-rules.md` and add a "Syntax rules" section to each skill instructing the model to read it before writing `.allium` files. Same content, different delivery.
|
|
|
|
### Hooks become instructions
|
|
|
|
The Claude Code version runs `allium-check.mjs` as a PostToolUse hook — every time the model writes or edits a file, validation runs automatically. Pi.dev has no equivalent hook system.
|
|
|
|
Solution: add a "Verification" section to skills that write `.allium` files, instructing the model to run `allium check <file>` after writing. It's not automatic, but it's explicit.
|
|
|
|
## Testing with a real project
|
|
|
|
I tested the port against [pi-turn-limit](https://gitea.apps.sustainabledelivery.com/QWAN/monotonic-pi-extensions), a pi.dev extension that limits conversation turns.
|
|
|
|
The workflow:
|
|
|
|
1. **Distill** (`/skill:distill`) — extracted a `.allium` spec from existing TypeScript code. The spec captured turn counting, enable/disable, config persistence, and UI separation.
|
|
2. **Propagate** (`/skill:propagate`) — generated 30 test obligations: 10 unit tests for pure functions, 8 config/command tests, 3 entity state tests, and 5 rule tests.
|
|
3. **Elicit** (`/skill:elicit`) — explored a new "disable turn limit" feature. Three design decisions emerged: unlimited means no boundary check, hard reset on re-enable, and config value is not entity state.
|
|
4. **TDD cycle** — wrote 8 new tests from propagate output (red: 8 failing), implemented unlimited mode, ran tests (green: 29/29 passing).
|
|
|
|
The ported skills produced the same quality of output as the Claude Code originals.
|
|
|
|
## Trade-offs
|
|
|
|
The symlink strategy works well but has trade-offs:
|
|
|
|
| Advantage | Trade-off |
|
|
|-----------------------------|----------------------------------------------------------|
|
|
| Zero content duplication | Symlinks must resolve — repo structure matters |
|
|
| Automatic upstream sync | No automatic sync — you still need to `git pull` |
|
|
| Minimal maintenance surface | Platform gaps (agents, hooks, rules) require workarounds |
|
|
| Clear attribution | Upstream content remains under JUXT's copyright |
|
|
|
|
## Known limitations
|
|
|
|
The port doesn't replicate three Claude Code features (yet):
|
|
|
|
1. **Model selection** — tend/weed run on your default model, not Opus
|
|
2. **Tool scoping** — skills can use any available tool, not a restricted set
|
|
3. **Automatic validation** — `allium check` is instructed, not enforced
|
|
|
|
|
|
## Conclusion
|
|
|
|
The symlink-based port pattern works well for adapting AI agent skills across platforms when:
|
|
|
|
- The core methodology is platform-agnostic
|
|
- Platform differences are mostly in metadata and invocation
|
|
- You want to stay in sync with upstream without forking
|
|
|
|
It's not a perfect solution — the agent and hook workarounds are manual. But it's maintainable, and it produces results.
|
|
|
|
The port is available at [github.com/.../pi-allium-port](https://github.com/.../pi-allium-port) (link TBD).
|
|
|
|
---
|
|
|
|
*Allium is developed by [JUXT Ltd](https://github.com/juxt) and licensed under MIT. This port adapts Allium for pi.dev while keeping upstream content by reference.*
|