marla.models
Plan Maker backends (local/remote), prompt construction, and response parsing.
Dependency-injection interface for Plan Maker inference backends.
The Plan Maker agent depends on this interface, not on any concrete model
library – local_backend.py (HF transformers) and remote_backend.py
(API-based, not implemented in v0.1) both satisfy it, and tests can inject
a trivial fake without touching either.
- class marla.models.plan_maker_backend.BackendResponse(raw_text: 'str', latency_ms: 'float')[source]
Bases:
object
- class marla.models.plan_maker_backend.PlanMakerBackend(*args, **kwargs)[source]
Bases:
Protocol- async generate(prompt, legal_action_ids)[source]
Generate a response scoring every one of
legal_action_ids.Backends that size their own generation budget (see
local_backend.py) need the actual action count – and the actual ID strings, not just how many there are, since a small model’s response is one JSON entry per action ID and longer IDs (e.g.exploit:host-2-0:e_elasticsearchvsfinish) cost more tokens – to avoid truncating the response as an episode’s legal action space grows mid-run.- Parameters:
- Return type:
Constructs the configured Plan Maker backend (spec section 18: device resolution is local).
- marla.models.backend_factory.build_backend(model_config, device)[source]
- Parameters:
model_config (PlanMakerModelConfig)
device (Literal['cpu', 'gpu', 'auto'])
- Return type:
Local HuggingFace transformers backend for the Plan Maker (Milestone 9).
Deliberate deviation from spec section 4.1 (“blocking Plan Maker inference
must not block the shared asyncio loop… should run via
asyncio.to_thread”): in this SPADE deployment, asyncio.to_thread (and an
explicit ThreadPoolExecutor – it isn’t the default-executor path
specifically) reproducibly hangs indefinitely when used to run a real
transformers generate() call from inside SPADE’s multi-agent async
context, which unconditionally runs on uvloop with no supported way to
disable it. A trivial non-torch call through the same executor (e.g.
asyncio.to_thread(time.sleep, ...)) does not hang, so this is specific
to torch/transformers generation off the main thread here, not threading or
pyjabber/presence in general (both were isolated and ruled out).
Given the spec’s own “no elapsed timeout” design, a hang here is silent and
permanent for a real training run – far worse than the brief blocking
delay of running generation directly on the event loop thread, which is
otherwise idle during inference anyway (nothing else can usefully proceed
without the Plan Maker’s response). This backend therefore calls
generate() synchronously. Device resolution follows the same
cpu | gpu | auto semantics as every other locally executed model (spec
section 18): a gpu request that can’t actually initialize CUDA raises
before the agent ever loads weights, let alone reports ready.
- class marla.models.local_backend.LocalTransformersBackend(model_name, device, max_new_tokens=512)[source]
Bases:
objectLoads a chat-instruct causal LM and generates via a plain user-turn prompt.
Remote API Plan Maker backend – not implemented in this release.
The config schema’s model.backend: local | remote is a real,
spec-defined choice, but the first release only ships the local HF
transformers backend (see local_backend.py). This stub keeps
backend: remote a valid, documented configuration value that fails
loudly and early rather than silently pretending to produce advice.
- class marla.models.remote_backend.RemoteApiBackend(model_name)[source]
Bases:
object- Parameters:
model_name (str)
Plan Maker prompt construction (spec section 9).
The model is only asked to produce the {action_id: score} mapping –
the surrounding advisory-response envelope (schema/run/request IDs, model
and knowledge versions, latency) is assembled deterministically by the
Plan Maker agent itself, never hallucinated by the LM.
- marla.models.prompt.build_correction_prompt(original_prompt, reason, detail, legal_action_ids)[source]
- marla.models.prompt.build_prompt(retrieved_rules, objective, observation, legal_actions)[source]
- Parameters:
retrieved_rules (list[KnowledgeRule])
objective (AdvisoryObjective)
observation (dict)
legal_actions (list[AdvisoryActionDescriptor])
- Return type:
Best-effort extraction of a scores mapping from raw (possibly noisy) LM output.
An unparsable or malformed response is a normal, expected outcome – the
Gatekeeper’s correction loop exists precisely to handle it – so this module
never raises; it returns None/drops entries it can’t make sense of.
- marla.models.response_parser.coerce_scores(raw_scores)[source]
Best-effort coercion of a raw {action_id: value} mapping to floats.
Entries that can’t be coerced are dropped (not defaulted) – the Gatekeeper’s exact-coverage check will then correctly flag the response as invalid rather than MARLA silently inventing a score.