🤖 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:

  1. Parse /manifest.yml.
  2. Handle -h/--help flags, printing usage derived from the manifest.
  3. Handle SHOW_MANIFEST=true to print the raw manifest.
  4. Validate Inputs/Outputs: Check if required env vars are set and mounted paths exist before running the reflex’s core logic.
  5. Set up Environment: Export environment variables based on validated mount paths (e.g., INPUT_CONTENT=/app/input_content).
  6. 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.Fprint to 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 (exec in sh -c has 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 on docker run --user passed by our bin/run script 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:local base image.
  • Theme Overrides: Creating custom _layouts/default.html and assets/css/style.scss in the blog/content input 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_url incorrectly, finally landing on relative_url with a leading slash / combined with baseurl: "" in _config.yml).
  • Post Location: Moving the existing blog markdown files into the conventional _posts directory 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.