--- - name: Deploy Kottos (Pallas FastAgent runtime) hosts: ubuntu vars: ansible_common_remote_group: "{{ kottos_group | default([]) }}" allow_world_readable_tmpfiles: true tasks: - name: Check if host has kottos service ansible.builtin.set_fact: has_kottos_service: "{{ 'kottos' in services | default([]) }}" - name: Skip hosts without kottos service ansible.builtin.meta: end_host when: not has_kottos_service - name: Create Kottos group become: true ansible.builtin.group: name: "{{ kottos_group }}" state: present - name: Create kottos user become: true ansible.builtin.user: name: "{{ kottos_user }}" group: "{{ kottos_group }}" home: "/home/{{ kottos_user }}" shell: /bin/bash system: false create_home: true - name: Add keeper_user to kottos group (optional — enables passwordless tailing) become: true ansible.builtin.user: name: "{{ keeper_user }}" groups: "{{ kottos_group }}" append: true when: keeper_user is defined - name: Reset connection to pick up new group membership ansible.builtin.meta: reset_connection - name: Create Kottos install directory become: true ansible.builtin.file: path: "{{ kottos_directory }}" owner: "{{ kottos_user }}" group: "{{ kottos_group }}" state: directory mode: '0750' - name: Ensure base packages for Python + Docker MCP workflows become: true ansible.builtin.apt: name: - tar - python3 - python3-venv - python3-dev - git state: present update_cache: true - name: Transfer and unarchive Kottos release become: true ansible.builtin.unarchive: src: "~/rel/kottos_{{ kottos_rel }}.tar" dest: "{{ kottos_directory }}" owner: "{{ kottos_user }}" group: "{{ kottos_group }}" mode: '0550' notify: restart kottos - name: Ensure .venv directory ownership is correct become: true ansible.builtin.file: path: "{{ kottos_directory }}/.venv" owner: "{{ kottos_user }}" group: "{{ kottos_group }}" state: directory recurse: true when: ansible_facts['file'] is defined or true - name: Create virtual environment for Kottos become: true become_user: "{{ kottos_user }}" ansible.builtin.command: cmd: "python3 -m venv {{ kottos_directory }}/.venv/" creates: "{{ kottos_directory }}/.venv/bin/activate" - name: Install wheel in the virtualenv become: true become_user: "{{ kottos_user }}" ansible.builtin.pip: name: - wheel state: latest virtualenv: "{{ kottos_directory }}/.venv" - name: Install Kottos (pyproject.toml — pulls in pallas-mcp and fast-agent-mcp) become: true become_user: "{{ kottos_user }}" ansible.builtin.pip: chdir: "{{ kottos_directory }}/kottos" name: . virtualenv: "{{ kottos_directory }}/.venv" virtualenv_command: python3 -m venv notify: restart kottos - name: Template agents.yaml become: true ansible.builtin.template: src: agents.yaml.j2 dest: "{{ kottos_directory }}/agents.yaml" owner: "{{ kottos_user }}" group: "{{ kottos_group }}" mode: '0640' notify: restart kottos - name: Template fastagent.config.yaml become: true ansible.builtin.template: src: fastagent.config.yaml.j2 dest: "{{ kottos_directory }}/fastagent.config.yaml" owner: "{{ kottos_user }}" group: "{{ kottos_group }}" mode: '0640' notify: restart kottos - name: Template fastagent.secrets.yaml (vault-rendered) become: true ansible.builtin.template: src: fastagent.secrets.yaml.j2 dest: "{{ kottos_directory }}/fastagent.secrets.yaml" owner: "{{ kottos_user }}" group: "{{ kottos_group }}" mode: '0600' notify: restart kottos no_log: true - name: Template runtime .env (PALLAS_LOG_STDOUT etc.) become: true ansible.builtin.template: src: .env.j2 dest: "{{ kottos_directory }}/.env" owner: "{{ kottos_user }}" group: "{{ kottos_group }}" mode: '0640' notify: restart kottos - name: Template systemd unit become: true ansible.builtin.template: src: kottos.service.j2 dest: /etc/systemd/system/kottos.service owner: root group: root mode: '0644' notify: restart kottos - name: Enable and start kottos service become: true ansible.builtin.systemd: name: kottos enabled: true state: started daemon_reload: true - name: Flush handlers before validation probes ansible.builtin.meta: flush_handlers # ── Validation ────────────────────────────────────────────────────────── # Registry is the only endpoint that responds with a deterministic JSON # payload without requiring an MCP session, so we probe it. Agent ports # are exercised by Daedalus's health-poll loop once registered. - name: Validate Kottos registry responds ansible.builtin.uri: url: "http://localhost:{{ kottos_registry_port | default(24100) }}/.well-known/mcp/server.json" status_code: 200 return_content: true register: registry_check retries: 10 delay: 3 until: registry_check.status == 200 handlers: - name: restart kottos become: true ansible.builtin.systemd: name: kottos state: restarted