🤖 Reflecting on Reflexes: A Journey in Human-AI Co-Creation 🛠️
· Categories: reflexes, docker, development-process, ai-collaboration, manifests, jekyll
What happens when you set out to build simple, containerized tools with an AI partner? You embark on a fascinating journey of refinement, debugging, and discovery! Over the last little while, we’ve been developing a concept called “Reflexes,” and this post chronicles that evolution, showcasing how human intuition and AI execution can collaborate to build something robust.
The Spark: Needing Deterministic Tools ✨
The core idea, born from conversations and captured in scratchpads like human_scratch_pad_reflexes_1.md, was simple: create small, deterministic, containerized units of functionality. Think of them like pure functions, but packaged as Docker images.
Key principles emerged:
- Build-time Freedom vs. Runtime Purity: Do whatever you need at build time (install, compile), but the runtime container must be self-contained, requiring no external network calls.
- Defined Interface: Clear inputs (env vars, mounted files/dirs) and outputs (stdout, mounted files/dirs).
- Idempotency (where applicable): Running it twice with the same input yields the same output.
The Manifest: Self-Documenting Reflexes 📜
How do you know how to use a reflex? We didn’t want users (human or AI) digging through Dockerfiles. The solution: a mandatory /manifest.yml inside each reflex image.
This YAML file defines:
- Metadata: Name, version, description.
- Inputs: Required/optional environment variables (
environment:), expected input mount points (input_paths:). - Outputs: Expected output format (
stdout:), output mount points (output_paths:).
This makes each reflex self-documenting. We even built a helper tool to parse this manifest and provide usage instructions!
(Initially, the manifest structure was a bit nested, but we refactored it based on clarity principles for a flatter, more explicit structure – a great example of iterative improvement during development!)
The Helper: nhi-entrypoint-helper 🏃♂️
To standardize the execution and interface, we created a Go-based entrypoint helper (nhi-entrypoint-helper). This tool became the standard ENTRYPOINT for reflexes.
Its job:
- Parse
/manifest.yml. - Handle
-h/--helpflags, printing usage derived from the manifest. - Handle
SHOW_MANIFEST=trueto print the raw manifest. - Validate Inputs/Outputs: Check if required env vars are set and mounted paths exist before running the reflex’s core logic.
- Set up Environment: Export environment variables based on validated mount paths (e.g.,
INPUT_CONTENT=/app/input_content). - Execute: Run the actual command specified by the user (e.g.,
python main.py,/app/process.sh).
The helper itself went through several refinements:
- Logging: Switched from basic
fmt.Fprintto structured logging (slog) for clearer output. - Environment Variables: Ensured variables set by the helper (like
INPUT_CONTENT) were correctly exported and available to the child process (execinsh -chas nuances!). - Permissions: Debugged tricky volume permission issues related to Docker creating host directories as root vs. the container running as a non-root user (
docker run --user). We ultimately confirmed that relying ondocker run --userpassed by ourbin/runscript was the canonical way, ensuring the helper started with the right permissions.
Putting it Together: The template and jekyll-site Reflexes 🧩
template: A simple Python reflex was built as an early example, taking text via an environment variable, processing it (e.g., doubling characters), and printing to stdout.jekyll-site: This was a more complex reflex designed to build this very blog! It takes markdown content and config files as input (input_paths) and outputs a static site (output_paths).
Building the jekyll-site reflex involved its own mini-journey:
- Base Images: Ensuring we used the correct, locally built
cortex/ruby:localbase image. - Theme Overrides: Creating custom
_layouts/default.htmlandassets/css/style.scssin theblog/contentinput directory to give the blog its unique, collaborative look and feel. - CSS Path Woes: Debugging why the CSS wasn’t applying correctly (initially using direct relative paths, then
relative_urlincorrectly, finally landing onrelative_urlwith a leading slash/combined withbaseurl: ""in_config.yml). - Post Location: Moving the existing blog markdown files into the conventional
_postsdirectory so Jekyll would process them correctly for the index page.
Looking Forward: Collaboration in Action ✨
This whole process, from defining the reflex concept to debugging CSS paths, perfectly illustrates the power of human-AI collaboration. The AI handles rapid implementation, code generation, and consistency checks, while the human provides architectural direction, debugging intuition (like spotting the root-owned directory!), and ensures the output aligns with broader project goals and conventions.
The manifest.yml and nhi-entrypoint-helper provide a solid foundation for building more complex, reliable, and understandable containerized tools.
What reflexes will we build next? Stay tuned!
This blog post reflects the collaborative development effort involving reflexes, manifests, and the Jekyll site build process.