# Tlamatini Agent Creation

> The authoritative, exhaustive end-to-end runbook for creating a BRAND-NEW Tlamatini workflow agent — every surface, in order, with 530+ numbered steps across 26 phases. Invoke whenever Angela says "create a new agent", "add an agent", "make a <X>er agent", "I want a new canvas agent", or asks to wire any new pool agent across backend + frontend + Multi-Turn + Parametrizer + FlowCreator + FlowHypervisor + watchdog + config dialog + demo prompts + Python tests + Playwright harness tests + docs + packaging. Covers naming, coloring, the inputs/outputs connector contract in agentic_control_panel.html, the Multi-Turn (wrapped chat-agent) tool, Exec Report, the configuration dialog, automated unit tests AND Playwright tests in Claude's harness. Pairs with tlamatini-agent-naming (casing) and the @-imported create_new_agent.md / create_new_mcp.md.

- Skill: `xaiht/tlamatini-agent-creation` (Agent Skill)
- Install (CLI): `npx skillmds@latest add xaiht/tlamatini-agent-creation`
- Raw SKILL.md: https://api.skillmd.com/api/skills/xaiht/tlamatini-agent-creation/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: XAIHT (https://skillmd.com/u/xaiht)
- Updated: 2026-09-21
- Page: https://skillmd.com/skills/xaiht/tlamatini-agent-creation

---

<!--
═══════════════════════════════════════════════════════════════════
  ✦  T L A M A T I N I  ✦   —   "one who knows"
  Created by  Angela López Mendoza   ·   @angelahack1
  Developer · Architect · Creator of Tlamatini
  Tlamatini Author Banner — do not remove (Angela's name is kept in every build)
═══════════════════════════════════════════════════════════════════
-->

# Tlamatini — Complete New-Agent Creation Runbook (700+ steps)

> **Audience:** Claude Code working ON the Tlamatini codebase for **Angela**.
> **Scope:** adding ONE brand-new workflow agent end-to-end across **every** surface
> Tlamatini touches — backend pool script, Django view/url, migration, Parametrizer,
> CSS coloring, all the frontend JS, the `agentic_control_panel.html` inputs/outputs
> connector contract, the configuration dialog, Multi-Turn (the wrapped chat-agent
> tool), the Exec Report, FlowCreator's `agentic_skill.md`, FlowHypervisor's
> `monitoring-prompt.pmt`, the command watchdog + orphan reaper, demo "Prompts
> example" creation, `requirements.txt`/`build.py` packaging, a full Python unit-test
> module, AND a Playwright regression in Claude's own harness — then docs, lint,
> migrate, and a **visible** dogfood run.
>
> **This skill is the master checklist.** The two `@`-imported guides
> (`Tlamatini/.agents/workflows/create_new_agent.md`, `Tlamatini/.mcps/create_new_mcp.md`)
> are the canonical mechanics for individual surfaces; this skill is the *superset* that
> also nails the things they leave implicit (watchdog, FlowHypervisor, config dialog,
> demo prompts, Python tests, Playwright tests, packaging). **Read both `@`-imports and
> the `tlamatini-agent-naming` skill first**, then execute the steps below in order.

---

## Placeholders used throughout (decide these in Phase 0, then NEVER drift)

| Token | Meaning | Example (`Pingerer`) |
|---|---|---|
| `<Display>` | exact-cased display name = DB `agentDescription` (single source of truth) | `Pingerer` |
| `<lower>` | `<Display>`.lower(), spaces→nothing for single-word; underscores for multi-word dir | `pingerer` |
| `<dash>` | `<Display>`.toLowerCase().replace(/\s+/g,'-') — CSS classMap key | `pingerer` |
| `<space>` | `<Display>`.toLowerCase() preserving spaces — JS connection checks | `pingerer` |
| `<Pascal>` | JS connector symbol fragment `update<Pascal>Connection` | `Pingerer` |
| `<CAPS>` | ALL-CAPS protocol token base — `INI_SECTION_<CAPS>` + `<CAPS> SPECIAL NOTES` | `PINGERER` |
| `<css>` | the canvas/exec-report CSS class root (== `<dash>` minus dashes for single-word, kept dashed for multi-word; equals `<lower>` for single-word) | `pingerer` |
| `N` | the agent's idAgent / migration sequence number | (next free) |

> **Worked multi-word example** (`Node Manager`): `<Display>=Node Manager`,
> `<lower>=node_manager` (dir/pool/file), `<dash>=node-manager` (CSS classMap key),
> `<space>=node manager` (connection checks), `<Pascal>=NodeManager`,
> `<CAPS>=NODE_MANAGER`, `<css>=node-manager` / `nodemanager-agent` (verify against an
> existing multi-word agent — historically some classMap values dropped the dash, e.g.
> `'node-manager': 'nodemanager-agent'`; COPY an existing sibling exactly).

---

> ### ⚠️ MODIFYING an existing agent rather than creating one?
>
> Most phases below still apply, but four of them are the ones that get skipped
> and cause silent damage. Run at least these:
>
> - **Phase 14 (257b/257c)** — if behaviour or a default changed, the wrapped
>   spec's `purpose` / `example_request` is the ONLY place the model learns it.
>   Leave it stale and you ship a feature that never fires while appearing to work.
> - **Phase 18 (325b)** — if the agent's runtime stopped being predictable from
>   its config, the FlowHypervisor will start raising false alarms about it.
> - **Phase 17** — FlowCreator's `agentic_skill.md` entry carries the config
>   defaults verbatim; a changed default makes that entry a lie, and it is what
>   the flow designer reads when it builds a `.flw`.
> - **Phase 20** — the doc sweep. Grep a SIBLING AGENT'S NAME, not the old value.
>
> And two habits worth keeping: report what HAPPENED rather than what was
> REQUESTED (a field that can differ from its input must be computed from the
> artefact), and APPEND new `INI_SECTION` fields — never rename or reorder the
> existing ones — keeping `agent_contracts._PARAMETRIZER_OUTPUT_FIELDS` in step
> in the same commit.

# PHASE 0 — Preflight, scoping & naming (lock these before any code)

1. Confirm with Angela the agent's **purpose** in one sentence (what task it performs).
2. Decide whether the agent is **deterministic** (no LLM) or **LLM-powered** — this changes config keys and FlowHypervisor timing notes.
3. Decide whether the agent is **state-changing** (mutates files/DB/remote/GUI/sends messages) or **observational/read-only** (Shoter/Camcorder/Recorder/AudioPlayer/VideoPlayer/Monitor-*). This decides Exec-Report membership.
4. Decide whether the agent is **Active** (starts downstream via `target_agents`) or **Terminal/Monitoring** (does not).
5. Decide whether the agent **produces structured output** consumed by Parametrizer (emits `INI_SECTION_<CAPS>`).
6. Decide whether the agent should be **LLM-callable in Multi-Turn** (a wrapped `chat_agent_<lower>` tool) — most new agents should be.
7. Decide whether the agent is **long-running** (Monitor-style) or **short-lived**.
8. Decide whether the agent **scaffolds project directories** (firmware/engine style → defaults to `<app>/Templates`) or writes **scratch** (→ `<app>/Temp`).
8a. If it delegates placement to Mover or cleanup to Deleter, lock the v1.48.13 contract: empty, relative, and legacy `C:/Temp/...` scratch paths re-root under `TLAMATINI_TEMP`; explicit absolute user paths remain authoritative; normalization never broadens deletion scope.
8b. If it adds UI, reuse `dialog_theme.css` and `dialog_policy.js`; place long-operation navigation controls in `LONG_OPERATION_DISABLED_MENU_BUTTONS`; bump `STATIC_VERSION` after every JavaScript/CSS/template edit.
9. Decide whether the agent **spawns console child processes** (relevant to the orphan reaper + command watchdog).
10. Decide whether the agent has a **singleton** constraint (only FlowCreator/FlowHypervisor are; a normal agent is not).
11. Lock the **`<Display>`** name with EXACT casing — this is `agentDescription` and the single source of truth.
12. **Invoke the `tlamatini-agent-naming` skill** and derive `<lower>`, `<dash>`, `<space>`, `<Pascal>`, `<CAPS>`, `<css>` from `<Display>` per its transform table.
13. NEVER let any non-identifier surface display a different casing of `<Display>` (Angela is emphatic — STM32er must never become STM32Er).
14. Pick the next free **idAgent / migration number** `N`: run `Glob` over `Tlamatini/agent/migrations/0*.py` and take `max+1`; confirm no `agentDescription='<Display>'` already exists.
15. Pick a reference sibling agent that most resembles the new one (e.g. Shoter for capture, Apirer for HTTP, Kalier for an API bridge, Camcorder/Recorder for media). You will COPY its structure.
16. Pick a **second** reference sibling that already does Multi-Turn + Parametrizer + Exec-Report so you can copy those wirings (Camcorder and Recorder are the most recent fully-wired examples).
17. Read the chosen sibling's `agent/agents/<sibling>/<sibling>.py` in full before writing anything.
18. Read the chosen sibling's `config.yaml` in full.
19. Read the sibling's `update_<sibling>_connection_view` in `views.py`.
20. Read the sibling's CSS block in `agentic_control_panel.css`.
21. Read the sibling's `ChatWrappedAgentSpec` in `chat_agent_registry.py`.
22. Read the sibling's `_PARAMETRIZER_OUTPUT_FIELDS` entry in `services/agent_contracts.py`.
23. Read the sibling's `_EXEC_REPORT_TOOLS` entry (if state-changing) in `mcp_agent.py`.
24. Read the sibling's `test_<sibling>_agent.py` in full — it is your test template.
25. Write down the **full list of `config.yaml` keys** the new agent needs (params + connection fields). This list is referenced by ~8 later surfaces; keeping it stable prevents silent drift.
26. Decide the agent's **connection-field shape**: `target_agents`+`source_agents` (normal), `target_agents_a/_b` (Asker/Forker), `target_agents_l/_g` (Counter), `source_agent_1/_2` (OR/AND), or `output_agents` (Stopper/Ender/Cleaner).
27. Decide the **INI_SECTION KV header fields** (what downstream agents can address) + whether there is a `response_body`.
28. Create a small scratch note (or a dated pivot file per `feedback_track_changes_pivot_file`) listing the verbatim request + every file you will touch, so a later "roll back just that change" is exact.

---

# PHASE 1 — Backend: the pool agent script + config.yaml

29. Create directory `Tlamatini/agent/agents/<lower>/`.
30. Create `Tlamatini/agent/agents/<lower>/config.yaml`.
31. In `config.yaml`, add a top comment `# <Display> Agent Configuration`.
32. Add each functional param key with a sensible default value (from your Phase-0 key list).
33. Add the connection fields that apply: `target_agents: []` if Active.
34. Add `source_agents: []` if it monitors upstream logs.
35. Use `output_agents: []` INSTEAD of `target_agents` ONLY for Stopper/Ender/Cleaner-style agents.
36. For OR/AND use scalar `source_agent_1: ""` / `source_agent_2: ""`.
37. For Asker/Forker use `target_agents_a: []` / `target_agents_b: []`.
38. For Counter use `target_agents_l: []` / `target_agents_g: []`.
39. Leave any credential/secret field as an **empty string** default (never hardcode a key — `regen_secrets.py` / Flow-Compiler redaction depends on this).
40. If a param is numeric, default it to a real number (not a string) so `yaml.safe_load` yields the right type.
41. If the agent has nested config, model it as a nested mapping (e.g. `llm:` block with `base_url`/`model`/`temperature`) mirroring the sibling.
42. Create `Tlamatini/agent/agents/<lower>/<lower>.py`.
43. Copy the FULL boilerplate from the reference sibling's `.py` (module preamble + all helpers + `main()` shape). DO NOT hand-roll helpers.
44. Make `os.environ['FOR_DISABLE_CONSOLE_CTRL_HANDLER'] = '1'` the FIRST statement after `import os, sys`.
45. Keep the standard helpers verbatim: `load_config`, `get_python_command`, `get_user_python_home`, `get_agent_env`, `get_pool_path`, `get_agent_directory`, `get_agent_script_path`, `is_agent_running`, `wait_for_agents_to_stop`, `start_agent`, `write_pid_file`, `remove_pid_file`.
46. Ensure the log file name is exactly `{directory_name}.log` (the canvas reads this; any other name breaks LED/log surfacing).
47. Compute `CURRENT_DIR_NAME` and `LOG_FILE_PATH` the same way the sibling does.
48. Keep the top-of-module `subprocess.Popen.__init__` monkey-patch (`_chg_guarded_init`) that defaults `creationflags` to `CREATE_NO_WINDOW` — this is the orphan seatbelt; do not remove it.
49. Implement the agent's **core logic** between `logging.info("🚀 <CAPS> AGENT STARTED")` and the target-trigger block.
50. Use a distinctive emoji + `<Display>`-cased phrase in the STARTED log line (FlowHypervisor markers key off these — see Phase 18).
51. Wrap external/hardware/network calls in try/except so a missing device/host produces a logged error, not a crash.
52. For any numeric `config.get(...)` that could arrive as a wrapped-parser string (e.g. `"5 from the default mic"`), coerce via a `_coerce_int`/`_coerce_float` helper that extracts the leading number and never raises (see the Recorder fix — this caught a real incident).
53. Write the PID file immediately at the top of `main()` via `write_pid_file()`.
54. Remove the PID file in a `finally:` block via `remove_pid_file()`.
55. Add a short `time.sleep(0.4)` before `remove_pid_file()` to keep the LED green briefly (sibling pattern).
56. End `main()` with `sys.exit(0)`.
57. If Active, place the target-trigger block at the END of the work: `if target_agents: wait_for_agents_to_stop(target_agents)` then a `for target in target_agents: start_agent(target)` loop.
58. The concurrency guard `wait_for_agents_to_stop(target_agents)` MUST come BEFORE the `start_agent` loop (prevents duplicate spawns in looping flows).
59. If state-changing, ensure `target_agents` are triggered REGARDLESS of success/failure (so a downstream Forker can branch on the outcome) — match the "ALWAYS triggers target_agents" contract used by Kalier/Unrealer/STM32er/Camcorder.
60. Do NOT trigger `target_agents` from a Terminal/Monitoring agent (Emailer/Notifier/Monitor-*) — those leave `target_agents` as canvas-only metadata.
61. Add a final completion log line `logging.info("🏁 <Display> agent finished.")` before the `finally`.
62. Do NOT import anything from `agent.*` (the `agent` Django package) inside the pool script — pool subprocesses have no `sys.path` back into it (`ModuleNotFoundError`). Port any needed runtime mechanics inline (~100–200 lines) as ACPXer does.
63. Resolve any bundled asset path for BOTH frozen (`os.path.dirname(sys.executable)`) and source (`os.path.dirname(os.path.abspath(__file__))`) modes.
64. If the agent needs a third-party lib (e.g. `opencv-python`, `sounddevice`), import it lazily inside the function that uses it and report a clean message if it is absent (do not crash at import time).
65. Keep `main()` callable under `if __name__ == "__main__": main()`.
66. Verify `config.yaml` round-trips: `python -c "import yaml; print(yaml.safe_load(open(r'...config.yaml')))"`.
67. Confirm the script has no top-level side effects beyond the documented `os.chdir`/`logging.basicConfig` that the test harness saves+restores.
68. Confirm there is no top-level `def` placed above the imports (that trips ruff E402) — if you need a module-top guard (temp policy), make it an `if`-block, not a `def`.
69. Re-read the whole `.py` once and diff it mentally against the sibling to ensure no helper was accidentally dropped.

---

# PHASE 2 — Reanimation & lifecycle (pause/resume correctness)

70. Right after `LOG_FILE_PATH` is set and BEFORE `logging.basicConfig(...)`, add `_IS_REANIMATED = os.environ.get('AGENT_REANIMATED') == '1'`.
71. Immediately after, add `if not _IS_REANIMATED: open(LOG_FILE_PATH, 'w').close()` (truncate only on a fresh start).
72. NEVER truncate the log when `_IS_REANIMATED` is true (resume appends).
73. In `main()`, when `_IS_REANIMATED`, log `🔄 <Display> REANIMATED (resuming from pause)` as the first line.
74. If the agent persists restart state (file offsets, counters, checkpoints), store it in files named `reanim*` (e.g. `reanim.pos`) so Ender can reset them on stop.
75. If the agent polls a source log, implement `save_reanim_offset(offset)` / `get_reanim_offset(...)` using `reanim.pos` (or `reanim_<source>.pos` per source).
76. Load the offset at startup and call `save_reanim_offset` after each read.
77. Confirm the agent is idempotent under resume: resuming produces the same behavior as if never interrupted.
78. Confirm the agent does NOT delete its own `reanim*` files (only Ender clears them on Stop).
79. Confirm a Counter-style agent uses `reanim.counter`; a Gatewayer-style uses `reanim_queue.json`/`reanim_dedup.json`; a registry agent uses `reanim_registry.json` (only if applicable).
80. Verify the three lifecycle modes mentally: Fresh start (no env var → truncate → STARTED), Reanimation (`AGENT_REANIMATED=1` → no truncate → REANIMATED → load reanim files), Stop (Ender clears reanim files).
81. Confirm pressing Start while PAUSED acts as Resume (no special code needed — the ACP handles it).
82. Confirm the agent does not assume it is always a fresh start anywhere in its logic.

---

# PHASE 3 — Structured output (INI_SECTION — Parametrizer producer)

83. If the agent feeds Parametrizer, define the section in the unified format: `INI_SECTION_<CAPS><<<` … `>>>END_SECTION_<CAPS>`.
84. Use `<CAPS>` = the UPPERCASE base name (single ALL-CAPS token convention — do NOT mixed-case it).
85. Put the KV header (one `key: value` per line) BEFORE the first blank line.
86. Put the multi-line body AFTER the first blank line (it becomes `response_body`).
87. If there is no body, omit the blank line (KV-only section).
88. Emit each section in a SINGLE atomic `logging.info(...)` call (concurrent writes interleave and corrupt otherwise).
89. Emit N separate sections for N results (one section per result/response).
90. Choose KV header field names that downstream agents will address (e.g. `output_path`, `status`, `url`, `return_code`, `success`).
91. Include `response_body` in the header field list ONLY if the section has a body.
92. Build the section string with explicit `\n` joins exactly like the sibling (do not use f-string multiline that swallows indentation).
93. Confirm the section start/end tokens match exactly (`INI_SECTION_<CAPS><<<` and `>>>END_SECTION_<CAPS>`).
94. If the agent ALWAYS emits the section even on failure (recommended for routable branching), document that in the section body (e.g. `status: error`).
95. Verify a round-trip parse: feed a sample log line through Parametrizer's `_parse_section_content` mentally or with a quick test (Phase 22 covers the real test).

---

# PHASE 4 — Temp & Templates directory policy (2026-06-02)

96. If the agent writes TEMPORARY/scratch files, route them under `<app>/Temp` — NEVER `C:\Temp`, `%TEMP%`, or a bare `tempfile.gettempdir()`.
97. Copy the module-top temp guard from `executer.py` verbatim: `if (os.environ.get('TLAMATINI_TEMP') or '').strip(): import tempfile as _tlt_tempfile; _tlt_tempfile.tempdir = os.environ['TLAMATINI_TEMP'].strip(); …`.
98. Keep that guard as an `if`-block (NOT a top-level `def`) so it sits above the imports without tripping ruff E402.
99. If the agent SCAFFOLDS a project/template directory (firmware/engine style), default its parent to `<app>/Templates` (`TLAMATINI_TEMPLATES`) unless Angela supplies a path.
100. Use `agent/path_guard.py` resolvers conceptually (`get_app_temp_root` / `get_app_templates_root`) — but remember the pool script can't import `agent.*`, so rely on the inherited `TLAMATINI_TEMP` / `TLAMATINI_TEMPLATES` env vars (the parent exports them).
101. Confirm `Temp` = throwaway scratch; `Templates` = deliverable project trees (never via `tempfile`).
102. Confirm no path the agent writes escapes the Tlamatini app root.
103. If you add an in-process `@tool` instead (rare), route its scratch through `path_guard.get_app_temp_root()` / `resolve_temp_path()` directly.
104. Note that `prompt.pmt` Rules 15/16 inject the absolute `{temp_directory}`/`{templates_directory}` for the LLM — you do not edit those unless the policy itself changes.

---

# PHASE 5 — Watchdog & orphan-reaper properties

105. If the agent spawns console child processes (`cmd`/`powershell`/external CLI), understand it is subject to the **command watchdog** (`agent/command_watchdog.py`).
106. Know the watchdog kills only console interpreters + descendants that make NO PROGRESS (CPU-seconds + IO bytes across the whole subtree) for N idle ticks past `hang_grace_seconds` — it is progress-based, NOT duration-based.
107. Ensure the agent's children make observable progress (CPU or IO) while working so the watchdog never kills a long-but-working job.
108. If the agent legitimately runs a long quiet external job, document expected behavior and consider that `command_watchdog_*` config keys are tunable (do not weaken the watchdog contract for one agent).
109. Confirm the agent never relies on a child that blocks on stdin with zero CPU+IO for the full grace window (that is exactly the hang class the watchdog targets — feed `DEVNULL`/EOF to children).
110. For an in-process `@tool` that runs a command, use the bounded `_run_command_bounded` pattern (Popen + stdin=DEVNULL + `communicate(timeout=...)` + whole-tree kill), not a naive `subprocess.run` (which is a fake guarantee for `shell=True` grandchildren).
111. Understand the **orphan reaper** (`agent/orphan_reaper.py`) Tier 1/2/3: if the new agent spawns console children via a NEW tool name, either add that tool name to `_PROCESS_SPAWNING_TOOL_NAMES` in `mcp_agent.py` (Tier-1 reap after it) or rely on Tier-2's pool-cmdline scan.
112. Confirm the agent's children carry the `CREATE_NO_WINDOW` default (the `_chg_guarded_init` monkey-patch handles this automatically — verify it is present from Phase 1).
113. Confirm the reaper/watchdog can never be tripped into killing the agent's OWN long-running python runtime (those are python, not console interpreters; the watchdog scopes to `cmd/powershell/pwsh` + descendants only).
114. For a VISIBLE/desktop agent (a window the user must SEE) that you launch yourself during dogfooding, recall the reaper protects ancestors + console-window owner + main PID — but the agent runs as its own subprocess, so it is reaped only if genuinely orphaned.
115. Note: the watchdog + reaper changes take effect in a frozen build only after `python build.py` — flag this to Angela if she runs the frozen `C:\Tlamatini` install.

---

# PHASE 6 — Backend: Django connection-update view + urls.py

116. Open `Tlamatini/agent/views.py`.
117. Copy `update_<sibling>_connection_view` and rename it `update_<lower>_connection_view`.
118. Keep the `@csrf_exempt` + `@require_POST` decorators.
119. Parse `data = json.loads(request.body.decode('utf-8'))`.
120. Read `target_agent`, `action` (default `'add'`), and `connection_type` (default `'target'`).
121. Return a 400 JSON error if `target_agent` is missing.
122. Normalize the agent id `<lower>-N` → pool name `<lower>_N` (split on `-`, pop trailing digit as cardinal, join base with `_`).
123. Reject path-traversal in the pool name (`'..'`, `'/'`, `''`) with a 400.
124. Build `config_path = os.path.join(get_pool_path(request), pool_name, 'config.yaml')`; 404 if missing.
125. Load the config with `yaml.safe_load(...) or {}`.
126. Normalize the `target_agent` id → target pool name the same way.
127. Choose the list key: `source_agents` if `connection_type=='source'` else `target_agents` (or the agent's special connection field per Phase 0).
128. Ensure the list exists (`if not isinstance(config.get(list_name), list): config[list_name] = []`).
129. On `action=='add'`, append the target pool name if not present.
130. On `action=='remove'`, remove it if present.
131. **Omit-if-empty rule:** never write an empty string into a connection field (the deep-merge in `save_agent_config_view` would destroy a template default).
132. Write the config back with `yaml.dump(..., default_flow_style=False, allow_unicode=True, sort_keys=False)`.
133. Return `{"success": True, ...}` JSON.
134. Wrap the whole body in try/except returning a 500 JSON on error.
135. If the agent uses a special connection shape (Asker/Forker/Counter/OR/AND/Ender), copy that sibling's view instead — those write `target_agents_a/_b`, `target_agents_l/_g`, `source_agent_1/_2`, or `output_agents`.
136. Open `Tlamatini/agent/urls.py`.
137. Add `path('update_<lower>_connection/<str:agent_name>/', views.update_<lower>_connection_view, name='update_<lower>_connection'),`.
138. Confirm the route name is unique and matches the connector fetch URL you will write in Phase 10.
139. If the agent needs any extra backend endpoint (rare), add it to both `views.py` and `urls.py` now and note it for the docs sweep.
140. Re-read the view once to confirm it matches the producer/consumer shape (a target-only producer like Shoter/Camcorder has no `source` branch usage in practice but should still accept `connection_type`).

---

# PHASE 7 — Database migration (seed the Agent row)

141. Find the highest existing migration with `Glob` `Tlamatini/agent/migrations/0*.py`.
142. Create `Tlamatini/agent/migrations/<NNNN>_add_<lower>.py` (next sequential number).
143. Implement `add_<lower>_agent(apps, schema_editor)` that gets the `Agent` model via `apps.get_model('agent','Agent')`.
144. Guard against duplicates: `if Agent.objects.filter(agentDescription='<Display>').exists(): return`.
145. Compute `next_id = (max idAgent or 0) + 1`.
146. Create the row: `Agent.objects.create(idAgent=next_id, agentName=f'agent-{next_id}', agentDescription='<Display>', agentContent='true')`.
147. Use the EXACT `<Display>` casing in `agentDescription`. **⛔ 2026-07-26 — the migration is NOT the source of truth:** `apps.py::ready()` DELETES every `Agent` row on each server start and re-derives the name from `agent/services/agent_paths.py::display_name_from_agent_type`, so you **MUST** also add `"<lower>": "<Display>"` to that `overrides` map or your agent ships `str.title()`-mangled ("Pdfer", "Sqler", "Esp32Er", "Latexer"). Use the **HYPHENATED** display form if `acp-canvas-core.js` only tests a hyphenated literal for it (`file-creator`, `video-analyzer`, `kyber-keygen`, `monitor-log`, …) — a spaced name matches nothing there and the canvas connection is silently never saved. Keep `chat_agent_registry.display_name` byte-identical in the same pass (it keys the fail-open `agent_<display>_status` enable gate). Verify: `python manage.py test agent.test_agent_display_names`.
148. Implement `remove_<lower>_agent` reverse that deletes the row by `agentDescription`.
149. Set `dependencies = [('agent', '<previous_migration_name>')]`.
150. Add `operations = [migrations.RunPython(add_<lower>_agent, remove_<lower>_agent)]`.
151. Do NOT edit `0002_populate_db.py` — always add a new migration.
152. If the agent is Multi-Turn-callable, you will ALSO create a SECOND migration in Phase 14 that seeds the `Tool` row for `chat_agent_<lower>` — note it now.
153. Run `python Tlamatini/manage.py makemigrations --check --dry-run` to confirm no model drift was introduced.
154. Do not run `migrate` yet (batch it in Phase 24 with the Tool-row migration), or run it now and re-run after Phase 14 — either is fine, just end Phase 24 with a clean migrate.

---

# PHASE 8 — Parametrizer registration (make the agent a usable source)

155. Open `Tlamatini/agent/agents/parametrizer/parametrizer.py`.
156. Add `'<lower>'` to the `SECTION_AGENT_TYPES` list (the generic parser handles the rest — no per-agent parser code).
157. **⚠️ CORRECTED 2026-08-23 — do NOT hand-edit `views.py` for this.** `views.PARAMETRIZER_SOURCE_OUTPUT_FIELDS` is **DERIVED** (`= get_parametrizer_source_fields()`), not a hand-maintained dict, so editing it is either a no-op or a fresh source of drift. Register the fields in ONE place — `agent_contracts.py`, the next step.
158. Open `Tlamatini/agent/services/agent_contracts.py` and add `'<lower>': (...)` to `_PARAMETRIZER_OUTPUT_FIELDS` with the same field tuple (this is the registry the Flow-Compiler reads).
159. Keep the **TWO** lists coherent: `parametrizer.py::SECTION_AGENT_TYPES` (membership) and `agent_contracts.py::_PARAMETRIZER_OUTPUT_FIELDS` (the field tuple). There is no third list to sync — `views.py` derives its copy from the second one.
160. Add the agent to the **Supported Source Agents** table in `README.md` (Phase 20 sweep, but note the field list now).
161. If the agent is NOT a Parametrizer source (no INI_SECTION), SKIP 155–160 entirely.
162. Confirm `get_agent_contract('<lower>')` will resolve (alias-normalized) — if the agent has an alias spelling, add it to the contract's `aliases` (override in `agent_contracts.py` builtin overrides if needed).
163. Decide the agent's `AgentContract` flags if it needs non-default behavior: `singleton`, `long_running`, `never_starts_targets`, `exclude_from_validation`, `no_input`, `no_output`, `special`. A normal agent needs none (synthesized default works).
164. If you add a builtin contract override, set `input_field_by_slot` / `output_field_by_slot` to match the agent's connection shape (slot 2 → `target_agents_b` for Forker, etc.).
165. Add `secret_paths` to the contract for any `config.yaml` dotted path holding a credential (so `.flw` export redacts it).
166. Confirm `connection_fields` on the contract covers every connection key the agent uses (so stale wiring is cleared on recompile).
167. Re-read the Parametrizer "strict single-lane queue" rule — one source, one target, one-at-a-time — to confirm the agent's section granularity (N results = N sections) matches that model.
168. Confirm the agent's `display_name` resolves through `agent_paths.display_name_from_agent_type` to exactly `<Display>` (centralized capitalization quirks live there).

---

# PHASE 9 — Frontend: CSS coloring (the gradient)

169. Open `Tlamatini/agent/static/agent/css/agentic_control_panel.css`.
170. Scan the WHOLE file for existing 4-color gradients to avoid a visual collision.
171. Choose a UNIQUE 4-stop gradient (`0% / 33% / 66% / 100%`) visually distinct from every existing agent.
172. Add the rule `.canvas-item.<css>-agent { background-color: #c1; background: linear-gradient(135deg, #c1 0%, #c2 33%, #c3 66%, #c4 100%); color: white; font-size: smaller; }`.
173. Add the hover rule `.canvas-item.<css>-agent:hover { background: linear-gradient(135deg, #c1l 0%, #c2l 33%, #c3l 66%, #c4l 100%); box-shadow: 0 6px 15px rgba(r,g,b,0.5); }`.
174. The gradient must live ONLY in CSS — never type a gradient string in JS.
175. Ensure the sidebar icon inherits the gradient via `applyAgentToolIconStyle(iconDiv, '<Display>')` (Phase 10) — no per-agent JS branch.
176. Confirm the CSS class root `<css>` matches the JS classMap value you will set (`<dash>': '<css>-agent'`).
177. Pick a memorable name for the gradient theme (e.g. "Deep-Ocean Teal") and note it for the memory + commit message.
178. If the agent is state-changing, you will MIRROR this gradient in the Exec-Report caption CSS in Phase 15 — keep the primary colors handy.
179. Verify the gradient renders by eye after deployment (Phase 25) for BOTH a freshly dragged node and a `.flw`-loaded node.
180. Confirm no existing selector accidentally also matches `.canvas-item.<css>-agent` (search for the class root).

---

# PHASE 10 — Frontend JS: connector + `acp-canvas-core.js` (6 locations)

181. Open `Tlamatini/agent/static/agent/js/acp-agent-connectors.js`.
182. Add `async function update<Pascal>Connection(agentId, targetAgentId, action, type = 'target') { ... }` modeled on the sibling connector.
183. Inside it, `fetch('/agent/update_<lower>_connection/${agentId}/', { method:'POST', headers:{'Content-Type':'application/json', ...getHeaders()}, credentials:'same-origin', body: JSON.stringify({ target_agent: targetAgentId, action, type }) })`.
184. Log a `console.error` on a non-ok response and on a thrown error (sibling pattern).
185. Open `Tlamatini/agent/static/agent/js/acp-canvas-core.js`.
186. **Location 1 — classMap** (`applyAgentTypeClass()`, ~line 32): add `'<dash>': '<css>-agent',` (KEY is the hyphenated form, VALUE is the CSS class).
187. **Location 2 — `AGENTS_NEVER_START_OTHERS`** (~line 94): add `'<dash>'` ONLY if the agent does NOT start downstream (Terminal/Monitoring). Skip for Active agents.
188. **Location 3 — `populateAgentsList()`** (~line 830): confirm it uses the shared `applyAgentToolIconStyle(iconDiv, description)` — do NOT add a per-agent gradient branch.
189. **Location 4 — `removeConnection()`** (~line 600): add SPACED-form branches `if (targetAgentName.toLowerCase() === '<space>') update<Pascal>Connection(targetId, sourceId, 'remove', 'source');` and the symmetric `sourceAgentName` branch with `'remove','target'`.
190. **Location 5 — `removeConnectionsFor()`** (~line 740): add SPACED-form branches with the deletion guards (`!targetBeingDeleted` / `!sourceBeingDeleted`).
191. **Location 6 — mouseup handler** (~line 1200): add SPACED-form branches with `'add'` instead of `'remove'`.
192. Use the HYPHENATED form ONLY in the classMap (Location 1) and `AGENTS_NEVER_START_OTHERS` (Location 2).
193. Use the SPACED form (`name.toLowerCase()`) in Locations 4, 5, 6 (connection handlers).
194. Confirm the connector symbol `update<Pascal>Connection` is referenced identically in all locations (case-exact identifier).
195. If the agent has a special connection shape, mirror the sibling that shares that shape (Forker for A/B, Counter for L/G, etc.) at every location.
196. Do NOT forget any of the 6 locations — missing one silently breaks creation, removal, undo, redo, or `.flw` load.
197. Confirm `applyAgentTypeClass` is what the canvas calls to set the node's CSS class (so the gradient applies).
198. Confirm the new branches do not shadow an existing agent whose name is a substring (use exact `===`, not `includes`).
199. Re-read the 6 edits as a group to confirm name-form correctness per location.
200. Note the connector symbol for the `/* global */` declarations in Phase 11.

---

# PHASE 11 — Frontend JS: undo/redo, .flw load, globals

201. Open `Tlamatini/agent/static/agent/js/acp-canvas-undo.js`.
202. Find an existing `update<Sibling>Connection` reference and mirror it in the UNDO section (SPACED form, `'add'` action).
203. Mirror it again in the REDO section (SPACED form, `'remove'` action).
204. Open `Tlamatini/agent/static/agent/js/acp-file-io.js`.
205. In `restoreAgentConnection`'s SOURCE-side switch, add `case '<space>': await update<Pascal>Connection(sourceId, targetId, 'add', 'target'); break;`.
206. In `restoreAgentConnection`'s TARGET-side switch, add `case '<space>': await update<Pascal>Connection(targetId, sourceId, 'add', 'source'); break;`.
207. If the agent persists Parametrizer mappings or other artifacts, confirm `acp-file-io.js` re-hydrates them on `.flw` load (only relevant if the agent is a Parametrizer node — normal agents need nothing extra).
208. Add `update<Pascal>Connection` to the `/* global ... */` declaration at the top of `acp-canvas-core.js`.
209. Add it to the `/* global ... */` declaration at the top of `acp-canvas-undo.js`.
210. Add it to the `/* global ... */` declaration at the top of `acp-file-io.js`.
211. Open `Tlamatini/eslint.config.mjs` and add `update<Pascal>Connection` to the `globals` block so the linter knows it.
212. If the agent introduces any other new global JS symbol, add it to `eslint.config.mjs` too.
213. Confirm the `.flw` load path calls `updateCanvasContentSize()` after restoring positions (it does generically — just don't break it).
214. Confirm no JS edit appends a node to `#submonitor-container` instead of `#canvas-content` (coordinate-frame contract).
215. Re-read all undo/redo/file-io edits as a group for name-form (all SPACED) and action correctness.

---

# PHASE 12 — `agentic_control_panel.html`: the inputs/outputs connector contract

216. Open `Tlamatini/agent/templates/agent/agentic_control_panel.html` and read how agent nodes render (the palette is server-injected from the `Agent` rows; the label comes from `agentDescription` verbatim via `consumers.agent_establishment`).
217. Confirm the sidebar label will render exactly `<Display>` (it reads the DB row — no HTML edit needed for the label).
218. Confirm the node's hover tooltip + canvas Description dialog come from `agents_descriptions.md` (the `## Workflow Agents` table) parsed into `agent_purpose_map` — you will add that row in Phase 20.
219. Understand the **inputs/outputs** model: a node's INPUT connectors accept arrows FROM upstream agents (writing into `source_agents`), its OUTPUT connectors start arrows TO downstream agents (writing into `target_agents`).
220. Decide the agent's input/output cardinality and ensure it matches the connection-field shape from Phase 0/6: most agents = 1 input + 1 output; OR/AND = 2 inputs + 1 output; Asker/Forker = 1 input + 2 outputs; Counter = 1 input + 2 outputs (L/G); Starter = 0 input + N outputs; Ender = N inputs + output_agents.
221. Confirm the canvas DOM contract: every `.canvas-item`, the SVG `#connections-layer`, and `#selection-box` live inside `#canvas-content` (the content layer), NOT `#submonitor-container` (the viewport).
222. Confirm coordinate math for the node uses `canvasContent.getBoundingClientRect()` (already generic — do not special-case the new agent).
223. Confirm the node's connector dots/handles are produced by the generic canvas-item renderer keyed off the CSS class — a normal agent needs NO bespoke HTML.
224. If the agent needs a NON-standard connector layout (e.g. a third output), study how Forker/Counter render their A/B/L/G handles and mirror that EXACTLY (this is the only case that touches connector rendering JS/HTML).
225. Verify the node is draggable from the sidebar palette after the migration runs (the palette is populated from `Agent` rows at page load).
226. Verify the node's output connector, when dragged to a target, fires the `update<Pascal>Connection(..., 'add', ...)` path (Phase 10, Location 6).
227. Verify the node's input connector, when receiving an arrow, writes into the correct list (`source_agents`) via the view.
228. Confirm `AGENTS_NEVER_START_OTHERS` correctly suppresses the OUTPUT-starts-downstream behavior for a Terminal agent (the canvas still draws the wire as metadata).
229. Confirm the node renders inside the scrollable canvas and the canvas grows (no upper clamp) when the node is placed far right/bottom.
230. Confirm right-click on the node opens the contextual menu (generic `contextual_menus.js`) and the Description entry shows the `agent_purpose_map` text.
231. Confirm double-click / the config entry opens the configuration dialog (Phase 13).
232. Do NOT add the agent to any hardcoded HTML list — the palette is dynamic from the DB; only MCP checkboxes are hardcoded (irrelevant here).
233. If the new agent must appear in a specific palette CATEGORY/section grouping in the sidebar, check whether `agentic_control_panel.html`/its JS groups by category and add the mapping if such grouping exists; otherwise it lists generically.

---

# PHASE 13 — The configuration dialog (canvas node settings)

234. Open `Tlamatini/agent/static/agent/js/canvas_item_dialog.js` and read how a node's config dialog is built.
235. Confirm the dialog is GENERIC: it reads the node's `config.yaml` (via the save/load endpoints) and renders a field per key — most agents need NO bespoke dialog code.
236. Confirm each `config.yaml` key from Phase 1 appears as an editable field with its default pre-filled.
237. Confirm nested config (e.g. `llm.model`) renders with dotted-key fields the dialog understands.
238. Confirm boolean fields render as checkboxes / true-false controls (match the sibling).
239. Confirm the dialog's Save posts to `save_agent_config_view`, which DEEP-MERGES the posted JSON over the template `config.yaml` — so empty fields must be omitted, not written as `''` (or they destroy defaults).
240. If the agent needs a SPECIAL dialog widget (a dropdown of enum actions, a file picker, a Parametrizer mapping UI), find the sibling that has it and mirror it; otherwise rely on the generic renderer.
241. If the agent is a Parametrizer, wire `acp-parametrizer-dialog.js` (only for Parametrizer itself — not a normal new agent).
242. Confirm the dialog shows the connection fields as read-only/managed (connections are set by dragging wires, not typed in the dialog).
243. Confirm credential fields render as empty inputs (never pre-filled with a secret).
244. Confirm the dialog title shows `<Display>` (it reads the node label).
245. Verify the dialog round-trips: open → edit a value → Save → reopen shows the new value (Phase 25 live check).
246. Confirm Save does not clobber a connection field that was set by wiring (deep-merge + omit-empty protects this).
247. If you added a bespoke dialog control, add any new JS global it introduces to `eslint.config.mjs` and the `/* global */` header.

---

# PHASE 14 — Multi-Turn enablement (the wrapped chat-agent tool)

248. Decide YES (recommended) to make the agent LLM-callable in Multi-Turn — this is "enable the 

…(truncated)
