| name | tlamatini-agent-creation |
| description | 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. |
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).
PHASE 0 — Preflight, scoping & naming (lock these before any code)
- Confirm with Angela the agent's purpose in one sentence (what task it performs).
- Decide whether the agent is deterministic (no LLM) or LLM-powered — this changes config keys and FlowHypervisor timing notes.
- 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.
- Decide whether the agent is Active (starts downstream via
target_agents) or Terminal/Monitoring (does not).
- Decide whether the agent produces structured output consumed by Parametrizer (emits
INI_SECTION_<CAPS>).
- Decide whether the agent should be LLM-callable in Multi-Turn (a wrapped
chat_agent_<lower> tool) — most new agents should be.
- Decide whether the agent is long-running (Monitor-style) or short-lived.
- Decide whether the agent scaffolds project directories (firmware/engine style → defaults to
<app>/Templates) or writes scratch (→ <app>/Temp).
- Decide whether the agent spawns console child processes (relevant to the orphan reaper + command watchdog).
- Decide whether the agent has a singleton constraint (only FlowCreator/FlowHypervisor are; a normal agent is not).
- Lock the
<Display> name with EXACT casing — this is agentDescription and the single source of truth.
- Invoke the
tlamatini-agent-naming skill and derive <lower>, <dash>, <space>, <Pascal>, <CAPS>, <css> from <Display> per its transform table.
- NEVER let any non-identifier surface display a different casing of
<Display> (Angela is emphatic — STM32er must never become STM32Er).
- 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.
- 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.
- 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).
- Read the chosen sibling's
agent/agents/<sibling>/<sibling>.py in full before writing anything.
- Read the chosen sibling's
config.yaml in full.
- Read the sibling's
update_<sibling>_connection_view in views.py.
- Read the sibling's CSS block in
agentic_control_panel.css.
- Read the sibling's
ChatWrappedAgentSpec in chat_agent_registry.py.
- Read the sibling's
_PARAMETRIZER_OUTPUT_FIELDS entry in services/agent_contracts.py.
- Read the sibling's
_EXEC_REPORT_TOOLS entry (if state-changing) in mcp_agent.py.
- Read the sibling's
test_<sibling>_agent.py in full — it is your test template.
- 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.
- 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).
- Decide the INI_SECTION KV header fields (what downstream agents can address) + whether there is a
response_body.
- 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
- Create directory
Tlamatini/agent/agents/<lower>/.
- Create
Tlamatini/agent/agents/<lower>/config.yaml.
- In
config.yaml, add a top comment # <Display> Agent Configuration.
- Add each functional param key with a sensible default value (from your Phase-0 key list).
- Add the connection fields that apply:
target_agents: [] if Active.
- Add
source_agents: [] if it monitors upstream logs.
- Use
output_agents: [] INSTEAD of target_agents ONLY for Stopper/Ender/Cleaner-style agents.
- For OR/AND use scalar
source_agent_1: "" / source_agent_2: "".
- For Asker/Forker use
target_agents_a: [] / target_agents_b: [].
- For Counter use
target_agents_l: [] / target_agents_g: [].
- Leave any credential/secret field as an empty string default (never hardcode a key —
regen_secrets.py / Flow-Compiler redaction depends on this).
- If a param is numeric, default it to a real number (not a string) so
yaml.safe_load yields the right type.
- If the agent has nested config, model it as a nested mapping (e.g.
llm: block with base_url/model/temperature) mirroring the sibling.
- Create
Tlamatini/agent/agents/<lower>/<lower>.py.
- Copy the FULL boilerplate from the reference sibling's
.py (module preamble + all helpers + main() shape). DO NOT hand-roll helpers.
- Make
os.environ['FOR_DISABLE_CONSOLE_CTRL_HANDLER'] = '1' the FIRST statement after import os, sys.
- 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.
- Ensure the log file name is exactly
{directory_name}.log (the canvas reads this; any other name breaks LED/log surfacing).
- Compute
CURRENT_DIR_NAME and LOG_FILE_PATH the same way the sibling does.
- 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.
- Implement the agent's core logic between
logging.info("🚀 <CAPS> AGENT STARTED") and the target-trigger block.
- Use a distinctive emoji +
<Display>-cased phrase in the STARTED log line (FlowHypervisor markers key off these — see Phase 18).
- Wrap external/hardware/network calls in try/except so a missing device/host produces a logged error, not a crash.
- 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).
- Write the PID file immediately at the top of
main() via write_pid_file().
- Remove the PID file in a
finally: block via remove_pid_file().
- Add a short
time.sleep(0.4) before remove_pid_file() to keep the LED green briefly (sibling pattern).
- End
main() with sys.exit(0).
- 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.
- The concurrency guard
wait_for_agents_to_stop(target_agents) MUST come BEFORE the start_agent loop (prevents duplicate spawns in looping flows).
- 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.
- Do NOT trigger
target_agents from a Terminal/Monitoring agent (Emailer/Notifier/Monitor-*) — those leave target_agents as canvas-only metadata.
- Add a final completion log line
logging.info("🏁 <Display> agent finished.") before the finally.
- 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.
- Resolve any bundled asset path for BOTH frozen (
os.path.dirname(sys.executable)) and source (os.path.dirname(os.path.abspath(__file__))) modes.
- 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).
- Keep
main() callable under if __name__ == "__main__": main().
- Verify
config.yaml round-trips: python -c "import yaml; print(yaml.safe_load(open(r'...config.yaml')))".
- Confirm the script has no top-level side effects beyond the documented
os.chdir/logging.basicConfig that the test harness saves+restores.
- 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.
- 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)
- Right after
LOG_FILE_PATH is set and BEFORE logging.basicConfig(...), add _IS_REANIMATED = os.environ.get('AGENT_REANIMATED') == '1'.
- Immediately after, add
if not _IS_REANIMATED: open(LOG_FILE_PATH, 'w').close() (truncate only on a fresh start).
- NEVER truncate the log when
_IS_REANIMATED is true (resume appends).
- In
main(), when _IS_REANIMATED, log 🔄 <Display> REANIMATED (resuming from pause) as the first line.
- 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.
- If the agent polls a source log, implement
save_reanim_offset(offset) / get_reanim_offset(...) using reanim.pos (or reanim_<source>.pos per source).
- Load the offset at startup and call
save_reanim_offset after each read.
- Confirm the agent is idempotent under resume: resuming produces the same behavior as if never interrupted.
- Confirm the agent does NOT delete its own
reanim* files (only Ender clears them on Stop).
- 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).
- 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).
- Confirm pressing Start while PAUSED acts as Resume (no special code needed — the ACP handles it).
- Confirm the agent does not assume it is always a fresh start anywhere in its logic.
PHASE 3 — Structured output (INI_SECTION — Parametrizer producer)
- If the agent feeds Parametrizer, define the section in the unified format:
INI_SECTION_<CAPS><<< … >>>END_SECTION_<CAPS>.
- Use
<CAPS> = the UPPERCASE base name (single ALL-CAPS token convention — do NOT mixed-case it).
- Put the KV header (one
key: value per line) BEFORE the first blank line.
- Put the multi-line body AFTER the first blank line (it becomes
response_body).
- If there is no body, omit the blank line (KV-only section).
- Emit each section in a SINGLE atomic
logging.info(...) call (concurrent writes interleave and corrupt otherwise).
- Emit N separate sections for N results (one section per result/response).
- Choose KV header field names that downstream agents will address (e.g.
output_path, status, url, return_code, success).
- Include
response_body in the header field list ONLY if the section has a body.
- Build the section string with explicit
\n joins exactly like the sibling (do not use f-string multiline that swallows indentation).
- Confirm the section start/end tokens match exactly (
INI_SECTION_<CAPS><<< and >>>END_SECTION_<CAPS>).
- If the agent ALWAYS emits the section even on failure (recommended for routable branching), document that in the section body (e.g.
status: error).
- 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)
- If the agent writes TEMPORARY/scratch files, route them under
<app>/Temp — NEVER C:\Temp, %TEMP%, or a bare tempfile.gettempdir().
- 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(); ….
- Keep that guard as an
if-block (NOT a top-level def) so it sits above the imports without tripping ruff E402.
- If the agent SCAFFOLDS a project/template directory (firmware/engine style), default its parent to
<app>/Templates (TLAMATINI_TEMPLATES) unless Angela supplies a path.
- 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).
- Confirm
Temp = throwaway scratch; Templates = deliverable project trees (never via tempfile).
- Confirm no path the agent writes escapes the Tlamatini app root.
- If you add an in-process
@tool instead (rare), route its scratch through path_guard.get_app_temp_root() / resolve_temp_path() directly.
- 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
- If the agent spawns console child processes (
cmd/powershell/external CLI), understand it is subject to the command watchdog (agent/command_watchdog.py).
- 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.
- Ensure the agent's children make observable progress (CPU or IO) while working so the watchdog never kills a long-but-working job.
- 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).
- 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).
- 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).
- 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.
- 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).
- 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).
- 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.
- 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
- Open
Tlamatini/agent/views.py.
- Copy
update_<sibling>_connection_view and rename it update_<lower>_connection_view.
- Keep the
@csrf_exempt + @require_POST decorators.
- Parse
data = json.loads(request.body.decode('utf-8')).
- Read
target_agent, action (default 'add'), and connection_type (default 'target').
- Return a 400 JSON error if
target_agent is missing.
- Normalize the agent id
<lower>-N → pool name <lower>_N (split on -, pop trailing digit as cardinal, join base with _).
- Reject path-traversal in the pool name (
'..', '/', '\\') with a 400.
- Build
config_path = os.path.join(get_pool_path(request), pool_name, 'config.yaml'); 404 if missing.
- Load the config with
yaml.safe_load(...) or {}.
- Normalize the
target_agent id → target pool name the same way.
- Choose the list key:
source_agents if connection_type=='source' else target_agents (or the agent's special connection field per Phase 0).
- Ensure the list exists (
if not isinstance(config.get(list_name), list): config[list_name] = []).
- On
action=='add', append the target pool name if not present.
- On
action=='remove', remove it if present.
- 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).
- Write the config back with
yaml.dump(..., default_flow_style=False, allow_unicode=True, sort_keys=False).
- Return
{"success": True, ...} JSON.
- Wrap the whole body in try/except returning a 500 JSON on error.
- 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.
- Open
Tlamatini/agent/urls.py.
- Add
path('update_<lower>_connection/<str:agent_name>/', views.update_<lower>_connection_view, name='update_<lower>_connection'),.
- Confirm the route name is unique and matches the connector fetch URL you will write in Phase 10.
- 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.
- 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)
- Find the highest existing migration with
Glob Tlamatini/agent/migrations/0*.py.
- Create
Tlamatini/agent/migrations/<NNNN>_add_<lower>.py (next sequential number).
- Implement
add_<lower>_agent(apps, schema_editor) that gets the Agent model via apps.get_model('agent','Agent').
- Guard against duplicates:
if Agent.objects.filter(agentDescription='<Display>').exists(): return.
- Compute
next_id = (max idAgent or 0) + 1.
- Create the row:
Agent.objects.create(idAgent=next_id, agentName=f'agent-{next_id}', agentDescription='<Display>', agentContent='true').
- Use the EXACT
<Display> casing in agentDescription (this is THE source of truth).
- Implement
remove_<lower>_agent reverse that deletes the row by agentDescription.
- Set
dependencies = [('agent', '<previous_migration_name>')].
- Add
operations = [migrations.RunPython(add_<lower>_agent, remove_<lower>_agent)].
- Do NOT edit
0002_populate_db.py — always add a new migration.
- 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.
- Run
python Tlamatini/manage.py makemigrations --check --dry-run to confirm no model drift was introduced.
- 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)
- Open
Tlamatini/agent/agents/parametrizer/parametrizer.py.
- Add
'<lower>' to the SECTION_AGENT_TYPES list (the generic parser handles the rest — no per-agent parser code).
- Open
Tlamatini/agent/views.py and add the field list to PARAMETRIZER_SOURCE_OUTPUT_FIELDS['<lower>'] = the KV header fields + response_body (if present).
- 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).
- Keep the three field lists IDENTICAL across
parametrizer.py (membership), views.py (PARAMETRIZER_SOURCE_OUTPUT_FIELDS), and agent_contracts.py (_PARAMETRIZER_OUTPUT_FIELDS).
- Add the agent to the Supported Source Agents table in
README.md (Phase 20 sweep, but note the field list now).
- If the agent is NOT a Parametrizer source (no INI_SECTION), SKIP 155–160 entirely.
- 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).
- 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).
- 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.).
- Add
secret_paths to the contract for any config.yaml dotted path holding a credential (so .flw export redacts it).
- Confirm
connection_fields on the contract covers every connection key the agent uses (so stale wiring is cleared on recompile).
- 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.
- 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)
- Open
Tlamatini/agent/static/agent/css/agentic_control_panel.css.
- Scan the WHOLE file for existing 4-color gradients to avoid a visual collision.
- Choose a UNIQUE 4-stop gradient (
0% / 33% / 66% / 100%) visually distinct from every existing agent.
- 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; }.
- 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); }.
- The gradient must live ONLY in CSS — never type a gradient string in JS.
- Ensure the sidebar icon inherits the gradient via
applyAgentToolIconStyle(iconDiv, '<Display>') (Phase 10) — no per-agent JS branch.
- Confirm the CSS class root
<css> matches the JS classMap value you will set (<dash>': '<css>-agent').
- Pick a memorable name for the gradient theme (e.g. "Deep-Ocean Teal") and note it for the memory + commit message.
- 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.
- Verify the gradient renders by eye after deployment (Phase 25) for BOTH a freshly dragged node and a
.flw-loaded node.
- 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)
- Open
Tlamatini/agent/static/agent/js/acp-agent-connectors.js.
- Add
async function update<Pascal>Connection(agentId, targetAgentId, action, type = 'target') { ... } modeled on the sibling connector.
- 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 }) }).
- Log a
console.error on a non-ok response and on a thrown error (sibling pattern).
- Open
Tlamatini/agent/static/agent/js/acp-canvas-core.js.
- Location 1 — classMap (
applyAgentTypeClass(), ~line 32): add '<dash>': '<css>-agent', (KEY is the hyphenated form, VALUE is the CSS class).
- Location 2 —
AGENTS_NEVER_START_OTHERS (~line 94): add '<dash>' ONLY if the agent does NOT start downstream (Terminal/Monitoring). Skip for Active agents.
- Location 3 —
populateAgentsList() (~line 830): confirm it uses the shared applyAgentToolIconStyle(iconDiv, description) — do NOT add a per-agent gradient branch.
- 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'.
- Location 5 —
removeConnectionsFor() (~line 740): add SPACED-form branches with the deletion guards (!targetBeingDeleted / !sourceBeingDeleted).
- Location 6 — mouseup handler (~line 1200): add SPACED-form branches with
'add' instead of 'remove'.
- Use the HYPHENATED form ONLY in the classMap (Location 1) and
AGENTS_NEVER_START_OTHERS (Location 2).
- Use the SPACED form (
name.toLowerCase()) in Locations 4, 5, 6 (connection handlers).
- Confirm the connector symbol
update<Pascal>Connection is referenced identically in all locations (case-exact identifier).
- 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.
- Do NOT forget any of the 6 locations — missing one silently breaks creation, removal, undo, redo, or
.flw load.
- Confirm
applyAgentTypeClass is what the canvas calls to set the node's CSS class (so the gradient applies).
- Confirm the new branches do not shadow an existing agent whose name is a substring (use exact
===, not includes).
- Re-read the 6 edits as a group to confirm name-form correctness per location.
- Note the connector symbol for the
/* global */ declarations in Phase 11.
PHASE 11 — Frontend JS: undo/redo, .flw load, globals
- Open
Tlamatini/agent/static/agent/js/acp-canvas-undo.js.
- Find an existing
update<Sibling>Connection reference and mirror it in the UNDO section (SPACED form, 'add' action).
- Mirror it again in the REDO section (SPACED form,
'remove' action).
- Open
Tlamatini/agent/static/agent/js/acp-file-io.js.
- In
restoreAgentConnection's SOURCE-side switch, add case '<space>': await update<Pascal>Connection(sourceId, targetId, 'add', 'target'); break;.
- In
restoreAgentConnection's TARGET-side switch, add case '<space>': await update<Pascal>Connection(targetId, sourceId, 'add', 'source'); break;.
- 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).
- Add
update<Pascal>Connection to the /* global ... */ declaration at the top of acp-canvas-core.js.
- Add it to the
/* global ... */ declaration at the top of acp-canvas-undo.js.
- Add it to the
/* global ... */ declaration at the top of acp-file-io.js.
- Open
Tlamatini/eslint.config.mjs and add update<Pascal>Connection to the globals block so the linter knows it.
- If the agent introduces any other new global JS symbol, add it to
eslint.config.mjs too.
- Confirm the
.flw load path calls updateCanvasContentSize() after restoring positions (it does generically — just don't break it).
- Confirm no JS edit appends a node to
#submonitor-container instead of #canvas-content (coordinate-frame contract).
- 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
- 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).
- Confirm the sidebar label will render exactly
<Display> (it reads the DB row — no HTML edit needed for the label).
- 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.
- 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).
- 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.
- 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).
- Confirm coordinate math for the node uses
canvasContent.getBoundingClientRect() (already generic — do not special-case the new agent).
- 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.
- 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).
- Verify the node is draggable from the sidebar palette after the migration runs (the palette is populated from
Agent rows at page load).
- Verify the node's output connector, when dragged to a target, fires the
update<Pascal>Connection(..., 'add', ...) path (Phase 10, Location 6).
- Verify the node's input connector, when receiving an arrow, writes into the correct list (
source_agents) via the view.
- Confirm
AGENTS_NEVER_START_OTHERS correctly suppresses the OUTPUT-starts-downstream behavior for a Terminal agent (the canvas still draws the wire as metadata).
- Confirm the node renders inside the scrollable canvas and the canvas grows (no upper clamp) when the node is placed far right/bottom.
- Confirm right-click on the node opens the contextual menu (generic
contextual_menus.js) and the Description entry shows the agent_purpose_map text.
- Confirm double-click / the config entry opens the configuration dialog (Phase 13).
- Do NOT add the agent to any hardcoded HTML list — the palette is dynamic from the DB; only MCP checkboxes are hardcoded (irrelevant here).
- 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)
- Open
Tlamatini/agent/static/agent/js/canvas_item_dialog.js and read how a node's config dialog is built.
- 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.
- Confirm each
config.yaml key from Phase 1 appears as an editable field with its default pre-filled.
- Confirm nested config (e.g.
llm.model) renders with dotted-key fields the dialog understands.
- Confirm boolean fields render as checkboxes / true-false controls (match the sibling).
- 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).
- 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.
- If the agent is a Parametrizer, wire
acp-parametrizer-dialog.js (only for Parametrizer itself — not a normal new agent).
- Confirm the dialog shows the connection fields as read-only/managed (connections are set by dragging wires, not typed in the dialog).
- Confirm credential fields render as empty inputs (never pre-filled with a secret).
- Confirm the dialog title shows
<Display> (it reads the node label).
- Verify the dialog round-trips: open → edit a value → Save → reopen shows the new value (Phase 25 live check).
- Confirm Save does not clobber a connection field that was set by wiring (deep-merge + omit-empty protects this).
- 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)
- Decide YES (recommended) to make the agent LLM-callable in Multi-Turn — this is "enable the agent to be Multi-turn".
- Open
Tlamatini/agent/chat_agent_registry.py.
- Append a
ChatWrappedAgentSpec(...) to WRAPPED_CHAT_AGENT_SPECS.
- Set
key="<lower>".
- Set
template_dir="<lower>" (MUST match agent/agents/<lower>).
- Set
tool_name="chat_agent_<lower>" (MUST start with chat_agent_).
- Set
tool_description="Chat-Agent-<Display>".
- Set
display_name="<Display>" (MUST equal the DB agentDescription).
- Set
purpose="..." — a crisp sentence telling the LLM WHEN to use it.
- Set
example_request="Run <Display> with param1='...', param2='...'" using the EXACT config.yaml key names (the wrapped parser maps key=value → config overrides).
- Set
aliases=("<lower>", "<space>", ...) for natural-language matching.
- Set
security_hints=(...) with keywords that help capability scoring select it.
- Set
poll_window_seconds=N only if the default 8 is wrong (short agents can lower it).
- Set
long_running=True only for watch-loop agents.
- Confirm the spec is picked up via
WRAPPED_CHAT_AGENT_BY_TOOL_NAME — no edits needed in mcp_agent.py/tools.py for the launcher itself.
- Create a SECOND migration
Tlamatini/agent/migrations/<NNNN+1>_add_chat_agent_<lower>_tool.py that seeds the Tool row (mirror the sibling's Tool migration; the Tool toggles the dynamic tool UI).
- In that migration, set the Tool's description to
Chat-Agent-<Display> consistently with the spec.
- Confirm the wrapped agent's
config.yaml keys exactly match the example_request field names (mismatch = silent default fallback).
- Add
<lower> to tools.py::_PROMOTE_SECTION_FIELDS_BY_TEMPLATE_DIR if you want a section field (e.g. output_path) surfaced at the top level of the wrapped tool's JSON result (Camcorder/Recorder do this).
- Understand the payload whitelist gotcha:
acpx_enabled, exec_report_enabled, ask_execs_enabled, conversation_user_id, multi_turn_enabled must stay in UnifiedAgentChain.invoke's payload-rebuild whitelist — you are NOT adding a new payload flag, so just don't disturb it.
- Understand Ask Execs is automatic: if the wrapped tool is state-changing, the Multi-Turn executor prompts Proceed/Deny before it runs — no wiring needed.
- If the wrapped tool is READ-ONLY/polling and should NOT be prompted, add its name to
_MANAGEMENT_TOOLS and/or _TOOL_QUOTA_EXEMPT in mcp_agent.py (and it is likely already absent from _EXEC_REPORT_TOOLS).
- Confirm the wrapped tool returns JSON with
run_id, status, log_excerpt, runtime_dir, log_path.
- Confirm launching creates a runtime copy under
agents/pools/_chat_runs_/<lower>_<N>_<id>/.
- If the agent runs a desktop/visible GUI when launched from chat, recall the dogfooding rule: foreground +
dangerouslyDisableSandbox (Phase 25), but the wrapped tool itself runs headless/background by default in Multi-Turn.
- DUAL ENABLE-GATE (2026-06-07 — do NOT bypass):
get_mcp_tools() binds your chat_agent_<lower> for the LLM ONLY when BOTH (a) the wrapper Tool row Chat-Agent-<Display> is enabled (Configure Mcps/Tools, the migration from step 263) AND (b) the Agent row <Display> is enabled (Configure Agents). Disabling EITHER makes the agent INVISIBLE to the LLM (reported as unknown). Both gates fail OPEN (no row → defaults enabled). This is exactly why step 255's display_name MUST equal the DB agentDescription — the agent gate is keyed on agent_<display>_status. VERIFY: uncheck the agent in Configure Agents (or the wrapper in Configure Mcps), ask the LLM to use it, confirm it is reported unavailable. Do NOT add the agent to the Tool table twice or to MCP context rows.
- Confirm the
_infer_execution_shell(tool_name, args) in mcp_agent.py returns a sensible shell for the Ask-Execs dialog; add a branch if the agent runs through an unusual interpreter.
- Confirm capability scoring will surface the tool — the
security_hints + purpose feed capability_registry.py; add an _EXTRA_HINTS_BY_TOOL_NAME entry only if scoring under-selects it.
PHASE 15 — Exec Report (MANDATORY for EVERY Multi-Turn agent)
⚠️ MANDATORY DIRECTIVE — NON-NEGOTIABLE (Angela, 2026-06-07): EVERY agent that can run in Multi-Turn (anything wired with a wrapped chat_agent_<lower> tool in Phase 14) MUST be captured and shown in the Exec Report — observational/output agents (Talker, Shoter, Camcorder, Recorder, AudioPlayer, VideoPlayer) and read-only LLM agents (Crawler, Prompter, Summarizer, File/Image interpreters, Monitor-*, Recmailer, …) INCLUDED, and every newly-created agent. A Multi-Turn agent that produces NO Exec-report row (Exec report ON) is a defect — that was the Talker bug. The old "state-changing only / SKIP if observational" rule is REVOKED.
- Capture is AUTOMATIC —
mcp_agent.py::_resolve_exec_report_spec captures ANY wrapped chat_agent_* (except _MANAGEMENT_TOOLS helpers) by deriving agent_key/display from the registry. If you did Phase 14, your agent is ALREADY captured with no Exec-report code. Do NOT skip the agent just because it is observational.
- (MANDATORY) VERIFY capture: run the agent in Multi-Turn with Exec report ON and confirm a
List of <Display> Operations table appears; and ensure agent.tests.ExecReportCaptureTests.test_every_multiturn_agent_is_capturable_including_observational stays green (it fails if any wrapped agent resolves to no row).
- OPTIONAL refinement (nicer styling / shared keys only): open
Tlamatini/agent/mcp_agent.py and add "chat_agent_<lower>": ("<css>", "<Display>"), to _EXEC_REPORT_TOOLS — do this to merge a direct @tool with its wrapped launch under one agent_key, or to fix the display casing the generic fallback derives. Otherwise skip it; the registry display name + default caption are used.
- If you add the entry and the agent also has a direct
@tool, give both the SAME agent_key so rows merge into one table.
- Confirm the
agent_key (<css>) matches the canvas CSS class root so a per-agent gradient feels native.
- (Optional CSS) Open
Tlamatini/agent/static/agent/css/agent_page.css.
- Add
.exec-report-caption-<css> { background: linear-gradient(135deg, #c1 0%, #c2 100%); color: #ffffff; } mirroring the canvas gradient — purely cosmetic; without it the readable default .exec-report-caption background applies.
- Add
.exec-report-<css> .exec-report-cmd { border-left: 3px solid #c1; } accent.
- If the caption background is DARK, add
<css> to the dark-header thead th { color:#f5f5f5; background:rgba(0,0,0,0.55) } selector list.
- Confirm capture is unconditional in
_invoke_tool (it ignores the per-request flag) — you add nothing there.
- Confirm
_extract_exec_report_command(tool_input, tool_name) produces a sensible command string for the agent; add a tool-name-aware branch only if the default is unhelpful.
- Confirm the row verdict uses the existing
call_success logic — do NOT add a separate classifier.
- Confirm the Exec-Report table caption will read
List of <Display> Operations.
- Confirm the
EXEC_REPORT_BOUNDARY sentinel stays byte-identical in response_parser.py and agent_page_chat.js (you are not editing it — just don't).
- Run
python Tlamatini/manage.py test agent.tests.ExecReportCaptureTests later (Phase 24) — it is generic (incl. the all-agents audit); no per-agent exec-report test needed.
PHASE 16 — Flow-Generator mapping (Create-Flow from a Multi-Turn answer)
- If the agent is NOT Multi-Turn-callable, SKIP this phase.
- Open
Tlamatini/agent/static/agent/js/agent_page_chat.js.
- Find
_mapToolArgsToAgentConfig(canonicalName, rawArgs, _toolName).
- Add a branch
} else if (lower === '<space>') { ... }.
- Inside it, use the
set(key, value) helper for each field (it refuses empty strings, protecting template defaults).
- Field names MUST match
config.yaml keys EXACTLY (mismatch = silent default fallback).
- For dotted nested keys (e.g.
smtp.host), use collectDotted('smtp') and assign the object only if non-empty.
- NEVER set
config.target_agents / config.source_agents here — _generateAndDownloadFlow adds cardinal-suffixed pool names.
- Confirm the generated
.flw node for the agent carries populated config fields (not empty defaults) after a Create-Flow.
- Confirm the mapping handles every advertised
example_request field from Phase 14.
- Re-read the branch against the
config.yaml keys to confirm 1:1 coverage.
PHASE 17 — FlowCreator specification (agentic_skill.md)
- Open
Tlamatini/agent/agents/flowcreator/agentic_skill.md.
- Add a numbered agent entry under Available Agents (use the next number; the entries are numbered #1..#N).
- Include
- **Purpose**: one line.
- Include
- **Used for**: a sentence of context.
- Include
- **Aimed at**: the design intent.
- Include
- **Application example**: a concrete flow scenario.
- Include
- **Pool name pattern**: \_``.
- Include
- **Starts other agents**: YES/NO per the Active/Terminal decision.
- Include
- **Config parameters**: listing EVERY config.yaml key with default + one-line description.
- List
source_agents/target_agents (or the special fields) in the config-parameters block with their semantics.
- Add the agent to the Quick-Reference: Agent Capabilities at a Glance table (
| **<lower>** | what it does | Starts Others | Category |).
- Add the agent to the Agent Categories lists (Active agents OR Terminal/Monitoring agents).
- If the agent has a special connection shape, document it in the Connection Rules section.
- If the agent emits structured output, note it in the Output Format Rules / Parametrizer-source context.
- If the agent is a good fit for a Common Task Pattern, add or extend a pattern example showing it in a flow.
- If the agent should be PREFERRED over Pythonxer/Executer for its task, add a row to the Agent Selection Priority Rules table.
- Confirm the entry's
agent_type token used by FlowCreator's JSON output equals <lower> exactly.
- Confirm the FlowCreator output-format example would emit
{"agent_type":"<lower>","config":{...}} with the agent's keys.
- Bump any "FlowCreator is agent #X" cross-reference if the numbering shifts.
- Re-read the entry against the real
config.yaml to confirm parameter parity (FlowCreator generates configs from this text).
PHASE 18 — FlowHypervisor monitoring (monitoring-prompt.pmt)
- Open
Tlamatini/agent/agents/flowhypervisor/monitoring-prompt.pmt.
- Add
<Display> to the SHORT-LIVED list (Section 3) if it starts/does-work/exits quickly.
- OR add it to the LONG-RUNNING list if it runs for the whole flow (Monitor-style).
- If the agent has nuanced behavior (zero-config bootstrap, long first run, observational capture, structured-output-on-failure, external window, REPL/transport timing), add a dedicated
<CAPS> SPECIAL NOTES: block modeled on the STM32er/Kalier/Camcorder notes.
- In the SPECIAL NOTES, state whether it is SHORT-LIVED/LONG-RUNNING and ACTIVE/terminal.
- State the typical duration and the threshold beyond which silence = stuck (e.g. "do NOT flag before ~5 min" for an LLM/build agent).
- State that its
INI_SECTION_<CAPS><<< block is NORMAL — never flag it as an error even when the body looks like a tool error (it is routable content for a downstream Forker).
- State which conditions ARE legitimate errors to flag (e.g. "Command not resolvable on PATH", "Cannot reach ", "Failed to start agent").
- Add the agent's STARTED marker (the emoji +
<Display> phrase from Phase 1, step 50) to the STARTUP markers list in Section 4.
- Add the agent's completion phrase (e.g.
🏁 <Display> agent finished.) consistency to Section 4 if it differs from the generic "finished"/"exiting".
- Add
<CAPS> to the structured-output-section producer list in Section 4 if it emits INI_SECTION.
- Add the agent to the TYPICAL TIMING list (Section 3 bottom) with its expected duration window.
- If observational (Camcorder/Recorder-style), add it to the observational-capture SPECIAL NOTES and the "THINGS THAT ARE NORMAL (DO NOT FLAG)" list (Section 6).
- If it has a fail-safe REFUSAL stage (preflight), state that a
stage: preflight REFUSED section is the agent working as DESIGNED, not a flow error.
- If its first run downloads a large toolchain, add the "do NOT flag a long FIRST build while Downloading/Installing progress keeps appearing" caveat.
- Keep
<CAPS> SPECIAL NOTES headers ALL-CAPS (the protocol convention — do not mixed-case).
- Confirm none of the new notes would make the watchdog flag the agent's NORMAL structured output or normal long silence.
- Re-read the additions against Section 5 (diagnostic checklist) to ensure no contradiction (e.g. a long-running agent must not trip CHECK 3's 5-minute stuck rule).
PHASE 19 — Demo "Prompts example" creation (the prompts catalog)
⚠️ MANDATORY DIRECTIVE — NON-NEGOTIABLE (Angela, 2026-06-07): if the agent is Multi-Turn-capable (it has a wrapped chat_agent_<lower> tool from Phase 14), you MUST create at least ONE example prompt for it in the Catalog of Prompts (the #prompts-catalog, seeded via a Prompt-model migration). This is a hard completion gate, NOT optional: a Multi-Turn agent shipped without at least one catalog prompt is an INCOMPLETE agent and the task is not done. (Canvas-only agents with no Multi-Turn tool are exempt — but every Multi-Turn agent needs its catalog prompt.) Do NOT skip this phase for a Multi-Turn agent under any circumstance.
- (MANDATORY for Multi-Turn agents) Seed at least one demo prompt (1 simple is the REQUIRED minimum; tiered basic/medium/hard like STM32er #63/#64/#65 is the gold standard). Skipping this for a Multi-Turn-capable agent is a defect — the agent is not considered finished until it has a catalog prompt.
- Read the prompts-catalog CONTIGUITY contract (relaxed to fallback-only in v1.38.1): the primary load is ONE
GET /agent/list_prompts/ call returning ALL prompts grouped by category; the legacy probe-loop fallback is gap-tolerant, order = promptName suffix, idPrompt stays contiguous. Since migration 0181 (2026-07-20) the DISPLAY ORDER inside a section is sort_rank, NOT idPrompt — the view orders by (category rank, sort_rank, idPrompt). So APPEND at max(id)+1 as always AND set sort_rank to the slot your prompt belongs in (ranks step by 10; rank 10 is RESERVED for the section's Step-by-Step opener; 0 = unranked and sorts LAST). Sections must read least-complex → most-complex, prerequisites first. Without a sort_rank your new prompt is visible but pinned to the END of its section.
- Find the current highest
idPrompt and the next free slot (read the latest prompt-seeding migration; the catalog cap is MAX_PROMPTS=256 in tools_dialog.js).
- Create a migration
Tlamatini/agent/migrations/<NNNN+k>_add_<lower>_demo_prompts.py that seeds rows into the prompts model.
- Each demo prompt should drive the new agent (via
chat_agent_<lower> if Multi-Turn) with a realistic, SAFE task.
- Make the prompts SAFE to run repeatedly (the daily chat test may execute them) — no destructive operations.
- Style the prompt banner to MIRROR a recent demo (e.g. ST-blue for STM32er; pick a theme matching the agent's CSS gradient) — copy the HTML banner pattern from the sibling's prompt migration.
- Set each prompt's mode expectation correctly: Multi-Turn ON for operator prompts; the prompt-catalog mode badges (one-shot/multi-turn/ACPX) auto-set the toolbar toggles, so phrase the prompt so the classifier infers the right modes (scrub any "do NOT use acp_spawn" clause that would confuse the classifier).
- Keep
idPrompt and promptName suffix contiguous and gap-free with the existing catalog.
- Implement the reverse migration to delete the seeded prompts.
- Set
dependencies on the previous migration.
- Run a quick round-trip:
makemigrations --check clean + a sqlmigrate mental check that rows insert.
- If inserting BEFORE existing prompts (to keep grouping), shift the existing
idPrompt+promptName suffixes accordingly (the catalog is order-sensitive).
- Document the new catalog range (e.g. "catalog now 1–66") for the memory + docs.
- Confirm the prompts appear in the
#prompts-catalog modal after migrate (Phase 25 live check).
- Confirm each prompt's title/description clearly names the agent so Angela can find it.
PHASE 20 — Documentation sweep (every doc surface)
- Open
agents_descriptions.md (repo root) and add a row to the appropriate ## Workflow Agents table — | **<Display>** | <Description / Purpose> | <config hint> |.
- The
Description cell becomes BOTH the sidebar tooltip AND the canvas Description dialog text — write it for that audience.
- Open
README.md and increment the agent count in ALL the places it appears (Overview, Key Features → Visual Workflow Designer, Workflow Agents header).
- Add the agent to the README Project Structure tree (
│ │ │ ├── <lower>/ # <brief>).
- Add the agent to the README Agent Architecture Deterministic OR LLM-powered list.
- Add a README Workflow Agents table row in the right category with the
Purpose cell.
- Add a README Glossary entry
| **<Display>** | <one-line definition> |.
- Prepend a README Changelog / Recent Updates entry
- **Added <Display> Agent** - <brief>.
- Add the README Connection Endpoints API row
| /update_<lower>_connection/<agent_name>/ | POST | Update <lower> connections |.
- If the agent is a Parametrizer source, add it to the README Supported Source Agents table (the field list from Phase 8).
- If Multi-Turn, bump the README "Multi-Turn tools" / "wrapped agents" counts.
- Open
CLAUDE.md and add the agent to the agents/ structure tree comment block (the long inline list of agent dirs).
- Open
docs/claude/agents.md and add the agent to the All Workflow Agent Types catalog under the right category with its full description.
- Bump the "74 total" style counts in
CLAUDE.md and docs/claude/agents.md.
- If the agent is a media/observational sibling, update the relevant family descriptions (Shoter/Camcorder/Recorder/AudioPlayer/VideoPlayer prose) so the family stays consistent.
- If the agent is state-changing, confirm
docs/claude/exec-report.md does not need a new note (it is generic — only add if behavior is unusual).
- Bump
package.json "version" to the release version Angela targets (per feedback_package_json_version_bump) — only running-example/current-state strings, not historical changelog refs.
- Update any
BookOfTlamatini.md "Recent Updates" narrative entry if Angela maintains it for releases.
- Confirm
agents_descriptions.md is shipped by build.py next to the exe (it is — just don't break it) so tooltips work in frozen mode.
- Re-grep the repo for the OLD agent count to catch any stray count you missed.
- Confirm every doc uses the EXACT
<Display> casing (run the naming-skill quick-check grep).
PHASE 21 — Dependencies & packaging (requirements.txt + build.py)
- If the agent needs a new third-party lib, add a PINNED version to
Tlamatini/requirements.txt (e.g. opencv-python==4.13.0.92).
- Add the lib to
build.py's _agent_libs verify list so the build fails loudly if it's missing.
- If the lib bundles binaries (ffmpeg/SDL via ffpyplayer), add the needed
--collect-all <lib> to build.py.
- Confirm
build.py ships the new agent/agents/<lower>/ directory (the agent-pool tree is bundled — verify the glob/add-data covers it).
- Confirm
build.py installs requirements.txt into BOTH the build python AND the carried PYTHON_HOME (pool agents run under the carried python — the lib must be there).
- If the agent ships a template project tree (firmware/engine), confirm
build.py bundles that scaffold directory.
382b. Self-modify snapshot (copy_source_assets.py, repo root) — the agent's source files flow into the TlamatiniSourceCode/ snapshot automatically (denylist: all text/source types are included by default; build.py --self-modify generates the snapshot fresh). Only act if the agent introduces (a) a NEW heavy binary asset type → add its extension to EXCLUDED_EXTENSIONS and, if a rebuild needs it, an entry in RESTORE_FROM_INSTALL; or (b) a NEW secret field name in its config.yaml whose key suffix isn't already matched by _SECRET_KEY_RE → extend the redaction pattern. See docs/claude/recent-fixes.md (2026-06-12).
- Add a static contract test to
test_build_scripts.py if the agent introduces a new bundled asset (mirror the existing "agents ship / assets referenced+exist" tests).
- Confirm the new agent's imports are pinned in the build's import-verify list if
test_build_scripts.py checks per-agent imports.
- Note in the final summary that the frozen
C:\Tlamatini install needs python build.py + reinstall for the agent to appear there.
- Do NOT run
build.py casually — it is ~18 min and there is a .build.lock PID guard; never run a background build while Angela may also build (they collide). Only build when Angela asks.
- Confirm
.gitignore already covers any generated artifacts the agent might create at runtime (Temp/Templates are ignored).
- If the agent adds a new sound/icon asset, place it under
static/agent/ and reference it from the build's data files.
PHASE 22 — Python unit tests (test_<lower>_agent.py)
- Create
Tlamatini/agent/test_<lower>_agent.py.
- Write a module docstring describing what the agent does and what the test covers (mirror
test_camcorder_agent.py).
- Load the pool script via
importlib.util.spec_from_file_location (it lives outside the agent package).
- Save+restore cwd AND logging handlers around the import (the module's top-level
os.chdir/open(LOG_FILE_PATH)/logging.basicConfig side effects must not leak).
- If the agent uses hardware/external libs, inject a FAKE pure-Python stand-in into
sys.modules (like the fake cv2/sounddevice) so REAL code paths run deterministically with no device.
- Test each helper: config load, output-dir resolution (default vs honored), unique-path/collision-proof naming, any
_coerce_int/_coerce_float robustness (feed it "5 from the mic" and assert it yields 5).
- Test the core capability against the fake backend (file written / request shaped / command built correctly).
- Test
emit_parametrizer_section: the atomic INI_SECTION_<CAPS> block round-trips through Parametrizer's parser.
- Test
main() end-stage: the section is emitted AND target_agents is triggered (even on the failure path, if that's the contract).
- Test the failure path: a missing device/host/lib is REPORTED, not crashed.
- Test reanimation: with
AGENT_REANIMATED=1 the log is NOT truncated and the REANIMATED line is logged.
- Write registry-integration tests (Django
SimpleTestCase): assert the ChatWrappedAgentSpec exists with the right tool_name/display_name.
- Assert the agent contract +
_PARAMETRIZER_OUTPUT_FIELDS['<lower>'] fields match the section header.
- Assert Exec-Report MEMBERSHIP (state-changing) or ABSENCE (observational) of
chat_agent_<lower> in _EXEC_REPORT_TOOLS.
- Assert the
config.yaml defaults parse and contain every documented key.
- Assert the CSS gradient class
.canvas-item.<css>-agent exists and is UNIQUE (no duplicate gradient).
- Assert the URL route
update_<lower>_connection resolves.
- Assert the view
update_<lower>_connection_view exists and handles add/remove.
- Assert
parametrizer.py::SECTION_AGENT_TYPES contains '<lower>' (if a source).
- Assert the migrations exist and
makemigrations --check is clean.
- Assert
requirements.txt pins the new lib (if any).
- Assert the JS wiring exists by reading the JS files and grepping for
update<Pascal>Connection in connectors + canvas-core (classMap) + undo + file-io (a static contract test like the build tests).
- Make the tests HARD per
feedback_hard_real_scenario_tests: errors + clean + overflow cases, drive REAL code (don't mock the thing under test), reproduce any real incident byte-faithfully.
- Do NOT mock the module's
time in real-client tests (it breaks timing-sensitive paths — known gotcha).
- Use a raw-string for any fake-server source written to a temp
.py subprocess (escaping gotcha).
- Run
python Tlamatini/manage.py test agent.test_<lower>_agent and get it fully green.
- Run the related suites too (the registry tests touch shared maps — confirm no cross-agent regression): the sibling's test +
ExecReportCaptureTests.
- Run
python -m ruff check Tlamatini/agent/test_<lower>_agent.py and fix all.
- Confirm the test count + green status for the final summary.
PHASE 23 — Playwright tests in Claude's harness (the daily-chat-test)
- Open
.claude/skills/tlamatini-daily-chat-test/harness/ and read config.py, run_test.py, questions.py, wrapped_questions.py, qualify.py.
- Understand the harness contract: it drives REAL Chrome via Playwright, logs into
agent_page.html, asks curated questions one-by-one, waits for completion, scrapes + qualifies the answer.
- Understand the answer-complete signal: input stops being
readOnly AND #wait-spinner is removed from #chat-log.
- Add KNOWLEDGE questions about the new agent to
harness/questions.py (safe, introspective: "What does the agent do?", "Which agents capture ?") — these run with Multi-Turn ON, ACPX/Ask-Execs/Exec-Report OFF.
- Add a WRAPPED-TOOL execution question to
harness/wrapped_questions.py so --bank wrapped --select <lower> exercises the live chat_agent_<lower> tool.
- Set the wrapped question's
id, category (wrapped:<lower>), wrapped key (<lower>), and display name (<Display>) so --select matching works (id/category/key/name, case-insensitive, substring, aliases).
- Add any alias mapping (e.g.
pinger→<lower>) to the harness alias table if Angela uses a colloquial name.
- Make the wrapped question SAFE to execute repeatedly (the bank may run 1000×/day) — benign, idempotent task.
- Add expected keywords to the question so the heuristic qualifier can PASS a correct answer.
- If the agent is observational/desktop, keep the wrapped question's action harmless (a single capture to the default folder, not a long video).
- Confirm the test's pinned toggles match the harness default: Multi-Turn ON, ACPX OFF, Ask-Execs OFF, Exec-Report OFF, Internet OFF (per
feedback_test_toggle_state — set AND verify, clear history first).
- Run a focused FOREGROUND check first:
python run_test.py --bank wrapped --select <lower> against a LIVE server (the single-select run is short).
- Use
--bank wrapped --list to confirm the new --select token is discoverable.
- Verify the server is up at
http://127.0.0.1:8000 first (curl the root); if down, ask Angela to start it — never start a second instance (single-bound ports).
- Get credentials from Angela (installer default
user/changeme is usually wrong on the dev box) via env TLAMATINI_USER/TLAMATINI_PASS or harness/.creds.env.
- On first-time harness setup,
pip install -r requirements.txt + python -m playwright install chrome in the harness dir.
- Confirm the question PASSES (answered, no error banner, expected keywords present); if WEAK/FAIL, read the report's heuristic reason + judge verdict and fix the agent or the question.
- Add the new wrapped key to the harness README's list of selectable agents if it maintains one.
- Do a small
--count 10 smoke of the knowledge bank to confirm the new knowledge questions don't regress.
- Run the harness's own
ruff check (the harness has a .ruff_cache) and keep it clean.
- Confirm
results.jsonl + summary.json + report.md are written under harness/reports/run_<timestamp>/ for the run.
- Report the focused-run outcome (asked/pass/weak/fail, pass-rate, avg time) to Angela.
- Do NOT add destructive prompts to either bank (the daily run executes them with Multi-Turn ON).
- If the chat UI selectors changed because of your work, fix
harness/config.py (selectors + ready/started JS) and re-verify with --count 2 before trusting a full run.
PHASE 24 — Lint, migrate, full verification
- Run
python -m ruff check over the repo and fix ALL issues (E402 from a top-level def above imports is the classic one).
- Run
npm run lint and fix ERRORS (warnings can stay) — the missing /* global */ or eslint.config.mjs global is the classic JS lint failure.
- Run
python Tlamatini/manage.py makemigrations --check --dry-run — must be clean (all rows seeded via your RunPython migrations, no model drift).
- Run
python Tlamatini/manage.py migrate and confirm the Agent row + Tool row + demo-prompt rows apply.
- Run the new test module green:
python Tlamatini/manage.py test agent.test_<lower>_agent.
- Run
python Tlamatini/manage.py test agent.tests.ExecReportCaptureTests (state-changing) — generic, must stay green.
- Run the sibling's test + any shared-registry test to confirm no cross-agent regression.
- Run
python Tlamatini/manage.py test agent.test_build_scripts if you touched build/packaging.
- Verify
get_agent_contract('<lower>') returns a contract with the right display_name and parametrizer_fields (quick shell check).
- Verify the wrapped tool is exposed: confirm
WRAPPED_CHAT_AGENT_BY_TOOL_NAME['chat_agent_<lower>'] resolves and get_mcp_tools() includes a tool named chat_agent_<lower>.
- Grep the repo for the old agent count to confirm every count was bumped.
- Run the naming-skill quick-check grep to confirm no mis-cased
<Display> slipped in.
- Confirm
python -c "import yaml; yaml.safe_load(open('...config.yaml'))" still parses.
- Confirm
eslint recognizes the new global (no no-undef for update<Pascal>Connection).
- Confirm the migration numbering has no gaps/duplicates with
Glob.
- Confirm the prompts catalog is contiguous (no gap that would break the dropdown).
- Re-read your scratch/pivot note and tick every file you intended to touch.
PHASE 25 — Live deploy, VISIBLE dogfood, and handoff
- If Angela's server is the SOURCE instance, restart it (or confirm a
--noreload instance) so the migration + new code load.
- Open
agent_page.html and confirm the new agent appears in the sidebar palette with the correct <Display> label and gradient icon.
- Drag the agent onto the canvas and confirm the node renders with the gradient + correct input/output connectors.
- Wire it to a Starter and an Ender; confirm the connection-update view writes
target_agents/source_agents correctly (check the pool config.yaml).
- Right-click the node → confirm the Description dialog shows the
agents_descriptions.md text.
- Open the node's configuration dialog → confirm every config key is editable with defaults → Save → reopen and confirm persistence.
- Save the flow as a
.flw, reload it, and confirm the node + connections + config restore (exercises acp-file-io.js).
- Press Start on a tiny flow and confirm the agent runs (LED green), writes its
<lower>.log, emits its INI_SECTION_<CAPS> (if a source), and triggers downstream.
- If the agent is VISIBLE/desktop (a window the user must SEE), and Angela asked to use Tlamatini's agents, launch it FOREGROUND with
dangerouslyDisableSandbox: true so the window renders on her real desktop (the Bash sandbox hides GUIs; run_in_background detaches them) — per feedback_run_tlamatini_agents_visible.
- To dogfood via Tlamatini's pool (not Claude's own tools): copy the agent to an isolated runtime dir, write a tailored
config.yaml, run python <lower>.py, then read <lower>.log for the result.
- In chat with Multi-Turn ON, ask the LLM to run the agent (
Run <Display> with ...) and confirm chat_agent_<lower> fires and returns the JSON result.
- If state-changing, toggle Exec Report ON and confirm the
List of <Display> Operations table renders with the correct gradient.
- If state-changing, toggle Ask Execs ON and confirm the Proceed/Deny prompt appears before the tool runs.
- Run the new demo prompt(s) from the catalog and confirm they execute end-to-end.
- Confirm the FlowHypervisor (start it on a flow with the agent) does NOT falsely flag the agent's normal output/timing.
- Confirm the command watchdog does not kill the agent's legitimate long-but-working run (if applicable).
- Confirm the orphan reaper leaves no
conhost.exe survivors after the agent finishes (check Task Manager / the Tier-2/3 logs).
- Write/update a memory file
project_<lower>_agent.md summarizing what was added, files touched, test counts, and the "frozen needs build.py / not committed" status — and add a one-line pointer to MEMORY.md.
- Per
feedback_track_changes_pivot_file, record the verbatim request + before/after of any prompt.pmt/registry/config.yaml default changes in a dated pivot note.
- Do NOT commit or push unless Angela explicitly asks (per the standard workflow + secret-leak caution — run
regen_secrets.py before any commit).
- If a commit IS requested, branch first if on
main, scrub secrets, and end the commit message with the required Co-Authored-By line.
- Tell Angela explicitly: the SOURCE instance reflects the change now; the FROZEN
C:\Tlamatini install needs python build.py + reinstall.
- Give Angela the final per-surface checklist (the summary below) so she can verify nothing was skipped.
Master per-surface checklist (every box must be ticked)
- ☐
agent/agents/<lower>/<lower>.py (boilerplate, _IS_REANIMATED before basicConfig, PID, concurrency guard, target trigger, INI_SECTION, temp guard, no agent.* import).
- ☐
agent/agents/<lower>/config.yaml (all params + connection fields, empty-string secrets, real numeric types).
- ☐
views.py::update_<lower>_connection_view + urls.py route.
- ☐ Migration
<NNNN>_add_<lower>.py (Agent row, exact <Display> casing).
- ☐ Migration
<NNNN+1>_add_chat_agent_<lower>_tool.py (Tool row) — if Multi-Turn.
- ☐
parametrizer.py::SECTION_AGENT_TYPES + views.py::PARAMETRIZER_SOURCE_OUTPUT_FIELDS + agent_contracts.py::_PARAMETRIZER_OUTPUT_FIELDS (identical field lists) — if a source.
- ☐
agentic_control_panel.css unique gradient (normal + hover).
- ☐
acp-agent-connectors.js connector update<Pascal>Connection.
- ☐
acp-canvas-core.js × 6 (classMap HYPHEN, NEVER_START HYPHEN, populateAgentsList shared helper, removeConnection/removeConnectionsFor/mouseup SPACED).
- ☐
acp-canvas-undo.js undo + redo (SPACED).
- ☐
acp-file-io.js both switches (SPACED).
- ☐
/* global */ in 3 JS files + eslint.config.mjs global.
- ☐
agentic_control_panel.html inputs/outputs connector cardinality correct (only special-shape agents touch rendering).
- ☐ Config dialog (
canvas_item_dialog.js) renders all keys (generic — bespoke only for special widgets).
- ☐
chat_agent_registry.py::ChatWrappedAgentSpec (key/template_dir/tool_name/display_name/purpose/example_request/aliases/security_hints) — if Multi-Turn.
- ☐
tools.py::_PROMOTE_SECTION_FIELDS_BY_TEMPLATE_DIR — if surfacing a section field.
- ☐
mcp_agent.py::_EXEC_REPORT_TOOLS + agent_page.css caption/accent — if state-changing.
- ☐
agent_page_chat.js::_mapToolArgsToAgentConfig branch — if Multi-Turn.
- ☐
flowcreator/agentic_skill.md entry + capability table + categories + selection-priority.
- ☐
flowhypervisor/monitoring-prompt.pmt SHORT/LONG list + SPECIAL NOTES + markers + TYPICAL TIMING + DO-NOT-FLAG.
- ☐ Demo-prompt migration (contiguous catalog, safe prompts, mode badges) + banner styling.
- ☐ Docs:
agents_descriptions.md, README.md (counts/tree/tables/glossary/changelog/API), CLAUDE.md, docs/claude/agents.md, package.json version.
- ☐
requirements.txt pin + build.py _agent_libs/--collect-all/bundle — if new dep.
- ☐
test_<lower>_agent.py (helpers, fake backend, INI round-trip, main end-stage, reanimation, registry integration, JS contract) — green + ruff clean.
- ☐ Playwright harness:
questions.py knowledge Qs + wrapped_questions.py execution Q + --select token — focused run PASS.
- ☐
ruff check clean + npm run lint errors clean + makemigrations --check clean + migrate applied.
- ☐ Live verify: palette → drag → wire → dialog → .flw round-trip → run → INI_SECTION → target trigger → Exec Report → Ask Execs → demo prompt → FlowHypervisor sane → reaper clean.
- ☐ Memory
project_<lower>_agent.md + MEMORY.md pointer + pivot note; "frozen needs build.py / not committed" stated.
Pitfalls index (the silent-failure traps — re-read before declaring done)
- Naming drift —
agentDescription is the only source of truth; CSS classMap (HYPHEN), connection handlers (SPACED), connector symbol (PascalCase), INI token (CAPS) each transform it differently. Fix it in the migration FIRST.
- Empty-string overwrites — writing
config.field='' (view, flow-generator, dialog) destroys the template default via the deep-merge. Always omit-if-empty / use the set() helper.
- Pool-name cardinal mismatch — emit
<lower>_N (underscore + cardinal), never bare <lower> or <lower>-N, into connection lists, or the Starter fails on the first hop.
- Forgetting
_IS_REANIMATED — without the marker before basicConfig, the log truncates on every resume.
- Missing concurrency guard —
wait_for_agents_to_stop must precede start_agent in looping flows.
_EXEC_REPORT_TOOLS miss — a state-changing agent without the map entry shows no table (silent data loss); an observational agent wrongly added shows a spurious table.
- Flow-Generator miss — Multi-Turn-callable agent without a
_mapToolArgsToAgentConfig branch produces a .flw node with empty config.
- 6 JS edit locations —
acp-canvas-core.js touches connections in 6 places; missing one breaks creation/removal/undo/redo/.flw-load.
- CSS gradient duplicated in JS — never type a gradient in JS; use
applyAgentToolIconStyle.
- Importing
agent.* from a pool subprocess — ModuleNotFoundError at runtime; port inline.
- Temp/Templates outside Tlamatini — scratch →
<app>/Temp, scaffold → <app>/Templates; never C:\Temp/%TEMP%/bare tempfile.
- Payload whitelist — don't disturb
UnifiedAgentChain.invoke's rebuild whitelist (acpx_enabled/exec_report_enabled/ask_execs_enabled/conversation_user_id/multi_turn_enabled).
- Parametrizer field-list drift — the three lists (parametrizer.py, views.py, agent_contracts.py) must be identical.
- Catalog contiguity — a gap in
idPrompt breaks the prompts dropdown at the first missing slot.
- FlowHypervisor false positives — a long-running/observational/structured-output agent without its SPECIAL NOTES gets wrongly flagged as stuck or errored.
- Watchdog — keep child processes making progress and fed EOF on stdin; never block with zero CPU+IO.
- Frozen vs source — source instance reflects edits immediately; frozen
C:\Tlamatini needs python build.py + reinstall. State this every time.
- Build collisions — never run a background
build.py while Angela may build (.build.lock, ~18 min, they clobber shared dirs).
- Test softness — make tests HARD (real code, real incident, error+clean+overflow); soft happy-path tests miss real bugs.
- Test toggles — automated chat tests set AND verify Multi-Turn ON / Exec-Report per intent / Ask-Execs OFF, and clear history first.
- Secret leak — run
regen_secrets.py before any commit; config carries live keys in dev.
Quick mental model (the one-paragraph version)
- A new Tlamatini agent is a self-contained Python pool subprocess (
agent/agents/<lower>/<lower>.py + config.yaml) that the canvas can drag, wire, configure, start, and monitor; that the Multi-Turn LLM can launch as chat_agent_<lower>; that Parametrizer can read as a source; that FlowCreator can design into a .flw; that FlowHypervisor watches; that the watchdog/reaper keep clean; and that is surfaced in CSS (gradient), the connection views, a migration (Agent + Tool rows), demo prompts, docs, packaging, a hard Python test module, and a Playwright harness question. Decide the name and shape ONCE (Phase 0), then propagate the SAME identity across all ~30 surfaces without drift. When in doubt, copy the most recent fully-wired sibling (Camcorder / Recorder) verbatim and change only the identity tokens.