simula 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
simula/__init__.py ADDED
@@ -0,0 +1,15 @@
1
+ """simula — a local-first engine for generating and inhabiting worlds and personas from the
2
+ user's own materials. See PLAN.md and PRINCIPLES.md.
3
+
4
+ One engine, two blueprint types (world | persona), one unified entity model (Simulacrum).
5
+ """
6
+ from .backends import Backend, Contract, Message, from_config
7
+ from .loop import Simulacrum, TurnResult, run_turn
8
+ from .workspace import bootstrap_workspace, default_workspace
9
+
10
+ __all__ = [
11
+ "Backend", "Contract", "Message", "from_config",
12
+ "Simulacrum", "TurnResult", "run_turn",
13
+ "bootstrap_workspace", "default_workspace",
14
+ ]
15
+ __version__ = "0.1.0"
simula/__main__.py ADDED
@@ -0,0 +1,42 @@
1
+ """Minimal CLI entry point for `simula`.
2
+
3
+ Currently (Phase 0) it only exposes `version`, `init` (workspace bootstrap), and `where`. The
4
+ engine is still a skeleton; see PLAN.md for the implementation phases.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import argparse
9
+
10
+ from . import __version__
11
+ from .workspace import bootstrap_workspace, default_workspace
12
+
13
+
14
+ def main(argv: list[str] | None = None) -> int:
15
+ parser = argparse.ArgumentParser(
16
+ prog="simula",
17
+ description="A local-first engine for generating and inhabiting worlds and personas.",
18
+ )
19
+ parser.add_argument("--version", action="version", version=f"simula {__version__}")
20
+ sub = parser.add_subparsers(dest="command")
21
+
22
+ p_init = sub.add_parser("init", help="Create the workspace folder tree.")
23
+ p_init.add_argument("path", nargs="?", default=None, help="Path (default: platform default).")
24
+
25
+ sub.add_parser("where", help="Print the default workspace path.")
26
+
27
+ args = parser.parse_args(argv)
28
+
29
+ if args.command == "init":
30
+ ws = bootstrap_workspace(args.path)
31
+ print(f"Workspace ready: {ws}")
32
+ return 0
33
+ if args.command == "where":
34
+ print(default_workspace())
35
+ return 0
36
+
37
+ parser.print_help()
38
+ return 0
39
+
40
+
41
+ if __name__ == "__main__":
42
+ raise SystemExit(main())
simula/backends.py ADDED
@@ -0,0 +1,109 @@
1
+ """Backend abstraction for simula.
2
+
3
+ One interface, two adapters. Local-first (llama.cpp + GBNF) but always able to run against
4
+ any OpenAI-compatible endpoint. "Constrained output" is the reliability backbone and is
5
+ implemented differently per backend (PLAN.md #5, PRINCIPLES.md #4).
6
+
7
+ This is a SKELETON: contracts + docstrings. Implement per build phases. stdlib only at the
8
+ contract level; concrete adapters may use `requests`/`httpx` and `sentence-transformers`.
9
+ """
10
+ from __future__ import annotations
11
+
12
+ from dataclasses import dataclass
13
+ from pathlib import Path
14
+ from typing import Protocol, Sequence
15
+
16
+
17
+ @dataclass(frozen=True)
18
+ class Message:
19
+ role: str # "system" | "user" | "assistant"
20
+ content: str
21
+
22
+
23
+ @dataclass(frozen=True)
24
+ class Contract:
25
+ """How to constrain structured output. Backend chooses how to honor it.
26
+
27
+ Exactly one of `gbnf_path` / `json_schema` is the primary mechanism; backends fall back
28
+ to a parse-and-repair loop if neither is supported.
29
+ """
30
+ gbnf_path: Path | None = None # used by llama.cpp native /completion
31
+ json_schema: dict | None = None # used by OpenAI-compat (response_format/tools)
32
+
33
+
34
+ class Backend(Protocol):
35
+ """Text + embedding generation. Implementations MUST guarantee that, when a Contract is
36
+ given, the returned string parses against it (raising on irrecoverable failure)."""
37
+
38
+ def complete(
39
+ self,
40
+ messages: Sequence[Message],
41
+ *,
42
+ contract: Contract | None = None,
43
+ temperature: float = 0.2,
44
+ max_tokens: int = 800,
45
+ ) -> str:
46
+ ...
47
+
48
+ def embed(self, texts: Sequence[str]) -> list[list[float]]:
49
+ ...
50
+
51
+
52
+ class LlamaCppBackend:
53
+ """Default, local. Talks to a llama.cpp server (e.g. :18083).
54
+
55
+ Constrained output: prefer the native /completion endpoint with a `grammar` (GBNF) field,
56
+ which guarantees valid structure at decode time. Embeddings stay local (e5-small).
57
+ """
58
+
59
+ def __init__(self, endpoint: str, model: str, *, prefer_native_grammar: bool = True) -> None:
60
+ self.endpoint = endpoint
61
+ self.model = model
62
+ self.prefer_native_grammar = prefer_native_grammar
63
+
64
+ def complete(self, messages, *, contract=None, temperature=0.2, max_tokens=800) -> str:
65
+ # Phase 0: implement.
66
+ # - If contract.gbnf_path and prefer_native_grammar: POST /completion with
67
+ # {"prompt": render(messages), "grammar": gbnf_text, "temperature": ..., "n_predict": ...}
68
+ # (Gemma: fold system into the prompt; see PRINCIPLES.md note on system role.)
69
+ # - Else: POST /v1/chat/completions (no hard grammar; rely on repair loop).
70
+ raise NotImplementedError
71
+
72
+ def embed(self, texts) -> list[list[float]]:
73
+ # Phase 1: local e5-small (sentence-transformers) or llama.cpp /embedding.
74
+ raise NotImplementedError
75
+
76
+
77
+ class OpenAICompatBackend:
78
+ """Any OpenAI-compatible endpoint + key + model. Never store the key in config; read env."""
79
+
80
+ def __init__(self, base_url: str, api_key: str, model: str, *, structured_output: str = "json_schema") -> None:
81
+ self.base_url = base_url
82
+ self.api_key = api_key
83
+ self.model = model
84
+ self.structured_output = structured_output # "json_schema" | "tools" | "repair"
85
+
86
+ def complete(self, messages, *, contract=None, temperature=0.2, max_tokens=800) -> str:
87
+ # Phase 0: implement.
88
+ # - structured_output == "json_schema": pass response_format with contract.json_schema.
89
+ # - "tools": expose a single tool whose params == contract.json_schema; force tool_choice.
90
+ # - "repair": free generation + JSON extraction + one repair retry.
91
+ raise NotImplementedError
92
+
93
+ def embed(self, texts) -> list[list[float]]:
94
+ # Default to local e5 even here (decoupling); only use remote embeddings if configured.
95
+ raise NotImplementedError
96
+
97
+
98
+ def from_config(cfg: dict) -> Backend:
99
+ """Construct the backend from a parsed simula.toml dict. See simula.toml.example."""
100
+ kind = cfg["backend"]["kind"]
101
+ if kind == "llamacpp":
102
+ c = cfg["backend"]["llamacpp"]
103
+ return LlamaCppBackend(c["endpoint"], c["model"], prefer_native_grammar=c.get("prefer_native_grammar", True))
104
+ if kind == "openai_compat":
105
+ import os
106
+ c = cfg["backend"]["openai_compat"]
107
+ return OpenAICompatBackend(c["base_url"], os.environ[c["api_key_env"]], c["model"],
108
+ structured_output=c.get("structured_output", "json_schema"))
109
+ raise ValueError(f"unknown backend.kind: {kind}")
simula/loop.py ADDED
@@ -0,0 +1,74 @@
1
+ """The turn loop and the unified entity model.
2
+
3
+ Everything is a Simulacrum = (blueprint, state, memory, contract). A World is a simulacrum of
4
+ place; a Persona is a simulacrum of agent; an NPC is a Persona embedded in a World. The loop is
5
+ the same for both; only blueprint and applied-delta semantics differ. (PLAN.md #1, #3)
6
+
7
+ SKELETON: contracts + docstrings.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ from dataclasses import dataclass, field
12
+ from pathlib import Path
13
+ from typing import Any
14
+
15
+ from .backends import Backend, Contract, Message
16
+
17
+
18
+ @dataclass
19
+ class Simulacrum:
20
+ """A world or a persona (or an NPC = persona-in-world)."""
21
+ id: str
22
+ kind: str # "world" | "persona"
23
+ blueprint: dict # validated against schemas/*.schema.json
24
+ state: dict = field(default_factory=dict) # engine-owned source of truth
25
+ # memory + ledger live in sqlite (library.sqlite); referenced by id, not held in RAM.
26
+
27
+
28
+ @dataclass
29
+ class TurnResult:
30
+ narration: str
31
+ applied: list[dict] # deltas the engine actually applied (after validation)
32
+ rejected: list[dict] # deltas rejected (invalid against state/ledger), for audit
33
+
34
+
35
+ COMMIT_DIRECTIVE = (
36
+ "Commit to a concrete, tangible detail rooted in the texture of this world/persona. "
37
+ "Never retreat into generic fantasy or vagueness. If an action is not feasible, say why, "
38
+ "concretely."
39
+ ) # The single highest-value prompt content (PRINCIPLES.md #1).
40
+
41
+
42
+ def run_turn(
43
+ sim: Simulacrum,
44
+ player_input: str,
45
+ backend: Backend,
46
+ *,
47
+ retrieve, # callable(query, top_k) -> list[chunk]
48
+ ledger, # fact ledger interface (read/append/contradicts)
49
+ transcript_window: list[Message],
50
+ contract: Contract,
51
+ temperature: float = 0.2,
52
+ max_tokens: int = 800,
53
+ ) -> TurnResult:
54
+ """One ORORO-minimal turn (PLAN.md #3):
55
+
56
+ 1. Observe - player_input is given.
57
+ 2. Retrieve - grounding from materials + relevant ledger facts.
58
+ 3. React - assemble a MINIMAL prompt (commit-directive + blueprint spine +
59
+ exemplars + current state + transcript window + input).
60
+ 4. Constrain - backend.complete(..., contract=contract) -> guaranteed-parsable TurnOutput.
61
+ 5. Validate - check each delta against state + ledger; apply valid, reject invalid.
62
+ 6. Persist - caller persists state + appends to ledger/transcript.
63
+
64
+ Returns narration + applied/rejected deltas. Does NOT mutate sqlite directly; the caller
65
+ persists (keeps this function pure-ish and testable for the eval rig).
66
+ """
67
+ raise NotImplementedError
68
+
69
+
70
+ def build_prompt(sim: Simulacrum, player_input: str, grounding: list, state: dict,
71
+ transcript_window: list[Message]) -> list[Message]:
72
+ """Assemble the minimal prompt. Keep it thin: spine + pointers-grounding, not a big ontology
73
+ (PRINCIPLES.md #2)."""
74
+ raise NotImplementedError
simula/py.typed ADDED
File without changes
simula/workspace.py ADDED
@@ -0,0 +1,35 @@
1
+ """Workspace bootstrap. Cross-platform via platformdirs + pathlib (PLAN.md #6, #7).
2
+
3
+ Creates ~/simula-workspace (or platform default) with the standard layout, and never ships
4
+ any corpus: the user supplies their own materials (PLAN.md #9).
5
+
6
+ SKELETON.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ from pathlib import Path
11
+
12
+ LAYOUT = ["materials", "blueprints", "saves", "evals"]
13
+
14
+
15
+ def default_workspace() -> Path:
16
+ """Platform-appropriate workspace path. Falls back to ~/simula-workspace."""
17
+ try:
18
+ import platformdirs
19
+ return Path(platformdirs.user_data_dir("simula")) / "workspace"
20
+ except Exception:
21
+ return Path.home() / "simula-workspace"
22
+
23
+
24
+ def bootstrap_workspace(path: Path | None = None) -> Path:
25
+ """Create the workspace folder tree and a starter config if missing. Returns the path."""
26
+ ws = path or default_workspace()
27
+ ws.mkdir(parents=True, exist_ok=True)
28
+ for sub in LAYOUT:
29
+ (ws / sub).mkdir(exist_ok=True)
30
+ cfg = ws / "simula.toml"
31
+ if not cfg.exists():
32
+ # Phase 0: copy simula.toml.example into place.
33
+ pass
34
+ # Phase 1: initialize library.sqlite (sqlite-vec + FTS5 tables).
35
+ return ws
@@ -0,0 +1,79 @@
1
+ Metadata-Version: 2.4
2
+ Name: simula
3
+ Version: 0.1.0
4
+ Summary: Lokalno-prvi pogon za sazdavanje i naseljavanje svetova i persona iz korisnikovih materijala.
5
+ Author-email: Peter Ofovik <pedjaurosevic@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/pedjaurosevic/simula
8
+ Project-URL: Repository, https://github.com/pedjaurosevic/simula
9
+ Keywords: llm,simulation,worldbuilding,persona,local-first,llama.cpp,gbnf
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
18
+ Classifier: Operating System :: OS Independent
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: platformdirs>=4.0
23
+ Provides-Extra: openai
24
+ Requires-Dist: requests>=2.28; extra == "openai"
25
+ Provides-Extra: embeddings
26
+ Requires-Dist: sentence-transformers>=2.2; extra == "embeddings"
27
+ Dynamic: license-file
28
+
29
+ # simula
30
+
31
+ **A local-first engine for generating and inhabiting worlds and personas from your own materials.**
32
+
33
+ One engine, two blueprint types (`world` | `persona`), one unified entity model (`Simulacrum`).
34
+ Local-first (llama.cpp + GBNF for hard-constrained output), but always able to run against any
35
+ OpenAI-compatible endpoint.
36
+
37
+ > **Status:** early alpha (Phase 0). The core is still a skeleton — see [`PLAN.md`](PLAN.md) for the
38
+ > implementation phases and [`PRINCIPLES.md`](PRINCIPLES.md) for the empirically derived lessons
39
+ > that drive the design.
40
+
41
+ ## Install
42
+
43
+ ```bash
44
+ pip install simula
45
+ ```
46
+
47
+ ## Quick start
48
+
49
+ ```bash
50
+ simula --version
51
+ simula init # create a workspace (materials/ blueprints/ saves/ evals/)
52
+ simula where # print the workspace path
53
+ ```
54
+
55
+ The workspace lives at a platform-appropriate path (via `platformdirs`), falling back to
56
+ `~/simula-workspace`. **No corpus is ever shipped** — you bring your own materials.
57
+
58
+ ## Configuration
59
+
60
+ Copy `simula.toml.example` into your workspace as `simula.toml` and edit the backend (llama.cpp or
61
+ OpenAI-compatible), embeddings, RAG, and experience mode (`world` | `persona`).
62
+
63
+ ## Design in brief
64
+
65
+ - **Constrained output is the reliability backbone:** GBNF on llama.cpp's `/completion`,
66
+ `json_schema` on OpenAI-compatible backends, with a parse-and-repair fallback.
67
+ - **Minimal prompt:** a commit directive + the blueprint spine + pointers into your materials (RAG),
68
+ not a large ontology.
69
+ - **Local-first and private:** embeddings and generation can stay on your own machine.
70
+ - **The engine holds the truth:** the LLM only *proposes* structured changes; the engine validates
71
+ and applies them against authoritative state.
72
+
73
+ ## Documentation
74
+
75
+ Full docs: **https://pedjaurosevic.github.io/simula/**
76
+
77
+ ## License
78
+
79
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,12 @@
1
+ simula/__init__.py,sha256=kzT6Q3-BBVJOsnQObJZdh0cGxUMrz4IWyYQ81N664Eo,599
2
+ simula/__main__.py,sha256=xUPAHYMVcbs8v4pMoXs-TDhCe0r3tnadnYIA5irHEAk,1298
3
+ simula/backends.py,sha256=N5kNKtex6cbbff0HOEsPQQhgnoWXEApRDFnWGNrX3G4,4554
4
+ simula/loop.py,sha256=B67WGQpV7PhDtizpyq4OXPR6W5X1hQRqwGflTs34KGc,2948
5
+ simula/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ simula/workspace.py,sha256=_9_9HLmuLo662AGEb5DFzVJTeHzMYKpov6v27arp1iM,1170
7
+ simula-0.1.0.dist-info/licenses/LICENSE,sha256=_RGI15CryF1YN2o9Ridxi3SUssmvCZN7uQSp2OhquvQ,1069
8
+ simula-0.1.0.dist-info/METADATA,sha256=klHIT8NSVfZIIEf4fdHhPpA2qGEFC_FkVsGN5vyv3vQ,3003
9
+ simula-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
10
+ simula-0.1.0.dist-info/entry_points.txt,sha256=Z38eQwHhgp4M_p8JKVQOiycl0Hxi2vShYpMnJGMQTAU,48
11
+ simula-0.1.0.dist-info/top_level.txt,sha256=mUmhwzusBASbf93Em7gngQaWRuqwUEN94c00KZL65JI,7
12
+ simula-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ simula = simula.__main__:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Peter Ofovik
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ simula