Skip to content

Latest commit

 

History

History
293 lines (254 loc) · 14.6 KB

File metadata and controls

293 lines (254 loc) · 14.6 KB

Internal Agent Runtime

English | 简体中文

The Internal Agent Runtime is an optional rin-control component. It advances budgeted tasks with persona, memory, skills, and a model while every world read, controller lease, policy decision, Operation, and authoritative outcome still uses the shared Control Plane. Without an Agent configuration, rin-control keeps its existing behavior.

The runtime currently advances explicitly created tasks. It does not create a new background task merely because a persona has an initiative_policy. A game may create tasks for a proactive greeting, checking player state, or continuing an unresolved topic from trusted events. Initiative then constrains expression and consecutive actions inside that task, preserving a visible trigger, cooldown, and cancellation path.

Identity boundaries

Identity Source Authority
Control Client RIN_CONTROL_PRINCIPAL and RIN_CONTROL_SCOPES /control/v2
Agent Client Configured client_principal Only task.read, task.execute, and task.cancel
Internal Runtime Created in process and never exposed over HTTP Only controls Actors with DecisionAuthority=internal

RIN_CONTROL_TOKEN and RIN_AGENT_TOKEN must use different values. Neither token can access the other route family. The Agent Client cannot receive host.admin, actor.*, or game-specific scopes.

Configuration

Create a JSON file readable only by the current user, such as /absolute/path/agent.json:

{
  "contract_version": "rin.agent.config/v1",
  "client_principal": {
    "id": "rin.agent-client",
    "granted_scopes": ["task.read", "task.execute", "task.cancel"]
  },
  "runtime_principal_id": "rin.internal",
  "model": {
    "provider": "openai-compatible",
    "base_url": "https://api.example.com/v1",
    "model": "example-model",
    "response_format": "json_schema",
    "authentication": "bearer-env",
    "max_context_characters": 64000,
    "max_output_tokens": 1500,
    "temperature": 0.2
  },
  "personas": [{
    "persona_id": "companion",
    "version": "v1",
    "identity": "A grounded companion.",
    "initiative_policy": {
      "enabled": true,
      "cooldown_millis": 5000,
      "max_consecutive_actions": 4
    }
  }],
  "persona_bindings": [{
    "persona_id": "companion",
    "version": "v1"
  }],
  "memory": {
    "semantic_embedding": {
      "enabled": true,
      "provider": "openai-compatible",
      "base_url": "https://api.example.com/v1",
      "model": "example-embedding-model",
      "authentication": "bearer-env",
      "allowed_domains": ["actor-episodic", "actor-semantic"],
      "min_local_matches": 4,
      "max_semantic_results": 4,
      "timeout_millis": 1500
    }
  },
  "learning": {
    "enabled": true,
    "publish_mode": "draft",
    "min_actions": 3,
    "adapter": "minecraft",
    "max_output_tokens": 1200
  }
}

A binding without actor_id is the explicit default for dynamically created actors. Exact actor-and-controller and actor-only bindings take precedence. Only one default is allowed, and it cannot select a controller.

chmod 600 /absolute/path/agent.json
export RIN_AGENT_CONFIG=/absolute/path/agent.json
export RIN_CONTROL_TOKEN="$(openssl rand -hex 32)"
export RIN_CONTROL_PRINCIPAL="local.player"
export RIN_AGENT_TOKEN="$(openssl rand -hex 32)"
export RIN_AGENT_API_KEY="provider-key-from-secret-store"
export RIN_AGENT_EMBEDDING_API_KEY="embedding-key-from-secret-store"
./bin/rin-control

API keys are not Agent configuration fields; adding api_key to that JSON is rejected. As an alternative to the environment variables above, the loopback Rin Console can store model and embedding keys in <data>/agent/agent-secrets.json. The file is mode 0600, responses expose presence only, and environment variables override local values. For an unauthenticated local OpenAI-compatible service, set authentication to none and leave RIN_AGENT_API_KEY unset. Startup validates configuration without sending a model probe request.

For providers such as DeepSeek that support JSON objects but not JSON Schema as a response format, set response_format to json_object. Rin includes the schema in the stable system message and still validates the returned object locally. thinking_mode is optional; set it to enabled or disabled only when the selected OpenAI-compatible provider implements that request field. For DeepSeek V4 Flash, use base_url: https://api.deepseek.com, model: deepseek-v4-flash, response_format: json_object, and thinking_mode: disabled for low-latency action decisions. This is only an adapter configuration example; Persona, Memory, Skill, the Agent Loop, Control Plane, and Host contracts do not depend on that provider or model name.

Persona, memory, and skills

PersonaProfile describes identity and presentation only: identity, traits, values, voice, boundaries, relationship stances, initiative, and presentation rules. It contains no scopes, policy rules, API keys, or executable hooks. Bindings resolve in Actor+Controller, Actor, then default order.

Memory namespaces provide structural isolation:

Domain Visibility Purpose
actor-episodic controllers of one Actor shared experiences
actor-semantic controllers of one Actor stable preferences, promises, and relationship facts
controller-working current controller current task working memory
controller-private current controller private reasoning and externally hidden content
controller-belief current controller unverified hypotheses

Each memory carries provenance, authority, confidence, importance, TTL, subjects, tags, and superseded records. Model-created candidates are always non-authoritative subjective records; Host outcomes provide authoritative world evidence. Retrieval is bounded by record and character budgets instead of sending all history to the prompt. Forget creates tombstones and consolidation can replace several records with a sourced summary.

Default retrieval is fully offline. SQL first constrains candidates by session, actor, controller, domain, tombstone, and expiry, then deterministically merges recent candidates, unicode61 FTS5/BM25, and trigram FTS5 for CJK substrings. Queries shorter than three characters never invoke the trigram path. Record and character budgets are applied after source ranks and existing memory scores are merged. Wiki projections, relation graphs, rerankers, and background vector services are not part of this path.

Remote semantic recall is an optional supplement. It is disabled unless memory.semantic_embedding.enabled=true, an OpenAI-compatible endpoint and model are configured, and allowed_domains explicitly permits a non-private memory domain. Only complex planned tasks request semantic recall, and only when local recall is insufficient. Document embeddings are generated on a bounded background queue and stored in the rebuildable memory_embeddings table keyed by configured model and content digest. Query vectors use a small process-local cache; results are rechecked against current visibility, expiry, filters, and content digest before use.

The embedding key is separate from JSON configuration. It may come from RIN_AGENT_EMBEDDING_API_KEY or the Console-managed secret file; the environment variable takes precedence. Private controller domains and text resembling credentials are never sent to the embedding endpoint. Timeout, transport failure, rate limiting, invalid model, invalid dimensions, or malformed vectors fall back to normal FTS5 and recent memory results. Rin does not download or run an embedding model, and the remote provider never owns Memory IDs, facts, permissions, or deletion.

rin-control exclusively owns <RIN_CONTROL_DATA_DIR>/agent/memory.db. SQLite is the only online source of truth for the Rin Memory domain and uses WAL, full synchronization, and FTS5. JSONL is an explicit manual interchange format; there is no parallel JSON persistence backend. Actions initiated by the internal Agent, external MCP, or a macro all reach the same projection, and only a committed Host Outcome can create shared actor-episodic memory. The canon_ref retains Host, World, Epoch, Sequence, and Digest evidence. It is a searchable projection of game-owned Canon and cannot mutate Canon.

A skill is inert procedural guidance containing a summary, trigger tags, instructions, and digest. It has no entrypoint, scope, or capability grant. The model first sees summaries and may expand at most one skill. Instructions asking for privileged behavior still cannot change the allowed capabilities, binding, or policy.

The catalog belongs to rin-control, not privately to the Internal Runtime. Configured built-ins, skills/installed, and skills/learned form one deterministic catalog used directly by the internal Agent and exposed to MCP through skill.read and skill.write. MCP skills remain available when the internal model runtime is disabled.

An external MCP controller keeps its persona and private memory in the external Agent. Internal persona does not override it and Rin does not automatically copy that private state into Internal Agent memory.

learning is disabled by default. When enabled, only a completed task with at least min_actions actions and an authoritative successful Host Outcome makes one additional model call. The default publish_mode=draft writes below skills/drafts and does not expose the result to the active catalog. Explicit learned mode writes below skills/learned. Draft input omits operation IDs, Host references, coordinates, world UUIDs, and credentials. Rin derives adapter and capability applicability from evidence instead of trusting the summarizing model. Learning failure never changes the task result.

Model decisions

Each completion request places the fixed protocol first, followed by one deterministically serialized static-context message containing the decision schema digest, Persona, sorted Capability summaries, and sorted Skill summaries. Task ID, goal, Observation, Epoch, targets, retrieved memory, inspection output, and PlanState remain in the final dynamic message. This byte-stable prefix lets compatible providers reuse their own prompt cache without a Rin cache service. Changing Persona, a Capability spec digest, a Skill digest, or the decision schema changes the private stable_prefix_digest recorded with the request.

The OpenAI-compatible adapter maps provider-reported cache hit, miss, and write tokens, including common compatible aliases, into provider.Usage; the task timeline records only those measured values. Missing fields remain unknown, not zero. Rin does not cache ActionRequest, Observation, Policy decisions, or world outcomes and sends no provider-specific cache parameter by default.

When a provider must translate the generic response schema into prompt text, it returns the final messages through the optional request-preparation interface. Rin then checks context size and computes request and stable-prefix digests. Providers that need no transformation implement nothing, while the resilience wrapper only delegates the preparation contract.

One model response is exactly action, wait, complete, or inspect:

  • action selects one allowed capability, strict JSON arguments, and listed target handles;
  • inspect expands at most four capabilities and one skill for one round;
  • wait means there is no grounded action now;
  • complete still requires the runtime to verify the goal through observation or outcome.

The trusted contract is separate from untrusted_context. Persona, memory, skills, observation, player text, and capability descriptions are untrusted data and cannot alter the allowed set, epoch, controller, or budgets.

State and calls

  • api/agent-openapi.json is the Task HTTP contract.
  • State is fixed at <RIN_CONTROL_DATA_DIR>/agent/tasks.json and memory.db.
  • Task snapshots use rin.cognition.tasks/v3.
  • Task files use private permissions and atomic replacement. Memory uses SQLite transactions, WAL, and a single-writer process lock. Configuration cannot redirect these paths.
  • scheduled=true only means background coordination was queued. It is not proof of model deliberation, game execution, or task completion.
  • allowed_capabilities is an optional task-local allowlist of at most 128 capability IDs. When non-empty, the Runtime exposes only its intersection with the current Host catalog and revalidates restored pending actions. An empty array uses the current full Host catalog. This field can only narrow authority; it cannot create capabilities or bypass Policy.
  • Shutdown cancels and joins Agent workers before releasing Task and Memory locks, then closes the Control Plane.

Macro parent-child loop

The internal Runtime and external MCP use the same parent-child Operation contract. After the model selects a capability declared as kind=macro with produces_child_operations=true, the Task records its macro_operation_id only when the Host advances the parent to accepted or running. The next observation carries a trusted parent_operation_id; every selected atomic child still passes through Host binding, Policy, execution, and authoritative Outcome reporting.

  • queued, delivered, awaiting-confirmation, accepted, and running are not completion evidence. The parent remains in the Task until an authoritative terminal state.
  • While a parent macro runs, the model sees only atomic capabilities. The Control Plane supports nested macros, but this Runtime does not create a second automatic parent level yet. A task-local allowlist must name both the parent Macro and its expected children; entering the Macro phase never expands task authority.
  • Cancelling a Task with a running child cancels the child before the parent; the Task remains cancelling until the parent settles.
  • An outcome-unknown child or parent retains the exact Operation ID and stops further decisions.
  • A pre-queue ActionGateway rejection records only a stable class such as gateway.stale, gateway.lease-expired, gateway.forbidden, or gateway.invalid; provider text and internal error details do not enter task history.
  • Provider failure or budget exhaustion pauses instead of releasing control and orphaning a parent macro; the Task can still be resumed or cancelled.

The model can only propose an ActionRequest grounded in the current Observation and Capability catalog. The Host still binds targets, previews Effects, applies Policy, mutates the world, and reports the Outcome. Persona, memory, and a Task token never grant world authority.