Sitemap
Nikky Juwe

Nikky Juwe

Setting Up an Agentic AI Workflow with Claude Code

11 min readAug 4, 2026

--

This week’s assignment was about building the foundation for agentic DevOps. Not writing infrastructure yet, just learning how to hand real work to an AI agent safely and repeatably. Seven pieces, each one building on the last: setup, project context, skills, subagents, MCP, hooks, and memory. Here’s the full breakdown, step by step, in case you’re doing this yourself or just want to understand what “agentic AI” actually means in practice.

Why this matters before touching any real infrastructure

You wouldn’t hand a new hire the AWS root credentials on day one. You’d watch them work, give them a runbook, restrict their permissions to what the task needs, and build trust gradually. That’s exactly the mental model for this whole assignment set. Claude Code is treated like a new team member: get it set up, teach it the project, give it narrow tools for specific jobs, split responsibilities across specialists, connect it to live systems, put guardrails in place, and give it a memory so it doesn’t forget the rules every session.

Part 1: Installing Claude Code and watching the Agentic Loop

Install and authenticate

Three commands, that’s it:

npm install -g @anthropic-ai/claude-code
claude --version
claude

Running claude opens a browser window for login. Once authenticated, you get a terminal prompt inside your project. If claude --version doesn't print a number, npm didn't install it globally, check your PATH.

Fork and clone the starter repo

git clone https://github.com/YOUR_USERNAME/REPO_NAME.git
cd REPO_NAME
code .

At this point the project should just have index.html, style.css, and an images/ folder. No .claude/ directory yet, that comes later. If a .claude folder, CLAUDE.md, or .github folder already exists in the fork, delete them first so you're starting clean.

Watching the loop: Gather, Act, Verify

This is the core idea of the whole assignment. Every agentic task Claude runs breaks into three phases:

  • Gather: Claude reads the relevant files before saying anything
  • Act: Claude does the actual work, runs a command, writes code, whatever the task needs
  • Verify: Claude checks its own output against what was asked

To see it live, open the Claude Code terminal inside VS Code and ask two questions:

What files are in this project and what does each one do?

Claude should read index.html, style.css, and the images folder before answering, not guess from the file names.

How many lines of CSS does this project have?

Here Claude should actually run something like wc -l style.css instead of estimating. That's the "Act" phase in action, a real shell command with a real, checkable answer.

Takeaway: if Claude ever answers a question like this without visibly reading a file or running a command first, that’s a sign it’s guessing, not gathering. Watch for that pattern before you trust it with anything higher stakes.

Part 2: Teaching Claude the project with CLAUDE.md

README is for humans, CLAUDE.md is for the AI

This distinction matters. A README explains the project to a person browsing GitHub. CLAUDE.md is the onboarding doc Claude reads automatically at the start of every session in that project. Without it, Claude has to infer your stack and your rules from the file tree alone.

Step 1: Capture the “before” baseline

Before any CLAUDE.md exists, ask:

What is this project and how should I deploy it?

Expect something generic: “this looks like a static HTML/CSS site, you could deploy it to Netlify, GitHub Pages, Vercel,” etc. That vagueness is the baseline you’re comparing against later.

Step 2: Generate a starter file with /init

/init

Claude scans the project and writes a CLAUDE.md with a basic Project Overview and Architecture section. It won't know anything project-specific yet, like your actual deployment target, because you haven't told it.

Step 3: Fill in all five sections yourself

This is the part that actually does the work. A complete CLAUDE.md has:

  1. Overview: what the project is and where it’s deployed. Example: “Static HTML/CSS portfolio website deployed to AWS using S3 and CloudFront, provisioned with Terraform, and automated via GitHub Actions.”
  2. Architecture: the technical constraints. Example: “Pure HTML5 and CSS3. No JavaScript. No build step. No framework.”
  3. Commands: the exact commands used in this project, e.g. terraform init, terraform plan, terraform apply.
  4. Conventions: the rules Claude has to follow. Example set:
  • All infrastructure changes go through Terraform, never modify AWS resources manually
  • No JavaScript in this project
  • CSS uses a mobile-first approach with breakpoints at 900px, 768px, and 600px
  1. Safety: what should never end up in this file. “Never put secrets in this file. No API keys, passwords, or AWS credentials.”

Step 4: Prove it worked

Start a completely new Claude Code session (close the terminal, reopen it) and ask the exact same question as before:

What is this project and how should I deploy it?

Now it should mention S3, CloudFront, and Terraform specifically, because it read the file.

Then test the guardrail:

Add a React component to the homepage.

Claude should push back and cite the “No JavaScript” rule from CLAUDE.md. If it doesn't, the file either isn't being read or the convention wasn't written clearly enough.

Step 5: Commit it

git status
git add CLAUDE.md
git commit -m "Add project-specific CLAUDE.md"
git push origin main

Why this matters: everything you write in CLAUDE.md applies to every future session automatically. A well-written one written once saves you from repeating the same corrections for months.

Part 3: Building reusable Skills

The problem skills solve

Typing out the same long prompt every time you want to run a repeatable task is slow and error prone. Skills turn a full workflow into a single slash command, with consistent behavior and enforced tool restrictions every time it runs.

Step 1: Create the folder structure

mkdir -p .claude/skills/scaffold-terraform
mkdir -p .claude/skills/tf-plan
mkdir -p .claude/skills/tf-apply
mkdir -p .claude/skills/deploy

Step 2: Drop in the skill files

Each skill folder needs its SKILL.md, renamed and placed correctly:

  • scaffold-terraform-SKILL.md → .claude/skills/scaffold-terraform/SKILL.md
  • scaffold-terraform-template-spec.md → .claude/skills/scaffold-terraform/template-spec.md
  • tf-plan-SKILL.md → .claude/skills/tf-plan/SKILL.md
  • tf-apply-SKILL.md → .claude/skills/tf-apply/SKILL.md
  • deploy-SKILL.md → .claude/skills/deploy/SKILL.md

Open tf-plan/SKILL.md and check the frontmatter. It should show something like:

allowed-tools: Bash, Read, Grep
disable-model-invocation: true

Notice there’s no Write in that list. This skill can read and run commands, but it can't modify files. That restriction is deliberate.

Step 3: Run the skill

/scaffold-terraform

Claude reads the template spec and generates main.tf, variables.tf, outputs.tf, providers.tf, and backend.tf inside a terraform/ folder, then gives you a summary checklist of what it created.

Step 4: Initialize and plan

cd terraform && terraform init

Then back in Claude Code:

/tf-plan

Without real AWS credentials, terraform plan will fail with an authentication error, and that's expected. The point of this step isn't a clean plan output, it's watching Claude run the command, hit the error, and explain what's actually wrong and what would fix it. That's error handling inside the Agentic Loop.

The underlying principle: this is least privilege, the same concept behind IAM roles and Kubernetes RBAC. A skill that can only read files can’t corrupt your infrastructure no matter how many times it runs. Write access has to be earned by the specific skill that needs it.

Part 4: Building a team of subagents

Skills vs subagents

Skills share your current conversation’s context. Subagents run in an isolated context, they’re specialists you delegate a whole task to, and they come back with a report instead of working alongside you turn by turn.

Step 1: Set up the agents folder

mkdir -p .claude/agents

Drop in three files, matching the name field in each file's frontmatter exactly:

  • security-auditor.md
  • tf-writer.md
  • cost-optimizer.md

Step 2: Understand why each one is configured differently

Open each file and check model and tools:

  • cost-optimizer runs on Haiku, not Sonnet, because cost checks are fast, pattern-based lookups, not deep reasoning. Speed matters more than depth here.
  • security-auditor deliberately has no Write in its tools list. It should only ever read and report, never modify infrastructure itself.
  • tf-writer uses model: inherit, meaning it runs on whatever model the main session is using, since generating Terraform code benefits from the same reasoning quality as your main conversation.

Step 3: Delegate to the security auditor

Audit my Terraform files for security issues.

Claude matches this prompt to the security-auditor’s description and shows a delegation message like “Launching security-auditor agent.” The report comes back organized by severity, typically flagging things like missing S3 encryption, outdated TLS versions, or missing access logging.

Step 4: Delegate to the cost optimizer

Review my Terraform infrastructure for cost optimization.

This one finishes noticeably faster than the security audit, that’s Haiku vs Sonnet in practice. The report covers things like CloudFront price class, lifecycle policies for versioned S3 objects, and estimated monthly cost.

Get Nikky Juwe’s stories in your inbox

Join Medium for free to get updates from this writer.

Why this matters: one overloaded general-purpose agent with every tool and capability works fine until something breaks, then it’s nearly impossible to trace what went wrong. Small, single-purpose subagents are easier to debug because you already know which agent and which checklist to look at.

Part 5: Connecting Claude to live external data with MCP

The problem MCP solves

Without it, Claude only knows what was in its training data, which could be months old and has zero knowledge of your actual GitHub repos, AWS account, or databases. MCP (Model Context Protocol) is how Claude connects to real, live external systems so answers are grounded in current data instead of guesses.

Step 1: Generate a GitHub token

GitHub → Settings → Developer Settings → Personal Access Tokens → Tokens (classic) → Generate new token (classic). Set an expiry, select the repo and read:user scopes, generate, and copy the token immediately since GitHub won't show it again. Never screenshot the token value itself, only the scope selection screen.

Step 2: Configure the MCP server

Create .mcp.json at the project root:

{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {}
}
}
}

This file is meant to be shared with the team and committed to version control. It defines which server to connect, not the credentials.

Step 3: Store the token locally, never committed

Create .claude/settings.local.json:

{
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "your_token_here"
},
"enabledMcpjsonServers": ["github"]
}

Confirm .claude/settings.local.json is listed in .gitignore. This split matters: .mcp.json is team configuration, settings.local.json is personal credentials. One gets committed, the other never does.

Step 4: Verify the connection

Close Claude Code fully and reopen it so it picks up the new config, then:

/mcp

You should see the github server listed as connected.

Step 5: Prove it with a live query

Use GitHub MCP to get the README.md file from <your-github-username>/Ultimate-Agentic-DevOps-with-Claude-Code

Claude calls the GitHub MCP tool and returns the actual live README content, not a guess based on training data. Compare it against the real file in your repo to confirm it matches.

Why this matters: MCP is an open standard, so the exact pattern you just used for GitHub applies identically to dozens of other tools, Postgres, Jira, Sentry, Notion. Learn the pattern once, reuse it everywhere.

Part 6: Putting guardrails on the agent with Hooks and Permissions

Why hooks aren’t optional

No team gives an AI agent unrestricted access to production. A junior engineer might type “delete all the old buckets” during a routine cleanup without meaning to trigger something destructive. Hooks intercept that intent before Claude even starts working on it, or block the dangerous command right before it executes.

Step 1: Build the folder structure

chmod +x .claude/hooks/*.sh

(Skip chmod on Windows and use Git Bash as your terminal instead.)

.claude/
├── settings.json
└── hooks/
├── user-prompt-guard.sh
├── pre-tool-guard.sh
└── post-tool-logger.sh

Step 2: UserPromptSubmit hook, catches destructive intent before Claude even processes the request

#!/bin/bash
# UserPromptSubmit hook — blocks destructive intent in user prompts
INPUT=$(cat)
PROMPT=$(echo "$INPUT" | jq -r '.prompt // empty')
if echo "$PROMPT" | grep -iqE "delete all|destroy everything|remove all resources|wipe|nuke|drop all"; then
echo '{"decision": "block", "reason": "Destructive intent detected. Please use /tf-destroy for controlled infrastructure teardown."}'
fi

Step 3: PreToolUse hook, blocks dangerous commands right before execution

#!/bin/bash
# PreToolUse hook — blocks dangerous Bash commands before execution
INPUT=$(cat)
CMD=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
if echo "$CMD" | grep -qE "terraform destroy|terraform apply.*-auto-approve|aws s3 rm|aws s3 rb"; then
echo '{"decision": "block", "reason": "Destructive command detected. Use /tf-destroy or /tf-apply commands for safety."}'
fi

Step 4: PostToolUse hook, logs what actually ran

#!/bin/bash
# PostToolUse hook — logs Terraform validation and formatting commands
INPUT=$(cat)
CMD=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
if echo "$CMD" | grep -qE "terraform fmt|terraform validate"; then
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] Terraform command executed: $CMD" >> .claude/deploy.log
fi

Step 5: Wire it all together in settings.json

{
"alwaysThinkingEnabled": true,
"permissions": {
"allow": [
"Bash(cd terraform && terraform init*)",
"Bash(cd terraform && terraform plan*)",
"Bash(cd terraform && terraform output*)",
"Bash(cd terraform && terraform show*)",
"Bash(cd terraform && terraform fmt*)",
"Bash(cd terraform && terraform validate*)",
"Bash(aws s3 ls*)",
"Bash(aws cloudfront get-distribution*)",
"Bash(aws sts get-caller-identity)"
],
"deny": [
"Bash(rm -rf *)",
"Bash(aws iam *)"
]
},
"hooks": {
"UserPromptSubmit": [
{ "matcher": "", "hooks": [{ "type": "command", "command": "bash .claude/hooks/user-prompt-guard.sh" }] }
],
"PreToolUse": [
{ "matcher": "Bash", "hooks": [{ "type": "command", "command": "bash .claude/hooks/pre-tool-guard.sh" }] }
],
"PostToolUse": [
{ "matcher": "Bash", "hooks": [{ "type": "command", "command": "bash .claude/hooks/post-tool-logger.sh" }] }
]
}
}

Step 6: Test all three, in order

Close and reopen Claude Code first so it loads the fresh config, then:

Test the prompt-level block:

delete all files in the terraform folder

Expect: blocked immediately, before Claude even starts reading anything.

Test the tool-level block:

Run terraform destroy in the terraform folder.

Expect: Claude starts to process it, but the command itself gets blocked before execution.

Test the logging hook:

Run terraform validate in the terraform folder.

Expect: the command succeeds normally, and a line gets appended to .claude/deploy.log recording it.

Why this matters: this mirrors the exact same logic behind pre-commit hooks and CI/CD approval gates. Intercept early, at the cheapest possible point. A prompt-level block that fires in milliseconds is a lot better than catching a terraform destroy ten minutes after it already ran.

Part 7: Giving Claude a memory across sessions

The problem

Every new Claude Code session starts from zero. No memory of prior decisions, conventions, or corrections. On a project running for months, that means re-explaining the same rules constantly.

Step 1: Find where memory actually lives

Where does your memory file live for this project? Show me the full path.

Claude gives you the absolute path, something like ~/.claude/projects/<encoded-path>/memory/MEMORY.md. It may not exist yet, that's normal.

Step 2: Teach it something and tell it to save

Remember the following for all future sessions: The CSS hero section uses a dark gradient from #1a1a2e to #16213e. The mobile breakpoints are 900px, 768px, and 600px. Never suggest adding JavaScript to this project. Save this to your memory file now.

Open MEMORY.md afterward and confirm all three facts actually landed there.

Step 3: Fully close the session

/exit

Then close VS Code entirely, wait about 30 seconds, and reopen it with a fresh Claude Code session in the same project. This step matters, a session left open in the background isn’t a real test of memory persistence.

Step 4: Prove recall without repeating yourself

In the brand new session, ask three separate questions:

What colors are used in the hero section of this project?

Should recall #1a1a2e to #16213e without being told again.

What are the mobile breakpoints for this project?

Should recall 900px, 768px, and 600px.

Should I add a JavaScript animation to the hero section?

Should refuse, citing the no-JavaScript rule from memory.

Why this matters: the memory file has a 200-line load limit by design. That’s not a limitation to work around, it’s a forcing function. Fill it with noise and you push out the things that actually matter. Treat it like a curated runbook: add deliberately, prune regularly, keep entries short and actionable.

What this week actually taught me

The thread running through all seven pieces is the same: never give an agent more trust than the task requires, and always make that trust explicit and checkable. CLAUDE.md gives Claude context. Skills restrict what a repeatable task is allowed to touch. Subagents split responsibility so a failure is easy to trace. MCP grounds answers in live data instead of guesses. Hooks stop dangerous actions before they happen instead of after. Memory keeps the rules from having to be re-taught every session.

None of this is really about Claude specifically. It’s the same discipline behind IAM policies, RBAC, and CI/CD approval gates, just applied to an AI agent instead of a human teammate or a pipeline. Calibration first, automation second. That’s the whole point of week one.

The P.S. line now points to your own graded progress page:
https://dmi.pravinmishra.com/s/NkJuwe.html
Start your DevOps journey:
https://lnkd.in/dFyndNrh

--

--