Architecture — OMA's Two-Layer Model
This page consolidates how oh-my-aidlcops lays itself out on the user's machine and what happens when a Claude Code or Kiro session starts. The goal is that an engineer can answer "which file do I edit to change behavior X" in one read.
Diagram labels are English, body prose is English, and every mermaid node and edge label is double-quoted.
Core model — three facts
OMA's architecture compresses into three statements. §1, §2, and §3 of this page each correspond to one statement.
-
Assets are split into two layers. Assets in the OMA repo (
~/.oma/or a git checkout) are symlinked or merged once into a user-global directory (~/.claude/or~/.kiro/) at install time. Each project the user works in gets its own<project>/.omao/containing only that project's policy and state. In short, capability is user-global; policy is project-local. -
The two harnesses pull policy in different ways.
- Claude Code is active. Hooks registered in
~/.claude/settings.jsonrun on every session start and every user prompt; they read the cwd's.omao/and inject the result into the system context asadditionalContextJSON. - Kiro is declarative. There are no hooks. Instead the Kiro engine reloads
~/.kiro/steering/,kiro.meta.yaml, andagents/*.jsonon every invocation, and SKILL bodies explicitly Read.omao/when they need policy values.
- Claude Code is active. Hooks registered in
-
.omao/is the shared surface across both harnesses. You can switch between Claude Code and Kiro on the same project without losing work — both read and write the sameprofile.yaml,ontology/,plans/,state/, andaudit.jsonlunder the same conventions. One asymmetry, caused by the absence of Kiro hooks, is documented in §2.6.
The safety property that holds across both harnesses is that the session is never mutated — only context is appended. In a project without .omao/, Claude Code hooks all no-op out, and Kiro simply runs without policy values, falling back to its static assets.
What this document covers — overview diagram
This diagram is the spine of the document. Each arrow is detailed in the per-harness sections — §1 (Claude Code) and §2 (Kiro) — and the shared piece is summarized in §3.
| Layer | Role | Change frequency | Created by |
|---|---|---|---|
~/.oma/ or ~/Dev/sample-oh-my-aidlcops/ | OMA source tree (plugins, skills, hook scripts, compiler) | At each OMA release | install.sh or git clone |
~/.claude/ | User-global config consumed by Claude Code | Once per OMA install | scripts/install/claude.sh or /plugin marketplace add |
~/.kiro/ | User-global assets consumed by Kiro | Once per OMA install | scripts/install/kiro.sh |
<project>/.omao/ | That project's policy and state | Continuously, per task | oma setup, oma init, and individual skills |
Document flow:
- §1 Claude Code harness — how hooks pinned in
settings.jsonturn.omao/into context on every call. - §2 Kiro harness — how steering, sidecars, and agent profiles re-pull policy rules every invocation without hooks.
- §3 Shared —
<project>/.omao/— the policy surface both harnesses share. Producer/consumer mapping and edit points.
1. Claude Code harness
The Claude Code harness centers on an active model: hook scripts emit additionalContext JSON on stdout, and Claude Code appends it to the system prompt. Static assets (plugins, commands, MCP) are exposed via ~/.claude/settings.json. Dynamic policy (.omao/) is read by the hooks on every session and every prompt.
1.1 What each asset references
§1.2 through §1.6 detail what every arrow above corresponds to in code.
1.2 Install — how ~/.claude/ gets populated
scripts/install/claude.sh seats the user-global assets in four steps. The native marketplace path (/plugin marketplace add ...) yields the same result — it just additionally caches a copy under ~/.claude/plugins/cache/ and records it in ~/.claude/installed_plugins.json.
| Step | Function | Result | Diagram node |
|---|---|---|---|
| 1 | install_plugins (claude.sh:108) | 4 symlinks under ~/.claude/plugins/<name>/ → SOURCE plugins/<name>/ | PLUG |
| 2 | install_commands (claude.sh:130) | ~/.claude/commands/oma/ symlink → SOURCE steering/commands/oma/ | CMD |
| 3 | install_mcp_servers (claude.sh:143) | jq-merge each plugin's .mcp.json mcpServers into ~/.claude/settings.json (existing keys preserved) | SET#mcpServers |
| 4 | install_hooks (claude.sh:169) | Register hook script paths under ~/.claude/settings.json#hooks.SessionStart and hooks.UserPromptSubmit | SET#hooks |
Resulting layout:
~/.claude/
plugins/<plugin>/ → SOURCE/plugins/<plugin> # PLUG (symlink)
commands/oma/ → SOURCE/steering/commands/oma # CMD (symlink)
settings.json
"mcpServers": { aws-documentation, aws-iac, aws-pricing, ... } # 11 entries (SET#mcp)
"hooks": {
"SessionStart": [{ "hooks": [{"command": "SOURCE/hooks/session-start.sh"}]}],
"UserPromptSubmit": [{ "hooks": [{"command": "SOURCE/hooks/user-prompt-submit.sh"}]}]
}
1.3 SessionStart hook — context injection at session start
Detail of the SET → SS → ST · ONT → AGENT arrows.
Context blocks emitted at every session start:
| Block | Source | Code location |
|---|---|---|
[OMA Session Context] Active Tier-0 Mode: ... | cwd/.omao/state/active-mode | session-start.sh:25 |
Project Memory: { ... } | cwd/.omao/project-memory.json | session-start.sh:39 |
[OMA Ontology] (one line per Budget · Incident · Deployment) | cwd/.omao/ontology/<type>/*.json | session-start.sh:53-87 |
Available OMA Tier-0 Commands: ... (static catalog) | hardcoded | session-start.sh:92 |
Safety guarantees:
- Honors
CLAUDE_PROJECT_DIRover cwd — the hook reads the right.omao/even when Claude Code spawns it from a different working directory (session-start.sh:20). - JSON is emitted via
jq,python3, orpython(in that order) — if none are present the hook exits 1. The hook never builds JSON via shell interpolation, so ontology files containing quotes, backslashes, or newlines remain safe (session-start.sh:118-150). - Kill switches —
OMA_DISABLE_TRIGGERS=1orOMA_DISABLE_ONTOLOGY=1env vars (session-start.sh:12,53).
1.4 UserPromptSubmit hook — keyword and budget checks per prompt
Detail of the SET → UPS → TRIG · ONT → AGENT arrows.
| Output | Trigger condition | Code location |
|---|---|---|
[MAGIC KEYWORD: OMA_TRIGGER] | A .omao/triggers.json keyword matches the prompt and (if present) every context_required token is also present | user-prompt-submit.sh:55-124 |
[MAGIC KEYWORD: OMA_BUDGET_WARN] | Any budget's spend_usd / limit_usd > 0.8 | user-prompt-submit.sh:130-155 |
| (none) | Nothing matches — prompt passes through normally | exit 0 |
Match rules:
- Slash commands (
/oma:agenticops) and multi-word phrases use substring matching. - Single tokens use
grep -qwword-boundary matching (e.g.,autodoes not match insideautomobile). - Explicit slash commands bypass
context_required.
1.5 Tier-0 dispatch — where static assets meet dynamic policy
Once the session is up, the user invokes a slash command such as /oma:autopilot. From that point on, static assets (PLUG, CMD) and dynamic policy (PROF, ONT, ST) coexist in the same context.
The top-of-stack hierarchy is enforced by steering/oma-hub.md:9-30 and steering/workflows/ontology-harness-mandate.md:11-49 — the seven absolute rules there override every SKILL.md body.
1.6 Where to edit (Claude Code side)
| Behavior to change | Single edit point | Follow-up command |
|---|---|---|
| Add or version-bump an MCP server | mcp: block in plugins/<plugin>/<plugin>.oma.yaml | python3 -m tools.oma_compile <file> then re-merge with bash scripts/install/claude.sh |
| New keyword trigger | triggers: block in <plugin>.oma.yaml | oma compile, then copy .omao/triggers.json into the user's project |
| New Tier-0 slash command | Add steering/commands/oma/<name>.md and update <plugin>.oma.yaml#triggers | The symlink already points at ~/.claude/commands/oma/, so a CLI restart is enough |
| New SKILL | Add plugins/<plugin>/skills/<skill>/SKILL.md | Claude Code picks it up immediately (symlink) |
| New session-start context block | Update the ADDITIONAL_CONTEXT accumulator in hooks/session-start.sh | Verify with bash hooks/session-start.sh |
| Change prompt match rules | hooks/user-prompt-submit.sh (e.g., word boundaries) | echo '{"prompt":"..."}' | bash hooks/user-prompt-submit.sh |
2. Kiro harness
Kiro has no hooks. Where Claude Code actively emits context via hook scripts, Kiro is declarative — its engine re-reads ~/.kiro/steering/, kiro.meta.yaml, and agents/*.json on every invocation, and SKILL bodies explicitly Read .omao/ when needed.
2.1 What each asset references
§2.2 through §2.5 detail every arrow above with code locations.
2.2 Install — how ~/.kiro/ gets populated
scripts/install/kiro.sh runs five steps. There is no hook-registration step — that is the decisive difference from Claude Code.
| Step | Function | Result | Diagram node |
|---|---|---|---|
| 1 | install_skills (kiro.sh:91) | Flattened symlinks under ~/.kiro/skills/<p>/<s>/. Two-level groups like aidlc/skills/inception/<s> get one extra descent | SK |
| 2 | install_steering (kiro.sh:145) | ~/.kiro/steering → SOURCE steering/ (manifest, workflows, oma-hub.md) | ST |
| 3 | install_guides (kiro.sh:157) | Per-plugin stage-gated guide directories | GD |
| 4 | install_agents (kiro.sh:176) | Kiro .agent.json profiles (MCP pins + autoApprove) | AG |
| 5 | install_settings (kiro.sh:199) | Copy template into ~/.kiro/settings/cli.json (preserved if already present) | CFG |
Resulting layout:
~/.kiro/
skills/<plugin>/<skill>/ → SOURCE/plugins/<plugin>/skills/<skill> # SK (symlink)
steering/ → SOURCE/steering # ST (symlink)
guides/<plugin>/ → SOURCE/plugins/<plugin>/guides # GD (symlink)
agents/*.agent.json → SOURCE/plugins/<plugin>/kiro-agents/*.json # AG (symlink)
settings/cli.json (file copy from scripts/kiro-cli.template.json) # CFG
2.3 Steering auto-load — absolute rule injection
The ST → AGENT arrow.
The Kiro engine auto-loads everything under ~/.kiro/steering/ on every invocation. Consequently the following content is always present in every Kiro session:
oma-hub.md— routing hub plus the seven ABSOLUTE RULESworkflows/ontology-harness-mandate.md— non-overridable absolute-rules textworkflows/diagram-authoring-standard.md— diagram-tool mandateworkflows/aidlc-full-loop.md,workflows/platform-bootstrap.md, ... — 5-checkpoint workflow definitionscommands/oma/*.md— Kiro does not dispatch slash commands but uses these files as skill-orchestration reference material
This is equivalent to the rules side of Claude Code's SessionStart hook — the differences are summarized in §2.6.
2.4 SKILL matching — sidecar trigger_keywords
The SK → AGENT arrow.
A kiro.meta.yaml sidecar can sit next to each SKILL (see kiro-setup.md:98-134). The Kiro engine reads it to auto-match natural-language inputs to a SKILL.
# kiro.meta.yaml — example for vllm-serving-setup
kiro:
trigger_keywords:
- "vllm"
- "model serving"
- "PagedAttention"
context_files:
- SKILL.md
- reference/vllm-config.yaml
mcp_required:
- eks-mcp-server
- aws-pricing-mcp-server
phase: operations
approval_required: true
| Field | Effect |
|---|---|
trigger_keywords | Boost SKILL matching priority on natural-language input |
context_files | Additional files to load alongside the SKILL |
mcp_required | Verify required MCP servers are connected before invocation |
phase | Tag as Inception / Construction / Operations |
approval_required | Whether checkpoint approval is required |
A SKILL without a sidecar still works fine — just on its SKILL.md frontmatter alone.
2.5 Agent profile — agents/*.agent.json
The AG → AGENT arrow.
Each Kiro agent profile is a compile output of tools/oma_compile reading SOURCE <plugin>.oma.yaml#agents. Where Claude Code consolidates 11 MCP servers in a single settings.json, Kiro pins MCPs per agent.
// ~/.kiro/agents/ai-infra.agent.json — symlink → SOURCE/plugins/ai-infra/kiro-agents/...
{
"name": "ai-infra",
"description": "AI runtime infrastructure architect ...",
"tools": ["*"],
"mcpServers": {
"awslabs.eks-mcp-server": { "command": "uvx", "args": ["awslabs.eks-mcp-server==0.1.28"], ... },
"awslabs.aws-documentation-mcp-server": { ... },
"awslabs.aws-pricing-mcp-server": { ... },
"awslabs.cloudwatch-mcp-server": { ... }
},
"autoApprove": { "readOnly": true, "fileWrites": false, "bashCommands": false },
"resources": ["file://.kiro/steering/oma-hub.md", "skill://.kiro/skills/ai-infra/**/*.md"]
}
At runtime the user activates a profile with @ai-infra deploy vllm 70b or similar.
2.6 Kiro asymmetry — the gap left by absent hooks
Some context that Claude Code's hooks actively push is not auto-pushed under Kiro. Operators must close the gap by Reading the relevant files explicitly inside SKILL procedures.
| Context | Claude Code | Kiro |
|---|---|---|
Steering absolute rules (oma-hub.md, mandate.md) | Auto-loaded by Claude Code without hooks | ~/.kiro/steering/ auto-load ✅ |
| SKILL bodies | ~/.claude/plugins/<p>/skills/<s>/SKILL.md | ~/.kiro/skills/<p>/<s>/SKILL.md ✅ |
| MCP server catalog | Global in settings.json#mcpServers | Per-agent in agent profile ✅ |
| Ontology current values snapshot (Budget remaining, open incidents, draft deployments) | Pushed by session-start.sh at every session start ✅ | ❌ No auto-push. SKILL body must Read explicitly |
| active-mode / project-memory | Pushed by session-start.sh ✅ | ❌ No auto-push |
Per-prompt budget threshold warning ([OMA_BUDGET_WARN]) | Checked by user-prompt-submit.sh on every prompt ✅ | ❌ No per-prompt check |
| Keyword triggers (natural language → Tier-0 command) | user-prompt-submit.sh + global .omao/triggers.json catalog ✅ | △ kiro.meta.yaml#trigger_keywords handles only SKILL matching (no Tier-0 catalog) |
Implications:
- When a Kiro user runs
@ai-infra deploy ..., the agent starts unaware of whether the budget is at 80%, whether a draft deployment exists, etc. The SKILL procedure must explicitly Read.omao/ontology/budgets/*.jsonand.omao/ontology/deployments/*.jsonto surface that. - Absolute Rule #4 from
steering/workflows/ontology-harness-mandate.md— "on receiving[MAGIC KEYWORD: OMA_BUDGET_WARN], the agent must surface the warning in its first response" — is Claude-Code-specific. Under Kiro the magic keyword is never emitted, so the rule lapses. Operationally, work around this by invoking thecost-governanceskill explicitly or registering the budget file underkiro.meta.yaml#context_files.
2.7 Where to edit (Kiro side)
| Behavior to change | Single edit point | Follow-up command |
|---|---|---|
| Kiro agent's MCP pins | agents: block in <plugin>.oma.yaml (runtime: kiro) | python3 -m tools.oma_compile <file> then re-run bash scripts/install/kiro.sh (refreshes symlinks) |
Kiro agent's autoApprove defaults | The compiler hardcodes {readOnly: true, fileWrites: false, bashCommands: false} (compile.py:213-217); change there or hand-edit the emitted kiro-agents/<id>.agent.json | Re-run install if you edited the compiler |
| New Kiro agent profile | Same place — agent id, description, tools, mcp, resources | Same — kiro-agents/<id>.agent.json is regenerated |
SKILL trigger_keywords | The SKILL directory's kiro.meta.yaml (create one if absent) | Symlink already points; restart Kiro |
| Absolute rules / new workflow definition | steering/oma-hub.md or steering/workflows/<name>.md | Reflected in every Kiro session immediately (steering auto-load) |
| New SKILL | plugins/<plugin>/skills/<skill>/ (SKILL.md plus optional kiro.meta.yaml) | Re-run bash scripts/install/kiro.sh to add the skill symlink |
| Kiro default model / autoApprove | ~/.kiro/settings/cli.json (user-editable; one-time copy from template) | Restart Kiro |
| Stage-gated guide | plugins/<plugin>/guides/stages/<stage>.md | Symlink already points; effect is immediate |
3. Shared — <project>/.omao/
.omao/ is the policy surface both harnesses share. The reason switching between Claude Code and Kiro on the same project does not lose work is that both read and write .omao/ under identical conventions.