1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>What is an AI Product Engineer?</title> 7 <meta name="description" content="Everyone is looking for AI Engineers, but that's a really ambiguous term. Here's the role I think actually matters."> 8
8<script defer data-domain="dwyer.co.za" src="https://plausible.io/js/script.js"></script>
8 9 <link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><rect width='32' height='32' rx='6' fill='%23f5f5fa'/><line x1='16' y1='4' x2='5' y2='26' stroke='%23ccc' stroke-width='1.5'/><line x1='16' y1='4' x2='27' y2='26' stroke='%23ccc' stroke-width='1.5'/><line x1='5' y1='26' x2='27' y2='26' stroke='%23ccc' stroke-width='1.5'/><circle cx='16' cy='4' r='3.5' fill='%23f59e0b'/><circle cx='5' cy='26' r='3.5' fill='%2334d399'/><circle cx='27' cy='26' r='3.5' fill='%2360a5fa'/></svg>"> 10 <style> 11 * { 12 margin: 0; 13 padding: 0; 14 box-sizing: border-box; 15 } 16 17 :root { 18 --bg-dark: #f5f5fa; 19 --bg-card: #ffffff; 20 --text-primary: #111118; 21 --text-secondary: #6b6b85; 22 --accent: #0891b2; 23 --accent-glow: rgba(8, 145, 178, 0.15); 24 --gradient-1: #0891b2; 25 --gradient-2: #0369a1; 26 --border: rgba(0, 0, 0, 0.09); 27 } 28 29 body.dark { 30 --bg-dark: #0a0a0f; 31 --bg-card: #12121a; 32 --text-primary: #e8e8ed; 33 --text-secondary: #8b8b9a; 34 --accent-glow: rgba(8, 145, 178, 0.3); 35 --border: rgba(255, 255, 255, 0.08); 36 } 37 38 html { 39 scroll-behavior: smooth; 40 } 41 42 body { 43 font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; 44 background: var(--bg-dark); 45 color: var(--text-primary); 46 line-height: 1.7; 47 overflow-x: hidden; 48 } 49 50 .bg-gradient { 51 position: fixed; 52 top: 0; 53 left: 0; 54 right: 0; 55 bottom: 0; 56 z-index: -1; 57 background: 58 radial-gradient(ellipse 80% 50% at 50% -20%, rgba(99, 102, 241, 0.07), transparent), 59 radial-gradient(ellipse 60% 40% at 100% 50%, rgba(168, 85, 247, 0.05), transparent); 60 } 61 62 body.dark .bg-gradient { 63 background: 64 radial-gradient(ellipse 80% 50% at 50% -20%, rgba(99, 102, 241, 0.15), transparent), 65 radial-gradient(ellipse 60% 40% at 100% 50%, rgba(168, 85, 247, 0.1), transparent), 66 radial-gradient(ellipse 60% 40% at 0% 80%, rgba(236, 72, 153, 0.08), transparent); 67 } 68 69 /* Hero */ 70 .hero { 71 min-height: 100vh; 72 display: flex; 73 flex-direction: column; 74 justify-content: center; 75 align-items: center; 76 text-align: center; 77 padding: 2rem; 78 position: relative; 79 } 80 81 .hero::after { 82 content: ''; 83 position: absolute; 84 bottom: 0; 85 left: 0; 86 right: 0; 87 height: 200px; 88 background: linear-gradient(to top, var(--bg-dark), transparent); 89 pointer-events: none; 90 } 91 92 .hero h1 { 93 font-size: clamp(2.5rem, 8vw, 5rem); 94 font-weight: 700; 95 line-height: 1.1; 96 margin-bottom: 1.5rem; 97 color: var(--text-primary); 98 } 99 100 .hero-subtitle { 101 font-size: 1.25rem; 102 color: var(--text-secondary); 103 max-width: 600px; 104 margin-bottom: 2rem; 105 } 106 107 .hero-meta { 108 font-size: 0.875rem; 109 color: var(--text-secondary); 110 } 111 112 .hero-meta a { 113 color: var(--accent); 114 text-decoration: none; 115 } 116 117 .scroll-indicator { 118 position: absolute; 119 bottom: 40px; 120 left: 50%; 121 transform: translateX(-50%); 122 z-index: 10; 123 } 124 125 .scroll-indicator svg { 126 width: 24px; 127 height: 24px; 128 stroke: var(--text-secondary); 129 } 130 131 /* Content */ 132 .content { 133 max-width: 800px; 134 margin: 0 auto; 135 padding: 0 2rem 6rem; 136 } 137 138 .section { 139 margin-bottom: 6rem; 140 } 141 142 h2 { 143 font-size: clamp(1.75rem, 4vw, 2.5rem); 144 font-weight: 600; 145 margin-bottom: 1.5rem; 146 color: var(--text-primary); 147 } 148 149 h3 { 150 font-size: 1.35rem; 151 font-weight: 600; 152 margin: 2.5rem 0 1rem; 153 color: var(--text-primary); 154 } 155 156 p { 157 font-size: 1.125rem; 158 color: var(--text-secondary); 159 margin-bottom: 1.5rem; 160 } 161 162 .diagram-label { 163 font-size: 0.75rem; 164 text-transform: uppercase; 165 letter-spacing: 0.12em; 166 color: var(--text-secondary); 167 text-align: center; 168 margin-bottom: 0.75rem; 169 } 170 171 .definition { 172 font-size: 1.2rem; 173 color: var(--text-primary); 174 font-style: italic; 175 margin-bottom: 2.5rem; 176 padding-left: 1rem; 177 border-left: 3px solid var(--accent); 178 } 179 180 ul { 181 margin: 0 0 1.5rem 1.25rem; 182 } 183 184 li { 185 font-size: 1.125rem; 186 color: var(--text-secondary); 187 margin-bottom: 0.5rem; 188 line-height: 1.6; 189 } 190 191 a { 192 color: var(--accent); 193 text-decoration: none; 194 transition: opacity 0.2s; 195 } 196 197 a:hover { opacity: 0.8; } 198 199 /* LinkedIn poll recreation */ 200 .li-wrap { 201 margin: 2.5rem 0; 202 display: flex; 203 justify-content: center; 204 } 205 206 .li-card {
207 background: #1b1f23; 208 border: 1px solid #2a2f35; 209 border-radius: 12px; 210 padding: 16px; 211 width: 100%; 212 max-width: 500px; 213 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; 214 } 215 216 .li-header { 217 display: flex; 218 align-items: flex-start; 219 gap: 10px; 220 margin-bottom: 14px; 221 } 222 223 .li-avatar { 224 width: 42px; 225 height: 42px; 226 border-radius: 50%; 227 background: linear-gradient(135deg, #0369a1, #0891b2); 228 flex-shrink: 0; 229 display: flex; 230 align-items: center; 231 justify-content: center; 232 font-size: 1.1rem; 233 color: white; 234 font-weight: 700; 235 } 236 237 .li-meta { 238 flex: 1; 239 } 240 241 .li-group { 242 font-size: 0.9rem; 243 font-weight: 600; 244 color: #e2e5e9; 245 line-height: 1.3; 246 } 247 248 .li-author { 249 font-size: 0.8rem; 250 color: #0a66c2; 251 font-weight: 500; 252 } 253 254 .li-time { 255 font-size: 0.78rem; 256 color: #888;
257 margin-top: 1px; 258 } 259 260 .li-close { 261 color: #888; 262 font-size: 1.1rem; 263 cursor: pointer; 264 padding: 0 4px; 265 } 266 267 .li-join { 268 font-size: 0.875rem; 269 font-weight: 600; 270 color: #0a66c2; 271 white-space: nowrap; 272 padding-top: 2px; 273 } 274 275 .li-body { 276 font-size: 0.9rem; 277 color: #a0a8b4; 278 line-height: 1.5; 279 margin-bottom: 14px; 280 } 281 282 .li-body em { 283 color: #e2e5e9; 284 font-style: normal; 285 } 286 287 .li-poll { 288 background: #13171b; 289 border: 1px solid #2a2f35; 290 border-radius: 10px; 291 padding: 14px; 292 margin-bottom: 12px; 293 } 294 295 .li-poll-q { 296 font-size: 0.925rem; 297 font-weight: 700; 298 color: #e2e5e9; 299 margin-bottom: 4px; 300 } 301 302 .li-poll-note { 303 font-size: 0.77rem; 304 color: #888; 305 margin-bottom: 12px; 306 } 307 308 .li-poll-note a { 309 color: #0a66c2; 310 text-decoration: none; 311 font-weight: 500; 312 } 313 314 .li-options { 315 display: flex; 316 flex-direction: column; 317 gap: 8px; 318 margin-bottom: 10px; 319 } 320 321 .li-option { 322 border: 2px solid #0a66c2; 323 border-radius: 999px; 324 padding: 9px 18px; 325 text-align: center; 326 font-size: 0.9rem; 327 font-weight: 600; 328 color: #0a66c2; 329 cursor: pointer; 330 transition: background 0.15s; 331 } 332 333 .li-option:hover { 334 background: rgba(10, 102, 194, 0.1); 335 } 336 337 .li-votes { 338 font-size: 0.78rem; 339 color: #888; 340 } 341 342 .li-footer { 343 display: flex; 344 justify-content: space-around; 345 border-top: 1px solid #2a2f35; 346 padding-top: 10px; 347 margin-top: 4px; 348 } 349 350 .li-action { 351 display: flex; 352 align-items: center; 353 gap: 5px; 354 font-size: 0.82rem; 355 color: #888; 356 cursor: pointer; 357 padding: 4px 8px; 358 border-radius: 6px; 359 transition: background 0.15s, color 0.15s; 360 } 361 362 .li-action:hover { 363 background: rgba(255,255,255,0.05); 364 color: #ccc; 365 } 366 367 .li-note { 368 font-size: 0.75rem; 369 color: #666; 370 text-align: center; 371 margin-top: 10px; 372 padding-top: 8px; 373 border-top: 1px solid #2a2f35; 374 } 375 376 /* Skill cards */ 377 .skill-grid { 378 display: grid; 379 grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); 380 gap: 1.25rem; 381 margin: 2.5rem 0; 382 } 383 384 .skill-card { 385 background: var(--bg-card); 386 border: 1px solid var(--border); 387 border-radius: 14px; 388 padding: 1.5rem; 389 transition: transform 0.3s ease, box-shadow 0.3s ease, border-color 0.3s ease; 390 } 391 392 .skill-card:hover { 393 transform: translateY(-3px); 394 border-color: rgba(99, 102, 241, 0.35); 395 box-shadow: 0 12px 40px -12px var(--accent-glow); 396 } 397 398 .skill-icon { 399 width: 44px; 400 height: 44px; 401 background: linear-gradient(135deg, var(--gradient-1), var(--gradient-2)); 402 border-radius: 12px; 403 display: flex; 404 align-items: center; 405 justify-content: center; 406 font-size: 1.4rem; 407 margin-bottom: 1rem; 408 } 409 410 .skill-card h4 { 411 font-size: 1.05rem; 412 font-weight: 600; 413 color: var(--text-primary); 414 margin-bottom: 0.5rem; 415 } 416 417 .skill-card p { 418 font-size: 0.95rem; 419 margin: 0; 420 } 421 422 /* Callout */ 423 .callout { 424 background: var(--bg-card); 425 border: 1px solid rgba(99, 102, 241, 0.3); 426 border-left: 3px solid var(--accent); 427 border-radius: 12px; 428 padding: 1.5rem 1.75rem; 429 margin: 2.5rem 0; 430 } 431 432 .callout p { 433 margin: 0; 434 color: var(--text-primary); 435 font-size: 1.1rem; 436 font-style: italic; 437 } 438 439 /* Criteria list */ 440 .criteria-list { 441 display: flex; 442 flex-direction: column; 443 gap: 1rem; 444 margin: 2rem 0; 445 } 446 447 .criteria-item { 448 display: flex; 449 align-items: flex-start; 450 gap: 1rem; 451 background: var(--bg-card); 452 border: 1px solid var(--border); 453 border-radius: 12px; 454 padding: 1.25rem 1.5rem; 455 transition: border-color 0.3s ease; 456 } 457 458 .criteria-item:hover { 459 border-color: rgba(99, 102, 241, 0.3); 460 } 461 462 .criteria-check { 463 width: 28px; 464 height: 28px; 465 background: linear-gradient(135deg, var(--gradient-1), var(--gradient-2)); 466 border-radius: 8px; 467 display: flex; 468 align-items: center; 469 justify-content: center; 470 flex-shrink: 0; 471 margin-top: 2px; 472 } 473 474 .criteria-check svg { 475 width: 14px; 476 height: 14px; 477 stroke: white; 478 stroke-width: 2.5; 479 fill: none; 480 } 481 482 .criteria-text strong { 483 display: block; 484 color: var(--text-primary); 485 font-size: 1rem; 486 margin-bottom: 0.25rem; 487 } 488 489 .criteria-text span { 490 color: var(--text-secondary); 491 font-size: 0.95rem; 492 } 493 494 /* Divider */ 495 .divider { 496 height: 1px; 497 background: var(--border); 498 margin: 4rem 0; 499 } 500 501 /* Footer */ 502 .footer { 503 text-align: center; 504 padding: 4rem 2rem; 505 color: var(--text-secondary); 506 font-size: 0.9rem; 507 } 508 509 .footer a { 510 color: var(--accent); 511 } 512 513 @media (max-width: 600px) { 514 .skill-grid { 515 grid-template-columns: 1fr; 516 } 517 } 518 519 /* Venn triangle diagram */ 520 .venn-wrap { 521 margin: 2.5rem 0; 522 background: #f0f0f8; 523 border-radius: 16px; 524 border: 1px solid var(--border); 525 overflow: hidden; 526 user-select: none; 527 } 528 529 body.dark .venn-wrap { 530 background: #060610; 531 } 532 533 .venn-wrap svg { 534 display: block; 535 width: 100%; 536 max-width: 480px; 537 margin: 0 auto; 538 } 539 540 .venn-node { 541 cursor: pointer; 542 } 543 544 .venn-node circle.outer { 545 transition: fill 0.3s ease, stroke 0.3s ease; 546 } 547 548 .venn-node circle.inner { 549 transition: fill 0.3s ease; 550 } 551 552 .venn-node text { 553 transition: fill 0.3s ease; 554 font-family: 'Inter', -apple-system, sans-serif; 555 font-weight: 600; 556 pointer-events: none; 557 } 558 559 .venn-description-wrap { 560 min-height: 72px; 561 display: flex; 562 align-items: center; 563 justify-content: center; 564 padding: 1rem 2rem 1.25rem; 565 border-top: 1px solid rgba(0,0,0,0.07); 566 } 567 568 body.dark .venn-description-wrap { 569 border-top-color: rgba(255,255,255,0.05); 570 } 571 572 .venn-description { 573 text-align: center; 574 font-size: 1rem; 575 transition: opacity 0.3s ease; 576 max-width: 480px; 577 } 578 579 .venn-description .combo-label { 580 font-size: 0.75rem; 581 text-transform: uppercase; 582 letter-spacing: 0.12em; 583 color: #6366f1; 584 margin-bottom: 0.35rem; 585 } 586 587 body.dark .venn-description .combo-label { 588 color: #22d3ee; 589 } 590 591 .venn-description .combo-text { 592 font-size: 1rem; 593 color: #111118; 594 line-height: 1.5; 595 } 596 597 body.dark .venn-description .combo-text { 598 color: #e8e8ed; 599 } 600 601 .venn-description .hint { 602 font-size: 0.85rem; 603 color: #999; 604 } 605 606 body.dark .venn-description .hint { 607 color: #6b6b85; 608 } 609 610 .venn-controls { 611 display: flex; 612 justify-content: center; 613 padding: 0 0 1.1rem; 614 } 615 616 .venn-play-btn { 617 display: flex; 618 align-items: center; 619 gap: 0.5rem; 620 background: none; 621 border: 1px solid rgba(0,0,0,0.15); 622 border-radius: 999px; 623 padding: 0.4rem 1rem; 624 color: #888; 625 font-size: 0.78rem; 626 font-family: inherit; 627 cursor: pointer; 628 transition: border-color 0.2s, color 0.2s, background 0.2s; 629 } 630 631 .venn-play-btn:hover { 632 border-color: rgba(0,0,0,0.3); 633 color: #333; 634 } 635 636 body.dark .venn-play-btn { 637 border-color: rgba(255,255,255,0.12); 638 color: #8b8b9a; 639 } 640 641 body.dark .venn-play-btn:hover { 642 border-color: rgba(255,255,255,0.25); 643 color: #e8e8ed; 644 background: rgba(255,255,255,0.04); 645 } 646 647 .venn-play-btn svg { 648 width: 13px; 649 height: 13px; 650 fill: currentColor; 651 } 652 653 /* Dark mode overrides for hardcoded li-card colours */
654 body.dark .li-card { background: #1b1f23; border-color: #2a2f35; } 655 body.dark .li-body { color: #a0a8b4; } 656 body.dark .li-group { color: #e2e5e9; } 657 body.dark .li-time { color: #888; } 658 body.dark .li-poll { background: #13171b; border-color: #2a2f35; } 659 body.dark .li-poll-q { color: #e2e5e9; } 660 body.dark .li-footer { border-top-color: #2a2f35; } 661 body.dark .li-note { color: #666; border-top-color: #2a2f35; } 662 body.dark .li-action { color: #888; } 663 body.dark .li-close { color: #888; } 664 body.dark .li-votes { color: #888; } 665 body.dark .li-poll-note { color: #888; } 666 667 /* Light mode li-card */ 668 .li-card { background: #f8f8fe; border-color: #e0e0ee; } 669 .li-body { color: #555570; } 670 .li-group { color: #111120; } 671 .li-time { color: #aaa; } 672 .li-poll { background: #efeffa; border-color: #dcdcee; } 673 .li-poll-q { color: #111120; } 674 .li-footer { border-top-color: #e0e0ee; } 675 .li-note { color: #bbb; border-top-color: #e0e0ee; } 676 .li-action { color: #999; } 677 .li-close { color: #bbb; } 678 .li-votes { color: #aaa; } 679 .li-poll-note { color: #aaa; } 680 681 /* Mode toggle button */ 682 .mode-btn { 683 position: fixed; 684 top: 1.1rem; 685 right: 1.25rem; 686 z-index: 100; 687 background: var(--bg-card); 688 border: 1px solid var(--border); 689 border-radius: 999px; 690 padding: 0.4rem 0.9rem; 691 font-size: 0.78rem; 692 font-family: inherit; 693 color: var(--text-secondary); 694 cursor: pointer; 695 display: flex; 696 align-items: center; 697 gap: 0.4rem; 698 transition: background 0.2s, border-color 0.2s, color 0.2s; 699 box-shadow: 0 1px 6px rgba(0,0,0,0.07); 700 } 701 702 .mode-btn:hover { 703 color: var(--text-primary); 704 border-color: var(--accent); 705 } 706 </style> 707</head> 708<body> 709 <button class="mode-btn" id="modeBtn" onclick="toggleMode()"> 710 <span id="modeIcon">ð</span><span id="modeLabel">Dark</span> 711 </button> 712 713 <div class="bg-gradient"></div> 714 715 716 <section class="hero"> 717 <h1>What is an AI<br>Product Engineer?</h1> 718 <p class="hero-subtitle">Everyone is looking for AI-pilled Product Engineers, but what does that actually mean?</p> 719 <p class="hero-meta">By <a href="/">Gareth Dwyer</a> · March 2026</p> 720 <div class="scroll-indicator"> 721 <svg viewBox="0 0 24 24" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 722 <polyline points="6 9 12 15 18 9"></polyline> 723 </svg> 724 </div> 725 </section> 726 727 <main class="content"> 728 729 <div class="section"> 730 <h2>Product x Engineering x AI</h2> 731 <p class="definition"><strong>AI Product Engineer (noun):</strong> Someone who ships delightful, correct solutions to real problems fast.</p> 732 <p>The crude distinction has always been: Product decides the <em>what</em>, Engineering decides the <em>how</em>.</p> 733 734 <p>Traditionally, companies have separated the functions. Product people talk to customers, actively use the product, and tell the engineers what to build. The engineers build systems that are scalable and correct.</p> 735 736 <p>Some companies â famously Posthog â hire Product Engineers: people who can do both. This leads to big efficiency gains as you remove the communication overhead, and often a lot of conflict and personality clashes at the same time.</p> 737 738 <p>This hasn't changed. People who understand both what would be valuable to a customer and how to create it are valuable but hard to find.</p> 739 740 <p>AI lets Product people and Engineers go faster:</p> 741 <ul> 742 <li>Product people with AI can build demos fast, but they have bugs and don't scale</li> 743 <li>Engineers with AI can build correct systems fast, but they don't solve customer problems or create Shareholder Valueâ¢</li> 744 </ul> 745 746 <p>
746But Product people without engineering skills are still limited. The "80% done" demo falls apart and needs to be rebuilt from scratch.</p> 747 748 <p>And Engineers without product skills are building better software forges to let AI build AI-building tools faster and faster, but others don't value or understand the solutions and they have rough edges.</p> 749 750 <p>Combine all three and you get someone who can produce customer value fast and correctly.</p> 751 752 <p class="diagram-label">Build your own AI Product Engineer</p> 753 754 <!-- Interactive triangle diagram --> 755 <div class="venn-wrap"> 756 <svg id="vennSvg" viewBox="0 0 360 310" xmlns="http://www.w3.org/2000/svg"> 757 <!-- Equilateral triangle: base 200, heightâ173, centred at x=180 --> 758 <!-- AI: (180,90) Product: (80,263) Engineer: (280,263) --> 759 <line id="line-ai-product" x1="180" y1="90" x2="80" y2="263" stroke="#c0c0d4" stroke-width="1.2"/> 760 <line id="line-ai-engineer" x1="180" y1="90" x2="280" y2="263" stroke="#c0c0d4" stroke-width="1.2"/> 761 <line id="line-prod-eng" x1="80" y1="263" x2="280" y2="263" stroke="#c0c0d4" stroke-width="1.2"/> 762 763 <!-- AI node (top) â label above --> 764 <g id="node-ai" class="venn-node" onclick="toggleNode('ai')"> 765 <circle class="outer" cx="180" cy="90" r="20" fill="#e4e4f0" stroke="#b8b8d0" stroke-width="1.5"/> 766 <circle class="inner" cx="180" cy="90" r="8" fill="#b8b8d0"/> 767 <text x="180" y="57" text-anchor="middle" fill="#999" font-size="13">AI</text> 768 </g> 769 770 <!-- Product node (bottom left) â label below --> 771 <g id="node-product" class="venn-node" onclick="toggleNode('product')"> 772 <circle class="outer" cx="80" cy="263" r="20" fill="#e4e4f0" stroke="#b8b8d0" stroke-width="1.5"/> 773 <circle class="inner" cx="80" cy="263" r="8" fill="#b8b8d0"/> 774 <text x="80" y="300" text-anchor="middle" fill="#999" font-size="13">Product</text> 775 </g> 776 777 <!-- Engineer node (bottom right) â label below --> 778 <g id="node-engineer" class="venn-node" onclick="toggleNode('engineer')"> 779 <circle class="outer" cx="280" cy="263" r="20" fill="#e4e4f0" stroke="#b8b8d0" stroke-width="1.5"/> 780 <circle class="inner" cx="280" cy="263" r="8" fill="#b8b8d0"/> 781 <text x="280" y="300" text-anchor="middle" fill="#999" font-size="13">Engineer</text> 782 </g> 783 </svg> 784 <div class="venn-description-wrap"> 785 <div class="venn-description" id="vennDesc"> 786 <div class="hint">Click a node to explore the skills</div> 787 </div> 788 </div> 789 <div class="venn-controls"> 790 <button class="venn-play-btn" id="vennPlayBtn" onclick="togglePlay()"> 791 <svg id="vennPlayIcon" viewBox="0 0 16 16"><path d="M3 2.5l10 5.5-10 5.5V2.5z"/></svg> 792 <span id="vennPlayLabel">Play</span> 793 </button> 794 </div> 795 </div> 796 797 <div class="skill-grid"> 798 <div class="skill-card"> 799 <div class="skill-icon">ð¤</div> 800 <h4>AI folk</h4> 801 <p>Heavy users of Claude Code or similar for months. Burnt billions of tokens to figure out how to use new tools effectively.</p> 802 </div> 803 <div class="skill-card"> 804 <div class="skill-icon">ð¯</div> 805 <h4>Product folk</h4> 806 <p>Understand the customer's problem. Creates delightful solutions. Communicates.</p> 807 </div> 808 <div class="skill-card"> 809 <div class="skill-icon">âï¸</div> 810 <h4>Engineers</h4> 811 <p>Efficiently and effectively create correct, scalable systems.</p> 812 </div> 813 </div> 814 </div> 815 816 <div class="section"> 817 <h2>What to look for in an AI Product Engineer (or how to become one)</h2> 818 819 <p>Some of the best AI Product Engineers I know are, or were, 'indiehackers'. They don't always have management skills, or sometimes they just prefer to work alone. But because they can build things that people value, they often find far higher returns by selling their own products rather than becoming employees to help someone else.</p> 820 821 <p>These people are hard to categorise because they tend to be the mavericks and misfits of the world, coming from varied backgrounds and skill-sets. But there are some things they tend to have in common.</p> 822 823 <h3>Great at communication</h3> 824 <p>Usually they've written millions of words in blog posts, explaining what they're learning and what they're selling to anyone who will listen.</p> 825 826 <h3>Disciplined</h3> 827 <p>They've honed their craft for hours every day for years or decades. There are many wannabes who look similar on the surface but never stick with any project for more than a few weeks or months at a time, and often only w
827ork on it sporadically. They iterate and refine on what they build, not just building it once and then losing interest.</p> 828 829 <h3>They actually ship</h3> 830 <p>Discipline is about doing the reps, but putting real things in public for others to use is a different muscle. Product Engineers haven't just built the side projects, they've published them and gotten some traction from real users.</p> 831 832 <h3>They care</h3> 833 <p>It bothers them if something they built is broken or has an issue. They've carefully used and refined the UX of their own products so they make sense to others too. If you email them with a sensible suggestion for improvement or to point out an issue, you'll often see a fix published within hours or days.</p> 834 835 <h3>Systems thinking</h3> 836 <p>Product Engineers can see how a new tool fits into a broader ecosystem, what problems it solves for individual products but also what means, where it moves constraints and bottlenecks, and what downstream effects it will have.</p> 837 838 <h3>Open-minded</h3> 839 <p>They're always happy to listen for a few minutes longer than average if you say something they disagree with. They love experimenting with new tools, technology, and ideas. They don't jump to the most obvious solution, rather considering different options before settling on a path forwards.</p> 840 841 <h3>Generalists</h3> 842 <p>They know a bit of marketing, sales, product, engineering, customer support. Not married to a specific tech stack.</p> 843 844 <h3>Enjoy helping others</h3> 845 <p>They actually like it when customers come to them with problems â unlike the stereotypical hardcore engineer who hates being distracted from building.</p> 846 847 <h2>Building Product skills from Engineering ones</h2> 848 <p>You can become a better AI Product Engineer by trying out new tools and seeing how they can help you. You can have â or at least pretend to have â more respect for non-engineers and ask them about their problems without assuming you can instantly solve them. Be less defensive if someone tells you something you built doesn't work or doesn't work well. Learn more about what makes good UX and how to delight users. Learn more about metrics and how to present them.</p> 849 850 <h3>Understand the customer</h3> 851 <p>You need to deeply grok what a customer actually needs. This might be different from what they say they need. What are they actually trying to achieve and why can't they do it with existing offerings? What do they value? What are they willing to pay for? What makes them <strong>delighted</strong>?</p> 852 853 <h3>Understand what's possible</h3> 854 <p>You need to understand what's feasible. If you think you can promise the earth with no tradeoffs and then throw it over the fence to an engineering team to figure out how to build, you're in sales, not product. That doesn't mean knowing every detail of how something will be built, but understanding what's technically challenging and what isn't â and what's impossible â is important for product too.</p> 855 856 <h3>Understand different languages</h3> 857 <p>You have to be able to speak a bit of engineering, a bit of sales, a bit of marketing, a bit of exec, a lot of whatever language your customer talks. These often sound the same but are actually quite different, so you can cause confusion and have things 'lost in translation' if you don't know the terminology of each profile.</p> 858 859 <h3>Be good at UX</h3> 860 <p>Usually this means actually play-acting as the user: clicking through interfaces, reading documentation, understanding and eliminating every friction point, creating delight.</p> 861 862 <h3>Understand the business</h3> 863 <p>You have budgets in terms of people, time, money. Don't spend $100 to create $5.</p> 864 865 <h2>Building Engineering skills from Product ones</h2> 866 <p>You can become a better AI Product Engineer by understanding fundamentals. Learn how to use a terminal. Ask Claude Code to help you bootstrap it. Learn about constraints like memory. Learn about data models. Learn some basic SQL. Learn some basic networking. Learn how to debug â how to form a hypothesis about what's wrong and test it.</p> 867 868 <h3>Efficiency</h3> 869 <p>
869In civil engineering, it's not difficult to build a bridge that stays up. It's difficult to build a bridge that <em>only just</em> stays up â without wasting resources. In software engineering this is not as widely understood. You shouldn't be firing up $1000/month K8s clusters to serve 1000 users.</p> 870 871 <h3>Effectiveness</h3> 872 <p>The opposite of efficiency in some ways. You should know when to waste one resource in order to preserve a more valuable one â waste money to save time, or waste time to save money. You get things done without getting bogged down in details, hyperfocusing on one metric above all others, or letting the fact that you don't have all the information about a goal stop you from moving towards it.</p> 873 874 <h3>Data modelling</h3> 875 <p>Getting the schema right is foundational and expensive to undo. Before writing a line of code, an engineer should be able to sketch the entities, their relationships, and where the boundaries are. Correcting a broken data model six months into a product is a rewrite, not a refactor.</p> 876 877 <h3>Knowing when to abstract</h3> 878 <p>Over-engineering is as dangerous as under-engineering. The skill is recognising the seam where a good abstraction will pay for itself many times over â and ignoring the temptation to create one everywhere else. Three instances of similar code is usually fine. A premature abstraction that doesn't quite fit is a tax on every future change.</p> 879 880 <h3>Observability</h3> 881 <p>Systems fail in ways you didn't anticipate. The engineers who recover fast are the ones who instrumented their systems before things broke, not after. Logs, traces, and metrics aren't overhead â they're how you know what's actually happening versus what you think is happening.</p> 882 883 <h3>Understanding tradeoffs</h3> 884 <p>There is no free lunch. Consistency vs. availability. Speed vs. correctness. Flexibility vs. simplicity. A good engineer doesn't pretend these tensions don't exist â they make them explicit, decide consciously, and document why.</p> 885 886 <h2>Building AI Skills</h2> 887 888 <p>Whether you're more Product or more Engineering, you can always up your AI game. It's new. Nobody knows how to hold it yet, and it's changing every day. Choose your own adventure but this is what I'd recommend.</p> 889 890 <h3>Try Claude Code in yolo mode</h3> 891 <p>You're right to be concerned but you don't know the true power of AI until you've let Claude Code rip with <code>--dangerously-skip-permissions</code>. With good prompting it's safer than you think, and as long as it doesn't have access to anything critically production, it's worth finding a few footguns the hard way in order to learn how to use it effectively.</p> 892 893 <h3>Try things you don't think it can do</h3> 894 <p>Regularly ask the AI to do something that you think it has a 90% chance of failing at or doing badly. You might be surprised.</p> 895 896 <h3>Ask it how it did things</h3> 897 <p>Once it's done something well, it's tempting to move on. But especially in cases where it surprised you, ask it how it managed.</p> 898 899 <h3>Read a lot of output</h3> 900 <p>Agents produce a lot of output and it's very tempting to just skip over it. But reading it can help understand how it works â what kind of searches is it doing, how is it exploring your file system or codebase.</p> 901 902 <h3>Write longer prompts</h3> 903 <p>Often you give an agent a 5 word prompt and it somehow knows exactly what you meant. The next time it doesn't, and you assume they're limiting the resources over at Anthropic or giving you a dumber model to save costs. In reality, it'll do what it thinks you want and sometimes it gets that wrong if you weren't clear enough. Try writing a 1000 word prompt. Try writing a prompt into a file. It feels like 'writing' is a waste of time and you're tempted to just ask the agent to write its own prompts, but if there's information only in your head that the agent needs, then typing it out â or dictating if you're a slow typer â is still the most efficient way to get that information to the agent.</p> 904 </div> 905 906 <div class="divider"></div> 907 908 </main> 909 910 <footer class="footer"> 911 <p><a href="/">
911gareth dwyer</a> · dwyer.co.za</p> 912 </footer> 913 914
914<script> 915 // Venn triangle 916 const state = { ai: false, product: false, engineer: false }; 917 918 const nodeConfig = { 919 ai: { 920 el: document.getElementById('node-ai'), 921 cx: 180, cy: 90, 922 outerR: 20, innerR: 8, 923 activeOuter: '#fef3c7', 924 activeOuterStroke: '#f59e0b', 925 activeInner: '#f59e0b', 926 activeText: '#92400e', 927 activeDarkText: '#fcd34d', 928 glowColorDark: 'rgba(245,158,11,0.3)', 929 glowColorLight: 'rgba(245,158,11,0.2)', 930 label: 'AI' 931 }, 932 product: { 933 el: document.getElementById('node-product'), 934 cx: 80, cy: 263, 935 outerR: 20, innerR: 8, 936 activeOuter: '#d4f0e4', 937 activeOuterStroke: '#34d399', 938 activeInner: '#34d399', 939 activeText: '#1a7a50', 940 activeDarkText: '#6ee7b7', 941 glowColorDark: 'rgba(52,211,153,0.3)', 942 glowColorLight: 'rgba(52,211,153,0.15)', 943 label: 'Product' 944 }, 945 engineer: { 946 el: document.getElementById('node-engineer'), 947 cx: 280, cy: 263, 948 outerR: 20, innerR: 8, 949 activeOuter: '#d4e8ff', 950 activeOuterStroke: '#60a5fa', 951 activeInner: '#60a5fa', 952 activeText: '#1a5a9a', 953 activeDarkText: '#93c5fd', 954 glowColorDark: 'rgba(96,165,250,0.3)', 955 glowColorLight: 'rgba(96,165,250,0.15)', 956 label: 'Engineer' 957 } 958 }; 959 960 function isDark() { return document.body.classList.contains('dark'); } 961 962 function inactiveAttrs() { 963 return isDark() 964 ? { outer: '#1a1a2e', stroke: '#3a3a5a', inner: '#3a3a5a', text: '#555' } 965 : { outer: '#e4e4f0', stroke: '#b8b8d0', inner: '#b8b8d0', text: '#999' }; 966 } 967 968 function lineInactiveStroke() { return isDark() ? '#2a2a3a' : '#c0c0d4'; } 969 function lineActiveStroke() { return isDark() ? '#5a5a7a' : '#8888aa'; } 970 971 const descriptions = { 972 '': { label: '', text: '<span class="hint">Click a node to explore the skills</span>' }, 973 'ai': { label: 'AI', text: 'Go fast' }, 974 'product': { label: 'Product', text: 'Understand what customers want' }, 975 'engineer': { label: 'Engineer', text: 'Build correct systems' }, 976 'ai+product': { label: 'AI × Product', text: 'Build demos fast that customers value' }, 977 'ai+engineer': { label: 'AI × Engineer', text: 'Build correct systems fast' }, 978 'product+engineer': { label: 'Product × Engineer', text: 'Build correct systems slowly that customers value' }, 979 'ai+product+engineer': { label: 'AI × Product × Engineer', text: 'Build correct systems fast that customers value' } 980 }; 981 982 const lineConfig = { 983 'ai+product': document.getElementById('line-ai-product'), 984 'ai+engineer': document.getElementById('line-ai-engineer'), 985 'product+engineer': document.getElementById('line-prod-eng') 986 }; 987 988 function getKey() { 989 return ['ai', 'product', 'engineer'].filter(k => state[k]).join('+'); 990 } 991 992 function updateNode(id) { 993 const cfg = nodeConfig[id]; 994 const on = state[id]; 995 const el = cfg.el; 996 const outer = el.querySelector('circle.outer'); 997 const inner = el.querySelector('circle.inner'); 998 const text = el.querySelector('text'); 999 const dark = isDark(); 1000 1001 if (on) { 1002 outer.setAttribute('fill', cfg.activeOuter); 1003 outer.setAttribute('stroke', cfg.activeOuterStroke); 1004 outer.setAttribute('stroke-width', '2'); 1005 inner.setAttribute('fill', cfg.activeInner); 1006 text.setAttribute('fill', dark ? cfg.activeDarkText : cfg.activeText); 1007 1008 let glow = el.querySelector('circle.glow'); 1009 if (!glow) { 1010 glow = document.createElementNS('http://www.w3.org/2000/svg', 'circle'); 1011 glow.setAttribute('class', 'glow'); 1012 glow.setAttribute('cx', cfg.cx); 1013 glow.setAttribute('cy', cfg.cy); 1014 glow.setAttribute('r', cfg.outerR + 16);
1015 glow.setAttribute('pointer-events', 'none'); 1016 el.insertBefore(glow, el.firstChild); 1017 } 1018 glow.setAttribute('fill', dark ? cfg.glowColorDark : cfg.glowColorLight); 1019 glow.style.display = ''; 1020 } else { 1021 const ia = inactiveAttrs(); 1022 outer.setAttribute('fill', ia.outer); 1023 outer.setAttribute('stroke', ia.stroke); 1024 outer.setAttribute('stroke-width', '1.5'); 1025 inner.setAttribute('fill', ia.inner); 1026 text.setAttribute('fill', ia.text); 1027 const glow = el.querySelector('circle.glow'); 1028 if (glow) glow.style.display = 'none'; 1029 } 1030 } 1031 1032 function updateLines() { 1033 Object.entries(lineConfig).forEach(([pair, lineEl]) => { 1034 const [a, b] = pair.split('+'); 1035 const active = state[a] && state[b]; 1036 lineEl.setAttribute('stroke', active ? lineActiveStroke() : lineInactiveStroke()); 1037 lineEl.setAttribute('stroke-width', active ? '1.8' : '1.2'); 1038 }); 1039 } 1040 1041 function updateAllNodes() { 1042 Object.keys(nodeConfig).forEach(updateNode); 1043 updateLines(); 1044 } 1045 1046 function updateDescription() { 1047 const key = getKey(); 1048 const desc = descriptions[key] || descriptions['']; 1049 const el = document.getElementById('vennDesc'); 1050 el.style.opacity = '0'; 1051 setTimeout(() => { 1052 if (desc.label) { 1053 el.innerHTML = `<div class="combo-label">${desc.label}</div><div class="combo-text">${desc.text}</div>`; 1054 } else { 1055 el.innerHTML = desc.text; 1056 } 1057 el.style.opacity = '1'; 1058 }, 150); 1059 } 1060 1061 function toggleNode(id) { 1062 // If playing, pause and hand control to user 1063 if (isPlaying) pausePlay(); 1064 state[id] = !state[id]; 1065 updateNode(id); 1066 updateLines(); 1067 updateDescription(); 1068 } 1069 1070 // --- Auto-play --- 1071 const sequence = [ 1072 { ai: true, product: false, engineer: false }, 1073 { ai: false, product: true, engineer: false }, 1074 { ai: false, product: false, engineer: true }, 1075 { ai: true, product: true, engineer: false }, 1076 { ai: true, product: false, engineer: true }, 1077 { ai: false, product: true, engineer: true }, 1078 { ai: true, product: true, engineer: true }, 1079 { ai: false, product: false, engineer: false }, // reset 1080 ]; 1081 1082 let isPlaying = false; 1083 let seqIndex = 0; 1084 let playTimer = null; 1085 const STEP_MS = 3600; 1086 1087 function applySequenceStep(step) { 1088 Object.keys(step).forEach(id => { 1089 state[id] = step[id]; 1090 updateNode(id); 1091 }); 1092 updateLines(); 1093 updateDescription(); 1094 } 1095 1096 function advanceSequence() { 1097 applySequenceStep(sequence[seqIndex]); 1098 seqIndex = (seqIndex + 1) % sequence.length; 1099 playTimer = setTimeout(advanceSequence, STEP_MS); 1100 } 1101 1102 function startPlay() { 1103 isPlaying = true; 1104 document.getElementById('vennPlayIcon').innerHTML = '<rect x="3" y="2" width="4" height="12"/><rect x="9" y="2" width="4" height="12"/>'; 1105 document.getElementById('vennPlayLabel').textContent = 'Pause'; 1106 advanceSequence(); 1107 } 1108 1109 function pausePlay() { 1110 isPlaying = false; 1111 clearTimeout(playTimer); 1112 playTimer = null; 1113 document.getElementById('vennPlayIcon').innerHTML = '<path d="M3 2.5l10 5.5-10 5.5V2.5z"/>'; 1114 document.getElementById('vennPlayLabel').textContent = 'Play'; 1115 } 1116 1117 function togglePlay() { 1118 if (isPlaying) { 1119 pausePlay(); 1120 } else { 1121 seqIndex = 0; 1122 startPlay(); 1123 } 1124 } 1125 1126 // Dark mode toggle 1127 function toggleMode() { 1128 const dark = document.body.classList.toggle('dark'); 1129 document.getElementById('modeIcon').textContent = dark ? 'âï¸' : 'ð'; 1130 document.getElementById('modeLabel').textContent = dark ? 'Light' : 'Dark'; 1131 updateAllNodes(); 1132 } 1133 1134 // Init node colours for light mode default 1135 updateAllNodes(); 1136 1137 // Auto-start on first scroll into view 1138 const vennObserver = new IntersectionObserver((entries) => {
1139 entries.forEach(e => { 1140 if (e.isIntersecting && !isPlaying && seqIndex === 0) { 1141 startPlay(); 1142 vennObserver.disconnect(); 1143 } 1144 }); 1145 }, { threshold: 0.5 }); 1146 vennObserver.observe(document.getElementById('vennSvg')); 1147 </script>
1147 1148</body> 1149</html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.