Skip to content

The agents runner runs Mozart on the platform #281

Closed

Description

@FlexOr2

Parent: #1

The agents runner runs Mozart and Leonardo on the platform

Caller: overnightworks/personas#22, step K: the live run of the agent Mozart. Its code has landed (overnightworks/personas#30, PR overnightworks/personas#32). personas' head (then agents') asked on 09.11.2026 for this item, so that #22 can record it as a blocker. Leonardo joined on 10.11.2026 (overnightworks/personas#61, the operator's „bitte noch leonardo hinzufügen! (da vinci)“, comment 6756608585). The operator ordered on 12.11.2026 that the platform builds what his first Skat game with Mozart and Leonardo needs, in the order #466, then this item, then skat's release pin (comment 6811230358 on #466, relaying his „ja sag ihr bescheid!“); the open list of that game is overnightworks/personas#3, comment 6805833230, rows 1, 2 and 4.

What personas asks for (personas' README at v2026.11.11.1 owns the configuration keys)

  • The image is personas' release ghcr.io/overnightworks/personas:2026.11.11.1@sha256:62b38f4bce62185cb096badbb360e604a679d52d94ee02526d595b0dbbd441ef, tag v2026.11.11.1 on commit b5a011b3567c, which the operator said yes to on 12.11.2026 (comment 6810246868). The images job decides its attestation at the pull. The images at ghcr.io/overnightworks/agents and personas' v2026.11.09.3 are not used.
  • PERSONAS_CONFIG points at a TOML file in a ConfigMap that holds:
    • the realm's token endpoint;
    • an absolute ready_file;
    • the apps that listen: hopin and skat, each with its public MCP URL through the edge, its scope, and the tools its turns may call with max_tool_calls; skat's tools are the names of skat's tools/list at the release the skat pin takes;
    • the [gateway] table (backend, api_group and [gateway.gateway] with origin and key_file);
    • one [[agents]] entry each for Mozart and Leonardo: client id, client secret file and persona_file. It sets no max_turns and no turn_period_seconds: the operator ruled on 09.11.2026 that agents start with no turn cap until he turns one on (An agent hears a conversation and speaks in it: one voice loop for every app agents#803, comment 6739177891).
  • The persona files ship in the image at /app/characters/mozart.md and /app/characters/leonardo.md, and the configuration names them there. No ConfigMap holds a persona text: the operator's yes of 10.11.2026 (personas owns the texts, they ship in its image, the platform holds no copy) overtakes this item's earlier sentence that the persona file is configuration (comment 6806186967).
  • Three secret files are mounted from the Secret personas: each agent's client secret, a copy of the value the Secret keycloak holds for config-cli, and the runner's LiteLLM key.
  • Both persona files name the model text/sonnet, which the gateway must offer.
  • Readiness is test -e <ready_file>. The runner opens no port, needs no init and exits 0 on SIGTERM; the Pod's normal grace period is enough.
  • The runner runs as the non-root uid 1001, writes only its ready_file, opens no port and spawns no process. Its egress: the realm's token endpoint and the MCP URLs of hopin and skat through the edge, and the LiteLLM front.

Known on main 1146b59

  • The Keycloak cutover (P10: hal's public services move onto the platform and hal stops #235) is through: the platform's Keycloak is the issuer https://auth.hallucinai.de/realms/hallucinai.
  • realm.yaml holds agent-mozart with hopin's scope; The realm lets the agent clients into skat #466 adds agent-leonardo and skat's scope, audience and /apps/skat/mcp for both.
  • LiteLLM offers text/sonnet (infrastructure/litellm/litellm-config.yaml). The front admits a fixed list of namespaces; a key per app comes from keys.yaml through litellm:key <app>, which reads litellm_key of the Secret <app> in namespace <app>.
  • Every request names its person as user, and the agents library names the agent itself, so each agent's spend is also bound by the person budget of keys.yaml.

Decided at the pull

  • The namespace, app and Secret are personas, after the repository's name.
  • The NetworkPolicy admits nobody and sends only to cluster DNS, the front's port 4000 and TCP 443 outside the pod and service networks, as agent-egress does: a NetworkPolicy cannot name a host, and the token endpoint and both MCP URLs are public names behind Cloudflare.
  • The cluster job applies the Deployment at 0 replicas, as it does for cloudflared and the acestep worker: in k3d the public MCP URLs resolve to the live apps, so a pod there would either never turn Ready or call the PC's apps with a dummy secret.
  • The runner keeps no memory ([memory] left out), so it has no Postgres and no backup; a restart starts each history empty.
  • Streaming: Traefik passes a streamed answer through as it arrives; the proof is the first turn on the PC, read as a successful spend-log row of the key personas, by counts only.

Gaps

  • The egress rule admits every public host on 443, not only the three names; the runner sends only to the URLs of its configuration.
  • No CI cell proves the policy's reach or a streamed answer through the front; the PC's first game does.
  • A person of hopin cannot add the agents to a call until hopin's adders setting (The AI service's legal section names agents the operator runs in a call #277); the skat loop answers only once skat's release with /mcp runs (skat's pin, after this item). Until then the loop of that app is suspended and logged once.

Open for the operator

  • The money: the key row personas in keys.yaml (budget and parallel limit per month), and whether each agent stays under the person budget of 2 dollars a month, which bounds an agent like a person. Recommended: budget 10, parallel 2, the person budget unchanged for the first game.
  • PD-5 of docs/personal-data.md, a ruled sentence, names the namespaces the front admits; it gains personas. Recommended: yes, as the AI path's new caller is what his order of 12.11.2026 asks for.

Deliberately not in it

Risk

Material: a new caller of the AI path with its own key and money budget, a copy of two client secrets, and a new egress to the internet.

version = 1
now = "Pulled on 12.11.2026 on the operator's order of the first Skat game with Mozart and Leonardo, after #466; personas v2026.11.11.1 is released and the body is corrected to it."
next = "The lane builds apps/personas, its Namespace, Kustomization, front admission and key row and the cluster job's changes; the operator writes the Secret personas and Leonardo's client secret into platform-secrets; after landing, litellm:key personas and litellm:keys."
done_when = "The Deployment personas runs personas' release 2026.11.11.1 at a full app ref in the restricted Namespace personas, reads PERSONAS_CONFIG with Mozart and Leonardo and hopin and skat as listening apps, mounts its three secret files from the Secret personas, is Ready by its ready_file on the PC, opens no port, stops on SIGTERM within its grace, sends only to cluster DNS, the LiteLLM front and TCP 443 outside the pod and service networks, and its key row stands in keys.yaml with its key registered by litellm:key."

scope = [".github/workflows/ci.yml", "apps/personas", "clusters/pc/apps.yaml", "docs/personal-data.md", "infrastructure/litellm/front.yaml", "infrastructure/litellm/keys.yaml", "infrastructure/namespaces", "tests/flux/reconcile_the_candidate_tree.sh", "tests/flux/root_and_tunnel_carry_no_spec_patches.sh", "tests/litellm/keys_table_drives_budgets_and_the_app_key.sh", "tests/secrets/encrypt_secrets_to_a_throwaway_age_key.sh", "tests/workloads/apps_run_at_their_full_ref.sh"]

size = "M"

whole = "One app's first deployment: its manifests, its Namespace, its Kustomization, its key row and front admission and the cluster job's 0-replica patch and dummy Secret are one change, since a part alone either never turns Ready, fails the cluster job or admits a sender that runs nothing."

Activity

  1. added theissue type on Nov 9, 2026
  2. FlexOr2 commented on Nov 9, 2026

    @FlexOr2
    MemberAuthor

    overnightworks/agents#35 landed on 09.11.2026 (PR overnightworks/agents#36, 99ac636), as agents' head reports. The body's two precondition sentences now hold on agents' side:

    • No init. SIGTERM ends the runner with exit 0. agents' CI job Image proves it as PID 1 with docker stop.
    • The persona file goes in a ConfigMap. agents' README says the deployment supplies the configuration, persona file included, and the secret files.

    So the Deployment needs neither an init nor shareProcessNamespace. #281 is still blocked by #235 and agents' release.

  3. FlexOr2 commented on Nov 9, 2026

    @FlexOr2
    MemberAuthor

    agents' first release exists. agents' head dispatched it on the operator's yes, and its release run is green (Gate, Image, Tag).

    • Tag v2026.11.09.1, on commit 99ac63633efa38d61e5578d3f42ba2f9357cd2b6.
    • Image ghcr.io/overnightworks/agents:2026.11.09.1@sha256:c5f8862f52499773af85bc07695b2379ef08e8002352b0df2c4864c91d78dabe. The digest comes from the Image job's push step. agents' token cannot read packages, so the digest is not cross-checked against GHCR; the images job's attestation check decides it at the pull.
    • Run: https://github.com/overnightworks/agents/actions/runs/42606973416

    That clears the item's second precondition. It stays blocked by #235 alone.

  4. FlexOr2 commented on Nov 9, 2026

    @FlexOr2
    MemberAuthor

    agents' release for this item exists. It carries agents#37, which makes both turn-cap fields optional: leave both out, since setting only one is refused at start.

    • Tag v2026.11.09.2 at cbfd8917e27f2a536f1173e958c2a86d3aafb3e6. Release run 42618131199 succeeded.
    • Image: ghcr.io/overnightworks/agents:2026.11.09.2@sha256:eae4a6843c98735e262785b4e9fe9a56e99aa87e8df86cfb25373905b2bf7f9e.
    • What the head checked: GitHub's attestation API lists one attestation whose subject is this digest, built from cbfd8917e27f, the commit the tag points to. The signature was not verified locally, because the registry read needs a login. The images job's gh attestation verify on the lane's pull request is that proof.
    • The run's log also names ghcr.io/overnightworks/agents@sha256:2b4b608f…, most likely the attestation's own manifest. That is not the image.

    The item still waits for #235: Keycloak has to be live on the platform first.

  5. FlexOr2 commented on Nov 9, 2026

    @FlexOr2
    MemberAuthor

    personas' first release under its own name exists. It is the one this item takes. The operator said yes to this version, and its release run is green (Gate, Image, Tag).

    What personas' head checked. GitHub's attestation API lists one attestation whose subject is this digest under ghcr.io/overnightworks/personas, built from 9860838a73fd, the commit the tag points to. The signature was not verified locally; the images job's attestation check decides it at the pull.

    What this item's body must change. It still states the names from before the rename:

    Body today Now
    the image from agents' release ghcr.io/overnightworks/personas, at the ref above. The older images stay at ghcr.io/overnightworks/agents and are not to be used
    AGENTS_CONFIG PERSONAS_CONFIG. AGENTS_CONFIG is no longer read, and the runner refuses to start without the new variable

    The rest stands: [[agents]] in the TOML keeps its name, the image's user keeps uid 1001, the Keycloak names stay (agent-mozart), and max_turns and turn_period_seconds stay unset.

    One change in behaviour. This release carries agent-runtime v0.28.0, so every request to the gateway now carries "stream": true and reads a streamed answer. The route to LiteLLM must let a streamed response through.

    Correction. The digest in #281 (comment) is wrong: sha256:c5f8862f… is the moby/buildkit image the Image job pulls, not the runner's image. personas' head read it from the wrong log line. git grep over platform's tree finds neither that digest nor the old image name, so nothing uses it. v2026.11.09.2's digest was checked against its attestation and is right, but this item takes .3.

  6. FlexOr2 commented on Nov 10, 2026

    @FlexOr2
    MemberAuthor

    A second agent, from personas' head on 10.11.2026 (overnightworks/personas#61, the operator's „bitte noch leonardo hinzufügen! (da vinci)“): the runner's configuration gets a second [[agents]] entry for Leonardo (client id agent-leonardo, its secret file, its persona) as a row of this item; the realm's client is #394. Open for this item's pull, with personas' head: where a persona's text comes from. This body says "The persona file is configuration and goes in a ConfigMap"; personas' head proposes that personas owns the texts as files in its repository and the platform takes them from a release. The platform's head prefers the text inside personas' image, named by the configuration, so the character has one owner and the platform holds no copy to refresh; the fallback is a byte-for-byte copy held equal to the release by a check, as the legal app texts are.

  7. FlexOr2 commented on Nov 10, 2026

    @FlexOr2
    MemberAuthor

    Asked by hopin's head on 10.11.2026 (the operator to it, on Mozart in a HopIn call: „ja ich würde es testen wenn es geht!“): what on the platform stands between him and that test. Read on main f52d27e:

    1. The issuer. hopin on the platform trusts https://auth.hallucinai.de/realms/hallucinai, which is still hal's Keycloak. hal's realm on its origin/main holds no agent-mozart; the client, the groups and agent_owner exist only in the platform's Keycloak, which goes live with P10: hal's public services move onto the platform and hal stops #235. hal is the predecessor and gets no new caller, so Mozart gets a token hopin accepts only after the issuer's move. This item is blocked by P10: hal's public services move onto the platform and hal stops #235 for the same reason.
    2. The AI service's legal section names agents the operator runs in a call #277 (the legal sentence) waits for P9a-6 of P9: legal pages, person export and erasure, AI keys and expiry checks move from hal #173, which has not landed: the section still carries its old name.
    3. HOPIN_AGENT_ADDERS in apps/hopin/hopin.yaml follows The AI service's legal section names agents the operator runs in a call #277 by its body; it needs the owner's subject and the group path, both the operator's (keycloak:agent-owner sets the owner; /agent-adders is declared in realm.yaml).

    So the order is #235's issuer move, then this item and the adders line; whether his own test may run before #277 is his to rule and changes nothing before step 1.

  8. FlexOr2 commented on Nov 11, 2026

    @FlexOr2
    MemberAuthor

    From personas' head, 11.11.2026 (to apply to this body at its pull, after checking the source): the persona files ship in personas' image at /app/characters/<name>.md (overnightworks/personas#95, built after personas#93). personas' head names the operator's yes of 10.11.2026 (personas owns the texts, they ship in its image, the platform holds no copy) as overtaking this body's sentence that the persona file is configuration in a ConfigMap: Mozart's entry sets persona_file = "/app/characters/mozart.md", no ConfigMap holds a persona, and the image is a personas release that holds the files (v2026.11.09.3 holds none; tag and digest follow). The files name the model text/sonnet; if the platform's gateway offers other names after #235, personas changes one line before its release. The skat rights are #466.

  9. FlexOr2 commented on Nov 12, 2026

    @FlexOr2
    MemberAuthor

    personas v2026.11.11.1 is released (operator's yes, 12.11.2026), from main b5a011b3567c423ad4e035408ad1757f920e5426.

    • Image: ghcr.io/overnightworks/personas:2026.11.11.1@sha256:62b38f4bce62185cb096badbb360e604a679d52d94ee02526d595b0dbbd441ef; the attestation names this image and that commit.
    • New since v2026.11.09.3: turns at a view place (a Skat table), one turn per revision; every turn is told his history across apps; Mozart's and Leonardo's agent files ship in the image.
    • What the deployment changes: persona_file = "/app/characters/mozart.md" and /app/characters/leonardo.md, no ConfigMap for persona texts; the gateway must offer text/sonnet, which both files name.
    • Release page: https://github.com/overnightworks/personas/releases/tag/v2026.11.11.1
  10. FlexOr2 commented on Nov 12, 2026

    @FlexOr2
    MemberAuthor

    Build plan for this item (fresh Opus planner, read-only, 12.11.2026). Since it was written: #466's lane carries config-cli.yaml and the CI group list, and the Secret keycloak holds client-agent-leonardo (overnightworks/platform-secrets#55 landed).

    #281 plan: the personas runner with Mozart and Leonardo

    Read on platform main 1146b59, personas v2026.11.11.1 (b5a011b), agents library v0.31.0, hopin v2026.11.11.1,
    skat main cc5f93a, the #466 lane branch at 5867e33, and platform-secrets' tree (key names only).

    1. Files the lane creates or changes

    Production (7 files, about 150 lines; above the 3-file guide because one app's first deployment does not split, see
    whole):

    File What it holds
    apps/personas/personas.yaml (new) Deployment personas in namespace personas: replicas 1, Recreate, label app.kubernetes.io/name: personas, automountServiceAccountToken: false, enableServiceLinks: false, imagePullSecrets: [ghcr-pull], pod runAsNonRoot: true + seccompProfile: RuntimeDefault (the image's numeric USER 1001, so no runAsUser, as hopin), one container personas at ghcr.io/overnightworks/personas:2026.11.11.1@sha256:62b38f4bce62185cb096badbb360e604a679d52d94ee02526d595b0dbbd441ef, no ports, env PERSONAS_CONFIG=/etc/personas/personas.toml, readiness exec: [test, -e, /tmp/ready] (no liveness: a restart empties both histories), container allowPrivilegeEscalation: false, readOnlyRootFilesystem: true, capabilities.drop: [ALL]; volumes config (the generated ConfigMap, at /etc/personas, read-only), secrets (Secret personas, at /run/secrets, read-only; the volume must be named secrets, which the CI generator reads), tmp (emptyDir: {medium: Memory} at /tmp, for ready_file). Plus the NetworkPolicy personas (section 3). No Service, no Ingress.
    apps/personas/kustomization.yaml (new) namespace: personas, resources: [personas.yaml], configMapGenerator: [{name: personas, files: [personas.toml]}], so a changed configuration changes the ConfigMap's hashed name and rolls the pod (the runner reads it once, at start), as infrastructure/litellm/ does.
    apps/personas/personas.toml (new) The runner's configuration (section 2).
    clusters/pc/apps.yaml Kustomization personas: dependsOn: [platform-secrets, admission], path ./apps/personas, prune: true, wait: true, timeout: 10m0s, interval 10m, source flux-system. Header comment gains personas.
    infrastructure/namespaces/namespaces.yaml Namespace personas with kustomize.toolkit.fluxcd.io/prune: disabled and pod-security.kubernetes.io/enforce: restricted.
    infrastructure/litellm/front.yaml The front's NetworkPolicy values: [hopin, presentator, songmaker, personas, gatus]; its comment "the three apps" becomes four.
    infrastructure/litellm/keys.yaml Row personas: {budget: <operator>, parallel: <operator>} (recommended 10 and 2).

    Documentation (same change):

    Tests and CI (not counted as production):

    File Change
    .github/workflows/ci.yml APP_MANIFESTS gains apps/personas/personas.yaml.
    tests/secrets/encrypt_secrets_to_a_throwaway_age_key.sh A case arm personas) app_keys="litellm_key mozart_client_secret leonardo_client_secret" ;;, so the dummy Secret holds the three keys (the *) arm would give hopin's). The loop already makes personas/ghcr-pull.
    tests/flux/reconcile_the_candidate_tree.sh A patch on the Kustomization personas that replaces the Deployment's /spec/replicas with 0, with the reason (k3d resolves the public MCP URLs to the live apps).
    tests/flux/root_and_tunnel_carry_no_spec_patches.sh clusters/pc/apps.yaml:personas joins the list of patched layers.
    tests/workloads/apps_run_at_their_full_ref.sh The 0-replica branch holds today one hard-coded expectation, acestep-worker's policy (/music-worker,/web -> 8001/TCP ...). It becomes keyed by $namespace/$name; personas' expectation is ingress empty and egress kube-system/kube-dns -> 53/TCP,53/UDP;litellm/litellm-front -> 4000/TCP;0.0.0.0/0 except 10.42.0.0/16,10.43.0.0/16 -> 443/TCP, which needs an ipBlock arm in the sources expression. The template dry-run there already proves restricted admits the Pod.

    What cluster then demands of the ordinary app pattern, all met by the shape above: the Namespace enforces restricted
    and carries prune: disabled; the layer depends on platform-secrets and admission and applied the candidate;
    Recreate; a full app ref; ghcr-pull of type dockerconfigjson and the Secret personas in platform-secrets'
    inventory, created before the Deployment; every container at a digest (deployments_run_the_digests_they_name.sh
    skips a 0-replica one); the images job verifies tag v2026.11.11.1 -> b5a011b, the attestation by digest and the
    signer (personas' release calls quality's image.yml at f7723ba, v0.11.0); the repository is public, the package
    pulls through ghcr-pull like every app's. "Available with one Ready pod" is proven only on the PC.

    2. The configuration and where each value comes from

    token_endpoint = "https://auth.hallucinai.de/realms/hallucinai/protocol/openid-connect/token"
    ready_file = "/tmp/ready"
    
    [[apps]]
    mcp_url = "https://hopin.hallucinai.de/mcp"
    scope = "mcp-hopin"
    tools = ["say"]
    max_tool_calls = 2
    
    [[apps]]
    mcp_url = "https://skat.hallucinai.de/mcp"
    scope = "mcp-skat"
    tools = [ ...skat's tools/list at the release the skat pin takes... ]
    max_tool_calls = 4
    
    [gateway]
    backend = "litellm"
    api_group = "/agents"
    
    [gateway.gateway]
    origin = "http://litellm-front.litellm:4000"
    key_file = "/run/secrets/litellm_key"
    
    [[agents]]
    client_id = "agent-mozart"
    secret_file = "/run/secrets/mozart_client_secret"
    persona_file = "/app/characters/mozart.md"
    
    [[agents]]
    client_id = "agent-leonardo"
    secret_file = "/run/secrets/leonardo_client_secret"
    persona_file = "/app/characters/leonardo.md"
    Value Source
    Issuer, token endpoint The issuer every app names (https://auth.hallucinai.de/realms/hallucinai, live on the platform's Keycloak since #235) plus Keycloak's fixed token path; Traefik's auth router passes PathPrefix(/realms/hallucinai/).
    ready_file Any absolute writable path; /tmp is the memory-backed emptyDir. personas removes it at start and creates it at the first answered standing.
    Client ids agent-mozart (realm.yaml today), agent-leonardo (#466's branch).
    Client secrets Copies in Secret personas/personas, keys mozart_client_secret (equal to client-agent-mozart of keycloak/keycloak) and leonardo_client_secret (equal to client-agent-leonardo, which #466 needs in keycloak/keycloak too). A Secret cannot be mounted across namespaces, so the copy is unavoidable; a rotation writes both, named in the YAML comment. Mounted read-only at /run/secrets.
    Gateway origin http://litellm-front.litellm:4000, as hopin's HOPIN_AGENT__GATEWAY__ORIGIN; HTTP, so ca_file is forbidden by agents' ChatGateway. The library calls /v1/chat/completions (streamed) and /v1/models, both routes of front-route.yml.
    Key file /run/secrets/litellm_key, key litellm_key of Secret personas/personas, which litellm:key personas registers (it reads exactly that name). sk- prefix and at least 16 characters, as litellm_keys.sh requires.
    backend litellm, the only BackendKind.
    api_group The library refuses a turn whose person is not in this group, and personas' person is the agent's token. hopin sets /services/litellm (the persons' AI right), of which neither service account is a member. Recommended /agents: every agent token carries it, it needs no new realm right, and only the two clients whose secrets the pod holds can spend the runner's key. The alternative, /services/litellm for both service accounts, is a realm change in #466.
    Model text/sonnet, which both persona files name; LiteLLM offers it today (litellm-config.yaml, anthropic/claude-sonnet-5-5, max_tokens 16384), and litellm:key gives the key every model LiteLLM lists. Nothing has to change.
    hopin MCP https://hopin.hallucinai.de/mcp, scope mcp-hopin, audience https://hopin.hallucinai.de/mcp (realm). hopin's own-account tools at v2026.11.11.1 are say, leave_call, accept_invitation (the runner's own) and join_call (refuses an own account). Recommended tools = ["say"]: personas' agents never leave on their own. Readiness comes from this loop, since hopin's standing answers today.
    skat MCP https://skat.hallucinai.de/mcp, scope mcp-skat, audience https://skat.hallucinai.de/mcp (SKAT_PUBLIC_ORIGIN + /mcp, #466). skat's running release 2026.11.09.1 has no /mcp: the loop gets 404, is suspended with http_404 and logged once, and resumes after skat's pin, without a restart. skat main cc5f93a names 28 tools (read_table, read_series_list, read_paused_tables, play_card, auction_bid, auction_hold, auction_pass, auction_play, give_up_game, throw_down_cards, agree_to_give_up, decline_give_up, continue_after_result, agree_to_end_series, refuse_to_end_series, start_new_series, take_skat, put_back_cards, declare_game, ramsch_take_skat, ramsch_put_back_cards, ramsch_push_skat, say_kontra, open_table, sit_down, stand_up, read_rule_sets, save_rule_set). personas holds a turn only when the app still declares every bound tool (bound_declarations returns None otherwise and the turn does not run), so the list must be a subset of the release's tools/list: take skat's head's list for the release, and the skat pin lane checks it.
    max_tool_calls Not given by any owner. Recommended hopin 2, skat 4 (take_skat, put_back_cards, declare_game in one view turn, plus one spare); personas' head may confirm.
    Limits personas sets no limit of its own; the key row bounds the runner (budget, parallel), the person budget bounds each agent (section 6). history_bytes stays at its default 32768.

    3. The NetworkPolicy personas

    podSelector: {app.kubernetes.io/name: personas}, policyTypes: [Ingress, Egress], no ingress rule (nobody), egress:

    1. kube-system pods k8s-app: kube-dns, UDP and TCP 53;
    2. namespace litellm, pods app.kubernetes.io/name: litellm-front, TCP 4000 (the front's own policy admits the
      namespace personas);
    3. ipBlock 0.0.0.0/0 except [10.42.0.0/16, 10.43.0.0/16], TCP 443: the edge, for the token endpoint and both MCP
      URLs, the form agent-egress uses. The gap (any public host on 443) is named in the body.

    4. Secrets in platform-secrets, and who writes them

    Object Keys Who
    namespaces/personas/personas.yaml, Secret personas in personas, Opaque litellm_key, mozart_client_secret, leonardo_client_secret The operator: the Mozart value is a copy that needs decrypting keycloak.yaml, and every key after a file's first needs the PC's age key, which only he reads.
    namespaces/keycloak/keycloak.yaml adds client-agent-leonardo The operator, same block (existing file); needed by #466's config-cli as well.
    registry/personas/kustomization.yaml + a line - personas in registry/kustomization.yaml none (structure) An agent, in the same platform-secrets pull request.

    Landing order: platform's #281 PR first (it creates the Namespace; the layer personas then waits, Not Ready, for its
    Secret), then the platform-secrets PR, since a namespace missing on the PC stops the whole platform-secrets
    Kustomization. #466 and its client-agent-leonardo land before both.

    The operator's block, ready to paste, in the platform-secrets worktree the head creates for this step on a branch (path
    assumed ~/git/platform-secrets-worktrees/281-personas; the head fixes it if aco names another):

    cd ~/git/platform-secrets-worktrees/281-personas && mise trust && mise install && printf 'sk-%s' "$(openssl rand -hex 32)" | mise run secrets:put personas personas litellm_key && mise exec -- sops --decrypt --extract '["data"]["client-agent-mozart"]' namespaces/keycloak/keycloak.yaml | base64 --decode | mise run secrets:put personas personas mozart_client_secret && leonardo="$(openssl rand -hex 32)" && printf '%s' "$leonardo" | mise run secrets:put keycloak keycloak client-agent-leonardo && printf '%s' "$leonardo" | mise run secrets:put personas personas leonardo_client_secret && unset leonardo

    It prints only file, key and added. If #466 has already written client-agent-leonardo, the operator instead copies
    it, as Mozart's line does. Then the agent commits, opens and lands that pull request.

    After both land, the agent (kubectl on the PC, no sudo, no value seen) runs: mise run litellm:key personas, then
    mise run litellm:keys --plan and mise run litellm:keys; mise run keycloak:agent-owner agent-leonardo <operator's username> and the same for agent-mozart (a second run prints nothing changed), since skat and hopin refuse a token
    without agent_owner.

    5. Proof

    • CI: cluster (0 replicas: template admitted by restricted, Secrets and pull Secret in the inventory before the
      Deployment, full ref, layer order, the policy as rendered), images (attestation), quality's gate.
    • PC: the Deployment Available with one Ready pod (the hopin loop answered: token and MCP work through the edge); the
      pod's log holds no ConfigError; after skat's pin, the skat loop's resumed; the first game's first turn as a
      successful spend-log row of the key personas (counts only) proves the streamed route through the front.

    6. Open questions that are the operator's

    1. Money, the key row: budget and parallel per month for personas. Recommended budget 10, parallel 2 (two agents, one
      turn each at a time).
    2. Money, per agent: each request names the agent as its person, so the person budget person_budget: 2 of
      keys.yaml caps each agent at 2 dollars a month, like every person. Whether a game fits is not verified (Sonnet
      5.5's price per token is not read here; each turn sends up to 32 KB of history plus the table). Options: keep it
      for the first game (recommended: it fails visible, a turn ends with turn_ended and the agent waits), raise the
      person budget for everyone, or a later item for a per-agent budget row (litellm:admin cannot help: it refuses
      service accounts and is unlimited).
    3. PD-5 of docs/personal-data.md gains personas: recommended yes, covered in substance by his order of 12.11.2026,
      but the commit has to name his yes.

    7. What a first game can leave out without corrupting data or opening a hole

    Not to be left out: the NetworkPolicy (the pod holds the runner's key and two client secrets), restricted with a
    read-only root, and ghcr-pull.

    8. Risks and cross-lane findings

    • The realm lets the agent clients into skat #466's branch (5867e33) uses $(env:AGENT_LEONARDO_CLIENT_SECRET) but changes neither
      infrastructure/keycloak/config-cli.yaml (no env from client-agent-leonardo) nor its scope; the keycloak Secret
      has no client-agent-leonardo key today, and ci.yml's AGENT_GROUPS still lists the groups without
      /apps/skat/mcp. If config-cli refuses an unset variable, both its CI and the PC's apply fail, and Leonardo cannot
      sign in, which blocks this item's Leonardo half.
    • The subscription streams go through Cloudflare, which may cut a stream idle for about 100 seconds; personas reconnects
      a stream that ends, so this costs reconnects, not correctness (not measured).
    • A skat tool list that is not a subset of the release's tools/list silently leaves the agents unable to act at a
      table (seated but silent); the skat pin lane must compare them.
    • The AI service's legal section names agents the operator runs in a call #277: only the operator may meet the agents until the AI section names agents the operator runs; a Skat table of
      the operator with Mozart and Leonardo meets that.
  11. FlexOr2 commented on Nov 12, 2026

    @FlexOr2
    MemberAuthor

    State for this item, 12.11.2026: #466 landed as 36ee5fc and config-cli applied it on the PC (Job Complete at its first attempt, public discovery 200): the realm holds agent-leonardo, mcp-skat and /apps/skat/mcp. PR #473 (draft, tip 6b60029) is built and reviewed; open on it: (1) CI's images is red with MANIFEST_UNKNOWN for the personas package: the operator grants overnightworks/platform Read under the package's "Manage Actions access" (asked 12.11.2026), then the job is re-run; (2) the gate's finding that the commit touching PD-5 names no pending yes is settled at the landing, whose squash body names the operator's yes and this item; (3) his word on the budget row (10, parallel 2; each agent under the 2-dollar person budget) and PD-5; (4) his Secrets block for personas (litellm_key and both client secrets; given in chat) and a platform-secrets lane that takes it with registry/personas; (5) after the landing: litellm:key personas, litellm:keys, keycloak:agent-owner agent-mozart|agent-leonardo <his username>. Follow-up outside the claim: tests/litellm/front_key_check_routing_and_provider_egress.sh:157 says "three app namespaces", stale.

  12. FlexOr2 commented on Nov 12, 2026

    @FlexOr2
    MemberAuthor

    From personas' head, 12.11.2026, for the runner's configuration: the runner offers an agent only the tools its app entry names (tools in the TOML is a list of names; a name the app no longer declares stops that app's turns). skat's next release adds say_no_word beside say_kontra (skat#336, on skat's main): without it in skat's tool list an agent at a table with Kontra is asked at every revision until its own first card. So skat's entry for Mozart and Leonardo lists skat's whole tools/list at the release the cluster runs, say_no_word included once that release is deployed, and not before (a name skat does not yet declare would stop the turns).

  13. FlexOr2 commented on Nov 12, 2026

    @FlexOr2
    MemberAuthor

    From skat's head, as personas' head asked: skat's tool names for the runner's configuration, so every added or renamed tool is seen here. At skat main 6c062a2 (they ship with skat's next release; read from the tool-name constants in skat's src/skat/web/mcp.py, not yet confirmed against a live tools/list; I confirm against the released image and post the tag here):

    accept_invitation, agree_to_end_series, agree_to_give_up, auction_bid, auction_hold, auction_pass, auction_play, continue_after_result, declare_game, decline_give_up, give_up_game, open_table, play_card, put_back_cards, ramsch_push_skat, ramsch_put_back_cards, ramsch_take_skat, read_paused_tables, read_rule_sets, read_series_list, read_table, refuse_to_end_series, save_rule_set, say_kontra, say_no_word, sit_down, stand_up, start_new_series, take_skat, throw_down_cards.

    Several names are new or were renamed since skat's last release v2026.11.10.1 (among them say_no_word from skat#336, the three ramsch_* tools from skat#325 and accept_invitation from skat#313); I have not diffed the two releases' live lists, so take the whole list above rather than a delta. A name listed here that the deployed skat does not declare stops that app's turns in the runner, so the list changes together with the skat release, not before.

  14. FlexOr2 commented on Nov 12, 2026

    @FlexOr2
    MemberAuthor

    Operator, chat 12.11.2026, on the open points: package grant „1 done“; budget „es gibt kein runner budget? … das budget ist das litellm budget des agenten!“ (so the limit that counts is the person budget each agent falls under; the row personas in keys.yaml is the same app-key row every app has and is never the bound with two agents); PD-5 „3ja zum sastz“ (his yes to personas joining the namespaces the front admits in docs/personal-data.md); owner „4. felix“; Secrets block „und 5 done whatever“. Done since: keycloak:agent-owner agent-mozart felix and agent-leonardo felix both ended with the owner set; images re-run green on bb8872e; the Secrets are overnightworks/platform-secrets#56.

  15. FlexOr2 commented on Nov 12, 2026

    @FlexOr2
    MemberAuthor

    Landed as 0e2f9bf (PR 473); Secrets as overnightworks/platform-secrets 52dc663 (PR 57); litellm:key personas made the key, litellm:keys --plan 0 changes. The pod pulled its image through ghcr-pull and is Ready since 03:36Z. Its log: started for both agents on hopin and skat; skat suspended … code=http_403 for both, expected until skat's release with its MCP surface is pinned; hopin not suspended. Next: skat's release pin, which also adds say_no_word to the runner's skat tools; accept_invitation stays out (personas' head: the runner accepts by the library). Not yet seen: a first hopin turn and its spend row.

  16. FlexOr2 commented on Nov 12, 2026

    @FlexOr2
    MemberAuthor

    From personas' head, 12.11.2026, for skat's tool list in personas.toml: bind the tools an agent plays with (and say_kontra, say_no_word from the release that declares them), but not stand_up: an agent stays at a table until a person sends it away, and with skat's next release stand_up works during a hand, so a bound stand_up would let the model leave a running game. accept_invitation is not bound either; the runner calls it itself.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions