Skip to content

Runtime .agent compatibility

The standalone CleanSlice runtime (cleanslice/runtime) stores an agent as a portable .agent/ folder (SOUL, MEMORY, skills, sessions, secrets, config…). Agentfy.ai 2.0 should be compatible with that format so we can: import existing runtime agents, run the runtime's agent loop unchanged in our worker, and export/back up an agent for portability.

The .agent/ layout

.agent/
├── SOUL.md                 # persona / system prompt (managed file)
├── USER.md                 # per-user context
├── MEMORY.md               # curated long-term memory
├── HEARTBEAT.md            # heartbeat prompt
├── agent.config.json       # IAgentConfig: maxIterations, maxTokens, accessStrategy,
│                           #   heartbeat, session(compaction), memory(limits/review),
│                           #   stopPhrases, managedFiles, syncSkills, tools{...}
├── memory/                 # rolling daily memory  (YYYY-MM-DD.md)
├── skills/<name>/SKILL.md   # skills (markdown + frontmatter)
└── data/
    ├── access.json          # users{ id: {status, inviteCode/accessCode, createdAt} }
    ├── secrets/<userId>.json # { "service:key": value }  (e.g. gmail:app_password)
    ├── sessions/<chan>:<user>.jsonl  # event log: {id,type,ts,data} per line
    ├── memory.sqlite         # FTS index over memory
    └── usage.json            # token usage
# + browser-state/<profile>.json  (live browser cookies/state)

Mapping .agent/ ↔ Agentfy.ai entities

.agent/Agentfy.ai 2.0 (close-compat — same names)
SOUL.mdagent.soul (persona / system prompt — stored verbatim)
USER.mdagent.user (human/profile context — stored verbatim)
HEARTBEAT.mdagent.heartbeat (heartbeat prompt)
agent.config.jsonagent.configstored verbatim as IAgentConfig; limits / accessStrategy / runtimeProfile are read from it, not renamed away into other slices
MEMORY.md + memory/*.mdagent/memory (curated = MEMORY.md, daily[date] = memory/YYYY-MM-DD.md — same text)
data/memory.sqlite— (FTS index — derived, rebuilt on boot; never stored/transferred)
skills/<name>/SKILL.mdagent/skill (name + frontmatter + body verbatim) (slice to add — see gaps)
data/sessions/<chan>:<uid>.jsonlchat (events kept as-is {id,type,ts,data}; idexternalId)
data/access.jsonagent/access (keeps users{ id:{status,inviteCode/accessCode,createdAt} } shape)
data/secrets/<userId>.jsonagent/secret (keeps service:key→value map; re-encrypted at rest)
data/usage.jsonsystem/usage (BillingEvents)
browser-state/*.jsonapp/account (connector state) / worker browser session
workspace/worker /workspace (emptyDir) + outputs → system/file

Close compatibility — store the runtime's own names

We deliberately keep the runtime's field names in the DB instead of renaming them into CleanSlice-conventional ones. SOUL.md is stored as soul, USER.md as user, MEMORY.md as memory, HEARTBEAT.md as heartbeat, and agent.config.json is persisted verbatim as config. The managed markdown files map 1:1 to columns of the same stem; the config JSON is queried in place (limits, accessStrategy, runtimeProfile) rather than exploded and renamed across setting / runtimeProfile / access.

Why: the adapter becomes a near-identity projection, not a semantic translation — round-trips are lossless, the runtime loop reads/writes the exact same names, and there is no rename layer to drift. When the runtime adds a new managed file later, it lands as a new same-named field with no mapping change. (Other slices like setting may still mirror/read values from config for cross-agent queries, but config stays the source of truth and is never rewritten.)

Strategy: .agent/ is the portable package + on-pod format

  • Postgres is the normalized source of truth (Agent row, memory, chat, secrets, access…).
  • .agent/ is a projection of that state — used as (a) the interchange/backup package and (b) the working directory on the pod when the worker runs the runtime loop.
  • A bidirectional adapter (agent/package) maps between the two; the worker materializes.agent/ from the DB before a turn and syncs deltas back after.

This is what lets the worker reuse the runtime's loop + tool-executors unchanged — they read and write .agent/, and the adapter keeps the DB in sync.

Materialize / sync at runtime

turn needs a runtime
   → worker manager hydrates .agent/ from Postgres (+ object storage) into the pod workspace
   → runtime loop runs against .agent/ (unchanged — reads SOUL/MEMORY/skills, writes sessions/…)
   → on completion: sync deltas back
        sessions/*.jsonl  → chat (new messages)
        MEMORY.md, memory/*.md → agent/memory
        data/secrets/*    → agent/secret (if changed)
        data/usage.json   → system/usage events
        browser-state/*   → app/account
   → workspace wiped; durable state already persisted

Reuse the runtime's existing diff-manifest S3 sync (pull-on-boot / push-on-change): the pod can pull a materialized .agent/ from object storage and push changes, so the sync layer is the one the runtime already ships — Agentfy.ai just owns the DB↔package projection.

Import / export

  • Import a .agent/ folder → create/update Agentfy.ai entities (onboard an existing runtime agent).
  • Export an agent → a .agent/ folder (backup, portability, or run it on the standalone runtime). Round-trips because both sides speak the same package format.

Gaps to close for full compatibility

  • Add agent/skill (markdown skills with frontmatter) to the agent group — the runtime has skills; our L5 breakdown dropped it. Also confirm agent/secret, agent/access, agent/heartbeat, agent/cron sub-slices exist (they map 1:1 to .agent/).
  • sessions/*.jsonlchat: define the event-type mapping (user/assistant/tool_call/ tool_result/summary → message rows + tool/summary records).
  • access.jsonuser: runtime keys users by channel id (e.g. Telegram id); map to Agentfy.ai user/team identities.
  • Versioning: pin the .agent schema version (the runtime evolves it) and keep the adapter tolerant of missing/extra files.

Where it lives

  • Adapter: apiagent/package (import/export + DB↔.agent/ mapping).
  • Materializer/sync: runtime/worker slice (hydrate before turn, sync deltas after), reusing the runtime's S3/diff sync.
  • Loop + tool-executors: the worker app borrows them from cleanslice/runtime and runs them against the materialized .agent/.