Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Harness Engineering on Rails

Harness Engineering on Rails

Agents have re-shaped how AI can work with code yet we still use them like ChatGPT and wonder why they disappoint. The quest to one-shot our code with the perfect prompt is a trap. We get much better results by hooking into signals from RSpec, Rubocop, or even a self-review. This requires a paradigm shift: from chatbot to reactive system. When your reaction to mediocre code is to fix the harness rather than the mistake, agents can become valuable assistants that write code you are proud of.

Originally given at RailsWorld 2026

Avatar for Joël Quenneville

Joël Quenneville

September 24, 2026

More Decks by Joël Quenneville

Other Decks in Technology

Transcript

  1. Raw API call curl https://api.anthropic.com/v1/messages \ -d "$(jq -n --rawfile

    tests fizz_buzz_test.rb '{ messages: [{ role: "user", content: ( "Implement fizz_buzz.rb so that all the tests pass.\n\n" + $tests ) }] }')"
  2. Writing a Ruby Wrapper prompt = <<~PROMPT Generate an implementation

    such that all the tests pass. #{File.read("fizz_buzz_test.rb")} PROMPT response = model.ask(prompt) File.write("fizz_buzz.rb", response)
  3. Mostly Correct Code def fizz_buzz(number) if number % 3 ==

    0 "Fizz" elsif number % 5 == 0 "Buzz" elsif number % 15 == 0 # will never trigger "FizzBuzz" else number.to_s end end
  4. Use a better prompt? response = <<~PROMPT Implement fizz_buzz.rb so

    that all the tests pass. #{File.read("fizz_buzz_test.rb")} Remember to handle numbers divisible by both 3 and 5. PROMPT
  5. Use a better prompt? prompt = <<~PROMPT Implement fizz_buzz.rb so

    that all the tests pass. #{File.read("fizz_buzz_test.rb")} Remember to handle numbers divisible by both 3 and 5. MAKE NO MISTAKES! PROMPT
  6. Human plumbing 1 1 You: Send test failures to LLM

    You have to copy-paste this 4 2 LLM: generates code 3 You: Save the LLM's new version 2 You have to copy-paste this 3 4 RSpec: runs the tests
  7. Adding a loop loop do test_result = run_tests("fizz_buzz_test.rb") break if

    test_result.success? prompt = <<~PROMPT Write an implementation so that all of the tests pass. Current implementation: #{File.read("fizz_buzz.rb")} Test failures: #{test_result.output} PROMPT response = model.ask(prompt) File.write("fizz_buzz.rb", response) end
  8. Sloppy Ruby def dashboard_path_for(user) if user.moderator? == true and user.suspended?

    == false return moderation_queue_path else dashboard_path end end
  9. AGENTS.md? ## Ruby style - Use `&&` and `||` instead

    of `and` and `or`. - Do not compare predicates with `true` or `false`. - Avoid explicit `return` at the end of a method.
  10. Human plumbing 1 1 You: Send lint failures to LLM

    You have to copy-paste this 4 2 LLM: generates code 3 You: Save the LLM's new version 2 You have to copy-paste this 3 4 Rubocop: Run the lint rules
  11. Claude hook "Stop": [ { "hooks": [ { "type": "command",

    "command": "grep -q '\"stop_hook_active\": *true' && exit 0; { git diff --nameonly --diff-filter=d HEAD 2>/dev/null; git ls-files -o --exclude-standard; } | grep \"\\.rb$\" | xargs -r bundle exec rubocop --force-exclusion >&2 || exit 2", } ] } ]
  12. Multi-tenant bugs # BAD def show @document = Document.find(params[:id]) end

    # GOOD def show @document = current_account.documents.find(params[:id]) end
  13. Custom Cop def_node_matcher :constant_lookup, <<~PATTERN (send (const {nil? cbase} $_)

    $_ ...) PATTERN def on_send(node) constant_lookup(node) do |model, method| association = tenant_owned_models[model.to_s] next unless association next unless restricted_methods.include?(method.to_s) receiver = node.receiver add_offense(receiver, message: message) do |corrector| corrector.replace(receiver, "#{tenant_scope}.#{association}") end end end
  14. AGENTS.md? When creating a migration: - Prefix the filename with

    a 14-digit UTC timestamp in `YYYYMMDDHHMMSS` format. - Ensure the migration number follows the existing migrations. - Use a snake_case filename whose name matches the migration class.
  15. Claude hook "PreToolUse": [ { "matcher": "Write", "hooks": [ {

    "type": "command", "command": "ruby \"$CLAUDE_PROJECT_DIR/.claude/hooks/block-db-migratewrites.rb\"", "statusMessage": "Checking migration path" } ] } ]
  16. Steer with the Error Message DENIAL_REASON = <<~MSG Creating files

    under db/migrate/ directly is blocked in this project. Use the Rails generator instead: bin/rails generate migration AddPartNumberToProducts part_number:string It picks the timestamped filename and matching class name. Once the file exists you can edit it freely to refine the migration — only creating it from scratch is blocked. Run `bin/rails generate migration --help` for the column and reference syntax it accepts. MSG
  17. AGENTS.md? - Separate branching from doing. - Prefer behavior on

    domain objects. - Avoid special cases through better abstractions.
  18. Human plumbing 1 4 1 You: Invoke skills 2 Sub-agent:

    review code 3 You: Share review 2 You have to copy-paste this 4 3 Main Agent: Make the fixes
  19. Orchestration via /implement Implement and verify the requested work following

    this workflow: 1. Implement the requested work. 2. Have a sub-agent run `/structural-review`; make appropriate fixes. 3. Have a sub-agent run `/method-review`; make appropriate fixes. 4. Have a sub-agent run `/hierarchical-documentation`; make appropriate fixes.
  20. MENTAL MODEL Harness Engineering for Coding Agent Users Birgitta Böckler

    martinfowler.com/articles/harness-engineering.html
  21. Let's be creative! Computational Rubocop Generators Flog Logs Playwrite Prompts

    Agentic Review AGENTS.md Inferential Security Scanner Tests Feedback Feed forward Reek Code Climate
  22. Let's be creative! Computational Rubocop Generators Templates Reek Setup Scripts

    Playwrite Prompts Agentic Review AGENTS.md Inferential Security Scanner Tests Feedback Feed forward Flog Logs Code Climate
  23. Let's be creative! Computational Rubocop Generators Templates Reek Setup Scripts

    Security Scanner Playwrite Prompts Agentic Review AGENTS.md Tests Agentic Security Scan Intelligent code analysis Inferential Feedback Feed forward Flog Logs Code Climate
  24. Let's be creative! Computational Rubocop Generators Reek Setup Scripts Playwrite

    Prompts Agentic Review AGENTS.md Nearby Source Code Security Scanner Tests Agentic Security Scan Intelligent code analysis Inferential Feedback Feed forward Templates Static Docs Flog Logs Code Climate
  25. When a measure becomes a target it ceases to be

    a good measure GOODHART'S LAW
  26. Document Pipeline is a Funnel Parse Text Extract IDs Parse

    Error Blank Doc Match Publish No IDs No Match Found Wrong Match
  27. So many mechanisms Computational Rubocop Generators Reek Setup Scripts Playwrite

    Prompts Agentic Review AGENTS.md Nearby Source Code Security Scanner Tests Agentic Security Scan Intelligent code analysis Inferential Feedback Feed forward Templates Static Docs Flog Logs Code Climate
  28. Human plumbing 1 4 1 You: Invoke tool 2 Tool:

    gives output 3 You: share output with LLM 4 LLM: process and suggest next tool invocation 2 3
  29. Questions to improve your harness Where did I have to

    intervene? What signals were missing? Was there anything I had to manually copy back and forth? Is there anything in my AGENTS.md that could be better done deterministically? Did the conversation degrade? Could this have been split? Are there any mistakes that have happened multiple times?