Skip to main content
ariefrse.my
HomeSkillsProjectsAI WorkWritingContact
Resume

AI Work

AI & Agentic Engineering Work

How I run AI agents on production software, and what that actually produces.

The system

Shipline: I built the workflow into a tool so the discipline is not optional.

Agents fail in three predictable ways. They write before they understand the codebase, they report success they never verified, and they repeat a mistake next session because nothing recorded it. Shipline is a set of commands that puts a written plan, pasted evidence and a persistent lessons file in the way of each one. Plain markdown in the repo, no external service.

One story moving through Shipline. Implementation is delegated to sub-agents working in parallel, and the work comes back to me at every point where a decision is mine to make.
Plan

Read the plan, approve or send it back

Test

Run the script by hand

Deliver

Read the diff, permit the commit

Build

Implementation, migrations, adapters

Verify

Types, tests, production build

Retro

What failed, and the rule that prevents it

Stops for meRuns without meSub-agents working in parallel
  1. PlanYou

    Read the plan, approve or send it back

  2. BuildSub-agents

    Implementation, migrations, adapters

  3. VerifySub-agents

    Types, tests, production build

  4. TestYou

    Run the script by hand

  5. RetroSub-agents

    What failed, and the rule that prevents it

  6. DeliverYou

    Read the diff, permit the commit

Four documents, or it is not done

plan.md
Ordered steps, acceptance criteria, risks, and what is out of scope.
testscript.md
Numbered cases I run by hand. The agent does not grade its own work.
retro.md
The actual error, the actual fix, and the command that proved it.
runbook.md
Cold-start instructions for someone who has never opened the repo.

Every retro has to answer one question: what rule, written down in advance, would have prevented this. The answer becomes a numbered guardrail that loads back into context before every future plan. That file is append-only, which is what turns a checklist into something that compounds.

The short version

AI does the typing. Engineering judgment stays mine.

I work as a software engineer with an agentic AI practice: from the command line I delegate backend, front end, migrations, tests, deployment configuration and documentation to sub-agents that run in parallel, while architecture, contracts and review stay with me.

Scoped by written plans, verified by type-checking, tests and a production build, and gated by human review on every merge.

What keeps it safe is the context file every session loads before touching code, and the rule that consequential decisions stay with a human. Both are written up with real diffs in the evidence below.

What it is worth

The same team, carrying the work that kept getting deferred.

Agent-assisted delivery changes the economics in three places. Migrations and cleanups that were never worth scheduling become ordinary work. Tests, API notes and release notes are produced alongside the implementation instead of lagging it. And repetitive implementation stops being a reason to add headcount, so budget stays with product decisions, design and review.

That is only worth saying because of the guardrails: the 8 artifacts below are the failure modes, reverts and audits that came out of reviewing agent output on production code, which is the only reason the speed is safe to claim.

How I work

  • Plan before the agent types

    Requirements become module scope, API assumptions, edge cases and a completion contract first.

  • Build with bounded tasks

    Sub-agents run in parallel from the CLI on scaffolding, refactors, API adapters and UI states.

  • Verify against the contract

    Type-check, test suite and production build on every change. I read every diff before it merges.

  • Write it down, once

    Implementation brief, an API issue note when contracts disagree, and release evidence with rollback notes.

Guardrails

  • Consequential decisions stay human: auth, permissions, money, data handling, destructive operations.
  • Client data, credentials and proprietary source stay out of unapproved tools.
  • Each repo keeps a context file of past mistakes, so the next agent session cannot repeat them.

Real work evidence

Actual diffs, commits and PR notes from delivery. Not descriptions of process. Open any of them.

The nav link that did nothing: root-caused live in a real browser, fixed, then published as a note

This portfolio: personal site & digital garden (ariefrse.my) · 2026-07

A user report "clicking Notes shows a blank page" became a systematic debugging session: reproduce in a real browser, isolate to next/link swallowing cross-origin URLs, fix, verify the click end-to-end, and publish the failure mode as a garden note. Traceable to commit ad8fdd1.

The report: clicking the Notes item in the sidebar blanked the content
area. No console error, no 404, and the nav item lit up as active. The
UI looked like it worked.

The debugging loop, run by an AI agent driving a real browser:

1. Reproduce on production: click intercepted, URL never changes.
2. Rule out the target: every page of the external notes site returned
   valid content when fetched directly.
3. Isolate: the sidebar rendered every nav item through next/link, 
   including one cross-origin URL the App Router can't route to. The
   click dies silently inside the router.
4. Fix: external hrefs branch to a plain <a target="_blank">, with an
   isExternalHref() helper and an outbound-arrow marker. The mobile
   drawer also closes onClick, since no route change ever fires.
5. Verify: dev server + real click → the garden opens. Then the same
   click checked again on production after deploy.

The fix is one commit (ad8fdd1); the failure mode became a public
garden note the same day: the mistake now documents itself.

CI that proves production actually deployed, built after catching the deploy loop frozen

This portfolio: personal site (ariefrse.my) · 2026-07

The auto-deploy pipeline (CI-built images + Watchtower) froze silently twice: green builds, stale production. The durable fix bakes the commit SHA into the HTML and adds a CI job that polls production until it serves that exact commit, failing loudly if it never does. Commit 6276eff.

Push-to-live pipelines fail in a uniquely quiet way: the build is
green, the image is published, and production just… stays old. First
cause: Watchtower's bundled Docker client spoke API 1.25 against a
daemon requiring 1.40+, every poll errored before reaching the
registry.

The durable fix makes the loop prove itself on every push:

+ <meta name="build-commit" content={process.env.NEXT_PUBLIC_COMMIT_SHA} />

  verify-deploy:
    needs: build
    steps:
      - run: |
          for i in $(seq 1 20); do
            live=$(curl -sf https://ariefrse.my/ \
              | grep -o 'build-commit" content="[^"]*"')
            [ "$live" = "$GITHUB_SHA" ] && exit 0
            sleep 30
          done
          exit 1   # production never picked up this commit

On its first run the job failed; correctly: Watchtower had frozen
again, and what used to be days of silently stale production became a
red X and an email within ten minutes of the push.

Next.js 14 → 16 migration: hunting the bugs that don’t crash

Anchorworx Web Platform: maritime job platform (private client work) · 2026-07

Migrating a Next 14 production platform toward 16, the dangerous changes aren’t the build errors; they’re the ones that keep rendering: dynamic route params silently becoming Promises. AI agents swept every route for the invisible class before anything shipped.

The compile errors in a Next 14 → 15/16 migration fix themselves. The
build tells you. The bugs worth an agent sweep are the ones that don't
crash:

- Dynamic route params: in 14, params is a plain object; from 15 on
  it's a Promise. Code reading params.id without await doesn't throw, 
  it reads a property off a Promise, gets undefined, and the page
  quietly renders its empty/fallback state. Same for searchParams.
- fetch caching defaults flipped in 15: pages silently change between
  cached and per-request behavior with zero code changes.
- cookies()/headers() went async, sync usage survives loose type
  checking and only fails on the runtime paths that touch it.

The canonical fix, applied route by route:

- export default function JobPage({ params }: { params: { id: string } }) {
-   const job = await getJob(params.id)
+ export default async function JobPage({ params }: { params: Promise<{ id: string }> }) {
+   const { id } = await params
+   const job = await getJob(id)

The AI workflow: inventory before touching anything, an agent pass
listing every dynamic segment ([id], [slug]), every params/searchParams
read, and every fetch with an implicit caching assumption. Then the
mechanical rewrites in bulk, human-reviewed per diff. Then the check
that actually matters: drive each migrated route and confirm real data
renders: because on this class of bug, "it builds and the page loads"
is exactly what failure looks like.

Employer dashboards, job boards, and the candidate directory are dense
with dynamic routes: silent-undefined params would have shipped as
blank screens, not stack traces.

Agent audit caught a payroll parity bug, human review reverted the one fix it got wrong

hrmydeck: Malaysian HRMS (production pilot, built end-to-end with AI agents) · 2026-06

Ran an AI "gap-finder" audit across the API and admin-panel code paths before release. It found real money-touching bugs, and one of its fixes was reverted the same day because it violated the intended approval chain.

All of hrmydeck is built through AI agent sessions, implementation,
fixes, deployment config, and docs, with human review gating every
merge. The commit trail carries the AI co-author trailers to prove it.
This entry is one of those sessions.

Audit brief: compare every API code path against its Filament admin-panel
equivalent and report behavioral gaps. Findings that shipped as fixes:

- Payroll calculation parity: the API's processPayroll was missing
  claims reimbursement, unpaid-leave deductions, and custom deductions
  that the admin panel's generatePayslips applied, two code paths were
  computing different salaries for the same employee
- API employee onboarding never sent the welcome invite the admin-panel
  flow sent, so API-created employees silently never got credentials
- Claim routes exposed show/update/destroy endpoints that were never
  implemented: restricted apiResource to index+store only
- Claim approver deadlock: no approval possible when no named approvers
  were configured in env

The deadlock "fix" (fall back to any MANAGER-role user as approver) was
technically sound and organizationally wrong, approvers must be
explicitly configured, not inferred from a role. Human review caught it
and it was reverted the same day:

  revert: remove MANAGER role fallback in claim approval
  CEO/COO approvers must be explicitly configured via CLAIM_APPROVER_*
  env vars. The MANAGER fallback is not the intended approval chain.

The audit was worth running for the payroll bug alone. The revert is the
point of human-in-the-loop: agents optimize for "no deadlock," humans
know which deadlocks are load-bearing.

Pre-release authorization sweep: scoping holes in claims, payroll, and the leave calendar

hrmydeck: Malaysian HRMS (production pilot, built end-to-end with AI agents) · 2026-06

A hardening pass before the pilot release found managers could mark other teams’ claims as paid, every employee could see the whole company’s leave calendar, and approver IDs were hardcoded.

A pre-release authorization sweep across claims, payroll, and the leave
calendar found scoping and hardcoding issues fixed before the pilot
went live:

- Move hardcoded claim approver employee IDs to env vars
  (CLAIM_APPROVER_1_ID/2_ID) so the approval chain doesn't break when
  staff change without a deploy
- Add employee scope check to ClaimsController::markPaid() to prevent
  managers marking claims outside their team as paid
- Remove MANAGER role from canAccessPanel('admin'), managers use the
  employee panel only
- Add explicit canAccess() to PayrollRunResource and LeaveBalanceResource
  for consistent defense-in-depth across all admin resources

Follow-up caught in the same sweep:

- Leave calendar: employees could see all approved leave requests
  regardless of role. Scoped the response to the employee's own leaves
  for non-approver roles (EMPLOYEE, FINANCE); approver roles still see
  the full calendar.

Encoded hard-won CI failures into agent context so no future session reintroduces them

hrmydeck: Malaysian HRMS (production pilot, built end-to-end with AI agents) · 2026-06

After a duplicate-ID seeder bug silently broke CI, the lesson went into the repo’s CLAUDE.md not a wiki so every future agent session loads it as context before touching the code.

The bug: MockDataSeeder plucked employee IDs without deduplication;
duplicate IDs broke seeding silently and CI went red with no obvious
cause.

The fix was one line. The durable fix was context engineering, a
standing section in CLAUDE.md that every agent session reads:

+ ## Known CI Footguns: Never Re-introduce
+ 1. Seeder dedup: always ->unique() on pluck('employeeId') in
+    MockDataSeeder: duplicate IDs will break seeding silently
+ 2. Leave calendar scoping: non-approver roles (EMPLOYEE, FINANCE)
+    must only see their own leaves; global scoping breaks this
+ 3. Staging Docker BuildKit cache is flaky, pre-existing, unrelated
+    to code changes

Agents forget between sessions; the repo doesn't. Every regression a
reviewer catches becomes a constraint the next agent session starts
with, instead of a mistake it gets to repeat.

Wrote the spec vault first, then shipped a multi-tenant crew SaaS foundation in two days

CrewSync: maritime crew management SaaS (personal product, deployed MVP) · 2026-06

Docs before code: an Obsidian vault mapping every module (safe manning, certificates, sign-on/off, sea service) became the agent context. The MVP foundation landed the next day: 196 files, ~24k lines, and it has been running deployed with a queue worker and scheduler since.

The planning vault came first: architecture, tech stack, and one note
per module (Auth & Authorization, Company & Multi-Tenancy, Crew
Management, Crew Matrix, Certificate Management, Crew Rotations, Safe
Manning Compliance, Sign-On/Sign-Off, Sea Service Records), so agent
sessions built against a written contract instead of improvising scope.

Commit trail:

  Jun 22  Initial commit: Laravel backend scaffold
  Jun 23  Build CrewSync MVP foundation
          196 files changed, 23,716 insertions

What that foundation covers: multi-tenant company isolation, crew and
vessel records, certificate validity tracking with expiry alerts, SOLAS
safe-manning checks before assignment, sign-on/sign-off workflows,
cumulative sea service records, a Filament admin panel, and a React
crew self-service portal (rest hours, payslips, PSC score, sea service).

Deployment is part of the evidence: the stack runs as app + queue
worker + scheduler + Postgres + Redis containers, serving real usage, 
not a repo that only ever ran on localhost.

Contract-first profile refactor on a live client platform, with a persistent memory bank

Anchorworx Web Platform: maritime job platform (private client work) · 2025-04

Realigned the crew-profile module to the backend’s api.json contract typed DTOs and a dedicated service layer and maintained a memory-bank of project context files so every AI session resumed with full state instead of re-deriving it.

From the refactor doc kept in the repo (PROFILE_REFACTOR.md):

- Created src/utils/profileTypes.ts with types matching the API schema:
  CrewProfileName, CrewProfileContact, ProfileDetail,
  CrewProfileSeamanDocument (passport, seaman ID, seaman book),
  CrewProfileExperienceVessel, CrewCertificate, CrewProfileResume,
  plus request DTOs (CrewExperienceDTO, CrewCertificateDTO, …)
- Created src/services/profileService.ts as the single API surface:
  getProfile, getProfileCompletionStatus, updateProfile,
  uploadProfilePicture, experience/certificate/education CRUD

The contract (api.json) was the source of truth: types were derived
from it, not from what the UI happened to send. That's what made
changing backend requirements survivable on a 2-person frontend team.

The workflow artifact worth copying: a memory-bank/ directory: 
projectbrief.md, productContext.md, systemPatterns.md, techContext.md,
activeContext.md, progress.md, updated as work progressed, so each AI
session started from the project's actual state and decisions instead
of rediscovering them from scratch.

Writing it up

The concepts behind the practice, including MCP and multi-agent orchestration.

MCP for Agentic EngineersAgent Teams

Share this

If the pipeline above is useful to someone building the same habit, send it on.

XLinkedInWhatsAppThreads

© 2026 Muhammad Arief Roseli

GitHubLinkedInSnapInvois