# Project layout and placeholders

Agent Mission Control developer docs > Claude skill, self-hosted

> Every file the creator generates, what each one does, and how each placeholder is filled.

**Before you start:** Read the [overview](/developers/self-hosted-overview/) first.

The creator writes a new directory, by default `azurefoundry_<short>_skills/`, next to the source skill in your repository. The skill folder stays where it is; the Dockerfile copies it into the image at build time. That is why every build runs from the repository root.

`Generated layout (plain-HTTP surface)`
```text
<repo-root>/
├── skills/report-generator/              # the source skill (unchanged)
│   ├── SKILL.md
│   └── scripts/
└── azurefoundry_report_generator_skills/
    ├── __main__.py                       # entry point: health probe, run_turn, uvicorn
    ├── claude_skills_agent.py            # the SDK core: options, prompt, events, tokens
    ├── claude_skills_agent_executor.py   # shared agent instance + per-context session map
    ├── http_agent_server.py              # transport: /health, /execute, /tasks/{id}/cancel
    ├── Dockerfile
    ├── .dockerignore
    ├── .env.example
    ├── pyproject.toml                    # what the image installs from
    ├── requirements.txt                  # kept in step with pyproject.toml
    ├── README.md
    └── tests/                            # local diagnostics, never shipped in the image
        ├── README.md
        ├── test_run.py
        ├── test_foundry_direct.py
        ├── test_anthropic_sdk_foundry.py
        ├── inspect_foundry_client.py
        └── debug_sdk.py
```

For the AMC variant, keep `claude_skills_agent.py` and the build files, skip `http_agent_server.py`, `claude_skills_agent_executor.py`, and the HTTP parts of `__main__.py`, and add `app.py`. See [Serve the agent to AMC with amc_sdk](https://www.agentmissioncontrol.ai/developers/self-hosted-amc-sdk/).

## What each file does

| File | Kind | Job |
| --- | --- | --- |
| `claude_skills_agent.py` | Templated | Builds `ClaudeAgentOptions`, wraps the user message in the prompt strategy, runs `query()`, translates SDK messages into wire events, and reads tokens and cost off `ResultMessage`. |
| `claude_skills_agent_executor.py` | Templated | Owns one shared agent instance behind a lock, initializes it at startup, and maps each `context_id` to a session id. |
| `__main__.py` | Templated | Loads `.env`, defines the health probe, maps agent strings to NDJSON events in `_run_turn`, and starts uvicorn. |
| `http_agent_server.py` | Verbatim | The plain-HTTP transport: request validation, NDJSON streaming, concurrency cap, and cancellation. |
| `Dockerfile` | Templated | `python:3.12-slim` image with the skill baked in at `/app/.claude/skills/<skill-name>/`, running as a non-root user. |
| `.dockerignore` | Verbatim | Keeps `.env`, `tests/`, `.git/`, virtualenvs, and caches out of the image. |
| `pyproject.toml` | Templated | The dependency list the image installs (`uv pip install --system -e .`). |
| `requirements.txt` | Templated | The same dependencies for local installs. The image never reads it. |
| `.env.example` | Templated | Every environment variable the agent reads, with defaults. Copy to `.env`. |
| `README.md` | Templated | Build, local smoke test, and troubleshooting for this agent. |
| `tests/test_run.py` | Templated | Phased local smoke test: env, SDK auth, skill discovery, adapter wiring. |
| `tests/*.py` (other four), `tests/README.md` | Verbatim | Credential and SDK diagnostics. See [Test the agent locally](https://www.agentmissioncontrol.ai/developers/self-hosted-testing/). |

> **Note:** A template is not valid Python until it is filled. `class {{AGENT_CLASS}}:` is a syntax error until `{{AGENT_CLASS}}` becomes a name.

## Simple placeholders

These are plain search-and-replace across every `.tpl` file. The examples are for a skill whose frontmatter says `name: report-generator`.

| Placeholder | What it is | Example |
| --- | --- | --- |
| `{{AGENT_DIR}}` | New agent directory | `azurefoundry_report_generator_skills` |
| `{{AGENT_CLASS}}` | Agent class name | `ReportGeneratorSkillsAgent` |
| `{{EXECUTOR_CLASS}}` | Executor class name | `ReportGeneratorSkillsExecutor` |
| `{{CREATE_EXECUTOR_FN}}` | Executor factory function | `create_report_generator_executor` |
| `{{INIT_FN}}` | Startup-init function | `initialize_report_generator_agents_at_startup` |
| `{{SKILL_NAME}}` | The skill's frontmatter `name:`, exactly | `report-generator` |
| `{{SKILL_SOURCE_PATH}}` | Skill folder, relative to the repo root | `skills/report-generator` |
| `{{AGENT_NAME_HUMAN}}` | Display name | `Report Generator Agent` |
| `{{AGENT_DESCRIPTION}}` | One-line description | `Builds a weekly report with the report-generator skill.` |
| `{{PORT}}` | TCP port for the plain-HTTP server | `9024` |
| `{{PACKAGE_NAME}}` | `pyproject.toml` name | `claude-skills-report-generator-agent` |
| `{{PACKAGE_DESCRIPTION}}` | `pyproject.toml` description | `Report generator agent powered by claude-agent-sdk` |
| `{{AGENT_DOCKER_TAG}}` | Image tag prefix and container app name | `report-generator-skills-agent` |
| `{{SKILL_DISPLAY_NAME}}` | Skill entry name in the README's registry snippet | `Report Generator` |
| `{{SKILL_DESCRIPTION_SHORT}}` | Skill entry description | `Weekly report rendered as HTML.` |
| `{{SKILL_ID_SNAKE}}` | Skill entry id | `report_generator` |
| `{{SKILL_TAGS}}` | Comma-separated tags | `reports, claude, skill` |

## Naming rules

| Identifier | Rule | Example |
| --- | --- | --- |
| Skill `name:` | kebab-case, used in full wherever `{{SKILL_NAME}}` appears | `report-generator` |
| `{{AGENT_DIR}}` | `azurefoundry_` + snake_case + `_skills` | `azurefoundry_report_generator_skills` |
| `{{AGENT_CLASS}}` | PascalCase, no underscores, suffix `SkillsAgent` | `ReportGeneratorSkillsAgent` |
| `{{PACKAGE_NAME}}` | `claude-skills-` + kebab-case + `-agent` | `claude-skills-report-generator-agent` |
| `{{AGENT_DOCKER_TAG}}` | short kebab-case + `-skills-agent` | `report-generator-skills-agent` |
| Container app name | `<short>-skills-agent-uami` | `report-generator-skills-agent-uami` |

If the derived names get unwieldy, strip filler words from the agent-side identifiers only. `{{SKILL_NAME}}` always keeps the skill's full name, because the SDK matches it.

## Block markers

Block markers are replaced with code rather than a single value. Each sits next to a comment in the template that explains the choice.

| Marker | File | Replace with |
| --- | --- | --- |
| `{{OPTIONAL_OUTPUTS_DIR_CONST}}` | `claude_skills_agent.py` | `OUTPUTS_DIR = os.getenv("REPORT_OUTPUTS_DIR", "/outputs")` if the skill writes files, else remove |
| `{{OPTIONAL_MAKEDIRS}}` | `claude_skills_agent.py`, in `create_agent()` | `os.makedirs(OUTPUTS_DIR, exist_ok=True)`, or remove |
| `{{SKILL_ENV_VARS}}` | `claude_skills_agent.py`, in `ClaudeAgentOptions(env=...)` | Env vars the skill's scripts read at runtime, or nothing |
| `{{PROMPT_STRATEGY_BODY}}` | `claude_skills_agent.py`, `_build_prompt()` | Pattern A or B from the prompt strategies |
| `{{TOOL_SCRIPT_LABELS}}` | `claude_skills_agent.py`, `_describe_tool()` | A map of `scripts/*.py` filenames to short progress labels |
| `{{OPTIONAL_OUTPUTS_HEALTH_CHECK}}` | `__main__.py`, `_health()` | An outputs-dir writability check, or remove |
| `{{OPTIONAL_OUTPUTS_MISSING_CHECK}}` | `__main__.py`, `_health()` | The matching `missing.append(...)` line, or remove |
| `{{DOCKERFILE_SKILL_PREP_BLOCK}}` | `Dockerfile` | One or more filesystem prep patterns, concatenated |
| `{{OPTIONAL_OUTPUTS_ENV}}` | `Dockerfile` | `ENV REPORT_OUTPUTS_DIR=/outputs`, or remove |
| `{{SKILL_EXTRA_DEPS}}` | `pyproject.toml` and `requirements.txt` | Only the packages the skill's scripts import, one per line |
| `{{AGENT_TURN_DESCRIPTION}}` | `README.md` | What one run does, end to end |
| `{{PREREQUISITES_BLOCK}}` | `README.md` | Volume mounts, network access, external accounts |
| `{{ADDITIONAL_VOLUMES_BLOCK}}` | `README.md`, in the `docker run` example | `-v "${PWD}/outputs:/outputs"` and similar, or empty |

A few more markers appear in the templates: `{{NONROOT_EXTRA_DIRS}}` (extra directories the non-root user must own, in the Dockerfile), `{{OUTPUTS_ENV_VAR}}` and `{{OPTIONAL_OUTPUTS_ENV_VAR}}` (the outputs variable's name, in the agent and `.env.example`), `{{OPTIONAL_OUTPUTS_ENV_SETUP}}` (pointing that variable at a temp dir in `tests/test_run.py`), and `{{PROMPT_DOCSTRING}}` (the `_build_prompt()` docstring).

> **Note:** If the skill produces no files, remove every outputs-related marker together.

## Related

- [The agent core and prompt strategies](https://www.agentmissioncontrol.ai/developers/self-hosted-agent-core/)
- [Build the Docker image](https://www.agentmissioncontrol.ai/developers/self-hosted-docker/)
- [Claude skill, self-hosted: overview and architecture](https://www.agentmissioncontrol.ai/developers/self-hosted-overview/)

---
Source: https://www.agentmissioncontrol.ai/developers/self-hosted-project-layout/ · Last updated 2026-10-21 · Full index: https://www.agentmissioncontrol.ai/llms.txt
